New code style refactor

This commit is contained in:
Jack Andersen 2018-12-07 19:20:55 -10:00
parent 500f88d7a7
commit e28b6551d0
10 changed files with 1821 additions and 2146 deletions

View File

@ -4,8 +4,7 @@
#include <cstdint> #include <cstdint>
#include <cstdlib> #include <cstdlib>
namespace jbus namespace jbus {
{
using s8 = int8_t; using s8 = int8_t;
using u8 = uint8_t; using u8 = uint8_t;
@ -24,114 +23,100 @@ using u64 = uint64_t;
/* Type-sensitive byte swappers */ /* Type-sensitive byte swappers */
template <typename T> template <typename T>
static inline T bswap16(T val) static inline T bswap16(T val) {
{
#if __GNUC__ #if __GNUC__
return __builtin_bswap16(val); return __builtin_bswap16(val);
#elif _WIN32 #elif _WIN32
return _byteswap_ushort(val); return _byteswap_ushort(val);
#else #else
return (val = (val << 8) | ((val >> 8) & 0xFF)); return (val = (val << 8) | ((val >> 8) & 0xFF));
#endif #endif
} }
template <typename T> template <typename T>
static inline T bswap32(T val) static inline T bswap32(T val) {
{
#if __GNUC__ #if __GNUC__
return __builtin_bswap32(val); return __builtin_bswap32(val);
#elif _WIN32 #elif _WIN32
return _byteswap_ulong(val); return _byteswap_ulong(val);
#else #else
val = (val & 0x0000FFFF) << 16 | (val & 0xFFFF0000) >> 16; val = (val & 0x0000FFFF) << 16 | (val & 0xFFFF0000) >> 16;
val = (val & 0x00FF00FF) << 8 | (val & 0xFF00FF00) >> 8; val = (val & 0x00FF00FF) << 8 | (val & 0xFF00FF00) >> 8;
return val; return val;
#endif #endif
} }
template <typename T> template <typename T>
static inline T bswap64(T val) static inline T bswap64(T val) {
{
#if __GNUC__ #if __GNUC__
return __builtin_bswap64(val); return __builtin_bswap64(val);
#elif _WIN32 #elif _WIN32
return _byteswap_uint64(val); return _byteswap_uint64(val);
#else #else
return ((val & 0xFF00000000000000ULL) >> 56) | return ((val & 0xFF00000000000000ULL) >> 56) | ((val & 0x00FF000000000000ULL) >> 40) |
((val & 0x00FF000000000000ULL) >> 40) | ((val & 0x0000FF0000000000ULL) >> 24) | ((val & 0x000000FF00000000ULL) >> 8) |
((val & 0x0000FF0000000000ULL) >> 24) | ((val & 0x00000000FF000000ULL) << 8) | ((val & 0x0000000000FF0000ULL) << 24) |
((val & 0x000000FF00000000ULL) >> 8) | ((val & 0x000000000000FF00ULL) << 40) | ((val & 0x00000000000000FFULL) << 56);
((val & 0x00000000FF000000ULL) << 8) |
((val & 0x0000000000FF0000ULL) << 24) |
((val & 0x000000000000FF00ULL) << 40) |
((val & 0x00000000000000FFULL) << 56);
#endif #endif
} }
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
static inline int16_t SBig(int16_t val) {return bswap16(val);} static inline int16_t SBig(int16_t val) { return bswap16(val); }
static inline uint16_t SBig(uint16_t val) {return bswap16(val);} static inline uint16_t SBig(uint16_t val) { return bswap16(val); }
static inline int32_t SBig(int32_t val) {return bswap32(val);} static inline int32_t SBig(int32_t val) { return bswap32(val); }
static inline uint32_t SBig(uint32_t val) {return bswap32(val);} static inline uint32_t SBig(uint32_t val) { return bswap32(val); }
static inline int64_t SBig(int64_t val) {return bswap64(val);} static inline int64_t SBig(int64_t val) { return bswap64(val); }
static inline uint64_t SBig(uint64_t val) {return bswap64(val);} static inline uint64_t SBig(uint64_t val) { return bswap64(val); }
static inline float SBig(float val) static inline float SBig(float val) {
{ int32_t ival = bswap32(*((int32_t*)(&val)));
int32_t ival = bswap32(*((int32_t*)(&val))); return *((float*)(&ival));
return *((float*)(&ival));
} }
static inline double SBig(double val) static inline double SBig(double val) {
{ int64_t ival = bswap64(*((int64_t*)(&val)));
int64_t ival = bswap64(*((int64_t*)(&val))); return *((double*)(&ival));
return *((double*)(&ival));
} }
#ifndef SBIG #ifndef SBIG
#define SBIG(q) ( ( (q) & 0x000000FF ) << 24 | ( (q) & 0x0000FF00 ) << 8 \ #define SBIG(q) (((q)&0x000000FF) << 24 | ((q)&0x0000FF00) << 8 | ((q)&0x00FF0000) >> 8 | ((q)&0xFF000000) >> 24)
| ( (q) & 0x00FF0000 ) >> 8 | ( (q) & 0xFF000000 ) >> 24 )
#endif #endif
static inline int16_t SLittle(int16_t val) {return val;} static inline int16_t SLittle(int16_t val) { return val; }
static inline uint16_t SLittle(uint16_t val) {return val;} static inline uint16_t SLittle(uint16_t val) { return val; }
static inline int32_t SLittle(int32_t val) {return val;} static inline int32_t SLittle(int32_t val) { return val; }
static inline uint32_t SLittle(uint32_t val) {return val;} static inline uint32_t SLittle(uint32_t val) { return val; }
static inline int64_t SLittle(int64_t val) {return val;} static inline int64_t SLittle(int64_t val) { return val; }
static inline uint64_t SLittle(uint64_t val) {return val;} static inline uint64_t SLittle(uint64_t val) { return val; }
static inline float SLittle(float val) {return val;} static inline float SLittle(float val) { return val; }
static inline double SLittle(double val) {return val;} static inline double SLittle(double val) { return val; }
#ifndef SLITTLE #ifndef SLITTLE
#define SLITTLE(q) (q) #define SLITTLE(q) (q)
#endif #endif
#else #else
static inline int16_t SLittle(int16_t val) {return bswap16(val);} static inline int16_t SLittle(int16_t val) { return bswap16(val); }
static inline uint16_t SLittle(uint16_t val) {return bswap16(val);} static inline uint16_t SLittle(uint16_t val) { return bswap16(val); }
static inline int32_t SLittle(int32_t val) {return bswap32(val);} static inline int32_t SLittle(int32_t val) { return bswap32(val); }
static inline uint32_t SLittle(uint32_t val) {return bswap32(val);} static inline uint32_t SLittle(uint32_t val) { return bswap32(val); }
static inline int64_t SLittle(int64_t val) {return bswap64(val);} static inline int64_t SLittle(int64_t val) { return bswap64(val); }
static inline uint64_t SLittle(uint64_t val) {return bswap64(val);} static inline uint64_t SLittle(uint64_t val) { return bswap64(val); }
static inline float SLittle(float val) static inline float SLittle(float val) {
{ int32_t ival = bswap32(*((int32_t*)(&val)));
int32_t ival = bswap32(*((int32_t*)(&val))); return *((float*)(&ival));
return *((float*)(&ival));
} }
static inline double SLittle(double val) static inline double SLittle(double val) {
{ int64_t ival = bswap64(*((int64_t*)(&val)));
int64_t ival = bswap64(*((int64_t*)(&val))); return *((double*)(&ival));
return *((double*)(&ival));
} }
#ifndef SLITTLE #ifndef SLITTLE
#define SLITTLE(q) ( ( (q) & 0x000000FF ) << 24 | ( (q) & 0x0000FF00 ) << 8 \ #define SLITTLE(q) (((q)&0x000000FF) << 24 | ((q)&0x0000FF00) << 8 | ((q)&0x00FF0000) >> 8 | ((q)&0xFF000000) >> 24)
| ( (q) & 0x00FF0000 ) >> 8 | ( (q) & 0xFF000000 ) >> 24 )
#endif #endif
static inline int16_t SBig(int16_t val) {return val;} static inline int16_t SBig(int16_t val) { return val; }
static inline uint16_t SBig(uint16_t val) {return val;} static inline uint16_t SBig(uint16_t val) { return val; }
static inline int32_t SBig(int32_t val) {return val;} static inline int32_t SBig(int32_t val) { return val; }
static inline uint32_t SBig(uint32_t val) {return val;} static inline uint32_t SBig(uint32_t val) { return val; }
static inline int64_t SBig(int64_t val) {return val;} static inline int64_t SBig(int64_t val) { return val; }
static inline uint64_t SBig(uint64_t val) {return val;} static inline uint64_t SBig(uint64_t val) { return val; }
static inline float SBig(float val) {return val;} static inline float SBig(float val) { return val; }
static inline double SBig(double val) {return val;} static inline double SBig(double val) { return val; }
#ifndef SBIG #ifndef SBIG
#define SBIG(q) (q) #define SBIG(q) (q)
#endif #endif
@ -142,24 +127,22 @@ class ThreadLocalEndpoint;
#endif #endif
enum EJStatFlags enum EJStatFlags {
{ GBA_JSTAT_MASK = 0x3a,
GBA_JSTAT_MASK = 0x3a, GBA_JSTAT_FLAGS_SHIFT = 4,
GBA_JSTAT_FLAGS_SHIFT = 4, GBA_JSTAT_FLAGS_MASK = 0x30,
GBA_JSTAT_FLAGS_MASK = 0x30, GBA_JSTAT_PSF1 = 0x20,
GBA_JSTAT_PSF1 = 0x20, GBA_JSTAT_PSF0 = 0x10,
GBA_JSTAT_PSF0 = 0x10, GBA_JSTAT_SEND = 0x08,
GBA_JSTAT_SEND = 0x08, GBA_JSTAT_RECV = 0x02
GBA_JSTAT_RECV = 0x02
}; };
enum EJoyReturn enum EJoyReturn {
{ GBA_READY = 0,
GBA_READY = 0, GBA_NOT_READY = 1,
GBA_NOT_READY = 1, GBA_BUSY = 2,
GBA_BUSY = 2, GBA_JOYBOOT_UNKNOWN_STATE = 3,
GBA_JOYBOOT_UNKNOWN_STATE = 3, GBA_JOYBOOT_ERR_INVALID = 4
GBA_JOYBOOT_ERR_INVALID = 4
}; };
/** @brief Standard callback for asynchronous jbus::Endpoint APIs. /** @brief Standard callback for asynchronous jbus::Endpoint APIs.
@ -182,5 +165,4 @@ static constexpr u64 GetGCTicksPerSec() { return 486000000ull; }
/** @brief Initialize platform specifics of JBus library */ /** @brief Initialize platform specifics of JBus library */
void Initialize(); void Initialize();
} } // namespace jbus

