mirror of https://github.com/AxioDL/jbus.git
New code style refactor
This commit is contained in:
parent
500f88d7a7
commit
e28b6551d0
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
@ -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
|
||||||
|
|
1344
lib/Endpoint.cpp
1344
lib/Endpoint.cpp
File diff suppressed because it is too large
Load Diff
170
lib/Listener.cpp
170
lib/Listener.cpp
|
@ -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
|
||||||
|
|
429
lib/Socket.cpp
429
lib/Socket.cpp
|
@ -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
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue