gba-link-connection/lib/_link_common.hpp
2025-02-15 03:44:31 -03:00

429 lines
10 KiB
C++

#ifndef LINK_COMMON_H
#define LINK_COMMON_H
#ifndef LINK_DEVELOPMENT
#pragma GCC system_header
#endif
/**
* @brief Enable mGBA debug logging.
*/
#ifndef LINK_ENABLE_DEBUG_LOGS
#define LINK_ENABLE_DEBUG_LOGS 0
#endif
#if LINK_ENABLE_DEBUG_LOGS != 0
#include <stdarg.h>
#include <stdio.h>
#endif
#define LINK_BARRIER asm volatile("" ::: "memory")
#define LINK_CODE_IWRAM \
__attribute__((section(".iwram"), target("arm"), noinline))
#define LINK_INLINE inline __attribute__((always_inline))
#define LINK_NOINLINE __attribute__((noinline))
#define LINK_PACKED __attribute__((packed))
#define LINK_WORDALIGNED __attribute__((aligned(4)))
#define LINK_UNUSED __attribute__((unused))
#define LINK_VERSION_TAG inline const char*
#define LINK_READ_TAG(TAG) (void)*((volatile const char*)TAG)
/**
* @brief This namespace contains shared code between all libraries.
* \warning Most of these things are borrowed from libtonc and gba-hpp.
*/
namespace Link {
// Types
using u32 = unsigned int;
using u16 = unsigned short;
using u8 = unsigned char;
using s16 = signed short;
using s8 = signed char;
using vu32 = volatile unsigned int;
using vs32 = volatile signed int;
using vu16 = volatile unsigned short;
using vs16 = volatile signed short;
using vu8 = volatile unsigned char;
using vs8 = volatile signed char;
// Globals
inline u32 randomSeed = 123;
// Structs
struct _TMR_REC {
union {
u16 start;
u16 count;
} LINK_PACKED;
u16 cnt;
} LINK_WORDALIGNED;
typedef struct {
u32 reserved1[5];
u8 handshake_data;
u8 padding;
u16 handshake_timeout;
u8 probe_count;
u8 client_data[3];
u8 palette_data;
u8 response_bit;
u8 client_bit;
u8 reserved2;
u8* boot_srcp;
u8* boot_endp;
u8* masterp;
u8* reserved3[3];
u32 system_work2[4];
u8 sendflag;
u8 probe_target_bit;
u8 check_wait;
u8 server_type;
} _MultiBootParam;
// I/O Registers
constexpr u32 _REG_BASE = 0x04000000;
inline vu16& _REG_RCNT = *reinterpret_cast<vu16*>(_REG_BASE + 0x0134);
inline vu16& _REG_SIOCNT = *reinterpret_cast<vu16*>(_REG_BASE + 0x0128);
inline vu32& _REG_SIODATA32 = *reinterpret_cast<vu32*>(_REG_BASE + 0x0120);
inline vu16& _REG_SIODATA8 = *reinterpret_cast<vu16*>(_REG_BASE + 0x012A);
inline vu16& _REG_SIOMLT_SEND = *reinterpret_cast<vu16*>(_REG_BASE + 0x012A);
inline vu16* const _REG_SIOMULTI = reinterpret_cast<vu16*>(_REG_BASE + 0x0120);
inline vu16& _REG_JOYCNT = *reinterpret_cast<vu16*>(_REG_BASE + 0x0140);
inline vu16& _REG_JOY_RECV_L = *reinterpret_cast<vu16*>(_REG_BASE + 0x0150);
inline vu16& _REG_JOY_RECV_H = *reinterpret_cast<vu16*>(_REG_BASE + 0x0152);
inline vu16& _REG_JOY_TRANS_L = *reinterpret_cast<vu16*>(_REG_BASE + 0x0154);
inline vu16& _REG_JOY_TRANS_H = *reinterpret_cast<vu16*>(_REG_BASE + 0x0156);
inline vu16& _REG_JOYSTAT = *reinterpret_cast<vu16*>(_REG_BASE + 0x0158);
inline vu16& _REG_VCOUNT = *reinterpret_cast<vu16*>(_REG_BASE + 0x0006);
inline vu16& _REG_KEYS = *reinterpret_cast<vu16*>(_REG_BASE + 0x0130);
inline vu16& _REG_TM1CNT_L = *reinterpret_cast<vu16*>(_REG_BASE + 0x0104);
inline vu16& _REG_TM1CNT_H = *reinterpret_cast<vu16*>(_REG_BASE + 0x0106);
inline vu16& _REG_TM2CNT_L = *reinterpret_cast<vu16*>(_REG_BASE + 0x0108);
inline vu16& _REG_TM2CNT_H = *reinterpret_cast<vu16*>(_REG_BASE + 0x010A);
inline vu16& _REG_IME = *reinterpret_cast<vu16*>(_REG_BASE + 0x0208);
inline volatile _TMR_REC* const _REG_TM =
reinterpret_cast<volatile _TMR_REC*>(_REG_BASE + 0x0100);
static constexpr u16 _KEY_ANY = 0x03FF; //!< Here's the Any key :)
static constexpr u16 _TM_FREQ_1 = 0; //!< 1 cycle/tick (16.7 MHz)
static constexpr u16 _TM_FREQ_64 = 0x0001; //!< 64 cycles/tick (262 kHz)
static constexpr u16 _TM_FREQ_256 = 0x0002; //!< 256 cycles/tick (66 kHz)
static constexpr u16 _TM_FREQ_1024 = 0x0003; //!< 1024 cycles/tick (16 kHz)
static constexpr u16 _TM_CASCADE =
0x0004; //!< Increment when preceding timer overflows
static constexpr u16 _TM_IRQ = 0x0040; //!< Enable timer irq
static constexpr u16 _TM_ENABLE = 0x0080; //!< Enable timer
static constexpr u16 _IRQ_VBLANK = 0x0001; //!< Catch VBlank irq
static constexpr u16 _IRQ_TIMER0 = 0x0008; //!< Catch timer 0 irq
static constexpr u16 _IRQ_TIMER1 = 0x0010; //!< Catch timer 1 irq
static constexpr u16 _IRQ_TIMER2 = 0x0020; //!< Catch timer 2 irq
static constexpr u16 _IRQ_TIMER3 = 0x0040; //!< Catch timer 3 irq
static constexpr u16 _IRQ_SERIAL = 0x0080; //!< Catch serial comm irq
static constexpr u16 _TIMER_IRQ_IDS[] = {_IRQ_TIMER0, _IRQ_TIMER1, _IRQ_TIMER2,
_IRQ_TIMER3};
// SWI
static LINK_INLINE void _IntrWait(bool clearCurrent, u32 flags) noexcept {
register auto r0 asm("r0") = clearCurrent;
register auto r1 asm("r1") = flags;
asm volatile inline("swi 0x4 << ((1f - . == 4) * -16); 1:"
: "+r"(r0), "+r"(r1)::"r3");
}
static LINK_INLINE auto _MultiBoot(const _MultiBootParam* param,
u32 mbmode) noexcept {
register union {
const _MultiBootParam* ptr;
int res;
} r0 asm("r0") = {param};
register auto r1 asm("r1") = mbmode;
asm volatile inline("swi 0x25 << ((1f - . == 4) * -16); 1:"
: "+r"(r0), "+r"(r1)::"r3");
return r0.res;
}
// Random
static inline int _qran() {
randomSeed = 1664525 * randomSeed + 1013904223;
return (randomSeed >> 16) & 0x7FFF;
}
static inline int _qran_range(int min, int max) {
return (_qran() * (max - min) >> 15) + min;
}
// Helpers
static LINK_INLINE u32 buildU32(u16 msB, u16 lsB) {
return (msB << 16) | lsB;
}
static LINK_INLINE u32 buildU32(u8 msB, u8 byte2, u8 byte3, u8 lsB) {
return ((msB & 0xFF) << 24) | ((byte2 & 0xFF) << 16) | ((byte3 & 0xFF) << 8) |
(lsB & 0xFF);
}
static LINK_INLINE u16 buildU16(u8 msB, u8 lsB) {
return (msB << 8) | lsB;
}
static LINK_INLINE u16 msB32(u32 value) {
return value >> 16;
}
static LINK_INLINE u16 lsB32(u32 value) {
return value & 0xFFFF;
}
static LINK_INLINE u8 msB16(u16 value) {
return value >> 8;
}
static LINK_INLINE u8 lsB16(u16 value) {
return value & 0xFF;
}
static LINK_INLINE int _max(int a, int b) {
return (a > b) ? (a) : (b);
}
static LINK_INLINE int _min(int a, int b) {
return (a < b) ? (a) : (b);
}
static inline void wait(u32 verticalLines) {
u32 count = 0;
u32 vCount = Link::_REG_VCOUNT;
while (count < verticalLines) {
if (Link::_REG_VCOUNT != vCount) {
count++;
vCount = Link::_REG_VCOUNT;
}
};
}
static inline u32 strlen(const char* s) {
u32 len = 0;
while (s[len] != '\0')
++len;
return len;
}
static inline bool areStrEqual(const char* s1, const char* s2) {
while (*s1 && (*s1 == *s2)) {
++s1;
++s2;
}
return *s1 == *s2;
}
static inline void intToStr5(char* buf, int num) {
char temp[6];
int pos = 0;
do {
temp[pos++] = '0' + (num % 10);
num /= 10;
} while (num && pos < 5);
int j = 0;
while (pos)
buf[j++] = temp[--pos];
buf[j] = '\0';
}
template <typename Func>
struct ScopeGuard {
Func f;
ScopeGuard(Func f) : f(f) {}
~ScopeGuard() { f(); }
};
// Interfaces
class AsyncMultiboot {
public:
enum class Result {
NONE = -1,
SUCCESS = 0,
INVALID_DATA = 1,
INIT_FAILED = 2,
FAILURE = 3
};
virtual bool sendRom(const u8* rom, u32 romSize) = 0;
virtual bool reset() = 0;
[[nodiscard]] virtual bool isSending() = 0;
virtual Result getResult(bool clear = true) = 0;
[[nodiscard]] virtual u8 playerCount() = 0;
[[nodiscard]] virtual u8 getPercentage() = 0;
[[nodiscard]] virtual bool isReady() = 0;
virtual void markReady() = 0;
virtual ~AsyncMultiboot() = default;
};
// Queue
template <typename T, u32 Size>
class Queue {
public:
void push(T item) {
if (isFull()) {
overflow = true; // (flag that the queue overflowed)
pop(); // (discard the oldest item to prioritize the new one)
}
rear = (rear + 1) % Size;
arr[rear] = item;
count = count + 1;
}
T pop() {
if (isEmpty())
return T{};
auto x = arr[front];
front = (front + 1) % Size;
count = count - 1;
return x;
}
T peek() {
if (isEmpty())
return T{};
return arr[front];
}
T* peekRef() {
if (isEmpty())
return nullptr;
return &arr[front];
}
template <typename F>
LINK_INLINE void forEach(F action) {
vs32 currentFront = front;
for (u32 i = 0; i < count; i++) {
if (!action(&arr[currentFront]))
return;
currentFront = (currentFront + 1) % Size;
}
}
void clear() {
front = 0;
count = 0;
rear = -1;
}
void startReading() { _isReading = true; }
void stopReading() { _isReading = false; }
void syncPush(T item) {
_isWriting = true;
LINK_BARRIER;
push(item);
LINK_BARRIER;
_isWriting = false;
LINK_BARRIER;
if (_needsClear) {
clear();
_needsClear = false;
}
}
T syncPop() {
_isReading = true;
LINK_BARRIER;
auto value = pop();
LINK_BARRIER;
_isReading = false;
LINK_BARRIER;
return value;
}
void syncClear() {
if (_isReading)
return; // (it will be cleared later anyway)
if (!_isWriting)
clear();
else
_needsClear = true;
}
u32 size() { return count; }
bool isEmpty() { return count == 0; }
bool isFull() { return count == Size; }
bool isReading() { return _isReading; }
bool isWriting() { return _isWriting; }
bool canMutate() { return !_isReading && !_isWriting; }
volatile bool overflow = false;
private:
T arr[Size];
vs32 front = 0;
vs32 rear = -1;
vu32 count = 0;
volatile bool _isReading = false;
volatile bool _isWriting = false;
volatile bool _needsClear = false;
};
// Reset communication registers
static inline void reset() {
_REG_RCNT = 1 << 15;
_REG_SIOCNT = 0;
}
// Transfers per frame -> Timer interval
static inline u16 perFrame(u16 packets) {
return (1667 * 1024) / (packets * 6104);
}
// mGBA Logging
#if LINK_ENABLE_DEBUG_LOGS != 0
inline vu16& _REG_LOG_ENABLE = *reinterpret_cast<vu16*>(0x4FFF780);
inline vu16& _REG_LOG_LEVEL = *reinterpret_cast<vu16*>(0x4FFF700);
static inline void log(const char* fmt, ...) {
_REG_LOG_ENABLE = 0xC0DE;
va_list args;
va_start(args, fmt);
char* const log = (char*)0x4FFF600;
vsnprintf(log, 0x100, fmt, args);
_REG_LOG_LEVEL = 0x102; // Level: WARN
va_end(args);
}
#endif
} // namespace Link
#endif // LINK_COMMON_H