View File

@ -6,277 +6,257 @@
#include <mutex> #include <mutex>
#include <condition_variable> #include <condition_variable>
namespace jbus namespace jbus {
{
/** Main class for performing JoyBoot and subsequent JoyBus I/O operations. /** Main class for performing JoyBoot and subsequent JoyBus I/O operations.
* Instances should be obtained though the jbus::Listener::accept method. */ * Instances should be obtained though the jbus::Listener::accept method. */
class Endpoint class Endpoint {
{ /** Self-contained class for solving Kawasedo's GBA BootROM challenge.
/** Self-contained class for solving Kawasedo's GBA BootROM challenge. * GBA will boot client_pad.bin code on completion.
* GBA will boot client_pad.bin code on completion. *
* * This class shouldn't be used directly. JoyBoot operations are started
* This class shouldn't be used directly. JoyBoot operations are started * via jbus::Endpoint::GBAJoyBootAsync. The JoyBoot status may be obtained
* via jbus::Endpoint::GBAJoyBootAsync. The JoyBoot status may be obtained * via jbus::Endpoint::GBAGetProcessStatus. */
* via jbus::Endpoint::GBAGetProcessStatus. */ class KawasedoChallenge {
class KawasedoChallenge /** DSP-hosted public-key unwrap and initial message crypt
{ * Reference: https://github.com/dolphin-emu/dolphin/blob/master/Source/Core/Core/HW/DSPHLE/UCodes/GBA.cpp */
/** DSP-hosted public-key unwrap and initial message crypt struct DSPSecParms {
* Reference: https://github.com/dolphin-emu/dolphin/blob/master/Source/Core/Core/HW/DSPHLE/UCodes/GBA.cpp */ /* Nonce challenge (first read from GBA, hence already little-endian) */
struct DSPSecParms u32 x0_gbaChallenge;
{
/* Nonce challenge (first read from GBA, hence already little-endian) */
u32 x0_gbaChallenge;
/* Palette of pulsing logo on GBA during transmission [0,6] */ /* Palette of pulsing logo on GBA during transmission [0,6] */
u32 x4_logoPalette; u32 x4_logoPalette;
/* Speed and direction of palette interpolation [-4,4] */ /* Speed and direction of palette interpolation [-4,4] */
u32 x8_logoSpeed; u32 x8_logoSpeed;
/* Length of JoyBoot program to upload */ /* Length of JoyBoot program to upload */
u32 xc_progLength; u32 xc_progLength;
/* Unwrapped public key */ /* Unwrapped public key */
u32 x20_key; u32 x20_key;
/* Message authentication code */ /* Message authentication code */
u32 x24_authInitCode; u32 x24_authInitCode;
void ProcessGBACrypto(); void ProcessGBACrypto();
} xf8_dspHmac; } xf8_dspHmac;
s32 x0_pColor; s32 x0_pColor;
s32 x4_pSpeed; s32 x4_pSpeed;
const u8* x8_progPtr; const u8* x8_progPtr;
u32 xc_progLen; u32 xc_progLen;
u8* x10_statusPtr; u8* x10_statusPtr;
FGBACallback x14_callback; FGBACallback x14_callback;
u8 x18_readBuf[4]; u8 x18_readBuf[4];
u8 x1c_writeBuf[4]; u8 x1c_writeBuf[4];
s32 x20_byteInWindow; s32 x20_byteInWindow;
u64 x28_ticksAfterXf; u64 x28_ticksAfterXf;
u32 x30_justStarted; u32 x30_justStarted;
u32 x34_bytesSent; u32 x34_bytesSent;
u32 x38_crc; u32 x38_crc;
u32 x3c_checkStore[7]; u32 x3c_checkStore[7];
s32 x58_currentKey; s32 x58_currentKey;
s32 x5c_initMessage; s32 x5c_initMessage;
s32 x60_gameId; s32 x60_gameId;
u32 x64_totalBytes; u32 x64_totalBytes;
bool m_started = true; bool m_started = true;
bool m_initialized = false; bool m_initialized = false;
void _0Reset(ThreadLocalEndpoint& endpoint, EJoyReturn status); void _0Reset(ThreadLocalEndpoint& endpoint, EJoyReturn status);
void _1GetStatus(ThreadLocalEndpoint& endpoint, EJoyReturn status); void _1GetStatus(ThreadLocalEndpoint& endpoint, EJoyReturn status);
void _2ReadChallenge(ThreadLocalEndpoint& endpoint, EJoyReturn status); void _2ReadChallenge(ThreadLocalEndpoint& endpoint, EJoyReturn status);
void _3DSPCrypto(ThreadLocalEndpoint& endpoint, EJoyReturn status); void _3DSPCrypto(ThreadLocalEndpoint& endpoint, EJoyReturn status);
void _DSPCryptoInit(); void _DSPCryptoInit();
void _DSPCryptoDone(ThreadLocalEndpoint& endpoint); void _DSPCryptoDone(ThreadLocalEndpoint& endpoint);
void _4TransmitProgram(ThreadLocalEndpoint& endpoint, EJoyReturn status); void _4TransmitProgram(ThreadLocalEndpoint& endpoint, EJoyReturn status);
void _5StartBootPoll(ThreadLocalEndpoint& endpoint, EJoyReturn status); void _5StartBootPoll(ThreadLocalEndpoint& endpoint, EJoyReturn status);
void _6BootPoll(ThreadLocalEndpoint& endpoint, EJoyReturn status); void _6BootPoll(ThreadLocalEndpoint& endpoint, EJoyReturn status);
void _7BootAcknowledge(ThreadLocalEndpoint& endpoint, EJoyReturn status); void _7BootAcknowledge(ThreadLocalEndpoint& endpoint, EJoyReturn status);
void _8BootDone(ThreadLocalEndpoint& endpoint, EJoyReturn status); void _8BootDone(ThreadLocalEndpoint& endpoint, EJoyReturn status);
auto bindThis(void(KawasedoChallenge::*ptmf)(ThreadLocalEndpoint&, EJoyReturn)) auto bindThis(void (KawasedoChallenge::*ptmf)(ThreadLocalEndpoint&, EJoyReturn)) {
{ return std::bind(ptmf, this, std::placeholders::_1, std::placeholders::_2);
return std::bind(ptmf, this, std::placeholders::_1, std::placeholders::_2);
}
public:
KawasedoChallenge() = default;
KawasedoChallenge(s32 paletteColor, s32 paletteSpeed,
const u8* programp, s32 length, u8* status, FGBACallback&& callback);
void start(Endpoint& endpoint);
bool started() const { return m_started; }
u8 percentComplete() const
{
if (!x64_totalBytes)
return 0;
return x34_bytesSent * 100 / x64_totalBytes;
}
bool isDone() const { return !x14_callback; }
operator bool() const { return m_initialized; }
};
friend class ThreadLocalEndpoint;
enum EJoybusCmds
{
CMD_RESET = 0xff,
CMD_STATUS = 0x00,
CMD_READ = 0x14,
CMD_WRITE = 0x15
};
static const u64 BITS_PER_SECOND = 115200;
static const u64 BYTES_PER_SECOND = BITS_PER_SECOND / 8;
net::Socket m_dataSocket;
net::Socket m_clockSocket;
std::thread m_transferThread;
std::mutex m_syncLock;
std::condition_variable m_syncCv;
std::condition_variable m_issueCv;
KawasedoChallenge m_joyBoot;
FGBACallback m_callback;
u8 m_buffer[5];
u8* m_readDstPtr = nullptr;
u8* m_statusPtr = nullptr;
u64 m_lastGCTick = 0;
u8 m_lastCmd = 0;
u8 m_chan;
bool m_booted = false;
bool m_cmdIssued = false;
bool m_running = true;
void clockSync();
void send(const u8* buffer);
size_t receive(u8* buffer);
size_t runBuffer(u8* buffer, std::unique_lock<std::mutex>& lk);
bool idleGetStatus(std::unique_lock<std::mutex>& lk);
void transferProc();
void transferWakeup(ThreadLocalEndpoint& endpoint, u8 status);
auto bindSync()
{
return std::bind(&Endpoint::transferWakeup, this,
std::placeholders::_1, std::placeholders::_2);
} }
public:
KawasedoChallenge() = default;
KawasedoChallenge(s32 paletteColor, s32 paletteSpeed, const u8* programp, s32 length, u8* status,
FGBACallback&& callback);
void start(Endpoint& endpoint);
bool started() const { return m_started; }
u8 percentComplete() const {
if (!x64_totalBytes)
return 0;
return x34_bytesSent * 100 / x64_totalBytes;
}
bool isDone() const { return !x14_callback; }
operator bool() const { return m_initialized; }
};
friend class ThreadLocalEndpoint;
enum EJoybusCmds { CMD_RESET = 0xff, CMD_STATUS = 0x00, CMD_READ = 0x14, CMD_WRITE = 0x15 };
static const u64 BITS_PER_SECOND = 115200;
static const u64 BYTES_PER_SECOND = BITS_PER_SECOND / 8;
net::Socket m_dataSocket;
net::Socket m_clockSocket;
std::thread m_transferThread;
std::mutex m_syncLock;
std::condition_variable m_syncCv;
std::condition_variable m_issueCv;
KawasedoChallenge m_joyBoot;
FGBACallback m_callback;
u8 m_buffer[5];
u8* m_readDstPtr = nullptr;
u8* m_statusPtr = nullptr;
u64 m_lastGCTick = 0;
u8 m_lastCmd = 0;
u8 m_chan;
bool m_booted = false;
bool m_cmdIssued = false;
bool m_running = true;
void clockSync();
void send(const u8* buffer);
size_t receive(u8* buffer);
size_t runBuffer(u8* buffer, std::unique_lock<std::mutex>& lk);
bool idleGetStatus(std::unique_lock<std::mutex>& lk);
void transferProc();
void transferWakeup(ThreadLocalEndpoint& endpoint, u8 status);
auto bindSync() { return std::bind(&Endpoint::transferWakeup, this, std::placeholders::_1, std::placeholders::_2); }
public: public:
/** @brief Request stop of I/O thread and block until joined. /** @brief Request stop of I/O thread and block until joined.
* Further use of this Endpoint will return GBA_NOT_READY. * Further use of this Endpoint will return GBA_NOT_READY.
* The destructor calls this implicitly. */ * The destructor calls this implicitly. */
void stop(); void stop();
/** @brief Get status of last asynchronous operation. /** @brief Get status of last asynchronous operation.
* @param percentOut Reference to output transfer percent of GBAJoyBootAsync. * @param percentOut Reference to output transfer percent of GBAJoyBootAsync.
* @return GBA_READY when idle, or GBA_BUSY when operation in progress. */ * @return GBA_READY when idle, or GBA_BUSY when operation in progress. */
EJoyReturn GBAGetProcessStatus(u8& percentOut); EJoyReturn GBAGetProcessStatus(u8& percentOut);
/** @brief Get JOYSTAT register from GBA asynchronously. /** @brief Get JOYSTAT register from GBA asynchronously.
* @param status Destination pointer for EJStatFlags. * @param status Destination pointer for EJStatFlags.
* @param callback Functor to execute when operation completes. * @param callback Functor to execute when operation completes.
* @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */ * @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */
EJoyReturn GBAGetStatusAsync(u8* status, FGBACallback&& callback); EJoyReturn GBAGetStatusAsync(u8* status, FGBACallback&& callback);
/** @brief Get JOYSTAT register from GBA synchronously. /** @brief Get JOYSTAT register from GBA synchronously.
* @param status Destination pointer for EJStatFlags. * @param status Destination pointer for EJStatFlags.
* @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */ * @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */
EJoyReturn GBAGetStatus(u8* status); EJoyReturn GBAGetStatus(u8* status);
/** @brief Send RESET command to GBA asynchronously. /** @brief Send RESET command to GBA asynchronously.
* @param status Destination pointer for EJStatFlags. * @param status Destination pointer for EJStatFlags.
* @param callback Functor to execute when operation completes. * @param callback Functor to execute when operation completes.
* @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */ * @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */
EJoyReturn GBAResetAsync(u8* status, FGBACallback&& callback); EJoyReturn GBAResetAsync(u8* status, FGBACallback&& callback);
/** @brief Send RESET command to GBA synchronously. /** @brief Send RESET command to GBA synchronously.
* @param status Destination pointer for EJStatFlags. * @param status Destination pointer for EJStatFlags.
* @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */ * @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */
EJoyReturn GBAReset(u8* status); EJoyReturn GBAReset(u8* status);
/** @brief Send READ command to GBA asynchronously. /** @brief Send READ command to GBA asynchronously.
* @param dst Destination pointer for 4-byte packet of data. * @param dst Destination pointer for 4-byte packet of data.
* @param status Destination pointer for EJStatFlags. * @param status Destination pointer for EJStatFlags.
* @param callback Functor to execute when operation completes. * @param callback Functor to execute when operation completes.
* @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */ * @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */
EJoyReturn GBAReadAsync(u8* dst, u8* status, FGBACallback&& callback); EJoyReturn GBAReadAsync(u8* dst, u8* status, FGBACallback&& callback);
/** @brief Send READ command to GBA synchronously. /** @brief Send READ command to GBA synchronously.
* @param dst Destination pointer for 4-byte packet of data. * @param dst Destination pointer for 4-byte packet of data.
* @param status Destination pointer for EJStatFlags. * @param status Destination pointer for EJStatFlags.
* @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */ * @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */
EJoyReturn GBARead(u8* dst, u8* status); EJoyReturn GBARead(u8* dst, u8* status);
/** @brief Send WRITE command to GBA asynchronously. /** @brief Send WRITE command to GBA asynchronously.
* @param src Source pointer for 4-byte packet of data. It is not required to keep resident. * @param src Source pointer for 4-byte packet of data. It is not required to keep resident.
* @param status Destination pointer for EJStatFlags. * @param status Destination pointer for EJStatFlags.
* @param callback Functor to execute when operation completes. * @param callback Functor to execute when operation completes.
* @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */ * @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */
EJoyReturn GBAWriteAsync(const u8* src, u8* status, FGBACallback&& callback); EJoyReturn GBAWriteAsync(const u8* src, u8* status, FGBACallback&& callback);
/** @brief Send WRITE command to GBA synchronously. /** @brief Send WRITE command to GBA synchronously.
* @param src Source pointer for 4-byte packet of data. It is not required to keep resident. * @param src Source pointer for 4-byte packet of data. It is not required to keep resident.
* @param status Destination pointer for EJStatFlags. * @param status Destination pointer for EJStatFlags.
* @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */ * @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */
EJoyReturn GBAWrite(const u8* src, u8* status); EJoyReturn GBAWrite(const u8* src, u8* status);
/** @brief Initiate JoyBoot sequence on this endpoint. /** @brief Initiate JoyBoot sequence on this endpoint.
* @param paletteColor Palette for displaying logo in ROM header [0,6]. * @param paletteColor Palette for displaying logo in ROM header [0,6].
* @param paletteSpeed Palette interpolation speed for displaying logo in ROM header [-4,4]. * @param paletteSpeed Palette interpolation speed for displaying logo in ROM header [-4,4].
* @param programp Pointer to program ROM data. * @param programp Pointer to program ROM data.
* @param length Length of program ROM data. * @param length Length of program ROM data.
* @param status Destination pointer for EJStatFlags. * @param status Destination pointer for EJStatFlags.
* @param callback Functor to execute when operation completes. * @param callback Functor to execute when operation completes.
* @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */ * @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */
EJoyReturn GBAJoyBootAsync(s32 paletteColor, s32 paletteSpeed, EJoyReturn GBAJoyBootAsync(s32 paletteColor, s32 paletteSpeed, const u8* programp, s32 length, u8* status,
const u8* programp, s32 length, u8* status, FGBACallback&& callback);
FGBACallback&& callback);
/** @brief Get virtual SI channel assigned to this endpoint. /** @brief Get virtual SI channel assigned to this endpoint.
* @return SI channel [0,3] */ * @return SI channel [0,3] */
unsigned getChan() const { return m_chan; } unsigned getChan() const { return m_chan; }
/** @brief Set virtual SI channel assigned to this endpoint. /** @brief Set virtual SI channel assigned to this endpoint.
* @param chan SI channel [0,3] */ * @param chan SI channel [0,3] */
void setChan(unsigned chan) void setChan(unsigned chan) {
{ if (chan > 3)
if (chan > 3) chan = 3;
chan = 3; m_chan = chan;
m_chan = chan; }
}
/** @brief Get connection status of this endpoint /** @brief Get connection status of this endpoint
* @return true if connected */ * @return true if connected */
bool connected() const { return m_running; } bool connected() const { return m_running; }
Endpoint(u8 chan, net::Socket&& data, net::Socket&& clock); Endpoint(u8 chan, net::Socket&& data, net::Socket&& clock);
~Endpoint(); ~Endpoint();
}; };
/** Lockless wrapper interface for jbus::Endpoint. /** Lockless wrapper interface for jbus::Endpoint.
* This class is constructed internally and supplied as a callback argument. * This class is constructed internally and supplied as a callback argument.
* It should not be constructed directly. */ * It should not be constructed directly. */
class ThreadLocalEndpoint class ThreadLocalEndpoint {
{ friend class Endpoint;
friend class Endpoint; Endpoint& m_ep;
Endpoint& m_ep; ThreadLocalEndpoint(Endpoint& ep) : m_ep(ep) {}
ThreadLocalEndpoint(Endpoint& ep) : m_ep(ep) {}
public: public:
/** @brief Get JOYSTAT register from GBA asynchronously. /** @brief Get JOYSTAT register from GBA asynchronously.
* @param status Destination pointer for EJStatFlags. * @param status Destination pointer for EJStatFlags.
* @param callback Functor to execute when operation completes. * @param callback Functor to execute when operation completes.
* @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */ * @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */
EJoyReturn GBAGetStatusAsync(u8* status, FGBACallback&& callback); EJoyReturn GBAGetStatusAsync(u8* status, FGBACallback&& callback);
/** @brief Send RESET command to GBA asynchronously. /** @brief Send RESET command to GBA asynchronously.
* @param status Destination pointer for EJStatFlags. * @param status Destination pointer for EJStatFlags.
* @param callback Functor to execute when operation completes. * @param callback Functor to execute when operation completes.
* @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */ * @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */
EJoyReturn GBAResetAsync(u8* status, FGBACallback&& callback); EJoyReturn GBAResetAsync(u8* status, FGBACallback&& callback);
/** @brief Send READ command to GBA asynchronously. /** @brief Send READ command to GBA asynchronously.
* @param dst Destination pointer for 4-byte packet of data. * @param dst Destination pointer for 4-byte packet of data.
* @param status Destination pointer for EJStatFlags. * @param status Destination pointer for EJStatFlags.
* @param callback Functor to execute when operation completes. * @param callback Functor to execute when operation completes.
* @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */ * @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */
EJoyReturn GBAReadAsync(u8* dst, u8* status, FGBACallback&& callback); EJoyReturn GBAReadAsync(u8* dst, u8* status, FGBACallback&& callback);
/** @brief Send WRITE command to GBA asynchronously. /** @brief Send WRITE command to GBA asynchronously.
* @param src Source pointer for 4-byte packet of data. It is not required to keep resident. * @param src Source pointer for 4-byte packet of data. It is not required to keep resident.
* @param status Destination pointer for EJStatFlags. * @param status Destination pointer for EJStatFlags.
* @param callback Functor to execute when operation completes. * @param callback Functor to execute when operation completes.
* @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */ * @return GBA_READY if submitted, or GBA_NOT_READY if another operation in progress. */
EJoyReturn GBAWriteAsync(const u8* src, u8* status, FGBACallback&& callback); EJoyReturn GBAWriteAsync(const u8* src, u8* status, FGBACallback&& callback);
/** @brief Get virtual SI channel assigned to this endpoint. /** @brief Get virtual SI channel assigned to this endpoint.
* @return SI channel */ * @return SI channel */
int getChan() const { return m_ep.getChan(); } int getChan() const { return m_ep.getChan(); }
}; };
} } // namespace jbus

