mirror of https://github.com/AxioDL/jbus.git
Initial commit
This commit is contained in:
commit
ab163e856a
|
@ -0,0 +1,12 @@
|
|||
cmake_minimum_required(VERSION 3.0)
|
||||
project(jbus)
|
||||
|
||||
include_directories(include)
|
||||
add_library(jbus
|
||||
include/jbus/optional.hpp
|
||||
include/jbus/Socket.hpp
|
||||
src/Common.cpp include/jbus/Common.hpp
|
||||
src/Endpoint.cpp include/jbus/Endpoint.hpp
|
||||
src/Listener.cpp include/jbus/Listener.hpp)
|
||||
add_executable(joyboot tools/joyboot.cpp)
|
||||
target_link_libraries(joyboot jbus)
|
|
@ -0,0 +1,8 @@
|
|||
## JBus
|
||||
|
||||
This is a library for communicating with emulated GameBoy Advance instances
|
||||
using the JoyBus protocol, linked over TCP.
|
||||
|
||||
Currently, only [VBA-M](https://github.com/visualboyadvance-m/visualboyadvance-m)
|
||||
is known to function. It uses the same networking method as the
|
||||
[Dolphin](https://github.com/dolphin-emu/dolphin) GameCube emulator.
|
|
@ -0,0 +1,171 @@
|
|||
#ifndef JBUS_COMMON_HPP
|
||||
#define JBUS_COMMON_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
namespace jbus
|
||||
{
|
||||
|
||||
using s8 = int8_t;
|
||||
using u8 = uint8_t;
|
||||
using s16 = int16_t;
|
||||
using u16 = uint16_t;
|
||||
using s32 = int32_t;
|
||||
using u32 = uint32_t;
|
||||
using s64 = int64_t;
|
||||
using u64 = uint64_t;
|
||||
|
||||
#undef bswap16
|
||||
#undef bswap32
|
||||
#undef bswap64
|
||||
|
||||
/* Type-sensitive byte swappers */
|
||||
template <typename T>
|
||||
static inline T bswap16(T val)
|
||||
{
|
||||
#if __GNUC__
|
||||
return __builtin_bswap16(val);
|
||||
#elif _WIN32
|
||||
return _byteswap_ushort(val);
|
||||
#else
|
||||
return (val = (val << 8) | ((val >> 8) & 0xFF));
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline T bswap32(T val)
|
||||
{
|
||||
#if __GNUC__
|
||||
return __builtin_bswap32(val);
|
||||
#elif _WIN32
|
||||
return _byteswap_ulong(val);
|
||||
#else
|
||||
val = (val & 0x0000FFFF) << 16 | (val & 0xFFFF0000) >> 16;
|
||||
val = (val & 0x00FF00FF) << 8 | (val & 0xFF00FF00) >> 8;
|
||||
return val;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline T bswap64(T val)
|
||||
{
|
||||
#if __GNUC__
|
||||
return __builtin_bswap64(val);
|
||||
#elif _WIN32
|
||||
return _byteswap_uint64(val);
|
||||
#else
|
||||
return ((val & 0xFF00000000000000ULL) >> 56) |
|
||||
((val & 0x00FF000000000000ULL) >> 40) |
|
||||
((val & 0x0000FF0000000000ULL) >> 24) |
|
||||
((val & 0x000000FF00000000ULL) >> 8) |
|
||||
((val & 0x00000000FF000000ULL) << 8) |
|
||||
((val & 0x0000000000FF0000ULL) << 24) |
|
||||
((val & 0x000000000000FF00ULL) << 40) |
|
||||
((val & 0x00000000000000FFULL) << 56);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
static inline int16_t SBig(int16_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 uint32_t SBig(uint32_t val) {return bswap32(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 float SBig(float val)
|
||||
{
|
||||
int32_t ival = bswap32(*((int32_t*)(&val)));
|
||||
return *((float*)(&ival));
|
||||
}
|
||||
static inline double SBig(double val)
|
||||
{
|
||||
int64_t ival = bswap64(*((int64_t*)(&val)));
|
||||
return *((double*)(&ival));
|
||||
}
|
||||
#ifndef SBIG
|
||||
#define SBIG(q) ( ( (q) & 0x000000FF ) << 24 | ( (q) & 0x0000FF00 ) << 8 \
|
||||
| ( (q) & 0x00FF0000 ) >> 8 | ( (q) & 0xFF000000 ) >> 24 )
|
||||
#endif
|
||||
|
||||
static inline int16_t SLittle(int16_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 uint32_t SLittle(uint32_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 float SLittle(float val) {return val;}
|
||||
static inline double SLittle(double val) {return val;}
|
||||
#ifndef SLITTLE
|
||||
#define SLITTLE(q) (q)
|
||||
#endif
|
||||
#else
|
||||
static inline int16_t SLittle(int16_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 uint32_t SLittle(uint32_t val) {return bswap32(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 float SLittle(float val)
|
||||
{
|
||||
int32_t ival = bswap32(*((int32_t*)(&val)));
|
||||
return *((float*)(&ival));
|
||||
}
|
||||
static inline double SLittle(double val)
|
||||
{
|
||||
int64_t ival = bswap64(*((int64_t*)(&val)));
|
||||
return *((double*)(&ival));
|
||||
}
|
||||
#ifndef SLITTLE
|
||||
#define SLITTLE(q) ( ( (q) & 0x000000FF ) << 24 | ( (q) & 0x0000FF00 ) << 8 \
|
||||
| ( (q) & 0x00FF0000 ) >> 8 | ( (q) & 0xFF000000 ) >> 24 )
|
||||
#endif
|
||||
|
||||
static inline int16_t SBig(int16_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 uint32_t SBig(uint32_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 float SBig(float val) {return val;}
|
||||
static inline double SBig(double val) {return val;}
|
||||
#ifndef SBIG
|
||||
#define SBIG(q) (q)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
class Endpoint;
|
||||
class EndpointLocal;
|
||||
|
||||
enum EJStatFlags
|
||||
{
|
||||
GBA_JSTAT_MASK = 0x3a,
|
||||
GBA_JSTAT_FLAGS_SHIFT = 4,
|
||||
GBA_JSTAT_FLAGS_MASK = 0x30,
|
||||
GBA_JSTAT_PSF1 = 0x20,
|
||||
GBA_JSTAT_PSF0 = 0x10,
|
||||
GBA_JSTAT_SEND = 0x08,
|
||||
GBA_JSTAT_RECV = 0x02
|
||||
};
|
||||
|
||||
enum EJoyReturn
|
||||
{
|
||||
GBA_READY = 0,
|
||||
GBA_NOT_READY = 1,
|
||||
GBA_BUSY = 2,
|
||||
GBA_JOYBOOT_UNKNOWN_STATE = 3,
|
||||
GBA_JOYBOOT_ERR_INVALID = 4
|
||||
};
|
||||
|
||||
using FGBACallback = std::function<void(EndpointLocal& endpoint, EJoyReturn status)>;
|
||||
|
||||
u64 GetGCTicks();
|
||||
void WaitGCTicks(u64 ticks);
|
||||
static constexpr u64 GetGCTicksPerSec() { return 486000000ull; }
|
||||
void Initialize();
|
||||
|
||||
}
|
||||
|
||||
#endif // JBUS_COMMON_HPP
|
|
@ -0,0 +1,214 @@
|
|||
#ifndef JBUS_ENDPOINT_HPP
|
||||
#define JBUS_ENDPOINT_HPP
|
||||
|
||||
#include "Common.hpp"
|
||||
#include "Socket.hpp"
|
||||
#include "optional.hpp"
|
||||
#include <thread>
|
||||
|
||||
namespace jbus
|
||||
{
|
||||
|
||||
/** Self-contained class for solving Kawasedo's GBA BootROM challenge.
|
||||
* GBA will boot client_pad.bin code on completion. */
|
||||
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 */
|
||||
struct DSPSecParms
|
||||
{
|
||||
/* Nonce challenge (first read from GBA, hence already little-endian) */
|
||||
u32 x0_gbaChallenge;
|
||||
|
||||
/* Palette of pulsing logo on GBA during transmission [0,6] */
|
||||
u32 x4_logoPalette;
|
||||
|
||||
/* Speed and direction of palette interpolation [-4,4] */
|
||||
u32 x8_logoSpeed;
|
||||
|
||||
/* Length of JoyBoot program to upload */
|
||||
u32 xc_progLength;
|
||||
|
||||
/* Unwrapped public key */
|
||||
u32 x20_publicKey;
|
||||
|
||||
/* Message authentication code */
|
||||
u32 x24_authInitCode;
|
||||
|
||||
void ProcessGBACrypto()
|
||||
{
|
||||
/* Unwrap key from challenge using 'sedo' magic number (to encrypt JoyBoot program) */
|
||||
x20_publicKey = x0_gbaChallenge ^ 0x6f646573;
|
||||
|
||||
/* Pack palette parameters */
|
||||
u16 paletteSpeedCoded;
|
||||
s16 logoSpeed = static_cast<s8>(x8_logoSpeed);
|
||||
if (logoSpeed < 0)
|
||||
paletteSpeedCoded = ((-logoSpeed + 2) * 2) | (x4_logoPalette << 4);
|
||||
else if (logoSpeed == 0)
|
||||
paletteSpeedCoded = (x4_logoPalette * 2) | 0x70;
|
||||
else /* logo_speed > 0 */
|
||||
paletteSpeedCoded = ((logoSpeed - 1) * 2) | (x4_logoPalette << 4);
|
||||
|
||||
/* JoyBoot ROMs start with a padded header; this is the length beyond that header */
|
||||
s32 lengthNoHeader = ROUND_UP_8(xc_progLength) - 0x200;
|
||||
|
||||
/* The JoyBus protocol transmits in 4-byte packets while flipping a state flag;
|
||||
* so the GBA BIOS counts the program length in 8-byte packet-pairs */
|
||||
u16 packetPairCount = (lengthNoHeader < 0) ? 0 : lengthNoHeader / 8;
|
||||
paletteSpeedCoded |= (packetPairCount & 0x4000) >> 14;
|
||||
|
||||
/* Pack together encoded transmission parameters */
|
||||
u32 t1 = (((packetPairCount << 16) | 0x3f80) & 0x3f80ffff) * 2;
|
||||
t1 += (static_cast<s16>(static_cast<s8>(t1 >> 8)) & packetPairCount) << 16;
|
||||
u32 t2 = ((paletteSpeedCoded & 0xff) << 16) + (t1 & 0xff0000) + ((t1 >> 8) & 0xffff00);
|
||||
u32 t3 = paletteSpeedCoded << 16 | ((t2 << 8) & 0xff000000) | (t1 >> 16) | 0x80808080;
|
||||
|
||||
/* Wrap with 'Kawa' or 'sedo' (Kawasedo is the author of the BIOS cipher) */
|
||||
x24_authInitCode = t3 ^ ((t3 & 0x200) != 0 ? 0x6f646573 : 0x6177614b);
|
||||
}
|
||||
} xf8_dspHmac;
|
||||
|
||||
s32 x0_pColor;
|
||||
s32 x4_pSpeed;
|
||||
u8* x8_progPtr;
|
||||
u32 xc_progLen;
|
||||
u8* x10_statusPtr;
|
||||
FGBACallback x14_callback;
|
||||
u8 x18_readBuf[4];
|
||||
u8 x1c_writeBuf[4];
|
||||
s32 x20_byteInWindow;
|
||||
u64 x28_ticksAfterXf;
|
||||
u32 x30_justStarted;
|
||||
u32 x34_bytesSent;
|
||||
u32 x38_crc;
|
||||
u32 x3c_checkStore[7];
|
||||
s32 x58_currentKey;
|
||||
s32 x5c_initMessage;
|
||||
s32 x60_gameId;
|
||||
u32 x64_totalBytes;
|
||||
bool m_started = true;
|
||||
|
||||
void F23(EndpointLocal& endpoint, EJoyReturn status);
|
||||
void F25(EndpointLocal& endpoint, EJoyReturn status);
|
||||
void F27(EndpointLocal& endpoint, EJoyReturn status);
|
||||
void F29(EndpointLocal& endpoint, EJoyReturn status);
|
||||
void GBAX02();
|
||||
void GBAX01(EndpointLocal& endpoint);
|
||||
void F31(EndpointLocal& endpoint, EJoyReturn status);
|
||||
void F33(EndpointLocal& endpoint, EJoyReturn status);
|
||||
void F35(EndpointLocal& endpoint, EJoyReturn status);
|
||||
void F37(EndpointLocal& endpoint, EJoyReturn status);
|
||||
void F39(EndpointLocal& endpoint, EJoyReturn status);
|
||||
|
||||
auto bindThis(void(KawasedoChallenge::*ptmf)(EndpointLocal&, EJoyReturn))
|
||||
{
|
||||
return std::bind(ptmf, this, std::placeholders::_1, std::placeholders::_2);
|
||||
}
|
||||
|
||||
public:
|
||||
KawasedoChallenge(Endpoint& endpoint, s32 paletteColor, s32 paletteSpeed,
|
||||
u8* programp, s32 length, u8* status, FGBACallback&& callback);
|
||||
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; }
|
||||
};
|
||||
|
||||
class Endpoint
|
||||
{
|
||||
friend class EndpointLocal;
|
||||
|
||||
enum EJoybusCmds
|
||||
{
|
||||
CMD_RESET = 0xff,
|
||||
CMD_STATUS = 0x00,
|
||||
CMD_READ = 0x14,
|
||||
CMD_WRITE = 0x15
|
||||
};
|
||||
|
||||
enum class EWaitResp
|
||||
{
|
||||
NoWait = 0,
|
||||
WaitCmd,
|
||||
WaitIdle
|
||||
};
|
||||
|
||||
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::experimental::optional<KawasedoChallenge> m_joyBoot;
|
||||
FGBACallback m_callback;
|
||||
EWaitResp m_waitingResp = EWaitResp::NoWait;
|
||||
size_t m_dataReceivedBytes = 0;
|
||||
u8 m_buffer[5];
|
||||
u64 m_timeSent = 0;
|
||||
u8* m_readDstPtr = nullptr;
|
||||
u8* m_statusPtr = nullptr;
|
||||
u64 m_lastGCTick = 0;
|
||||
u64 m_timeCmdSent = 0;
|
||||
u8 m_lastCmd = 0;
|
||||
u8 m_chan;
|
||||
bool m_booted = false;
|
||||
bool m_cmdIssued = false;
|
||||
bool m_running = true;
|
||||
|
||||
static u64 getTransferTime(u8 cmd);
|
||||
void clockSync();
|
||||
void send(const u8* buffer);
|
||||
size_t seceive(u8* buffer);
|
||||
size_t runBuffer(u8* buffer, u64& remTicks, EWaitResp resp);
|
||||
bool idleGetStatus(u64& remTicks);
|
||||
void transferProc();
|
||||
void transferWakeup(EndpointLocal& endpoint, u8 status);
|
||||
|
||||
auto bindSync()
|
||||
{
|
||||
return std::bind(&Endpoint::transferWakeup, this,
|
||||
std::placeholders::_1, std::placeholders::_2);
|
||||
}
|
||||
|
||||
public:
|
||||
void stop();
|
||||
EJoyReturn GBAGetProcessStatus(u8* percentp);
|
||||
EJoyReturn GBAGetStatusAsync(u8* status, FGBACallback&& callback);
|
||||
EJoyReturn GBAGetStatus(u8* status);
|
||||
EJoyReturn GBAResetAsync(u8* status, FGBACallback&& callback);
|
||||
EJoyReturn GBAReset(u8* status);
|
||||
EJoyReturn GBAReadAsync(u8* dst, u8* status, FGBACallback&& callback);
|
||||
EJoyReturn GBARead(u8* dst, u8* status);
|
||||
EJoyReturn GBAWriteAsync(const u8* src, u8* status, FGBACallback&& callback);
|
||||
EJoyReturn GBAWrite(const u8* src, u8* status);
|
||||
EJoyReturn GBAJoyBootAsync(s32 paletteColor, s32 paletteSpeed,
|
||||
u8* programp, s32 length, u8* status,
|
||||
FGBACallback&& callback);
|
||||
int GetChan() const { return m_chan; }
|
||||
Endpoint(u8 chan, net::Socket&& data, net::Socket&& clock);
|
||||
~Endpoint();
|
||||
};
|
||||
|
||||
class EndpointLocal
|
||||
{
|
||||
friend class Endpoint;
|
||||
Endpoint& m_ep;
|
||||
EndpointLocal(Endpoint& ep) : m_ep(ep) {}
|
||||
public:
|
||||
EJoyReturn GBAGetStatusAsync(u8* status, FGBACallback&& callback);
|
||||
EJoyReturn GBAResetAsync(u8* status, FGBACallback&& callback);
|
||||
EJoyReturn GBAReadAsync(u8* dst, u8* status, FGBACallback&& callback);
|
||||
EJoyReturn GBAWriteAsync(const u8* src, u8* status, FGBACallback&& callback);
|
||||
int GetChan() const { return m_ep.GetChan(); }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // JBUS_ENDPOINT_HPP
|
|
@ -0,0 +1,35 @@
|
|||
#ifndef JBUS_LISTENER_HPP
|
||||
#define JBUS_LISTENER_HPP
|
||||
|
||||
#include "Common.hpp"
|
||||
#include "Socket.hpp"
|
||||
#include <thread>
|
||||
#include <queue>
|
||||
|
||||
namespace jbus
|
||||
{
|
||||
|
||||
class Listener
|
||||
{
|
||||
net::Socket m_dataServer = {false};
|
||||
net::Socket m_clockServer = {false};
|
||||
std::thread m_listenerThread;
|
||||
std::mutex m_queueLock;
|
||||
std::queue<std::unique_ptr<Endpoint>> m_endpointQueue;
|
||||
bool m_running = false;
|
||||
|
||||
static const uint32_t DataPort = 0xd6ba;
|
||||
static const uint32_t ClockPort = 0xc10c;
|
||||
|
||||
void listenerProc();
|
||||
|
||||
public:
|
||||
void start();
|
||||
void stop();
|
||||
std::unique_ptr<Endpoint> accept();
|
||||
~Listener();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // JBUS_LISTENER_HPP
|
|
@ -0,0 +1,328 @@
|
|||
#ifndef JBUS_SOCKET_HPP
|
||||
#define JBUS_SOCKET_HPP
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <netdb.h>
|
||||
#include <errno.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <string>
|
||||
|
||||
#include "Common.hpp"
|
||||
|
||||
namespace jbus
|
||||
{
|
||||
namespace net
|
||||
{
|
||||
|
||||
/* Define the low-level send/receive flags, which depend on the OS */
|
||||
#ifdef __linux__
|
||||
static const int _flags = MSG_NOSIGNAL;
|
||||
#else
|
||||
static const int _flags = 0;
|
||||
#endif
|
||||
|
||||
/** IP address class derived from SFML */
|
||||
class IPAddress
|
||||
{
|
||||
uint32_t m_address = 0;
|
||||
bool m_valid = false;
|
||||
|
||||
void resolve(const std::string& address)
|
||||
{
|
||||
m_address = 0;
|
||||
m_valid = false;
|
||||
|
||||
if (address == "255.255.255.255")
|
||||
{
|
||||
/* The broadcast address needs to be handled explicitly,
|
||||
* because it is also the value returned by inet_addr on error */
|
||||
m_address = INADDR_BROADCAST;
|
||||
m_valid = true;
|
||||
}
|
||||
else if (address == "0.0.0.0")
|
||||
{
|
||||
m_address = INADDR_ANY;
|
||||
m_valid = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Try to convert the address as a byte representation ("xxx.xxx.xxx.xxx") */
|
||||
uint32_t ip = inet_addr(address.c_str());
|
||||
if (ip != INADDR_NONE)
|
||||
{
|
||||
m_address = ip;
|
||||
m_valid = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Not a valid address, try to convert it as a host name */
|
||||
addrinfo hints;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_INET;
|
||||
addrinfo* result = NULL;
|
||||
if (getaddrinfo(address.c_str(), NULL, &hints, &result) == 0)
|
||||
{
|
||||
if (result)
|
||||
{
|
||||
ip = reinterpret_cast<sockaddr_in*>(result->ai_addr)->sin_addr.s_addr;
|
||||
freeaddrinfo(result);
|
||||
m_address = ip;
|
||||
m_valid = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
IPAddress(const std::string& address)
|
||||
{
|
||||
resolve(address);
|
||||
}
|
||||
|
||||
uint32_t toInteger() const
|
||||
{
|
||||
return ntohl(m_address);
|
||||
}
|
||||
|
||||
operator bool() const { return m_valid; }
|
||||
};
|
||||
|
||||
/** Server-oriented TCP socket class derived from SFML */
|
||||
class Socket
|
||||
{
|
||||
int m_socket = -1;
|
||||
bool m_isBlocking;
|
||||
|
||||
static sockaddr_in createAddress(uint32_t address, unsigned short port)
|
||||
{
|
||||
sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_addr.s_addr = htonl(address);
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
|
||||
#ifdef __APPLE__
|
||||
addr.sin_len = sizeof(addr);
|
||||
#endif
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
bool openSocket()
|
||||
{
|
||||
if (isOpen())
|
||||
return false;
|
||||
|
||||
m_socket = socket(PF_INET, SOCK_STREAM, 0);
|
||||
if (m_socket == -1)
|
||||
{
|
||||
fprintf(stderr, "Can't allocate socket");
|
||||
return false;
|
||||
}
|
||||
|
||||
int one = 1;
|
||||
setsockopt(m_socket, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char*>(&one), sizeof(one));
|
||||
#ifdef __APPLE__
|
||||
setsockopt(m_socket, SOL_SOCKET, SO_NOSIGPIPE, reinterpret_cast<char*>(&one), sizeof(one));
|
||||
#endif
|
||||
|
||||
setBlocking(m_isBlocking);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void setRemoteSocket(int remSocket)
|
||||
{
|
||||
close();
|
||||
m_socket = remSocket;
|
||||
setBlocking(m_isBlocking);
|
||||
}
|
||||
|
||||
public:
|
||||
enum class EResult
|
||||
{
|
||||
OK,
|
||||
Error,
|
||||
Busy
|
||||
};
|
||||
|
||||
Socket(bool blocking)
|
||||
: m_isBlocking(blocking) {}
|
||||
~Socket() { close(); }
|
||||
|
||||
Socket(const Socket& other) = delete;
|
||||
Socket& operator=(const Socket& other) = delete;
|
||||
Socket(Socket&& other)
|
||||
: m_socket(other.m_socket), m_isBlocking(other.m_isBlocking)
|
||||
{
|
||||
other.m_socket = -1;
|
||||
}
|
||||
Socket& operator=(Socket&& other)
|
||||
{
|
||||
close();
|
||||
m_socket = other.m_socket;
|
||||
other.m_socket = -1;
|
||||
m_isBlocking = other.m_isBlocking;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void setBlocking(bool blocking)
|
||||
{
|
||||
m_isBlocking = blocking;
|
||||
int status = fcntl(m_socket, F_GETFL);
|
||||
if (m_isBlocking)
|
||||
fcntl(m_socket, F_SETFL, status & ~O_NONBLOCK);
|
||||
else
|
||||
fcntl(m_socket, F_SETFL, status | O_NONBLOCK);
|
||||
}
|
||||
|
||||
bool isOpen() const { return m_socket != -1; }
|
||||
bool openAndListen(const IPAddress& address, uint32_t port)
|
||||
{
|
||||
if (!openSocket())
|
||||
return false;
|
||||
|
||||
sockaddr_in addr = createAddress(address.toInteger(), port);
|
||||
if (bind(m_socket, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == -1)
|
||||
{
|
||||
/* Not likely to happen, but... */
|
||||
fprintf(stderr, "Failed to bind listener socket to port %d", port);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (::listen(m_socket, 0) == -1)
|
||||
{
|
||||
/* Oops, socket is deaf */
|
||||
fprintf(stderr, "Failed to listen to port %d", port);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
EResult accept(Socket& remoteSocketOut, sockaddr_in& fromAddress)
|
||||
{
|
||||
if (!isOpen())
|
||||
return EResult::Error;
|
||||
|
||||
/* Accept a new connection */
|
||||
socklen_t length = sizeof(sockaddr_in);
|
||||
int remoteSocket = ::accept(m_socket, reinterpret_cast<sockaddr*>(&fromAddress), &length);
|
||||
|
||||
/* Check for errors */
|
||||
if (remoteSocket == -1)
|
||||
{
|
||||
EResult res = (errno == EAGAIN) ? EResult::Busy : EResult::Error;
|
||||
if (res == EResult::Error)
|
||||
fprintf(stderr, "Failed to accept incoming connection: %s", strerror(errno));
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Initialize the new connected socket */
|
||||
remoteSocketOut.setRemoteSocket(remoteSocket);
|
||||
|
||||
return EResult::OK;
|
||||
}
|
||||
|
||||
EResult accept(Socket& remoteSocketOut)
|
||||
{
|
||||
sockaddr_in fromAddress;
|
||||
return accept(remoteSocketOut, fromAddress);
|
||||
}
|
||||
|
||||
EResult 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 close()
|
||||
{
|
||||
if (!isOpen())
|
||||
return;
|
||||
::close(m_socket);
|
||||
m_socket = -1;
|
||||
}
|
||||
|
||||
EResult 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 */
|
||||
ssize_t 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)
|
||||
return (errno == EAGAIN) ? EResult::Busy : EResult::Error;
|
||||
}
|
||||
|
||||
transferred = len;
|
||||
return EResult::OK;
|
||||
}
|
||||
|
||||
EResult send(const void* buf, size_t len)
|
||||
{
|
||||
size_t transferred;
|
||||
return send(buf, len, transferred);
|
||||
}
|
||||
|
||||
EResult 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)
|
||||
return (errno == EAGAIN) ? EResult::Busy : EResult::Error;
|
||||
else if (result == 0)
|
||||
return EResult::Error;
|
||||
|
||||
transferred = result;
|
||||
return EResult::OK;
|
||||
}
|
||||
|
||||
EResult recv(void* buf, size_t len)
|
||||
{
|
||||
size_t transferred;
|
||||
return recv(buf, len, transferred);
|
||||
}
|
||||
|
||||
operator bool() const { return isOpen(); }
|
||||
|
||||
int GetInternalSocket() const { return m_socket; }
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // JBUS_SOCKET_HPP
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,55 @@
|
|||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#if __APPLE__
|
||||
#include <mach/mach_time.h>
|
||||
#endif
|
||||
#else
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include "jbus/Common.hpp"
|
||||
|
||||
namespace jbus
|
||||
{
|
||||
|
||||
#if __APPLE__
|
||||
static u64 MachToDolphinNum;
|
||||
static u64 MachToDolphinDenom;
|
||||
#endif
|
||||
|
||||
u64 GetGCTicks()
|
||||
{
|
||||
#if __APPLE__
|
||||
return mach_absolute_time() * MachToDolphinNum / MachToDolphinDenom;
|
||||
#elif __linux__
|
||||
struct timespec tp;
|
||||
clock_gettime(CLOCK_MONOTONIC, &tp);
|
||||
|
||||
return u64((tp.tv_sec * 1000000000ull) + tp.tv_nsec) * GetGCTicksPerSec() / 1000000000ull;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void WaitGCTicks(u64 ticks)
|
||||
{
|
||||
struct timeval tv = {};
|
||||
tv.tv_sec = ticks / GetGCTicksPerSec();
|
||||
tv.tv_usec = (ticks % GetGCTicksPerSec()) * 1000000 / GetGCTicksPerSec();
|
||||
select(0, NULL, NULL, NULL, &tv);
|
||||
}
|
||||
|
||||
void Initialize()
|
||||
{
|
||||
#if __APPLE__
|
||||
mach_timebase_info_data_t timebase;
|
||||
mach_timebase_info(&timebase);
|
||||
MachToDolphinNum = GetGCTicksPerSec() * timebase.numer;
|
||||
MachToDolphinDenom = 1000000000ull * timebase.denom;
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,877 @@
|
|||
#include "jbus/Endpoint.hpp"
|
||||
|
||||
namespace jbus
|
||||
{
|
||||
|
||||
void KawasedoChallenge::F23(EndpointLocal& endpoint, EJoyReturn status)
|
||||
{
|
||||
if (status != GBA_READY ||
|
||||
(status = endpoint.GBAResetAsync(x10_statusPtr,
|
||||
bindThis(&KawasedoChallenge::F25))) != GBA_READY)
|
||||
{
|
||||
x28_ticksAfterXf = 0;
|
||||
if (x14_callback)
|
||||
{
|
||||
x14_callback(endpoint, status);
|
||||
x14_callback = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KawasedoChallenge::F25(EndpointLocal& endpoint, EJoyReturn status)
|
||||
{
|
||||
if (status == GBA_READY)
|
||||
if (*x10_statusPtr != GBA_JSTAT_SEND)
|
||||
status = GBA_JOYBOOT_UNKNOWN_STATE;
|
||||
|
||||
if (status != GBA_READY ||
|
||||
(status = endpoint.GBAGetStatusAsync(x10_statusPtr,
|
||||
bindThis(&KawasedoChallenge::F27))) != GBA_READY)
|
||||
{
|
||||
x28_ticksAfterXf = 0;
|
||||
if (x14_callback)
|
||||
{
|
||||
x14_callback(endpoint, status);
|
||||
x14_callback = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KawasedoChallenge::F27(EndpointLocal& endpoint, EJoyReturn status)
|
||||
{
|
||||
if (status == GBA_READY)
|
||||
if (*x10_statusPtr != (GBA_JSTAT_PSF0 | GBA_JSTAT_SEND))
|
||||
status = GBA_JOYBOOT_UNKNOWN_STATE;
|
||||
|
||||
if (status != GBA_READY ||
|
||||
(status = endpoint.GBAReadAsync(x18_readBuf, x10_statusPtr,
|
||||
bindThis(&KawasedoChallenge::F29))) != GBA_READY)
|
||||
{
|
||||
x28_ticksAfterXf = 0;
|
||||
if (x14_callback)
|
||||
{
|
||||
x14_callback(endpoint, status);
|
||||
x14_callback = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KawasedoChallenge::F29(EndpointLocal& endpoint, EJoyReturn status)
|
||||
{
|
||||
if (status != GBA_READY)
|
||||
{
|
||||
x28_ticksAfterXf = 0;
|
||||
if (x14_callback)
|
||||
{
|
||||
x14_callback(endpoint, status);
|
||||
x14_callback = {};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GBAX02();
|
||||
GBAX01(endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
void KawasedoChallenge::GBAX02()
|
||||
{
|
||||
xf8_dspHmac.x0_gbaChallenge = reinterpret_cast<u32&>(x18_readBuf);
|
||||
xf8_dspHmac.x4_logoPalette = x0_pColor;
|
||||
xf8_dspHmac.x8_logoSpeed = x4_pSpeed;
|
||||
xf8_dspHmac.xc_progLength = xc_progLen;
|
||||
xf8_dspHmac.ProcessGBACrypto();
|
||||
}
|
||||
|
||||
void KawasedoChallenge::GBAX01(EndpointLocal& endpoint)
|
||||
{
|
||||
x58_currentKey = xf8_dspHmac.x20_publicKey;
|
||||
x5c_initMessage = xf8_dspHmac.x24_authInitCode;
|
||||
|
||||
x20_byteInWindow = ROUND_UP_8(xc_progLen);
|
||||
if (x20_byteInWindow < 512)
|
||||
x20_byteInWindow = 512;
|
||||
x64_totalBytes = x20_byteInWindow;
|
||||
x20_byteInWindow -= 512;
|
||||
x20_byteInWindow /= 8;
|
||||
|
||||
reinterpret_cast<u32&>(x1c_writeBuf) = x5c_initMessage;
|
||||
|
||||
x38_crc = 0x15a0;
|
||||
x34_bytesSent = 0;
|
||||
|
||||
x28_ticksAfterXf = GetGCTicks();
|
||||
x30_justStarted = 1;
|
||||
|
||||
EJoyReturn status;
|
||||
if ((status = endpoint.GBAWriteAsync(x1c_writeBuf, x10_statusPtr,
|
||||
bindThis(&KawasedoChallenge::F31))) != GBA_READY)
|
||||
{
|
||||
x28_ticksAfterXf = 0;
|
||||
if (x14_callback)
|
||||
{
|
||||
x14_callback(endpoint, status);
|
||||
x14_callback = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KawasedoChallenge::F31(EndpointLocal& endpoint, EJoyReturn status)
|
||||
{
|
||||
if (status != GBA_READY)
|
||||
{
|
||||
x28_ticksAfterXf = 0;
|
||||
if (x14_callback)
|
||||
{
|
||||
x14_callback(endpoint, status);
|
||||
x14_callback = {};
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
printf("PROG [%d/%d]\n", x34_bytesSent, x64_totalBytes);
|
||||
if (x30_justStarted)
|
||||
{
|
||||
x30_justStarted = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(*x10_statusPtr & GBA_JSTAT_PSF1) ||
|
||||
(*x10_statusPtr & GBA_JSTAT_PSF0) >> 4 != (x34_bytesSent & 4) >> 2)
|
||||
{
|
||||
x28_ticksAfterXf = 0;
|
||||
if (x14_callback)
|
||||
{
|
||||
x14_callback(endpoint, GBA_JOYBOOT_UNKNOWN_STATE);
|
||||
x14_callback = {};
|
||||
}
|
||||
return;
|
||||
}
|
||||
x34_bytesSent += 4;
|
||||
}
|
||||
|
||||
if (x34_bytesSent <= x64_totalBytes)
|
||||
{
|
||||
u32 cryptWindow;
|
||||
if (x34_bytesSent != x64_totalBytes)
|
||||
{
|
||||
x20_byteInWindow = 0;
|
||||
cryptWindow = 0;
|
||||
while (x20_byteInWindow < 4)
|
||||
{
|
||||
if (xc_progLen)
|
||||
{
|
||||
cryptWindow |= *x8_progPtr++ << (x20_byteInWindow * 8);
|
||||
--xc_progLen;
|
||||
}
|
||||
++x20_byteInWindow;
|
||||
}
|
||||
|
||||
if (x34_bytesSent == 0xac)
|
||||
{
|
||||
x60_gameId = cryptWindow;
|
||||
}
|
||||
else if (x34_bytesSent == 0xc4)
|
||||
{
|
||||
cryptWindow = endpoint.GetChan() << 0x8;
|
||||
}
|
||||
|
||||
if (x34_bytesSent >= 0xc0)
|
||||
{
|
||||
u32 shiftWindow = cryptWindow;
|
||||
u32 shiftCrc = x38_crc;
|
||||
for (int i=0 ; i<32 ; ++i)
|
||||
{
|
||||
if ((shiftWindow ^ shiftCrc) & 0x1)
|
||||
shiftCrc = (shiftCrc >> 1) ^ 0xa1c1;
|
||||
else
|
||||
shiftCrc >>= 1;
|
||||
|
||||
shiftWindow >>= 1;
|
||||
}
|
||||
x38_crc = shiftCrc;
|
||||
}
|
||||
|
||||
if (x34_bytesSent == 0x1f8)
|
||||
{
|
||||
x3c_checkStore[0] = cryptWindow;
|
||||
}
|
||||
else if (x34_bytesSent == 0x1fc)
|
||||
{
|
||||
x20_byteInWindow = 1;
|
||||
x3c_checkStore[x20_byteInWindow] = cryptWindow;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cryptWindow = x38_crc | x34_bytesSent << 16;
|
||||
}
|
||||
|
||||
if (x34_bytesSent > 0xbf)
|
||||
{
|
||||
x58_currentKey = 0x6177614b * x58_currentKey + 1;
|
||||
|
||||
cryptWindow ^= x58_currentKey;
|
||||
cryptWindow ^= -(0x2000000 + x34_bytesSent);
|
||||
cryptWindow ^= 0x20796220;
|
||||
}
|
||||
|
||||
x1c_writeBuf[0] = cryptWindow >> 0;
|
||||
x1c_writeBuf[1] = cryptWindow >> 8;
|
||||
x1c_writeBuf[2] = cryptWindow >> 16;
|
||||
x1c_writeBuf[3] = cryptWindow >> 24;
|
||||
|
||||
if (x34_bytesSent == 0x1f8)
|
||||
x3c_checkStore[2] = cryptWindow;
|
||||
|
||||
if (x20_byteInWindow < 4)
|
||||
{
|
||||
x3c_checkStore[2 + x20_byteInWindow] = cryptWindow;
|
||||
x3c_checkStore[5 - x20_byteInWindow] =
|
||||
x3c_checkStore[1 + x20_byteInWindow] * x3c_checkStore[4 - x20_byteInWindow];
|
||||
x3c_checkStore[4 + x20_byteInWindow] =
|
||||
x3c_checkStore[1 + x20_byteInWindow] * x3c_checkStore[1 - x20_byteInWindow];
|
||||
x3c_checkStore[7 - x20_byteInWindow] =
|
||||
x3c_checkStore[-1 + x20_byteInWindow] * x3c_checkStore[4 - x20_byteInWindow];
|
||||
}
|
||||
|
||||
if ((status = endpoint.GBAWriteAsync(x1c_writeBuf, x10_statusPtr,
|
||||
bindThis(&KawasedoChallenge::F31))) != GBA_READY)
|
||||
{
|
||||
x28_ticksAfterXf = 0;
|
||||
if (x14_callback)
|
||||
{
|
||||
x14_callback(endpoint, status);
|
||||
x14_callback = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
else // x34_bytesWritten > x64_totalBytes
|
||||
{
|
||||
if ((status = endpoint.GBAReadAsync(x18_readBuf, x10_statusPtr,
|
||||
bindThis(&KawasedoChallenge::F33))) != GBA_READY)
|
||||
{
|
||||
x28_ticksAfterXf = 0;
|
||||
if (x14_callback)
|
||||
{
|
||||
x14_callback(endpoint, status);
|
||||
x14_callback = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KawasedoChallenge::F33(EndpointLocal& endpoint, EJoyReturn status)
|
||||
{
|
||||
if (status != GBA_READY ||
|
||||
(status = endpoint.GBAGetStatusAsync(x10_statusPtr,
|
||||
bindThis(&KawasedoChallenge::F35))) != GBA_READY)
|
||||
{
|
||||
x28_ticksAfterXf = 0;
|
||||
if (x14_callback)
|
||||
{
|
||||
x14_callback(endpoint, status);
|
||||
x14_callback = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KawasedoChallenge::F35(EndpointLocal& endpoint, EJoyReturn status)
|
||||
{
|
||||
if (status == GBA_READY)
|
||||
if (*x10_statusPtr & (GBA_JSTAT_FLAGS_MASK | GBA_JSTAT_RECV))
|
||||
status = GBA_JOYBOOT_UNKNOWN_STATE;
|
||||
|
||||
if (status != GBA_READY)
|
||||
{
|
||||
x28_ticksAfterXf = 0;
|
||||
if (x14_callback)
|
||||
{
|
||||
x14_callback(endpoint, status);
|
||||
x14_callback = {};
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (*x10_statusPtr != GBA_JSTAT_SEND)
|
||||
{
|
||||
if ((status = endpoint.GBAGetStatusAsync(x10_statusPtr,
|
||||
bindThis(&KawasedoChallenge::F35))) != GBA_READY)
|
||||
{
|
||||
x28_ticksAfterXf = 0;
|
||||
if (x14_callback)
|
||||
{
|
||||
x14_callback(endpoint, status);
|
||||
x14_callback = {};
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ((status = endpoint.GBAReadAsync(x18_readBuf, x10_statusPtr,
|
||||
bindThis(&KawasedoChallenge::F37))) != GBA_READY)
|
||||
{
|
||||
x28_ticksAfterXf = 0;
|
||||
if (x14_callback)
|
||||
{
|
||||
x14_callback(endpoint, status);
|
||||
x14_callback = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KawasedoChallenge::F37(EndpointLocal& endpoint, EJoyReturn status)
|
||||
{
|
||||
if (status != GBA_READY ||
|
||||
(status = endpoint.GBAWriteAsync(x18_readBuf, x10_statusPtr,
|
||||
bindThis(&KawasedoChallenge::F39))) != GBA_READY)
|
||||
{
|
||||
x28_ticksAfterXf = 0;
|
||||
if (x14_callback)
|
||||
{
|
||||
x14_callback(endpoint, status);
|
||||
x14_callback = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KawasedoChallenge::F39(EndpointLocal& endpoint, EJoyReturn status)
|
||||
{
|
||||
if (status == GBA_READY)
|
||||
*x10_statusPtr = GBA_READY;
|
||||
|
||||
x28_ticksAfterXf = 0;
|
||||
|
||||
if (x14_callback)
|
||||
{
|
||||
x14_callback(endpoint, status);
|
||||
x14_callback = {};
|
||||
}
|
||||
}
|
||||
|
||||
KawasedoChallenge::KawasedoChallenge(Endpoint& endpoint, s32 paletteColor, s32 paletteSpeed,
|
||||
u8* programp, s32 length, u8* status, FGBACallback&& callback)
|
||||
: x0_pColor(paletteColor), x4_pSpeed(paletteSpeed), x8_progPtr(programp), xc_progLen(length),
|
||||
x10_statusPtr(status), x14_callback(std::move(callback)), x34_bytesSent(0)
|
||||
{
|
||||
if (endpoint.GBAGetStatusAsync(x10_statusPtr,
|
||||
bindThis(&KawasedoChallenge::F23)) != GBA_READY)
|
||||
{
|
||||
x14_callback = {};
|
||||
m_started = false;
|
||||
}
|
||||
}
|
||||
|
||||
u64 Endpoint::getTransferTime(u8 cmd)
|
||||
{
|
||||
u64 bytes = 0;
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
case CMD_RESET:
|
||||
case CMD_STATUS:
|
||||
{
|
||||
bytes = 4;
|
||||
break;
|
||||
}
|
||||
case CMD_READ:
|
||||
{
|
||||
bytes = 6;
|
||||
break;
|
||||
}
|
||||
case CMD_WRITE:
|
||||
{
|
||||
bytes = 1;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
bytes = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return bytes * GetGCTicksPerSec() / BYTES_PER_SECOND;
|
||||
}
|
||||
|
||||
void Endpoint::clockSync()
|
||||
{
|
||||
if (!m_clockSocket)
|
||||
{
|
||||
m_running = false;
|
||||
return;
|
||||
}
|
||||
|
||||
u32 TickDelta = 0;
|
||||
if (!m_lastGCTick)
|
||||
{
|
||||
m_lastGCTick = GetGCTicks();
|
||||
TickDelta = GetGCTicksPerSec() / 60;
|
||||
}
|
||||
else
|
||||
TickDelta = GetGCTicks() - m_lastGCTick;
|
||||
|
||||
/* Scale GameCube clock into GBA clock */
|
||||
TickDelta = u32(u64(TickDelta) * 16777216 / GetGCTicksPerSec());
|
||||
m_lastGCTick = GetGCTicks();
|
||||
TickDelta = SBig(TickDelta);
|
||||
if (m_clockSocket.send(&TickDelta, 4) == net::Socket::EResult::Error)
|
||||
m_running = false;
|
||||
}
|
||||
|
||||
void Endpoint::send(const u8* buffer)
|
||||
{
|
||||
m_lastCmd = buffer[0];
|
||||
|
||||
net::Socket::EResult result;
|
||||
size_t sentBytes;
|
||||
if (m_lastCmd == CMD_WRITE)
|
||||
result = m_dataSocket.send(buffer, 5, sentBytes);
|
||||
else
|
||||
result = m_dataSocket.send(buffer, 1, sentBytes);
|
||||
|
||||
if (m_lastCmd != CMD_STATUS)
|
||||
m_booted = true;
|
||||
|
||||
if (result != net::Socket::EResult::OK)
|
||||
{
|
||||
m_running = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Send %02x [> %02x%02x%02x%02x] (%lu)\n", buffer[0],
|
||||
buffer[1], buffer[2], buffer[3], buffer[4], sentBytes);
|
||||
}
|
||||
|
||||
m_timeCmdSent = GetGCTicks();
|
||||
}
|
||||
|
||||
size_t Endpoint::seceive(u8* buffer)
|
||||
{
|
||||
if (!m_dataSocket)
|
||||
{
|
||||
m_running = false;
|
||||
return 5;
|
||||
}
|
||||
|
||||
size_t recvBytes = 0;
|
||||
u64 transferTime = getTransferTime(m_lastCmd);
|
||||
bool block = (GetGCTicks() - m_timeCmdSent) > transferTime;
|
||||
if (m_lastCmd == CMD_STATUS && !m_booted)
|
||||
block = false;
|
||||
|
||||
if (block)
|
||||
{
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(m_dataSocket.GetInternalSocket(), &fds);
|
||||
struct timeval tv = {};
|
||||
tv.tv_sec = 1;
|
||||
select(m_dataSocket.GetInternalSocket() + 1, &fds, NULL, NULL, &tv);
|
||||
}
|
||||
|
||||
net::Socket::EResult result = m_dataSocket.recv(buffer, 5, recvBytes);
|
||||
if (result == net::Socket::EResult::Busy)
|
||||
{
|
||||
recvBytes = 0;
|
||||
}
|
||||
else if (result == net::Socket::EResult::Error)
|
||||
{
|
||||
m_running = false;
|
||||
return 5;
|
||||
}
|
||||
|
||||
if (recvBytes > 5)
|
||||
recvBytes = 5;
|
||||
|
||||
if (recvBytes > 0)
|
||||
{
|
||||
if (m_lastCmd == CMD_STATUS || m_lastCmd == CMD_RESET)
|
||||
{
|
||||
printf("Stat/Reset [< %02x%02x%02x%02x%02x] (%lu)\n",
|
||||
(u8)buffer[0], (u8)buffer[1], (u8)buffer[2],
|
||||
(u8)buffer[3], (u8)buffer[4], recvBytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Receive [< %02x%02x%02x%02x%02x] (%lu)\n",
|
||||
(u8)buffer[0], (u8)buffer[1], (u8)buffer[2],
|
||||
(u8)buffer[3], (u8)buffer[4], recvBytes);
|
||||
}
|
||||
}
|
||||
|
||||
return recvBytes;
|
||||
}
|
||||
|
||||
size_t Endpoint::runBuffer(u8* buffer, u64& remTicks, EWaitResp resp)
|
||||
{
|
||||
if (m_waitingResp == EWaitResp::NoWait)
|
||||
{
|
||||
m_dataReceivedBytes = 0;
|
||||
clockSync();
|
||||
send(buffer);
|
||||
m_timeSent = GetGCTicks();
|
||||
m_waitingResp = resp;
|
||||
}
|
||||
|
||||
if (m_waitingResp != EWaitResp::NoWait && m_dataReceivedBytes == 0)
|
||||
{
|
||||
m_dataReceivedBytes = seceive(buffer);
|
||||
}
|
||||
|
||||
u64 ticksSinceSend = GetGCTicks() - m_timeSent;
|
||||
u64 targetTransferTime = getTransferTime(m_lastCmd);
|
||||
if (targetTransferTime > ticksSinceSend)
|
||||
{
|
||||
remTicks = targetTransferTime - ticksSinceSend;
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
remTicks = 0;
|
||||
if (m_dataReceivedBytes != 0)
|
||||
m_waitingResp = EWaitResp::NoWait;
|
||||
return m_dataReceivedBytes;
|
||||
}
|
||||
}
|
||||
|
||||
bool Endpoint::idleGetStatus(u64& remTicks)
|
||||
{
|
||||
u8 buffer[] = { CMD_STATUS, 0, 0, 0, 0 };
|
||||
return runBuffer(buffer, remTicks, EWaitResp::WaitIdle);
|
||||
}
|
||||
|
||||
void Endpoint::transferProc()
|
||||
{
|
||||
printf("Starting JoyBus transfer thread for channel %d\n", m_chan);
|
||||
|
||||
std::unique_lock<std::mutex> lk(m_syncLock);
|
||||
while (m_running)
|
||||
{
|
||||
u64 remTicks;
|
||||
if ((m_cmdIssued && m_waitingResp != EWaitResp::WaitIdle) ||
|
||||
m_waitingResp == EWaitResp::WaitCmd)
|
||||
{
|
||||
/* This inner loop performs first-response on commands invoked from callback
|
||||
* on this thread; avoiding excessive locking */
|
||||
do
|
||||
{
|
||||
m_cmdIssued = false;
|
||||
if (runBuffer(m_buffer, remTicks, EWaitResp::WaitCmd) || !m_running)
|
||||
{
|
||||
EJoyReturn xferStatus;
|
||||
if (m_running)
|
||||
{
|
||||
xferStatus = GBA_READY;
|
||||
}
|
||||
else
|
||||
{
|
||||
xferStatus = GBA_NOT_READY;
|
||||
remTicks = 0;
|
||||
}
|
||||
|
||||
/* Handle message response */
|
||||
switch (m_lastCmd) {
|
||||
case CMD_RESET:
|
||||
case CMD_STATUS:
|
||||
if (m_statusPtr)
|
||||
*m_statusPtr = m_buffer[2];
|
||||
break;
|
||||
case CMD_WRITE:
|
||||
if (m_statusPtr)
|
||||
*m_statusPtr = m_buffer[0];
|
||||
break;
|
||||
case CMD_READ:
|
||||
if (m_statusPtr)
|
||||
*m_statusPtr = m_buffer[4];
|
||||
if (m_readDstPtr)
|
||||
memmove(m_readDstPtr, m_buffer, 4);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
m_statusPtr = nullptr;
|
||||
m_readDstPtr = nullptr;
|
||||
if (m_callback)
|
||||
{
|
||||
FGBACallback cb = std::move(m_callback);
|
||||
m_callback = {};
|
||||
EndpointLocal ep(*this);
|
||||
cb(ep, xferStatus);
|
||||
}
|
||||
|
||||
if (!m_running)
|
||||
break;
|
||||
}
|
||||
} while (m_cmdIssued);
|
||||
}
|
||||
else if (!m_booted)
|
||||
{
|
||||
/* Poll bus with status messages when inactive */
|
||||
if (idleGetStatus(remTicks))
|
||||
remTicks = GetGCTicksPerSec() * 4 / 60;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Wait for the duration of a write otherwise */
|
||||
remTicks = getTransferTime(CMD_WRITE);
|
||||
}
|
||||
|
||||
if (m_running && remTicks)
|
||||
{
|
||||
lk.unlock();
|
||||
WaitGCTicks(remTicks);
|
||||
lk.lock();
|
||||
}
|
||||
}
|
||||
|
||||
m_syncCv.notify_all();
|
||||
m_dataSocket.close();
|
||||
m_clockSocket.close();
|
||||
printf("Stopping JoyBus transfer thread for channel %d\n", m_chan);
|
||||
}
|
||||
|
||||
void Endpoint::transferWakeup(EndpointLocal& endpoint, u8 status)
|
||||
{
|
||||
m_syncCv.notify_all();
|
||||
}
|
||||
|
||||
void Endpoint::stop()
|
||||
{
|
||||
m_running = false;
|
||||
if (m_transferThread.joinable())
|
||||
m_transferThread.join();
|
||||
}
|
||||
|
||||
EJoyReturn Endpoint::GBAGetProcessStatus(u8* percentp)
|
||||
{
|
||||
if (m_joyBoot)
|
||||
{
|
||||
*percentp = m_joyBoot->percentComplete();
|
||||
if (!m_joyBoot->isDone())
|
||||
return GBA_BUSY;
|
||||
}
|
||||
|
||||
if (m_cmdIssued || m_waitingResp == EWaitResp::WaitCmd)
|
||||
return GBA_BUSY;
|
||||
|
||||
return GBA_READY;
|
||||
}
|
||||
|
||||
EJoyReturn Endpoint::GBAGetStatusAsync(u8* status, FGBACallback&& callback)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_syncLock);
|
||||
if (m_cmdIssued || m_waitingResp == EWaitResp::WaitCmd)
|
||||
return GBA_NOT_READY;
|
||||
|
||||
m_cmdIssued = true;
|
||||
m_statusPtr = status;
|
||||
m_buffer[0] = CMD_STATUS;
|
||||
m_callback = std::move(callback);
|
||||
|
||||
return GBA_READY;
|
||||
}
|
||||
|
||||
EJoyReturn Endpoint::GBAGetStatus(u8* status)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_syncLock);
|
||||
if (m_cmdIssued || m_waitingResp == EWaitResp::WaitCmd)
|
||||
return GBA_NOT_READY;
|
||||
|
||||
m_cmdIssued = true;
|
||||
m_statusPtr = status;
|
||||
m_buffer[0] = CMD_STATUS;
|
||||
m_callback = bindSync();
|
||||
|
||||
m_syncCv.wait(lk);
|
||||
|
||||
return GBA_READY;
|
||||
}
|
||||
|
||||
EJoyReturn Endpoint::GBAResetAsync(u8* status, FGBACallback&& callback)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_syncLock);
|
||||
if (m_cmdIssued || m_waitingResp == EWaitResp::WaitCmd)
|
||||
return GBA_NOT_READY;
|
||||
|
||||
m_cmdIssued = true;
|
||||
m_statusPtr = status;
|
||||
m_buffer[0] = CMD_RESET;
|
||||
m_callback = std::move(callback);
|
||||
|
||||
return GBA_READY;
|
||||
}
|
||||
|
||||
EJoyReturn Endpoint::GBAReset(u8* status)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_syncLock);
|
||||
if (m_cmdIssued || m_waitingResp == EWaitResp::WaitCmd)
|
||||
return GBA_NOT_READY;
|
||||
|
||||
m_cmdIssued = true;
|
||||
m_statusPtr = status;
|
||||
m_buffer[0] = CMD_RESET;
|
||||
m_callback = bindSync();
|
||||
|
||||
m_syncCv.wait(lk);
|
||||
|
||||
return GBA_READY;
|
||||
}
|
||||
|
||||
EJoyReturn Endpoint::GBAReadAsync(u8* dst, u8* status, FGBACallback&& callback)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_syncLock);
|
||||
if (m_cmdIssued || m_waitingResp == EWaitResp::WaitCmd)
|
||||
return GBA_NOT_READY;
|
||||
|
||||
m_cmdIssued = true;
|
||||
m_statusPtr = status;
|
||||
m_readDstPtr = dst;
|
||||
m_buffer[0] = CMD_READ;
|
||||
m_callback = std::move(callback);
|
||||
|
||||
return GBA_READY;
|
||||
}
|
||||
|
||||
EJoyReturn Endpoint::GBARead(u8* dst, u8* status)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_syncLock);
|
||||
if (m_cmdIssued || m_waitingResp == EWaitResp::WaitCmd)
|
||||
return GBA_NOT_READY;
|
||||
|
||||
m_cmdIssued = true;
|
||||
m_statusPtr = status;
|
||||
m_readDstPtr = dst;
|
||||
m_buffer[0] = CMD_READ;
|
||||
m_callback = bindSync();
|
||||
|
||||
m_syncCv.wait(lk);
|
||||
|
||||
return GBA_READY;
|
||||
}
|
||||
|
||||
EJoyReturn Endpoint::GBAWriteAsync(const u8* src, u8* status, FGBACallback&& callback)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_syncLock);
|
||||
if (m_cmdIssued || m_waitingResp == EWaitResp::WaitCmd)
|
||||
return GBA_NOT_READY;
|
||||
|
||||
m_cmdIssued = true;
|
||||
m_statusPtr = status;
|
||||
m_buffer[0] = CMD_WRITE;
|
||||
for (int i=0 ; i<4 ; ++i)
|
||||
m_buffer[i+1] = src[i];
|
||||
m_callback = std::move(callback);
|
||||
|
||||
return GBA_READY;
|
||||
}
|
||||
|
||||
EJoyReturn Endpoint::GBAWrite(const u8* src, u8* status)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_syncLock);
|
||||
if (m_cmdIssued || m_waitingResp == EWaitResp::WaitCmd)
|
||||
return GBA_NOT_READY;
|
||||
|
||||
m_cmdIssued = true;
|
||||
m_statusPtr = status;
|
||||
m_buffer[0] = CMD_WRITE;
|
||||
for (int i=0 ; i<4 ; ++i)
|
||||
m_buffer[i+1] = src[i];
|
||||
m_callback = bindSync();
|
||||
|
||||
m_syncCv.wait(lk);
|
||||
|
||||
return GBA_READY;
|
||||
}
|
||||
|
||||
EJoyReturn Endpoint::GBAJoyBootAsync(s32 paletteColor, s32 paletteSpeed,
|
||||
u8* programp, s32 length, u8* status,
|
||||
FGBACallback&& callback)
|
||||
{
|
||||
if (m_chan > 3)
|
||||
return GBA_JOYBOOT_ERR_INVALID;
|
||||
|
||||
if (!length || length >= 0x40000)
|
||||
return GBA_JOYBOOT_ERR_INVALID;
|
||||
|
||||
if (paletteSpeed < -4 || paletteSpeed > 4)
|
||||
return GBA_JOYBOOT_ERR_INVALID;
|
||||
|
||||
if (paletteColor < 0 || paletteColor > 6)
|
||||
return GBA_JOYBOOT_ERR_INVALID;
|
||||
|
||||
if (programp[0xac] * programp[0xac] * programp[0xac] * programp[0xac] == 0)
|
||||
return GBA_JOYBOOT_ERR_INVALID;
|
||||
|
||||
m_joyBoot.emplace(*this, paletteColor, paletteSpeed, programp, length, status,
|
||||
std::move(callback));
|
||||
if (!m_joyBoot->started())
|
||||
return GBA_NOT_READY;
|
||||
|
||||
return GBA_READY;
|
||||
}
|
||||
|
||||
Endpoint::Endpoint(u8 chan, net::Socket&& data, net::Socket&& clock)
|
||||
: m_dataSocket(std::move(data)), m_clockSocket(std::move(clock)), m_chan(chan)
|
||||
{
|
||||
m_transferThread = std::thread(std::bind(&Endpoint::transferProc, this));
|
||||
}
|
||||
|
||||
Endpoint::~Endpoint() { stop(); }
|
||||
|
||||
EJoyReturn EndpointLocal::GBAGetStatusAsync(u8* status, FGBACallback&& callback)
|
||||
{
|
||||
if (m_ep.m_cmdIssued)
|
||||
return GBA_NOT_READY;
|
||||
|
||||
m_ep.m_cmdIssued = true;
|
||||
m_ep.m_statusPtr = status;
|
||||
m_ep.m_buffer[0] = Endpoint::CMD_STATUS;
|
||||
m_ep.m_callback = std::move(callback);
|
||||
|
||||
return GBA_READY;
|
||||
}
|
||||
|
||||
EJoyReturn EndpointLocal::GBAResetAsync(u8* status, FGBACallback&& callback)
|
||||
{
|
||||
if (m_ep.m_cmdIssued)
|
||||
return GBA_NOT_READY;
|
||||
|
||||
m_ep.m_cmdIssued = true;
|
||||
m_ep.m_statusPtr = status;
|
||||
m_ep.m_buffer[0] = Endpoint::CMD_RESET;
|
||||
m_ep.m_callback = std::move(callback);
|
||||
|
||||
return GBA_READY;
|
||||
}
|
||||
|
||||
EJoyReturn EndpointLocal::GBAReadAsync(u8* dst, u8* status, FGBACallback&& callback)
|
||||
{
|
||||
if (m_ep.m_cmdIssued)
|
||||
return GBA_NOT_READY;
|
||||
|
||||
m_ep.m_cmdIssued = true;
|
||||
m_ep.m_statusPtr = status;
|
||||
m_ep.m_readDstPtr = dst;
|
||||
m_ep.m_buffer[0] = Endpoint::CMD_READ;
|
||||
m_ep.m_callback = std::move(callback);
|
||||
|
||||
return GBA_READY;
|
||||
}
|
||||
|
||||
EJoyReturn EndpointLocal::GBAWriteAsync(const u8* src, u8* status, FGBACallback&& callback)
|
||||
{
|
||||
if (m_ep.m_cmdIssued)
|
||||
return GBA_NOT_READY;
|
||||
|
||||
m_ep.m_cmdIssued = true;
|
||||
m_ep.m_statusPtr = status;
|
||||
m_ep.m_buffer[0] = Endpoint::CMD_WRITE;
|
||||
for (int i=0 ; i<4 ; ++i)
|
||||
m_ep.m_buffer[i+1] = src[i];
|
||||
m_ep.m_callback = std::move(callback);
|
||||
|
||||
return GBA_READY;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
#include "jbus/Listener.hpp"
|
||||
#include "jbus/Endpoint.hpp"
|
||||
|
||||
namespace jbus
|
||||
{
|
||||
|
||||
void Listener::listenerProc()
|
||||
{
|
||||
printf("JoyBus listener started\n");
|
||||
|
||||
net::IPAddress localhost("0.0.0.0");
|
||||
bool dataBound = false;
|
||||
bool clockBound = false;
|
||||
while (m_running && (!dataBound || !clockBound))
|
||||
{
|
||||
if (!dataBound)
|
||||
{
|
||||
if (!(dataBound = m_dataServer.openAndListen(localhost, DataPort)))
|
||||
{
|
||||
m_dataServer = net::Socket(false);
|
||||
printf("data open failed %s; will retry\n", strerror(errno));
|
||||
sleep(1);
|
||||
}
|
||||
else
|
||||
printf("data listening on port %d\n", DataPort);
|
||||
}
|
||||
if (!clockBound)
|
||||
{
|
||||
if (!(clockBound = m_clockServer.openAndListen(localhost, ClockPort)))
|
||||
{
|
||||
m_clockServer = net::Socket(false);
|
||||
printf("clock open failed %s; will retry\n", strerror(errno));
|
||||
sleep(1);
|
||||
}
|
||||
else
|
||||
printf("clock listening on port %d\n", ClockPort);
|
||||
}
|
||||
}
|
||||
|
||||
net::Socket acceptData = {false};
|
||||
net::Socket acceptClock = {false};
|
||||
std::string hostname;
|
||||
u8 chan = 1;
|
||||
while (m_running && chan < 4)
|
||||
{
|
||||
if (m_dataServer.accept(acceptData, hostname) == net::Socket::EResult::OK)
|
||||
printf("accepted data connection from %s\n", hostname.c_str());
|
||||
if (m_clockServer.accept(acceptClock, hostname) == net::Socket::EResult::OK)
|
||||
printf("accepted clock connection from %s\n", hostname.c_str());
|
||||
if (acceptData && acceptClock)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_queueLock);
|
||||
m_endpointQueue.push(std::make_unique<Endpoint>(
|
||||
chan++, std::move(acceptData), std::move(acceptClock)));
|
||||
}
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
m_dataServer.close();
|
||||
m_clockServer.close();
|
||||
printf("JoyBus listener stopped\n");
|
||||
}
|
||||
|
||||
void Listener::start()
|
||||
{
|
||||
stop();
|
||||
m_running = true;
|
||||
m_listenerThread = std::thread(std::bind(&Listener::listenerProc, this));
|
||||
}
|
||||
|
||||
void Listener::stop()
|
||||
{
|
||||
m_running = false;
|
||||
if (m_listenerThread.joinable())
|
||||
m_listenerThread.join();
|
||||
}
|
||||
|
||||
std::unique_ptr<Endpoint> Listener::accept()
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_queueLock);
|
||||
if (m_endpointQueue.size())
|
||||
{
|
||||
std::unique_ptr<Endpoint> ret;
|
||||
ret = std::move(m_endpointQueue.front());
|
||||
m_endpointQueue.pop();
|
||||
return ret;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Listener::~Listener() { stop(); }
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
int main(int argc, char** argv)
|
||||
{
|
||||
|
||||
}
|
Loading…
Reference in New Issue