commit ab163e856aaa7f36c9ba9545d34f203016d9eb6d Author: Jack Andersen Date: Fri Jan 6 17:13:23 2017 -1000 Initial commit diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5924afb --- /dev/null +++ b/CMakeLists.txt @@ -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) diff --git a/README.md b/README.md new file mode 100644 index 0000000..57382b8 --- /dev/null +++ b/README.md @@ -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. diff --git a/include/jbus/Common.hpp b/include/jbus/Common.hpp new file mode 100644 index 0000000..e19cf15 --- /dev/null +++ b/include/jbus/Common.hpp @@ -0,0 +1,171 @@ +#ifndef JBUS_COMMON_HPP +#define JBUS_COMMON_HPP + +#include +#include +#include + +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 +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 +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 +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; + +u64 GetGCTicks(); +void WaitGCTicks(u64 ticks); +static constexpr u64 GetGCTicksPerSec() { return 486000000ull; } +void Initialize(); + +} + +#endif // JBUS_COMMON_HPP diff --git a/include/jbus/Endpoint.hpp b/include/jbus/Endpoint.hpp new file mode 100644 index 0000000..6289e5a --- /dev/null +++ b/include/jbus/Endpoint.hpp @@ -0,0 +1,214 @@ +#ifndef JBUS_ENDPOINT_HPP +#define JBUS_ENDPOINT_HPP + +#include "Common.hpp" +#include "Socket.hpp" +#include "optional.hpp" +#include + +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(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(static_cast(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 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 diff --git a/include/jbus/Listener.hpp b/include/jbus/Listener.hpp new file mode 100644 index 0000000..f4f966e --- /dev/null +++ b/include/jbus/Listener.hpp @@ -0,0 +1,35 @@ +#ifndef JBUS_LISTENER_HPP +#define JBUS_LISTENER_HPP + +#include "Common.hpp" +#include "Socket.hpp" +#include +#include + +namespace jbus +{ + +class Listener +{ + net::Socket m_dataServer = {false}; + net::Socket m_clockServer = {false}; + std::thread m_listenerThread; + std::mutex m_queueLock; + std::queue> 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 accept(); + ~Listener(); +}; + +} + +#endif // JBUS_LISTENER_HPP diff --git a/include/jbus/Socket.hpp b/include/jbus/Socket.hpp new file mode 100644 index 0000000..4f47184 --- /dev/null +++ b/include/jbus/Socket.hpp @@ -0,0 +1,328 @@ +#ifndef JBUS_SOCKET_HPP +#define JBUS_SOCKET_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(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(&one), sizeof(one)); +#ifdef __APPLE__ + setsockopt(m_socket, SOL_SOCKET, SO_NOSIGPIPE, reinterpret_cast(&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(&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(&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(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(buf), static_cast(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 diff --git a/include/jbus/optional.hpp b/include/jbus/optional.hpp new file mode 100644 index 0000000..2955d04 --- /dev/null +++ b/include/jbus/optional.hpp @@ -0,0 +1,1049 @@ +// Copyright (C) 2011 - 2012 Andrzej Krzemienski. +// +// Use, modification, and distribution is subject to the Boost Software +// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// The idea and interface is based on Boost.Optional library +// authored by Fernando Luis Cacciola Carballal + +# ifndef ___OPTIONAL_HPP___ +# define ___OPTIONAL_HPP___ + +# include +# include +# include +# include +# include +# include +# include "athena/Global.hpp" + +# define TR2_OPTIONAL_REQUIRES(...) typename enable_if<__VA_ARGS__::value, bool>::type = false + +# if defined __GNUC__ // NOTE: GNUC is also defined for Clang +# if (__GNUC__ == 4) && (__GNUC_MINOR__ >= 8) +# define TR2_OPTIONAL_GCC_4_8_AND_HIGHER___ +# elif (__GNUC__ > 4) +# define TR2_OPTIONAL_GCC_4_8_AND_HIGHER___ +# endif +# +# if (__GNUC__ == 4) && (__GNUC_MINOR__ >= 7) +# define TR2_OPTIONAL_GCC_4_7_AND_HIGHER___ +# elif (__GNUC__ > 4) +# define TR2_OPTIONAL_GCC_4_7_AND_HIGHER___ +# endif +# +# if (__GNUC__ == 4) && (__GNUC_MINOR__ == 8) && (__GNUC_PATCHLEVEL__ >= 1) +# define TR2_OPTIONAL_GCC_4_8_1_AND_HIGHER___ +# elif (__GNUC__ == 4) && (__GNUC_MINOR__ >= 9) +# define TR2_OPTIONAL_GCC_4_8_1_AND_HIGHER___ +# elif (__GNUC__ > 4) +# define TR2_OPTIONAL_GCC_4_8_1_AND_HIGHER___ +# endif +# endif +# +# if defined __clang_major__ +# if (__clang_major__ == 3 && __clang_minor__ >= 5) +# define TR2_OPTIONAL_CLANG_3_5_AND_HIGHTER_ +# elif (__clang_major__ > 3) +# define TR2_OPTIONAL_CLANG_3_5_AND_HIGHTER_ +# endif +# if defined TR2_OPTIONAL_CLANG_3_5_AND_HIGHTER_ +# define TR2_OPTIONAL_CLANG_3_4_2_AND_HIGHER_ +# elif (__clang_major__ == 3 && __clang_minor__ == 4 && __clang_patchlevel__ >= 2) +# define TR2_OPTIONAL_CLANG_3_4_2_AND_HIGHER_ +# endif +# endif +# +# if defined _MSC_VER +# if (_MSC_VER >= 1900) +# define TR2_OPTIONAL_MSVC_2015_AND_HIGHER___ +# endif +# endif + +# if defined __clang__ +# if (__clang_major__ > 2) || (__clang_major__ == 2) && (__clang_minor__ >= 9) +# define OPTIONAL_HAS_THIS_RVALUE_REFS 1 +# else +# define OPTIONAL_HAS_THIS_RVALUE_REFS 0 +# endif +# elif defined TR2_OPTIONAL_GCC_4_8_1_AND_HIGHER___ +# define OPTIONAL_HAS_THIS_RVALUE_REFS 1 +# elif defined TR2_OPTIONAL_MSVC_2015_AND_HIGHER___ +# define OPTIONAL_HAS_THIS_RVALUE_REFS 1 +# else +# define OPTIONAL_HAS_THIS_RVALUE_REFS 0 +# endif + + +# if defined TR2_OPTIONAL_GCC_4_8_1_AND_HIGHER___ +# define OPTIONAL_HAS_CONSTEXPR_INIT_LIST 1 +# define OPTIONAL_CONSTEXPR_INIT_LIST constexpr +# else +# define OPTIONAL_HAS_CONSTEXPR_INIT_LIST 0 +# define OPTIONAL_CONSTEXPR_INIT_LIST +# endif + +# if defined TR2_OPTIONAL_CLANG_3_5_AND_HIGHTER_ && (defined __cplusplus) && (__cplusplus != 201103L) +# define OPTIONAL_HAS_MOVE_ACCESSORS 1 +# else +# define OPTIONAL_HAS_MOVE_ACCESSORS 0 +# endif + +# // In C++11 constexpr implies const, so we need to make non-const members also non-constexpr +# if (defined __cplusplus) && (__cplusplus == 201103L) +# define OPTIONAL_MUTABLE_CONSTEXPR +# else +# define OPTIONAL_MUTABLE_CONSTEXPR constexpr +# endif + +namespace std{ + +namespace experimental{ + +// BEGIN workaround for missing is_trivially_destructible +# if defined TR2_OPTIONAL_GCC_4_8_AND_HIGHER___ + // leave it: it is already there +# elif defined TR2_OPTIONAL_CLANG_3_4_2_AND_HIGHER_ + // leave it: it is already there +# elif defined TR2_OPTIONAL_MSVC_2015_AND_HIGHER___ + // leave it: it is already there +# elif defined TR2_OPTIONAL_DISABLE_EMULATION_OF_TYPE_TRAITS + // leave it: the user doesn't want it +# else + template + using is_trivially_destructible = std::has_trivial_destructor; +# endif +// END workaround for missing is_trivially_destructible + +# if (defined TR2_OPTIONAL_GCC_4_7_AND_HIGHER___) + // leave it; our metafunctions are already defined. +# elif defined TR2_OPTIONAL_CLANG_3_4_2_AND_HIGHER_ + // leave it; our metafunctions are already defined. +# elif defined TR2_OPTIONAL_MSVC_2015_AND_HIGHER___ + // leave it: it is already there +# elif defined TR2_OPTIONAL_DISABLE_EMULATION_OF_TYPE_TRAITS + // leave it: the user doesn't want it +# else + + +// workaround for missing traits in GCC and CLANG +template +struct is_nothrow_move_constructible +{ + constexpr static bool value = std::is_nothrow_constructible::value; +}; + + +template +struct is_assignable +{ + template + constexpr static bool has_assign(...) { return false; } + + template () = std::declval(), true)) > + // the comma operator is necessary for the cases where operator= returns void + constexpr static bool has_assign(bool) { return true; } + + constexpr static bool value = has_assign(true); +}; + + +template +struct is_nothrow_move_assignable +{ + template + struct has_nothrow_move_assign { + constexpr static bool value = false; + }; + + template + struct has_nothrow_move_assign { + constexpr static bool value = noexcept( std::declval() = std::declval() ); + }; + + constexpr static bool value = has_nothrow_move_assign::value>::value; +}; +// end workaround + + +# endif + + + +// 20.5.4, optional for object types +template class optional; + +// 20.5.5, optional for lvalue reference types +template class optional; + + +// workaround: std utility functions aren't constexpr yet +template inline constexpr T&& constexpr_forward(typename std::remove_reference::type& t) noexcept +{ + return static_cast(t); +} + +template inline constexpr T&& constexpr_forward(typename std::remove_reference::type&& t) noexcept +{ + static_assert(!std::is_lvalue_reference::value, "!!"); + return static_cast(t); +} + +template inline constexpr typename std::remove_reference::type&& constexpr_move(T&& t) noexcept +{ + return static_cast::type&&>(t); +} + + +#if defined NDEBUG +# define TR2_OPTIONAL_ASSERTED_EXPRESSION(CHECK, EXPR) (EXPR) +#else +# define TR2_OPTIONAL_ASSERTED_EXPRESSION(CHECK, EXPR) ((CHECK) ? (EXPR) : ([]{assert(!#CHECK);}(), (EXPR))) +#endif + + +namespace detail_ +{ + +// static_addressof: a constexpr version of addressof +template +struct has_overloaded_addressof +{ + template + constexpr static bool has_overload(...) { return false; } + + template ().operator&()) > + constexpr static bool has_overload(bool) { return true; } + + constexpr static bool value = has_overload(true); +}; + +template )> +constexpr T* static_addressof(T& ref) +{ + return &ref; +} + +template )> +T* static_addressof(T& ref) +{ + return std::addressof(ref); +} + + +// the call to convert(b) has return type A and converts b to type A iff b decltype(b) is implicitly convertible to A +template +U convert(U v) { return v; } + +} // namespace detail + + +constexpr struct trivial_init_t{} trivial_init{}; + + +// 20.5.6, In-place construction +constexpr struct in_place_t{} in_place{}; + + +// 20.5.7, Disengaged state indicator +struct nullopt_t +{ + struct init{}; + constexpr explicit nullopt_t(init){} +}; +constexpr nullopt_t nullopt{nullopt_t::init()}; + + +// 20.5.8, class bad_optional_access +class bad_optional_access : public logic_error { +public: + explicit bad_optional_access(const string& what_arg) : logic_error{what_arg} {} + explicit bad_optional_access(const char* what_arg) : logic_error{what_arg} {} +}; + + +template +union storage_t +{ + unsigned char dummy_; + T value_; + + constexpr storage_t( trivial_init_t ) noexcept : dummy_() {}; + + template + constexpr storage_t( Args&&... args ) : value_(constexpr_forward(args)...) {} + + ~storage_t(){} +}; + + +template +union constexpr_storage_t +{ + unsigned char dummy_; + T value_; + + constexpr constexpr_storage_t( trivial_init_t ) noexcept : dummy_() {}; + + template + constexpr constexpr_storage_t( Args&&... args ) : value_(constexpr_forward(args)...) {} + + ~constexpr_storage_t() = default; +}; + + +template +struct optional_base +{ + bool init_; + storage_t storage_; + + constexpr optional_base() noexcept : init_(false), storage_(trivial_init) {}; + + explicit constexpr optional_base(const T& v) : init_(true), storage_(v) {} + + explicit constexpr optional_base(T&& v) : init_(true), storage_(constexpr_move(v)) {} + + template explicit optional_base(in_place_t, Args&&... args) + : init_(true), storage_(constexpr_forward(args)...) {} + + template >)> + explicit optional_base(in_place_t, std::initializer_list il, Args&&... args) + : init_(true), storage_(il, std::forward(args)...) {} + + ~optional_base() { if (init_) storage_.value_.T::~T(); } +}; + + +template +struct constexpr_optional_base +{ + bool init_; + constexpr_storage_t storage_; + + constexpr constexpr_optional_base() noexcept : init_(false), storage_(trivial_init) {}; + + explicit constexpr constexpr_optional_base(const T& v) : init_(true), storage_(v) {} + + explicit constexpr constexpr_optional_base(T&& v) : init_(true), storage_(constexpr_move(v)) {} + + template explicit constexpr constexpr_optional_base(in_place_t, Args&&... args) + : init_(true), storage_(constexpr_forward(args)...) {} + + template >)> + OPTIONAL_CONSTEXPR_INIT_LIST explicit constexpr_optional_base(in_place_t, std::initializer_list il, Args&&... args) + : init_(true), storage_(il, std::forward(args)...) {} + + ~constexpr_optional_base() = default; +}; + +template +using OptionalBase = typename std::conditional< + is_trivially_destructible::value, + constexpr_optional_base, + optional_base +>::type; + + + +template +class optional : private OptionalBase +{ + static_assert( !std::is_same::type, nullopt_t>::value, "bad T" ); + static_assert( !std::is_same::type, in_place_t>::value, "bad T" ); + + + constexpr bool initialized() const noexcept { return OptionalBase::init_; } + T* dataptr() { return std::addressof(OptionalBase::storage_.value_); } + constexpr const T* dataptr() const { return detail_::static_addressof(OptionalBase::storage_.value_); } + +# if OPTIONAL_HAS_THIS_RVALUE_REFS == 1 + constexpr const T& contained_val() const& { return OptionalBase::storage_.value_; } +# if OPTIONAL_HAS_MOVE_ACCESSORS == 1 + OPTIONAL_MUTABLE_CONSTEXPR T&& contained_val() && { return std::move(OptionalBase::storage_.value_); } + OPTIONAL_MUTABLE_CONSTEXPR T& contained_val() & { return OptionalBase::storage_.value_; } +# else + T& contained_val() & { return OptionalBase::storage_.value_; } + T&& contained_val() && { return std::move(OptionalBase::storage_.value_); } +# endif +# else + constexpr const T& contained_val() const { return OptionalBase::storage_.value_; } + T& contained_val() { return OptionalBase::storage_.value_; } +# endif + + void clear() noexcept { + if (initialized()) dataptr()->T::~T(); + OptionalBase::init_ = false; + } + + template + void initialize(Args&&... args) noexcept(noexcept(T(std::forward(args)...))) + { + assert(!OptionalBase::init_); + ::new (static_cast(dataptr())) T(std::forward(args)...); + OptionalBase::init_ = true; + } + + template + void initialize(std::initializer_list il, Args&&... args) noexcept(noexcept(T(il, std::forward(args)...))) + { + assert(!OptionalBase::init_); + ::new (static_cast(dataptr())) T(il, std::forward(args)...); + OptionalBase::init_ = true; + } + +public: + typedef T value_type; + + // 20.5.5.1, constructors + constexpr optional() noexcept : OptionalBase() {}; + constexpr optional(nullopt_t) noexcept : OptionalBase() {}; + + optional(const optional& rhs) + : OptionalBase() + { + if (rhs.initialized()) { + ::new (static_cast(dataptr())) T(*rhs); + OptionalBase::init_ = true; + } + } + + optional(optional&& rhs) noexcept(is_nothrow_move_constructible::value) + : OptionalBase() + { + if (rhs.initialized()) { + ::new (static_cast(dataptr())) T(std::move(*rhs)); + OptionalBase::init_ = true; + } + } + + constexpr optional(const T& v) : OptionalBase(v) {} + + constexpr optional(T&& v) : OptionalBase(constexpr_move(v)) {} + + template + explicit constexpr optional(in_place_t, Args&&... args) + : OptionalBase(in_place_t{}, constexpr_forward(args)...) {} + + template >)> + OPTIONAL_CONSTEXPR_INIT_LIST explicit optional(in_place_t, std::initializer_list il, Args&&... args) + : OptionalBase(in_place_t{}, il, constexpr_forward(args)...) {} + + // 20.5.4.2, Destructor + ~optional() = default; + + // 20.5.4.3, assignment + optional& operator=(nullopt_t) noexcept + { + clear(); + return *this; + } + + optional& operator=(const optional& rhs) + { + if (initialized() == true && rhs.initialized() == false) clear(); + else if (initialized() == false && rhs.initialized() == true) initialize(*rhs); + else if (initialized() == true && rhs.initialized() == true) contained_val() = *rhs; + return *this; + } + + optional& operator=(optional&& rhs) + noexcept(is_nothrow_move_assignable::value && is_nothrow_move_constructible::value) + { + if (initialized() == true && rhs.initialized() == false) clear(); + else if (initialized() == false && rhs.initialized() == true) initialize(std::move(*rhs)); + else if (initialized() == true && rhs.initialized() == true) contained_val() = std::move(*rhs); + return *this; + } + + template + auto operator=(U&& v) + -> typename enable_if + < + is_same::type, T>::value, + optional& + >::type + { + if (initialized()) { contained_val() = std::forward(v); } + else { initialize(std::forward(v)); } + return *this; + } + + + template + void emplace(Args&&... args) + { + clear(); + initialize(std::forward(args)...); + } + + template + void emplace(initializer_list il, Args&&... args) + { + clear(); + initialize(il, std::forward(args)...); + } + + // 20.5.4.4, Swap + void swap(optional& rhs) noexcept(is_nothrow_move_constructible::value && noexcept(swap(declval(), declval()))) + { + if (initialized() == true && rhs.initialized() == false) { rhs.initialize(std::move(**this)); clear(); } + else if (initialized() == false && rhs.initialized() == true) { initialize(std::move(*rhs)); rhs.clear(); } + else if (initialized() == true && rhs.initialized() == true) { using std::swap; swap(**this, *rhs); } + } + + // 20.5.4.5, Observers + + explicit constexpr operator bool() const noexcept { return initialized(); } + + constexpr T const* operator ->() const { + return TR2_OPTIONAL_ASSERTED_EXPRESSION(initialized(), dataptr()); + } + +# if OPTIONAL_HAS_MOVE_ACCESSORS == 1 + + OPTIONAL_MUTABLE_CONSTEXPR T* operator ->() { + assert (initialized()); + return dataptr(); + } + + constexpr T const& operator *() const& { + return TR2_OPTIONAL_ASSERTED_EXPRESSION(initialized(), contained_val()); + } + + OPTIONAL_MUTABLE_CONSTEXPR T& operator *() & { + assert (initialized()); + return contained_val(); + } + + OPTIONAL_MUTABLE_CONSTEXPR T&& operator *() && { + assert (initialized()); + return constexpr_move(contained_val()); + } + + constexpr T const& value() const& { + if (!initialized()) atFatal("bad optional access"); + return contained_val(); + } + + OPTIONAL_MUTABLE_CONSTEXPR T& value() & { + if (!initialized()) atFatal("bad optional access"); + return contained_val(); + } + + OPTIONAL_MUTABLE_CONSTEXPR T&& value() && { + if (!initialized()) atFatal("bad optional access"); + return std::move(contained_val()); + } + +# else + + T* operator ->() { + assert (initialized()); + return dataptr(); + } + + constexpr T const& operator *() const { + return TR2_OPTIONAL_ASSERTED_EXPRESSION(initialized(), contained_val()); + } + + T& operator *() { + assert (initialized()); + return contained_val(); + } + + constexpr T const& value() const { + if (!initialized()) + atFatal("bad optional access"); + return contained_val(); + } + + T& value() { + if (!initialized()) + atFatal("bad optional access"); + return contained_val(); + } + +# endif + +# if OPTIONAL_HAS_THIS_RVALUE_REFS == 1 + + template + constexpr T value_or(V&& v) const& + { + return *this ? **this : detail_::convert(constexpr_forward(v)); + } + +# if OPTIONAL_HAS_MOVE_ACCESSORS == 1 + + template + OPTIONAL_MUTABLE_CONSTEXPR T value_or(V&& v) && + { + return *this ? constexpr_move(const_cast&>(*this).contained_val()) : detail_::convert(constexpr_forward(v)); + } + +# else + + template + T value_or(V&& v) && + { + return *this ? constexpr_move(const_cast&>(*this).contained_val()) : detail_::convert(constexpr_forward(v)); + } + +# endif + +# else + + template + constexpr T value_or(V&& v) const + { + return *this ? **this : detail_::convert(constexpr_forward(v)); + } + +# endif + +}; + + +template +class optional +{ + static_assert( !std::is_same::value, "bad T" ); + static_assert( !std::is_same::value, "bad T" ); + T* ref; + +public: + + // 20.5.5.1, construction/destruction + constexpr optional() noexcept : ref(nullptr) {} + + constexpr optional(nullopt_t) noexcept : ref(nullptr) {} + + constexpr optional(T& v) noexcept : ref(detail_::static_addressof(v)) {} + + optional(T&&) = delete; + + constexpr optional(const optional& rhs) noexcept : ref(rhs.ref) {} + + explicit constexpr optional(in_place_t, T& v) noexcept : ref(detail_::static_addressof(v)) {} + + explicit optional(in_place_t, T&&) = delete; + + ~optional() = default; + + // 20.5.5.2, mutation + optional& operator=(nullopt_t) noexcept { + ref = nullptr; + return *this; + } + + // optional& operator=(const optional& rhs) noexcept { + // ref = rhs.ref; + // return *this; + // } + + // optional& operator=(optional&& rhs) noexcept { + // ref = rhs.ref; + // return *this; + // } + + template + auto operator=(U&& rhs) noexcept + -> typename enable_if + < + is_same::type, optional>::value, + optional& + >::type + { + ref = rhs.ref; + return *this; + } + + template + auto operator=(U&& rhs) noexcept + -> typename enable_if + < + !is_same::type, optional>::value, + optional& + >::type + = delete; + + void emplace(T& v) noexcept { + ref = detail_::static_addressof(v); + } + + void emplace(T&&) = delete; + + + void swap(optional& rhs) noexcept + { + std::swap(ref, rhs.ref); + } + + // 20.5.5.3, observers + constexpr T* operator->() const { + return TR2_OPTIONAL_ASSERTED_EXPRESSION(ref, ref); + } + + constexpr T& operator*() const { + return TR2_OPTIONAL_ASSERTED_EXPRESSION(ref, *ref); + } + + constexpr T& value() const { + if (!ref) atFatal("bad optional access"); + return *ref; + } + + explicit constexpr operator bool() const noexcept { + return ref != nullptr; + } + + template + constexpr typename decay::type value_or(V&& v) const + { + return *this ? **this : detail_::convert::type>(constexpr_forward(v)); + } +}; + + +template +class optional +{ + static_assert( sizeof(T) == 0, "optional rvalue references disallowed" ); +}; + + +// 20.5.8, Relational operators +template constexpr bool operator==(const optional& x, const optional& y) +{ + return bool(x) != bool(y) ? false : bool(x) == false ? true : *x == *y; +} + +template constexpr bool operator!=(const optional& x, const optional& y) +{ + return !(x == y); +} + +template constexpr bool operator<(const optional& x, const optional& y) +{ + return (!y) ? false : (!x) ? true : *x < *y; +} + +template constexpr bool operator>(const optional& x, const optional& y) +{ + return (y < x); +} + +template constexpr bool operator<=(const optional& x, const optional& y) +{ + return !(y < x); +} + +template constexpr bool operator>=(const optional& x, const optional& y) +{ + return !(x < y); +} + + +// 20.5.9, Comparison with nullopt +template constexpr bool operator==(const optional& x, nullopt_t) noexcept +{ + return (!x); +} + +template constexpr bool operator==(nullopt_t, const optional& x) noexcept +{ + return (!x); +} + +template constexpr bool operator!=(const optional& x, nullopt_t) noexcept +{ + return bool(x); +} + +template constexpr bool operator!=(nullopt_t, const optional& x) noexcept +{ + return bool(x); +} + +template constexpr bool operator<(const optional&, nullopt_t) noexcept +{ + return false; +} + +template constexpr bool operator<(nullopt_t, const optional& x) noexcept +{ + return bool(x); +} + +template constexpr bool operator<=(const optional& x, nullopt_t) noexcept +{ + return (!x); +} + +template constexpr bool operator<=(nullopt_t, const optional&) noexcept +{ + return true; +} + +template constexpr bool operator>(const optional& x, nullopt_t) noexcept +{ + return bool(x); +} + +template constexpr bool operator>(nullopt_t, const optional&) noexcept +{ + return false; +} + +template constexpr bool operator>=(const optional&, nullopt_t) noexcept +{ + return true; +} + +template constexpr bool operator>=(nullopt_t, const optional& x) noexcept +{ + return (!x); +} + + + +// 20.5.10, Comparison with T +template constexpr bool operator==(const optional& x, const T& v) +{ + return bool(x) ? *x == v : false; +} + +template constexpr bool operator==(const T& v, const optional& x) +{ + return bool(x) ? v == *x : false; +} + +template constexpr bool operator!=(const optional& x, const T& v) +{ + return bool(x) ? *x != v : true; +} + +template constexpr bool operator!=(const T& v, const optional& x) +{ + return bool(x) ? v != *x : true; +} + +template constexpr bool operator<(const optional& x, const T& v) +{ + return bool(x) ? *x < v : true; +} + +template constexpr bool operator>(const T& v, const optional& x) +{ + return bool(x) ? v > *x : true; +} + +template constexpr bool operator>(const optional& x, const T& v) +{ + return bool(x) ? *x > v : false; +} + +template constexpr bool operator<(const T& v, const optional& x) +{ + return bool(x) ? v < *x : false; +} + +template constexpr bool operator>=(const optional& x, const T& v) +{ + return bool(x) ? *x >= v : false; +} + +template constexpr bool operator<=(const T& v, const optional& x) +{ + return bool(x) ? v <= *x : false; +} + +template constexpr bool operator<=(const optional& x, const T& v) +{ + return bool(x) ? *x <= v : true; +} + +template constexpr bool operator>=(const T& v, const optional& x) +{ + return bool(x) ? v >= *x : true; +} + + +// Comparison of optional with T +template constexpr bool operator==(const optional& x, const T& v) +{ + return bool(x) ? *x == v : false; +} + +template constexpr bool operator==(const T& v, const optional& x) +{ + return bool(x) ? v == *x : false; +} + +template constexpr bool operator!=(const optional& x, const T& v) +{ + return bool(x) ? *x != v : true; +} + +template constexpr bool operator!=(const T& v, const optional& x) +{ + return bool(x) ? v != *x : true; +} + +template constexpr bool operator<(const optional& x, const T& v) +{ + return bool(x) ? *x < v : true; +} + +template constexpr bool operator>(const T& v, const optional& x) +{ + return bool(x) ? v > *x : true; +} + +template constexpr bool operator>(const optional& x, const T& v) +{ + return bool(x) ? *x > v : false; +} + +template constexpr bool operator<(const T& v, const optional& x) +{ + return bool(x) ? v < *x : false; +} + +template constexpr bool operator>=(const optional& x, const T& v) +{ + return bool(x) ? *x >= v : false; +} + +template constexpr bool operator<=(const T& v, const optional& x) +{ + return bool(x) ? v <= *x : false; +} + +template constexpr bool operator<=(const optional& x, const T& v) +{ + return bool(x) ? *x <= v : true; +} + +template constexpr bool operator>=(const T& v, const optional& x) +{ + return bool(x) ? v >= *x : true; +} + +// Comparison of optional with T +template constexpr bool operator==(const optional& x, const T& v) +{ + return bool(x) ? *x == v : false; +} + +template constexpr bool operator==(const T& v, const optional& x) +{ + return bool(x) ? v == *x : false; +} + +template constexpr bool operator!=(const optional& x, const T& v) +{ + return bool(x) ? *x != v : true; +} + +template constexpr bool operator!=(const T& v, const optional& x) +{ + return bool(x) ? v != *x : true; +} + +template constexpr bool operator<(const optional& x, const T& v) +{ + return bool(x) ? *x < v : true; +} + +template constexpr bool operator>(const T& v, const optional& x) +{ + return bool(x) ? v > *x : true; +} + +template constexpr bool operator>(const optional& x, const T& v) +{ + return bool(x) ? *x > v : false; +} + +template constexpr bool operator<(const T& v, const optional& x) +{ + return bool(x) ? v < *x : false; +} + +template constexpr bool operator>=(const optional& x, const T& v) +{ + return bool(x) ? *x >= v : false; +} + +template constexpr bool operator<=(const T& v, const optional& x) +{ + return bool(x) ? v <= *x : false; +} + +template constexpr bool operator<=(const optional& x, const T& v) +{ + return bool(x) ? *x <= v : true; +} + +template constexpr bool operator>=(const T& v, const optional& x) +{ + return bool(x) ? v >= *x : true; +} + + +// 20.5.12, Specialized algorithms +template +void swap(optional& x, optional& y) noexcept(noexcept(x.swap(y))) +{ + x.swap(y); +} + + +template +constexpr optional::type> make_optional(T&& v) +{ + return optional::type>(constexpr_forward(v)); +} + +template +constexpr optional make_optional(reference_wrapper v) +{ + return optional(v.get()); +} + + +} // namespace experimental +} // namespace std + +namespace std +{ + template + struct hash> + { + typedef typename hash::result_type result_type; + typedef std::experimental::optional argument_type; + + constexpr result_type operator()(argument_type const& arg) const { + return arg ? std::hash{}(*arg) : result_type{}; + } + }; + + template + struct hash> + { + typedef typename hash::result_type result_type; + typedef std::experimental::optional argument_type; + + constexpr result_type operator()(argument_type const& arg) const { + return arg ? std::hash{}(*arg) : result_type{}; + } + }; +} + +# undef TR2_OPTIONAL_REQUIRES +# undef TR2_OPTIONAL_ASSERTED_EXPRESSION + +# endif //___OPTIONAL_HPP___ diff --git a/src/Common.cpp b/src/Common.cpp new file mode 100644 index 0000000..9faefec --- /dev/null +++ b/src/Common.cpp @@ -0,0 +1,55 @@ +#ifndef _WIN32 +#include +#if __APPLE__ +#include +#endif +#else +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#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 +} + +} diff --git a/src/Endpoint.cpp b/src/Endpoint.cpp new file mode 100644 index 0000000..a2fe304 --- /dev/null +++ b/src/Endpoint.cpp @@ -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(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(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 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 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 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 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 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 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 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 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 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; +} + +} diff --git a/src/Listener.cpp b/src/Listener.cpp new file mode 100644 index 0000000..974790f --- /dev/null +++ b/src/Listener.cpp @@ -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 lk(m_queueLock); + m_endpointQueue.push(std::make_unique( + 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 Listener::accept() +{ + std::unique_lock lk(m_queueLock); + if (m_endpointQueue.size()) + { + std::unique_ptr ret; + ret = std::move(m_endpointQueue.front()); + m_endpointQueue.pop(); + return ret; + } + return {}; +} + +Listener::~Listener() { stop(); } + +} diff --git a/tools/joyboot.cpp b/tools/joyboot.cpp new file mode 100644 index 0000000..d32091f --- /dev/null +++ b/tools/joyboot.cpp @@ -0,0 +1,4 @@ +int main(int argc, char** argv) +{ + +}