View File

@ -6,37 +6,34 @@
#include <queue> #include <queue>
#include <mutex> #include <mutex>
namespace jbus namespace jbus {
{
/** Server interface for accepting incoming connections from GBA emulator instances. */ /** Server interface for accepting incoming connections from GBA emulator instances. */
class Listener class Listener {
{ net::Socket m_dataServer = {false};
net::Socket m_dataServer = {false}; net::Socket m_clockServer = {false};
net::Socket m_clockServer = {false}; std::thread m_listenerThread;
std::thread m_listenerThread; std::mutex m_queueLock;
std::mutex m_queueLock; std::queue<std::unique_ptr<Endpoint>> m_endpointQueue;
std::queue<std::unique_ptr<Endpoint>> m_endpointQueue; bool m_running = false;
bool m_running = false;
static const uint32_t DataPort = 0xd6ba; static const uint32_t DataPort = 0xd6ba;
static const uint32_t ClockPort = 0xc10c; static const uint32_t ClockPort = 0xc10c;
void listenerProc(); void listenerProc();
public: public:
/** @brief Start listener thread. */ /** @brief Start listener thread. */
void start(); void start();
/** @brief Request stop of listener thread and block until joined. */ /** @brief Request stop of listener thread and block until joined. */
void stop(); void stop();
/** @brief Pop jbus::Endpoint off Listener's queue. /** @brief Pop jbus::Endpoint off Listener's queue.
* @return Endpoint instance, ready to issue commands. */ * @return Endpoint instance, ready to issue commands. */
std::unique_ptr<Endpoint> accept(); std::unique_ptr<Endpoint> accept();
~Listener(); ~Listener();
}; };
} } // namespace jbus

