Skip to content

Instantly share code, notes, and snippets.

@stblr
Created August 9, 2023 21:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stblr/c99aa7bcdc26a0be78b4a50d6dc3359b to your computer and use it in GitHub Desktop.
Save stblr/c99aa7bcdc26a0be78b4a50d6dc3359b to your computer and use it in GitHub Desktop.
// Copyright 2023 Pablo Stebler
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// To be compiled with NXP 'CodeWarrior Special Edition' for MPC55xx/MPC56xx v2.10
// [wine] mwcceppc -codeaddr 0x93000000 -Cpp_exceptions off -lang c99 -m Start -O4,p -sdata 0 -sdata2 0 -use_lmw_stmw on dol2rvl.c -o dol2rvl.elf
#define ARInit 0x800b1520
#define ARAlloc 0x800b14b8
#define ARGetSize 0x800b15e4
#define ARQInit 0x800b3028
#define ARQPostRequest 0x800b3098
#define GXInitTlutObj 0x800bf940
#define DOLPHIN 1
typedef int BOOL;
typedef signed long s32;
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned long u32;
#define alignas(alignment) __attribute__((aligned(alignment)))
#define static_assert(cond) __static_assert((cond), #cond)
#define NULL ((void *)0)
static void *Memcpy(void *dest, const void *src, u32 n) {
u8 *d = dest;
const u8 *s = src;
while (n-- > 0) {
*d++ = *s++;
}
return dest;
}
static u32 AlignDown(u32 val, u32 alignment) {
return val / alignment * alignment;
}
static u32 AlignUp(u32 val, u32 alignment) {
return AlignDown(val + alignment - 1, alignment);
}
static void FlushDCache(const void *start, u32 size) {
if (size == 0) {
return;
}
u32 address = (u32)start;
size = AlignUp(size, 0x20);
do {
asm("dcbf 0, %0" : : "rm"(address));
address += 0x20;
size -= 0x20;
} while (size > 0);
asm("sync");
}
static void InvalidateDCache(void *start, u32 size) {
if (size == 0) {
return;
}
u32 address = (u32)start;
size = AlignUp(size, 0x20);
do {
asm("dcbi 0, %0" : : "rm"(address));
address += 0x20;
size -= 0x20;
} while (size > 0);
}
static void InvalidateICache(void *start, u32 size) {
if (size == 0) {
return;
}
u32 address = (u32)start;
size = AlignUp(size, 0x20);
do {
asm("icbi 0, %0" : : "m"(address));
address += 0x20;
size -= 0x20;
} while (size > 0);
asm("sync; isync");
}
static void WaitMilliseconds(u32 milliseconds) {
u32 duration = milliseconds * 60750;
u32 start;
asm volatile("mfspr %0, 268" : "=r"(start));
for (u32 current = start; current - start < duration;) {
asm volatile("mfspr %0, 268" : "=r"(current));
}
}
static volatile u32 *const disr = (u32 *)0xcc006000;
static volatile u32 *const dicvr = (u32 *)0xcc006004;
static volatile u32 *const dicmdbuf0 = (u32 *)0xcc006008;
static volatile u32 *const dicmdbuf1 = (u32 *)0xcc00600c;
static volatile u32 *const dicmdbuf2 = (u32 *)0xcc006010;
static volatile u32 *const dimar = (u32 *)0xcc006014;
static volatile u32 *const dilength = (u32 *)0xcc006018;
static volatile u32 *const dicr = (u32 *)0xcc00601c;
static volatile u32 *const diimmbuf = (u32 *)0xcc006020;
static volatile u32 *const gpio_out = (u32 *)0xcd8000e0;
static volatile u32 *const resets = (u32 *)0xcd800194;
static BOOL DIReadDiscID() {
*disr = 0x54;
*dicmdbuf0 = 0xa8000040;
*dicmdbuf1 = 0x0;
*dicmdbuf2 = 0x20;
*dimar = 0x80000000 & 0x1fffffff;
*dilength = 0x20;
*dicr = 0x3;
InvalidateDCache((void *)0x80000000, 0x20);
while (*dicr & 0x1) {}
return !(*disr & 0x4);
}
static BOOL DIRead(void *dst, u32 size, u32 offset) {
*disr = 0x54;
*dicmdbuf0 = 0xa8000000;
*dicmdbuf1 = offset >> 2;
*dicmdbuf2 = size;
*dimar = (u32)dst & 0x1fffffff;
*dilength = size;
*dicr = 0x3;
InvalidateDCache(dst, size);
while (*dicr & 0x1) {}
return !(*disr & 0x4);
}
static BOOL DIIsInserted() {
*dicmdbuf0 = 0xe0000000;
*diimmbuf = 0x0;
*dicr = 0x1;
while (*dicr & 0x1) {}
return !(*diimmbuf & 0x1000000);
}
static void DIReset() {
*gpio_out &= ~0x10;
*resets &= ~0x400;
WaitMilliseconds(1);
*resets |= 0x400;
}
typedef void (*ApploaderReportFunc)(const char *format, ...);
typedef void (*GameEntryFunc)();
typedef void (*ApploaderInitFunc)(ApploaderReportFunc);
typedef s32 (*ApploaderMainFunc)(void **dst, u32 *size, u32 *shiftedOffset);
typedef GameEntryFunc (*ApploaderCloseFunc)(void);
typedef void (*ApploaderEntryFunc)(ApploaderInitFunc *init, ApploaderMainFunc *main,
ApploaderCloseFunc *close);
typedef struct ApploaderHeader {
char revision[0x10];
ApploaderEntryFunc entry;
u32 size;
u32 trailer;
u8 _1c[0x20 - 0x1c];
} ApploaderHeader;
static_assert(sizeof(ApploaderHeader) == 0x20);
static void Report(const char * /* format */, ...) {}
static GameEntryFunc LoadAndRunApploader() {
if (!DIReadDiscID()) {
return NULL;
}
ApploaderHeader *const header = 0x81100000;
if (!DIRead(header, sizeof(*header), 0x2440)) {
return NULL;
}
if (!DIRead((void *)0x81200000, AlignUp(header->size + header->trailer, 0x20), 0x2460)) {
return NULL;
}
InvalidateICache((void *)0x81200000, header->size + header->trailer);
ApploaderInitFunc init;
ApploaderMainFunc main;
ApploaderCloseFunc close;
header->entry(&init, &main, &close);
init(Report);
void *dst;
u32 size;
u32 offset;
while (main(&dst, &size, &offset)) {
if (!DIRead(dst, size, offset)) {
return NULL;
}
InvalidateICache(dst, size);
}
return close();
}
static u32 arStackPointer;
static u32 arFreeBlocks;
static u32 *arBlockLength;
static u32 MyARInit(u32 *stack_index_addr, u32 num_entries) {
arStackPointer = 0x10004000;
arFreeBlocks = num_entries;
arBlockLength = stack_index_addr;
return arStackPointer;
}
static u32 MyARAlloc(u32 length) {
u32 block = arStackPointer;
arStackPointer += length;
*arBlockLength = length;
arFreeBlocks--;
return block;
}
static u32 MyARGetSize(void) {
return 0x1000000;
}
typedef void (*ARQCallback)(u32 pointerToARQRequest);
typedef struct ARQRequest {
struct ARQRequest *next;
u32 owner;
u32 type;
u32 priority;
u32 source;
u32 dest;
u32 length;
ARQCallback callback;
} ARQRequest;
static_assert(sizeof(ARQRequest) == 0x20);
static void MyARQInit() {}
static void MyARQPostRequest(ARQRequest *task, u32 owner, u32 type, u32 priority, u32 source,
u32 dest, u32 length, ARQCallback callback) {
task->next = NULL;
task->owner = owner;
task->type = type;
task->priority = priority;
task->source = source + length;
task->dest = dest + length;
task->length = 0;
task->callback = callback;
InvalidateDCache((void *)(source | 0x80000000), length);
Memcpy((void *)(dest | 0x80000000), (void *)(source | 0x80000000), length);
FlushDCache((void *)(dest | 0x80000000), length);
if (callback) {
callback((u32)task);
}
}
typedef struct GXTlutObj {
s32 fmt;
u32 lut;
u16 n_entries;
u8 _a[0xc - 0xa];
} GXTlutObj;
static_assert(sizeof(GXTlutObj) == 0xc);
void MyGXInitTlutObj(GXTlutObj *tlut_obj, void *lut, s32 fmt, u16 n_entries) {
tlut_obj->fmt = fmt << 10;
// The Dolphin SDK doesn't reset the middle bits, which is an issue in Wii mode as they are not
// masked. Reference (Dolphin Emulator): https://is.gd/ghHrLe
tlut_obj->lut = 0x64 | ((u32)lut & 0x1ffffff) >> 5;
tlut_obj->n_entries = n_entries;
}
static void PatchBranch(void *from, void *to) {
u32 insts[4] = {
0x3d80 << 16 | (u32)to >> 16 & 0xffff, // lis r12, to@h
0x618c << 16 | (u32)to >> 0 & 0xffff, // ori r12, r12, to@l
0x7d8903a6, // mtctr r12
0x4e800420, // bctr
};
Memcpy(from, insts, sizeof(insts));
FlushDCache(from, sizeof(insts));
InvalidateICache(from, sizeof(insts));
}
static volatile u32 *const aicr = (u32 *)0xcc006c00;
static volatile u32 *const armirqmask = (u32 *)0xcd80003c;
static volatile u32 *const aipprot = (u32 *)0xcd800070;
static u32 *const consoleType = (u32 *)0x8000002c;
static void Run() {
// Use compat MMIO ranges for DI/SI/EXI/AI
*aipprot &= ~0x1;
// Prevent Starlet from receiving DI interrupts
*armirqmask &= ~(1 << 18);
// Reset the DSP: libogc apps like the HBC cannot initialize it properly, but the SDK can.
*aicr = 0;
GameEntryFunc gameEntry;
while (!(gameEntry = LoadAndRunApploader())) {
while (!DIIsInserted()) {
WaitMilliseconds(100);
}
DIReset();
}
PatchBranch((void *)ARInit, MyARInit);
PatchBranch((void *)ARAlloc, MyARAlloc);
PatchBranch((void *)ARGetSize, MyARGetSize);
PatchBranch((void *)ARQInit, MyARQInit);
PatchBranch((void *)ARQPostRequest, MyARQPostRequest);
PatchBranch((void *)GXInitTlutObj, MyGXInitTlutObj);
#if DOLPHIN
// Enable OSReport over EXI
*consoleType = 0x10000006;
#endif
(*gameEntry)();
}
asm void Start() {
// clang-format off
nofralloc
// Initialize the stack pointer
lis r1, 0x92000000@h
ori r1, r1, 0x92000000@l
// Jump to C code
bl Run
// We should never get there
loop:
b loop
// Dummy: the presence of such an instruction is the criterion for Dolphin to load an ELF in Wii
// mode instead of GameCube.
mtspr 1011, r3
// clang-format on
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment