RuntimeCodeModules/source/ldr.c
2023-05-06 16:41:22 -05:00

313 lines
8.0 KiB
C

/*---------------------------------------------------------------------------*\
Revision history:
05/06/2023 Garhoogin:
Add zero-value relocation flag
Allow using external symbols as hook addresses
Removed auxiliary hook entry processing
11/28/2022 Garhoogin:
Add data8 hook type
08/22/2022 Garhoogin:
Add hooks to load for different scenes
08/19/2022 Garhoogin:
Add cache invalidation for RCM unloading
06/12/2022 Garhoogin:
Fix bug where relocations would sometimes be applied twice
05/08/2022 Garhoogin:
Implement relocations and remove dispatch hook type
04/25/2022 Garhoogin:
Initial commit
\*---------------------------------------------------------------------------*/
#include <nds.h>
#include "hook.h"
extern void *arc_get2dArcFile(const char *path);
extern void *arc_getCourseArcFile(const char *path);
extern void *arc_getKartModelArcFile(const char *path);
extern void *arc_getMainRaceMainEffectArcFile(const char *path);
extern void *arc_getSceneArcFile(const char *path);
#define SCENE_RACE 2
extern int scproc_getCurrentScene(void);
//
// Calls a module's load callbacks after loading
//
void LDR_CallLoadCallback(void *pMod) {
RCM_HEADER *hdr = (RCM_HEADER *) pMod;
HOOK_TABLE_ENTRY *hookTable = hdr->hookTable;
u32 base = (u32) pMod;
int i;
for (i = 0; i < hdr->nHook; i++) {
if (hookTable[i].type != HOOK_TYPE_LOAD) continue;
RCM_LOAD_CALLBACK callback = (RCM_LOAD_CALLBACK) (hookTable[i].branchDestAddr + base);
callback(base);
}
}
//
// Calls a module's unload callbacks after unloading
//
void LDR_CallUnloadCallback(void *pMod) {
RCM_HEADER *hdr = (RCM_HEADER *) pMod;
HOOK_TABLE_ENTRY *hookTable = hdr->hookTable;
u32 base = (u32) pMod;
int i;
for (i = 0; i < hdr->nHook; i++) {
if (hookTable[i].type != HOOK_TYPE_UNLOAD) continue;
RCM_UNLOAD_CALLBACK callback = (RCM_UNLOAD_CALLBACK) (hookTable[i].branchDestAddr + base);
callback(base);
}
}
//
// Process relocations in a module
//
void LDR_Relocate(void *pMod) {
RCM_HEADER *hdr = (RCM_HEADER *) pMod;
u32 base = (u32) pMod;
if (hdr->relocated) return;
u16 relocOffset = hdr->relocOffset;
u16 nReloc = hdr->relocSize;
u16 i;
RELOCATION_ENTRY *entry = (RELOCATION_ENTRY *) (base + relocOffset);
for (i = 0; i < nReloc; i++) {
int type = entry->type & R_TYPE_MASK;
u32 destAddr = base + entry->offset;
u32 value = 0;
if (!(entry->type & R_ZERO_VALUE)) {
value = entry->value;
}
switch (type) {
case R_ARM_ABS32:
*(u32 *) destAddr += value;
break;
case R_ARM_CALL:
case R_ARM_JUMP24:
{
u32 instr = *(u32 *) destAddr;
s32 offset = *(u32 *) destAddr;
if ((value & 1) == 0) { //not THUMB
offset = (offset & 0x00FFFFFF) << 2;
if (offset & 0x02000000) offset -= 0x04000000;
offset = ((offset + value - destAddr) >> 2) & 0x00FFFFFF;
*(u32 *) destAddr = (instr & 0xFF000000) | offset;
} else { //to THUMB
offset = ((offset & 0x00FFFFFF) << 2);
if ((instr & 0xF0000000) == 0xF0000000) offset |= (((offset >> 24) & 1) << 1); //only if the instruction is already in ARM->THUMB encoding
if (offset & 0x02000000) offset -= 0x04000000;
offset += (value & 0xFFFFFFFE) - destAddr;
*(u32 *) destAddr = (instr & 0xFE000000) | ((offset >> 2) & 0x00FFFFFF) | (((offset >> 1) & 1) << 24) | 0xF0000000;
}
break;
}
case R_ARM_BASE_ABS:
*(u32 *) destAddr += value + base;
break;
}
//next relocation. Advance whole relocation for nonzero value
if (entry->type & R_ZERO_VALUE) {
entry = (RELOCATION_ENTRY *) &entry->value;
} else {
entry++;
}
}
}
//
// Load module by its base address.
//
void LDR_LoadModule(void *pMod) {
RCM_HEADER *hdr = (RCM_HEADER *) pMod;
int nHook = hdr->nHook;
int i;
//process relocation table
LDR_Relocate(pMod);
//iterate through the hooks and insert patches
HOOK_TABLE_ENTRY *hookTable = hdr->hookTable;
for (i = 0; i < nHook; i++) {
u32 hookType = hookTable[i].type;
u32 hookDest = hookTable[i].branchDestAddr;
u32 hookSrc = hookTable[i].branchSrcAddr;
//don't do writes for load/unload callback types
if (hookType == HOOK_TYPE_LOAD || hookType == HOOK_TYPE_UNLOAD) continue;
//data8 is special in its alignment requirements
if (hookType == HOOK_TYPE_DATA8) {
//swap byte to table
u8 newByte = hookTable[i].newData[0];
u8 oldByte = *(u8 *) hookSrc;
*(u8 *) hookSrc = newByte;
hookTable[i].oldData[0] = oldByte;
} else {
//read old data (NOTE: read in 16-bit units, thumb need not be 32-bit aligned)
u16 ohw0 = *(u16 *) (hookSrc + 0);
u16 ohw1 = *(u16 *) (hookSrc + 2);
//create patch.
s32 rel = hookDest - (hookSrc + 8);
switch (hookType) {
case HOOK_TYPE_AREPL:
*(u32 *) hookSrc = 0xEB000000 | ((rel >> 2) & 0x00FFFFFF); //BL instruction
break;
case HOOK_TYPE_ANSUB:
*(u32 *) hookSrc = 0xEA000000 | ((rel >> 2) & 0x00FFFFFF); //B instruction
break;
case HOOK_TYPE_TREPL:
rel += 4; //accommodate thumb encoding
*(u16 *) (hookSrc + 0) = 0xF000 | ((rel >> 12) & 0x7FF);
*(u16 *) (hookSrc + 2) = 0xE800 | ((rel >> 1) & 0x7FF);
break;
case HOOK_TYPE_DATA32: //full word replacement
*(u16 *) (hookSrc + 0) = hookTable[i].newDataHw0;
*(u16 *) (hookSrc + 2) = hookTable[i].newDataHw1;
break;
case HOOK_TYPE_DATA16: //halfword replacement, only first halfword used
*(u16 *) hookSrc = hookTable[i].newDataHw0;
break;
}
//write old data to table
hookTable[i].oldDataHw0 = ohw0;
hookTable[i].oldDataHw1 = ohw1;
}
}
//flush DC, invalidate IC
DC_FlushAll();
IC_InvalidateAll();
LDR_CallLoadCallback(pMod);
}
//
// Unload a module by its base address.
//
void LDR_UnloadModule(void *pMod) {
RCM_HEADER *hdr = (RCM_HEADER *) pMod;
int nHook = hdr->nHook;
int i;
HOOK_TABLE_ENTRY *hookTable = hdr->hookTable;
for (i = nHook; i >= 0; i--) {
u16 *patchLocation = (u16 *) (u32) hookTable[i].branchSrcAddr;
int type = hookTable[i].type;
if (type != HOOK_TYPE_LOAD && type != HOOK_TYPE_UNLOAD) {
//data8 has to be unaligned
if (type == HOOK_TYPE_DATA8) {
//swap byte to table
u8 newByte = hookTable[i].newData[0];
u8 oldByte = *(u8 *) patchLocation;
*(u8 *) patchLocation = newByte;
hookTable[i].oldData[0] = oldByte;
} else {
//write back in units of 16
patchLocation[0] = hookTable[i].oldDataHw0;
if (type != HOOK_TYPE_DATA16) patchLocation[1] = hookTable[i].oldDataHw1;
}
}
}
//flush DC, invalidate IC
DC_FlushAll();
IC_InvalidateAll();
LDR_CallUnloadCallback(pMod);
}
//
// Try to load a Runtime Code Module for a given archive lookup function.
//
void TryLoadRCM(void *(*getFilePtrFn) (const char *)) {
void *fptr = getFilePtrFn("code.rcm");
if (fptr != NULL) {
//found code module :0
LDR_LoadModule(fptr);
}
}
//
// Try to unload a Runtime Code Module for a given archive lookup function.
//
void TryUnloadRCM(void *(*getFilePtrFn) (const char *)) {
void *fptr = getFilePtrFn("code.rcm");
if (fptr != NULL) {
//found code module :0
LDR_LoadModule(fptr);
}
}
/*
TODO:
Load
-GeneralMenu DONE (NEEDS TESTING)
-MainRace DONE (NEEDS TESTING)
-Logo DONE (NEEDS TESTING)
-Menu DONE (NEEDS TESTING)
-Option DONE (NEEDS TESTING)
-Record DONE (NEEDS TESTING)
-Title DONE (NEEDS TESTING)
-Result DONE (NEEDS TESTING)
-WiFiMenu DONE (NEEDS TESTING)
-WLMenu DONE (NEEDS TESTING)
*/
static inline int IsSceneGeneralMenu(void) {
int scene = scproc_getCurrentScene();
return scene != SCENE_RACE;
}
void TryLoadSceneRCM(void) {
//if the current scene is a menu scene, load the general menu RCM
if (IsSceneGeneralMenu()) {
TryLoadRCM(arc_get2dArcFile);
}
//load scene-specific RCM
TryLoadRCM(arc_getSceneArcFile);
}
void TryUnloadSceneRCM(void) {
//if the current scene is a menu scene, unload the general menu RCM
if (IsSceneGeneralMenu()) {
TryUnloadRCM(arc_get2dArcFile);
}
//unload scene-specific RCM
TryUnloadRCM(arc_getSceneArcFile);
}
void TryLoadRaceRCM() {
TryLoadSceneRCM();
TryLoadRCM(arc_getCourseArcFile);
}
void TryUnloadRaceRCM() {
TryUnloadRCM(arc_getCourseArcFile);
TryUnloadSceneRCM();
}