View File

@ -14,89 +14,71 @@ typedef UINT_PTR SOCKET;
struct sockaddr_in; struct sockaddr_in;
namespace jbus::net namespace jbus::net {
{
/** IP address class derived from SFML */ /** IP address class derived from SFML */
class IPAddress class IPAddress {
{ uint32_t m_address = 0;
uint32_t m_address = 0; bool m_valid = false;
bool m_valid = false;
void resolve(const std::string& address); void resolve(const std::string& address);
public: public:
IPAddress(const std::string& address) IPAddress(const std::string& address) { resolve(address); }
{
resolve(address);
}
uint32_t toInteger() const; uint32_t toInteger() const;
operator bool() const { return m_valid; } operator bool() const { return m_valid; }
}; };
/** Server-oriented TCP socket class derived from SFML */ /** Server-oriented TCP socket class derived from SFML */
class Socket class Socket {
{
#ifndef _WIN32 #ifndef _WIN32
using SocketTp = int; using SocketTp = int;
#else #else
using SocketTp = SOCKET; using SocketTp = SOCKET;
#endif #endif
SocketTp m_socket = -1; SocketTp m_socket = -1;
bool m_isBlocking; bool m_isBlocking;
bool openSocket(); bool openSocket();
void setRemoteSocket(int remSocket); void setRemoteSocket(int remSocket);
public: public:
enum class EResult enum class EResult { OK, Error, Busy };
{
OK,
Error,
Busy
};
#ifdef _WIN32 #ifdef _WIN32
static EResult LastWSAError(); static EResult LastWSAError();
#endif #endif
Socket(bool blocking) Socket(bool blocking) : m_isBlocking(blocking) {}
: m_isBlocking(blocking) {} ~Socket() { close(); }
~Socket() { close(); }
Socket(const Socket& other) = delete; Socket(const Socket& other) = delete;
Socket& operator=(const Socket& other) = delete; Socket& operator=(const Socket& other) = delete;
Socket(Socket&& other) Socket(Socket&& other) : m_socket(other.m_socket), m_isBlocking(other.m_isBlocking) { other.m_socket = -1; }
: m_socket(other.m_socket), m_isBlocking(other.m_isBlocking) Socket& operator=(Socket&& other) {
{ close();
other.m_socket = -1; m_socket = other.m_socket;
} other.m_socket = -1;
Socket& operator=(Socket&& other) m_isBlocking = other.m_isBlocking;
{ return *this;
close(); }
m_socket = other.m_socket;
other.m_socket = -1;
m_isBlocking = other.m_isBlocking;
return *this;
}
void setBlocking(bool blocking); void setBlocking(bool blocking);
bool isOpen() const { return m_socket != -1; } bool isOpen() const { return m_socket != -1; }
bool openAndListen(const IPAddress& address, uint32_t port); bool openAndListen(const IPAddress& address, uint32_t port);
EResult accept(Socket& remoteSocketOut, sockaddr_in& fromAddress); EResult accept(Socket& remoteSocketOut, sockaddr_in& fromAddress);
EResult accept(Socket& remoteSocketOut); EResult accept(Socket& remoteSocketOut);
EResult accept(Socket& remoteSocketOut, std::string& fromHostname); EResult accept(Socket& remoteSocketOut, std::string& fromHostname);
void close(); void close();
EResult send(const void* buf, size_t len, size_t& transferred); EResult send(const void* buf, size_t len, size_t& transferred);
EResult send(const void* buf, size_t len); EResult send(const void* buf, size_t len);
EResult recv(void* buf, size_t len, size_t& transferred); EResult recv(void* buf, size_t len, size_t& transferred);
EResult recv(void* buf, size_t len); EResult recv(void* buf, size_t len);
operator bool() const { return isOpen(); } operator bool() const { return isOpen(); }
SocketTp GetInternalSocket() const { return m_socket; } SocketTp GetInternalSocket() const { return m_socket; }
}; };
} } // namespace jbus::net

File diff suppressed because it is too large Load Diff

View File

@ -14,8 +14,7 @@
#include "jbus/Common.hpp" #include "jbus/Common.hpp"
namespace jbus namespace jbus {
{
#if __APPLE__ #if __APPLE__
static u64 MachToDolphinNum; static u64 MachToDolphinNum;
@ -24,61 +23,56 @@ static u64 MachToDolphinDenom;
static LARGE_INTEGER PerfFrequency; static LARGE_INTEGER PerfFrequency;
#endif #endif
u64 GetGCTicks() u64 GetGCTicks() {
{
#if __APPLE__ #if __APPLE__
return mach_absolute_time() * MachToDolphinNum / MachToDolphinDenom; return mach_absolute_time() * MachToDolphinNum / MachToDolphinDenom;
#elif __linux__ || __FreeBSD__ #elif __linux__ || __FreeBSD__
struct timespec tp; struct timespec tp;
clock_gettime(CLOCK_MONOTONIC, &tp); clock_gettime(CLOCK_MONOTONIC, &tp);
return u64((tp.tv_sec * 1000000000ull) + tp.tv_nsec) * GetGCTicksPerSec() / 1000000000ull; return u64((tp.tv_sec * 1000000000ull) + tp.tv_nsec) * GetGCTicksPerSec() / 1000000000ull;
#elif _WIN32 #elif _WIN32
LARGE_INTEGER perf; LARGE_INTEGER perf;
QueryPerformanceCounter(&perf); QueryPerformanceCounter(&perf);
perf.QuadPart *= GetGCTicksPerSec(); perf.QuadPart *= GetGCTicksPerSec();
perf.QuadPart /= PerfFrequency.QuadPart; perf.QuadPart /= PerfFrequency.QuadPart;
return perf.QuadPart; return perf.QuadPart;
#else #else
return 0; return 0;
#endif #endif
} }
void WaitGCTicks(u64 ticks) void WaitGCTicks(u64 ticks) {
{
#ifndef _WIN32 #ifndef _WIN32
struct timeval tv = {}; struct timeval tv = {};
tv.tv_sec = ticks / GetGCTicksPerSec(); tv.tv_sec = ticks / GetGCTicksPerSec();
tv.tv_usec = (ticks % GetGCTicksPerSec()) * 1000000 / GetGCTicksPerSec(); tv.tv_usec = (ticks % GetGCTicksPerSec()) * 1000000 / GetGCTicksPerSec();
select(0, NULL, NULL, NULL, &tv); select(0, NULL, NULL, NULL, &tv);
#else #else
if (ticks < GetGCTicksPerSec() / 60) if (ticks < GetGCTicksPerSec() / 60) {
{ /* NT is useless for scheduling sub-millisecond intervals */
/* NT is useless for scheduling sub-millisecond intervals */ u64 start = GetGCTicks();
u64 start = GetGCTicks(); do {
do { Sleep(0); } while (GetGCTicks() - start < ticks); Sleep(0);
} } while (GetGCTicks() - start < ticks);
else } else {
{ /* Use normal Sleep() for durations longer than ~16ms */
/* Use normal Sleep() for durations longer than ~16ms */ Sleep(ticks * 1000 / GetGCTicksPerSec() + (ticks % GetGCTicksPerSec()) * 1000 / GetGCTicksPerSec());
Sleep(ticks * 1000 / GetGCTicksPerSec() + }
(ticks % GetGCTicksPerSec()) * 1000 / GetGCTicksPerSec());
}
#endif #endif
} }
void Initialize() void Initialize() {
{
#if __APPLE__ #if __APPLE__
mach_timebase_info_data_t timebase; mach_timebase_info_data_t timebase;
mach_timebase_info(&timebase); mach_timebase_info(&timebase);
MachToDolphinNum = GetGCTicksPerSec() * timebase.numer; MachToDolphinNum = GetGCTicksPerSec() * timebase.numer;
MachToDolphinDenom = 1000000000ull * timebase.denom; MachToDolphinDenom = 1000000000ull * timebase.denom;
#elif _WIN32 #elif _WIN32
WSADATA initData; WSADATA initData;
WSAStartup(MAKEWORD(2, 2), &initData); WSAStartup(MAKEWORD(2, 2), &initData);
QueryPerformanceFrequency(&PerfFrequency); QueryPerformanceFrequency(&PerfFrequency);
#endif #endif
} }
} } // namespace jbus

File diff suppressed because it is too large Load Diff

View File

@ -3,117 +3,97 @@
#define LOG_LISTENER 0 #define LOG_LISTENER 0
namespace jbus namespace jbus {
{
void Listener::listenerProc() void Listener::listenerProc() {
{
#if LOG_LISTENER #if LOG_LISTENER
printf("JoyBus listener started\n"); printf("JoyBus listener started\n");
#endif #endif
net::IPAddress localhost("127.0.0.1"); net::IPAddress localhost("127.0.0.1");
bool dataBound = false; bool dataBound = false;
bool clockBound = false; bool clockBound = false;
while (m_running && (!dataBound || !clockBound)) while (m_running && (!dataBound || !clockBound)) {
{ if (!dataBound) {
if (!dataBound) if (!(dataBound = m_dataServer.openAndListen(localhost, DataPort))) {
{ m_dataServer = net::Socket(false);
if (!(dataBound = m_dataServer.openAndListen(localhost, DataPort)))
{
m_dataServer = net::Socket(false);
#if LOG_LISTENER #if LOG_LISTENER
printf("data open failed %s; will retry\n", strerror(errno)); printf("data open failed %s; will retry\n", strerror(errno));
#endif #endif
WaitGCTicks(GetGCTicksPerSec());
}
else
{
#if LOG_LISTENER
printf("data listening on port %d\n", DataPort);
#endif
}
}
if (!clockBound)
{
if (!(clockBound = m_clockServer.openAndListen(localhost, ClockPort)))
{
m_clockServer = net::Socket(false);
#if LOG_LISTENER
printf("clock open failed %s; will retry\n", strerror(errno));
#endif
WaitGCTicks(GetGCTicksPerSec());
}
else
{
#if LOG_LISTENER
printf("clock listening on port %d\n", ClockPort);
#endif
}
}
}
/* We use blocking I/O since we have a dedicated transfer thread */
net::Socket acceptData = {true};
net::Socket acceptClock = {true};
std::string hostname;
while (m_running)
{
if (m_dataServer.accept(acceptData, hostname) == net::Socket::EResult::OK)
{
#if LOG_LISTENER
printf("accepted data connection from %s\n", hostname.c_str());
#endif
}
if (m_clockServer.accept(acceptClock, hostname) == net::Socket::EResult::OK)
{
#if LOG_LISTENER
printf("accepted clock connection from %s\n", hostname.c_str());
#endif
}
if (acceptData && acceptClock)
{
std::unique_lock<std::mutex> lk(m_queueLock);
m_endpointQueue.push(std::make_unique<Endpoint>(
0, std::move(acceptData), std::move(acceptClock)));
}
WaitGCTicks(GetGCTicksPerSec()); WaitGCTicks(GetGCTicksPerSec());
} } else {
m_dataServer.close();
m_clockServer.close();
#if LOG_LISTENER #if LOG_LISTENER
printf("JoyBus listener stopped\n"); printf("data listening on port %d\n", DataPort);
#endif
}
}
if (!clockBound) {
if (!(clockBound = m_clockServer.openAndListen(localhost, ClockPort))) {
m_clockServer = net::Socket(false);
#if LOG_LISTENER
printf("clock open failed %s; will retry\n", strerror(errno));
#endif
WaitGCTicks(GetGCTicksPerSec());
} else {
#if LOG_LISTENER
printf("clock listening on port %d\n", ClockPort);
#endif
}
}
}
/* We use blocking I/O since we have a dedicated transfer thread */
net::Socket acceptData = {true};
net::Socket acceptClock = {true};
std::string hostname;
while (m_running) {
if (m_dataServer.accept(acceptData, hostname) == net::Socket::EResult::OK) {
#if LOG_LISTENER
printf("accepted data connection from %s\n", hostname.c_str());
#endif
}
if (m_clockServer.accept(acceptClock, hostname) == net::Socket::EResult::OK) {
#if LOG_LISTENER
printf("accepted clock connection from %s\n", hostname.c_str());
#endif
}
if (acceptData && acceptClock) {
std::unique_lock<std::mutex> lk(m_queueLock);
m_endpointQueue.push(std::make_unique<Endpoint>(0, std::move(acceptData), std::move(acceptClock)));
}
WaitGCTicks(GetGCTicksPerSec());
}
m_dataServer.close();
m_clockServer.close();
#if LOG_LISTENER
printf("JoyBus listener stopped\n");
#endif #endif
} }
void Listener::start() void Listener::start() {
{ stop();
stop(); m_running = true;
m_running = true; m_listenerThread = std::thread(std::bind(&Listener::listenerProc, this));
m_listenerThread = std::thread(std::bind(&Listener::listenerProc, this));
} }
void Listener::stop() void Listener::stop() {
{ m_running = false;
m_running = false; if (m_listenerThread.joinable())
if (m_listenerThread.joinable()) m_listenerThread.join();
m_listenerThread.join();
} }
std::unique_ptr<Endpoint> Listener::accept() std::unique_ptr<Endpoint> Listener::accept() {
{ std::unique_lock<std::mutex> lk(m_queueLock);
std::unique_lock<std::mutex> lk(m_queueLock); if (m_endpointQueue.size()) {
if (m_endpointQueue.size()) std::unique_ptr<Endpoint> ret;
{ ret = std::move(m_endpointQueue.front());
std::unique_ptr<Endpoint> ret; m_endpointQueue.pop();
ret = std::move(m_endpointQueue.front()); return ret;
m_endpointQueue.pop(); }
return ret; return {};
}
return {};
} }
Listener::~Listener() { stop(); } Listener::~Listener() { stop(); }
} } // namespace jbus

View File

@ -13,8 +13,7 @@
#include <Ws2tcpip.h> #include <Ws2tcpip.h>
#endif #endif
namespace jbus::net namespace jbus::net {
{
/* Define the low-level send/receive flags, which depend on the OS */ /* Define the low-level send/receive flags, which depend on the OS */
#ifdef __linux__ #ifdef __linux__
@ -23,282 +22,248 @@ static const int _flags = MSG_NOSIGNAL;
static const int _flags = 0; static const int _flags = 0;
#endif #endif
void IPAddress::resolve(const std::string& address) void IPAddress::resolve(const std::string& address) {
{ m_address = 0;
m_address = 0; m_valid = false;
m_valid = false;
if (address == "255.255.255.255") if (address == "255.255.255.255") {
{ /* The broadcast address needs to be handled explicitly,
/* The broadcast address needs to be handled explicitly, * because it is also the value returned by inet_addr on error */
* because it is also the value returned by inet_addr on error */ m_address = INADDR_BROADCAST;
m_address = INADDR_BROADCAST; m_valid = true;
m_valid = true; } else if (address == "0.0.0.0") {
} m_address = INADDR_ANY;
else if (address == "0.0.0.0") m_valid = true;
{ } else {
m_address = INADDR_ANY; /* Try to convert the address as a byte representation ("xxx.xxx.xxx.xxx") */
m_valid = true; struct in_addr addr;
} if (inet_pton(AF_INET, address.c_str(), &addr) == 1) {
else m_address = addr.s_addr;
{ m_valid = true;
/* Try to convert the address as a byte representation ("xxx.xxx.xxx.xxx") */ } else {
struct in_addr addr; /* Not a valid address, try to convert it as a host name */
if (inet_pton(AF_INET, address.c_str(), &addr) == 1) addrinfo hints;
{ memset(&hints, 0, sizeof(hints));
m_address = addr.s_addr; hints.ai_family = AF_INET;
m_valid = true; addrinfo* result = NULL;
} if (getaddrinfo(address.c_str(), NULL, &hints, &result) == 0) {
else if (result) {
{ addr = reinterpret_cast<sockaddr_in*>(result->ai_addr)->sin_addr;
/* Not a valid address, try to convert it as a host name */ freeaddrinfo(result);
addrinfo hints; m_address = addr.s_addr;
memset(&hints, 0, sizeof(hints)); m_valid = true;
hints.ai_family = AF_INET;
addrinfo* result = NULL;
if (getaddrinfo(address.c_str(), NULL, &hints, &result) == 0)
{
if (result)
{
addr = reinterpret_cast<sockaddr_in*>(result->ai_addr)->sin_addr;
freeaddrinfo(result);
m_address = addr.s_addr;
m_valid = true;
}
}
} }
}
} }
}
} }
uint32_t IPAddress::toInteger() const uint32_t IPAddress::toInteger() const { return ntohl(m_address); }
{
return ntohl(m_address);
}
static sockaddr_in createAddress(uint32_t address, unsigned short port) static sockaddr_in createAddress(uint32_t address, unsigned short port) {
{ sockaddr_in addr;
sockaddr_in addr; memset(&addr, 0, sizeof(addr));
memset(&addr, 0, sizeof(addr)); addr.sin_addr.s_addr = htonl(address);
addr.sin_addr.s_addr = htonl(address); addr.sin_family = AF_INET;
addr.sin_family = AF_INET; addr.sin_port = htons(port);
addr.sin_port = htons(port);
#ifdef __APPLE__ #ifdef __APPLE__
addr.sin_len = sizeof(addr); addr.sin_len = sizeof(addr);
#endif #endif
return addr; return addr;
} }
bool Socket::openSocket() bool Socket::openSocket() {
{ if (isOpen())
if (isOpen()) return false;
return false;
m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (m_socket == -1) if (m_socket == -1) {
{ fprintf(stderr, "Can't allocate socket\n");
fprintf(stderr, "Can't allocate socket\n"); return false;
return false; }
}
int one = 1; int one = 1;
setsockopt(m_socket, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char*>(&one), sizeof(one)); setsockopt(m_socket, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char*>(&one), sizeof(one));
#ifdef __APPLE__ #ifdef __APPLE__
setsockopt(m_socket, SOL_SOCKET, SO_NOSIGPIPE, reinterpret_cast<char*>(&one), sizeof(one)); setsockopt(m_socket, SOL_SOCKET, SO_NOSIGPIPE, reinterpret_cast<char*>(&one), sizeof(one));
#endif #endif
setBlocking(m_isBlocking); setBlocking(m_isBlocking);
return true; return true;
} }
void Socket::setRemoteSocket(int remSocket) void Socket::setRemoteSocket(int remSocket) {
{ close();
close(); m_socket = remSocket;
m_socket = remSocket; setBlocking(m_isBlocking);
setBlocking(m_isBlocking);
} }
#ifdef _WIN32 #ifdef _WIN32
Socket::EResult Socket::LastWSAError() Socket::EResult Socket::LastWSAError() {
{ switch (WSAGetLastError()) {
switch (WSAGetLastError()) case WSAEWOULDBLOCK:
{ case WSAEALREADY:
case WSAEWOULDBLOCK: return EResult::Busy;
case WSAEALREADY: default:
return EResult::Busy; return EResult::Error;
default: }
return EResult::Error;
}
} }
#endif #endif
void Socket::setBlocking(bool blocking) void Socket::setBlocking(bool blocking) {
{ m_isBlocking = blocking;
m_isBlocking = blocking;
#ifndef _WIN32 #ifndef _WIN32
int status = fcntl(m_socket, F_GETFL); int status = fcntl(m_socket, F_GETFL);
if (m_isBlocking) if (m_isBlocking)
fcntl(m_socket, F_SETFL, status & ~O_NONBLOCK); fcntl(m_socket, F_SETFL, status & ~O_NONBLOCK);
else else
fcntl(m_socket, F_SETFL, status | O_NONBLOCK); fcntl(m_socket, F_SETFL, status | O_NONBLOCK);
#else #else
u_long b = blocking ? 0 : 1; u_long b = blocking ? 0 : 1;
ioctlsocket(m_socket, FIONBIO, &b); ioctlsocket(m_socket, FIONBIO, &b);
#endif #endif
} }
bool Socket::openAndListen(const IPAddress& address, uint32_t port) bool Socket::openAndListen(const IPAddress& address, uint32_t port) {
{ if (!openSocket())
if (!openSocket()) return false;
return false;
sockaddr_in addr = createAddress(address.toInteger(), port); sockaddr_in addr = createAddress(address.toInteger(), port);
if (bind(m_socket, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == -1) if (bind(m_socket, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == -1) {
{ /* Not likely to happen, but... */
/* Not likely to happen, but... */ // fprintf(stderr, "Failed to bind listener socket to port %d\n", port);
//fprintf(stderr, "Failed to bind listener socket to port %d\n", port); return false;
return false; }
}
if (::listen(m_socket, 0) == -1) if (::listen(m_socket, 0) == -1) {
{ /* Oops, socket is deaf */
/* Oops, socket is deaf */ // fprintf(stderr, "Failed to listen to port %d\n", port);
//fprintf(stderr, "Failed to listen to port %d\n", port); return false;
return false; }
}
return true; return true;
} }
Socket::EResult Socket::accept(Socket& remoteSocketOut, sockaddr_in& fromAddress) Socket::EResult Socket::accept(Socket& remoteSocketOut, sockaddr_in& fromAddress) {
{ if (!isOpen())
if (!isOpen()) return EResult::Error;
return EResult::Error;
/* Accept a new connection */ /* Accept a new connection */
socklen_t length = sizeof(sockaddr_in); socklen_t length = sizeof(sockaddr_in);
int remoteSocket = ::accept(m_socket, reinterpret_cast<sockaddr*>(&fromAddress), &length); int remoteSocket = ::accept(m_socket, reinterpret_cast<sockaddr*>(&fromAddress), &length);
/* Check for errors */
if (remoteSocket == -1) {
#ifndef _WIN32
EResult res = (errno == EAGAIN) ? EResult::Busy : EResult::Error;
if (res == EResult::Error)
fprintf(stderr, "Failed to accept incoming connection: %s\n", strerror(errno));
#else
EResult res = LastWSAError();
if (res == EResult::Error)
fprintf(stderr, "Failed to accept incoming connection");
#endif
return res;
}
/* Initialize the new connected socket */
remoteSocketOut.setRemoteSocket(remoteSocket);
return EResult::OK;
}
Socket::EResult Socket::accept(Socket& remoteSocketOut) {
sockaddr_in fromAddress;
return accept(remoteSocketOut, fromAddress);
}
Socket::EResult Socket::accept(Socket& remoteSocketOut, std::string& fromHostname) {
sockaddr_in fromAddress;
socklen_t len = sizeof(fromAddress);
char name[NI_MAXHOST];
EResult res = accept(remoteSocketOut, fromAddress);
if (res == EResult::OK)
if (getnameinfo((sockaddr*)&fromAddress, len, name, NI_MAXHOST, nullptr, 0, 0) == 0)
fromHostname.assign(name);
return res;
}
void Socket::close() {
if (!isOpen())
return;
#ifndef _WIN32
::close(m_socket);
#else
closesocket(m_socket);
#endif
m_socket = -1;
}
Socket::EResult Socket::send(const void* buf, size_t len, size_t& transferred) {
transferred = 0;
if (!isOpen())
return EResult::Error;
if (!buf || !len)
return EResult::Error;
/* Loop until every byte has been sent */
int result = 0;
for (size_t sent = 0; sent < len; sent += result) {
/* Send a chunk of data */
result = ::send(m_socket, static_cast<const char*>(buf) + sent, len - sent, _flags);
/* Check for errors */ /* Check for errors */
if (remoteSocket == -1)
{
#ifndef _WIN32
EResult res = (errno == EAGAIN) ? EResult::Busy : EResult::Error;
if (res == EResult::Error)
fprintf(stderr, "Failed to accept incoming connection: %s\n", strerror(errno));
#else
EResult res = LastWSAError();
if (res == EResult::Error)
fprintf(stderr, "Failed to accept incoming connection");
#endif
return res;
}
/* Initialize the new connected socket */
remoteSocketOut.setRemoteSocket(remoteSocket);
return EResult::OK;
}
Socket::EResult Socket::accept(Socket& remoteSocketOut)
{
sockaddr_in fromAddress;
return accept(remoteSocketOut, fromAddress);
}
Socket::EResult Socket::accept(Socket& remoteSocketOut, std::string& fromHostname)
{
sockaddr_in fromAddress;
socklen_t len = sizeof(fromAddress);
char name[NI_MAXHOST];
EResult res = accept(remoteSocketOut, fromAddress);
if (res == EResult::OK)
if (getnameinfo((sockaddr*)&fromAddress, len, name, NI_MAXHOST, nullptr, 0, 0) == 0)
fromHostname.assign(name);
return res;
}
void Socket::close()
{
if (!isOpen())
return;
#ifndef _WIN32
::close(m_socket);
#else
closesocket(m_socket);
#endif
m_socket = -1;
}
Socket::EResult Socket::send(const void* buf, size_t len, size_t& transferred)
{
transferred = 0;
if (!isOpen())
return EResult::Error;
if (!buf || !len)
return EResult::Error;
/* Loop until every byte has been sent */
int result = 0;
for (size_t sent = 0; sent < len; sent += result)
{
/* Send a chunk of data */
result = ::send(m_socket, static_cast<const char*>(buf) + sent, len - sent, _flags);
/* Check for errors */
if (result < 0)
#ifndef _WIN32
return (errno == EAGAIN) ? EResult::Busy : EResult::Error;
#else
return LastWSAError();
#endif
}
transferred = len;
return EResult::OK;
}
Socket::EResult Socket::send(const void* buf, size_t len)
{
size_t transferred;
return send(buf, len, transferred);
}
Socket::EResult Socket::recv(void* buf, size_t len, size_t& transferred)
{
transferred = 0;
if (!isOpen())
return EResult::Error;
if (!buf)
return EResult::Error;
if (!len)
return EResult::OK;
/* Receive a chunk of bytes */
int result = ::recv(m_socket, static_cast<char*>(buf), static_cast<int>(len), _flags);
if (result < 0) if (result < 0)
#ifndef _WIN32 #ifndef _WIN32
return (errno == EAGAIN) ? EResult::Busy : EResult::Error; return (errno == EAGAIN) ? EResult::Busy : EResult::Error;
#else #else
return LastWSAError(); return LastWSAError();
#endif #endif
else if (result == 0) }
return EResult::Error;
transferred = result; transferred = len;
return EResult::OK;
}
Socket::EResult Socket::send(const void* buf, size_t len) {
size_t transferred;
return send(buf, len, transferred);
}
Socket::EResult Socket::recv(void* buf, size_t len, size_t& transferred) {
transferred = 0;
if (!isOpen())
return EResult::Error;
if (!buf)
return EResult::Error;
if (!len)
return EResult::OK; return EResult::OK;
/* Receive a chunk of bytes */
int result = ::recv(m_socket, static_cast<char*>(buf), static_cast<int>(len), _flags);
if (result < 0)
#ifndef _WIN32
return (errno == EAGAIN) ? EResult::Busy : EResult::Error;
#else
return LastWSAError();
#endif
else if (result == 0)
return EResult::Error;
transferred = result;
return EResult::OK;
} }
Socket::EResult Socket::recv(void* buf, size_t len) Socket::EResult Socket::recv(void* buf, size_t len) {
{ size_t transferred;
size_t transferred; return recv(buf, len, transferred);
return recv(buf, len, transferred);
} }
} } // namespace jbus::net

View File

@ -3,128 +3,111 @@
#include "jbus/Endpoint.hpp" #include "jbus/Endpoint.hpp"
#include <functional> #include <functional>
static void clientPadComplimentCheck(jbus::u8* buffer) static void clientPadComplimentCheck(jbus::u8* buffer) {
{ jbus::u8 check = 0x19;
jbus::u8 check = 0x19; for (int i = 0xa0; i < 0xbd; ++i)
for (int i=0xa0 ; i<0xbd ; ++i) check += buffer[i];
check += buffer[i]; buffer[0xbd] = -check;
buffer[0xbd] = -check;
} }
static jbus::EJoyReturn BootStatus = jbus::GBA_JOYBOOT_ERR_INVALID; static jbus::EJoyReturn BootStatus = jbus::GBA_JOYBOOT_ERR_INVALID;
static void JoyBootDone(jbus::ThreadLocalEndpoint& endpoint, jbus::EJoyReturn status) static void JoyBootDone(jbus::ThreadLocalEndpoint& endpoint, jbus::EJoyReturn status) { BootStatus = status; }
{
BootStatus = status;
}
static bool DonePoll(jbus::Endpoint& endpoint) static bool DonePoll(jbus::Endpoint& endpoint) {
{ jbus::u8 status;
jbus::u8 status; if (endpoint.GBAReset(&status) == jbus::GBA_NOT_READY)
if (endpoint.GBAReset(&status) == jbus::GBA_NOT_READY) if (endpoint.GBAReset(&status) == jbus::GBA_NOT_READY)
if (endpoint.GBAReset(&status) == jbus::GBA_NOT_READY) return false;
return false;
if (endpoint.GBAGetStatus(&status) == jbus::GBA_NOT_READY) if (endpoint.GBAGetStatus(&status) == jbus::GBA_NOT_READY)
return false; return false;
if (status != (jbus::GBA_JSTAT_PSF1 | jbus::GBA_JSTAT_SEND)) if (status != (jbus::GBA_JSTAT_PSF1 | jbus::GBA_JSTAT_SEND))
return false; return false;
return true; return true;
} }
int main(int argc, char** argv) int main(int argc, char** argv) {
{ if (argc < 2) {
if (argc < 2) printf("Usage: joyboot <client_pad.bin>\n");
{ return 1;
printf("Usage: joyboot <client_pad.bin>\n"); }
return 1;
FILE* fp = fopen(argv[1], "rb");
if (!fp) {
fprintf(stderr, "Unable to open %s\n", argv[1]);
return 1;
}
fseek(fp, 0, SEEK_END);
int fsize = ftell(fp);
fseek(fp, 0, SEEK_SET);
std::unique_ptr<jbus::u8[]> data(new jbus::u8[fsize]);
fread(data.get(), 1, fsize, fp);
fclose(fp);
if (fsize < 512) {
fprintf(stderr, "%s must be at least 512 bytes\n", argv[1]);
return 1;
}
clientPadComplimentCheck(data.get());
jbus::Initialize();
printf("Listening for client\n");
jbus::Listener listener;
listener.start();
std::unique_ptr<jbus::Endpoint> endpoint;
while (true) {
jbus::s64 frameStart = jbus::GetGCTicks();
endpoint = listener.accept();
if (endpoint)
break;
jbus::s64 frameEnd = jbus::GetGCTicks();
jbus::s64 passedTicks = frameEnd - frameStart;
jbus::s64 waitTicks = jbus::GetGCTicksPerSec() / 60 - passedTicks;
if (waitTicks > 0)
jbus::WaitGCTicks(waitTicks);
}
printf("Waiting 4 sec\n");
jbus::WaitGCTicks(jbus::GetGCTicksPerSec() * 4);
jbus::u8 status;
if (endpoint->GBAJoyBootAsync(2, 2, data.get(), fsize, &status,
std::bind(JoyBootDone, std::placeholders::_1, std::placeholders::_2)) !=
jbus::GBA_READY) {
fprintf(stderr, "Unable to start JoyBoot\n");
return 1;
}
jbus::s64 start = jbus::GetGCTicks();
jbus::u8 percent = 0;
jbus::u8 lastpercent = 0;
while (endpoint->GBAGetProcessStatus(percent) == jbus::GBA_BUSY) {
if (percent != lastpercent) {
lastpercent = percent;
printf("\rUpload %d%%", percent);
fflush(stdout);
} }
jbus::s64 curTime = jbus::GetGCTicks();
FILE* fp = fopen(argv[1], "rb"); jbus::s64 passedTicks = curTime - start;
if (!fp) if (passedTicks > jbus::GetGCTicksPerSec() * 10) {
{ fprintf(stderr, "JoyBoot timeout\n");
fprintf(stderr, "Unable to open %s\n", argv[1]); return 1;
return 1;
} }
jbus::WaitGCTicks(jbus::GetGCTicksPerSec() / 60);
}
printf("\nJoy Boot finished with %d status\n", status);
fseek(fp, 0, SEEK_END); while (!DonePoll(*endpoint)) {
int fsize = ftell(fp); jbus::s64 curTime = jbus::GetGCTicks();
fseek(fp, 0, SEEK_SET); jbus::s64 passedTicks = curTime - start;
std::unique_ptr<jbus::u8[]> data(new jbus::u8[fsize]); if (passedTicks > jbus::GetGCTicksPerSec() * 15) {
fread(data.get(), 1, fsize, fp); fprintf(stderr, "JoyBoot timeout\n");
fclose(fp); return 1;
if (fsize < 512)
{
fprintf(stderr, "%s must be at least 512 bytes\n", argv[1]);
return 1;
} }
jbus::WaitGCTicks(jbus::GetGCTicksPerSec() / 60);
}
clientPadComplimentCheck(data.get()); return 0;
jbus::Initialize();
printf("Listening for client\n");
jbus::Listener listener;
listener.start();
std::unique_ptr<jbus::Endpoint> endpoint;
while (true)
{
jbus::s64 frameStart = jbus::GetGCTicks();
endpoint = listener.accept();
if (endpoint)
break;
jbus::s64 frameEnd = jbus::GetGCTicks();
jbus::s64 passedTicks = frameEnd - frameStart;
jbus::s64 waitTicks = jbus::GetGCTicksPerSec() / 60 - passedTicks;
if (waitTicks > 0)
jbus::WaitGCTicks(waitTicks);
}
printf("Waiting 4 sec\n");
jbus::WaitGCTicks(jbus::GetGCTicksPerSec() * 4);
jbus::u8 status;
if (endpoint->GBAJoyBootAsync(2, 2, data.get(), fsize,
&status, std::bind(JoyBootDone,
std::placeholders::_1,
std::placeholders::_2)) != jbus::GBA_READY)
{
fprintf(stderr, "Unable to start JoyBoot\n");
return 1;
}
jbus::s64 start = jbus::GetGCTicks();
jbus::u8 percent = 0;
jbus::u8 lastpercent = 0;
while (endpoint->GBAGetProcessStatus(percent) == jbus::GBA_BUSY)
{
if (percent != lastpercent)
{
lastpercent = percent;
printf("\rUpload %d%%", percent);
fflush(stdout);
}
jbus::s64 curTime = jbus::GetGCTicks();
jbus::s64 passedTicks = curTime - start;
if (passedTicks > jbus::GetGCTicksPerSec() * 10)
{
fprintf(stderr, "JoyBoot timeout\n");
return 1;
}
jbus::WaitGCTicks(jbus::GetGCTicksPerSec() / 60);
}
printf("\nJoy Boot finished with %d status\n", status);
while (!DonePoll(*endpoint))
{
jbus::s64 curTime = jbus::GetGCTicks();
jbus::s64 passedTicks = curTime - start;
if (passedTicks > jbus::GetGCTicksPerSec() * 15)
{
fprintf(stderr, "JoyBoot timeout\n");
return 1;
}
jbus::WaitGCTicks(jbus::GetGCTicksPerSec() / 60);
}
return 0;
} }