From 5c8e98a03fe98674a4f23eb54a8a6744e001a27b Mon Sep 17 00:00:00 2001 From: Gericom Date: Sun, 4 Jan 2026 15:39:47 +0100 Subject: [PATCH] Initial work on homebrew return to loader --- arm7/source/fat/dldi.cpp | 5 + arm7/source/fat/dldi.h | 1 + arm7/source/header.cpp | 3 +- arm7/source/loader/NdsLoader.cpp | 17 ++ arm7/source/loader/NdsLoader.h | 13 +- arm7/source/loader/dldiHeader.h | 1 - arm7/source/main.cpp | 1 + arm9/source/main.cpp | 39 +++ .../patches/homebrew/BootstubPatchCode.h | 38 +++ .../patches/homebrew/BootstubPatchCode.s | 237 ++++++++++++++++++ common/HomebrewBootstub.h | 13 + common/ipcCommands.h | 1 + include/picoLoader7.h | 23 +- 13 files changed, 387 insertions(+), 5 deletions(-) create mode 100644 arm9/source/patches/homebrew/BootstubPatchCode.h create mode 100644 arm9/source/patches/homebrew/BootstubPatchCode.s create mode 100644 common/HomebrewBootstub.h diff --git a/arm7/source/fat/dldi.cpp b/arm7/source/fat/dldi.cpp index 5bea8a0..76b8b39 100644 --- a/arm7/source/fat/dldi.cpp +++ b/arm7/source/fat/dldi.cpp @@ -95,3 +95,8 @@ bool dldi_patchTo(dldi_header_t* stub) { return sDldiDriver.PatchTo(stub); } + +void dldi_copyTo(void* target) +{ + memcpy(target, sDldiBuffer, sizeof(sDldiBuffer)); +} diff --git a/arm7/source/fat/dldi.h b/arm7/source/fat/dldi.h index 84581eb..95ede13 100644 --- a/arm7/source/fat/dldi.h +++ b/arm7/source/fat/dldi.h @@ -3,6 +3,7 @@ bool dldi_init(); bool dldi_patchTo(dldi_header_t* stub); +void dldi_copyTo(void* target); #ifdef __cplusplus extern "C" { diff --git a/arm7/source/header.cpp b/arm7/source/header.cpp index e1db34b..0af66c7 100644 --- a/arm7/source/header.cpp +++ b/arm7/source/header.cpp @@ -10,5 +10,6 @@ extern u8 __bss_size[]; pload_header7_t gLoaderHeader { .entryPoint = (void*)&_start, - .apiVersion = PICO_LOADER_API_VERSION + .apiVersion = PICO_LOADER_API_VERSION, + .launcherPath = "/_picoboot.nds" }; \ No newline at end of file diff --git a/arm7/source/loader/NdsLoader.cpp b/arm7/source/loader/NdsLoader.cpp index 7709061..736d800 100644 --- a/arm7/source/loader/NdsLoader.cpp +++ b/arm7/source/loader/NdsLoader.cpp @@ -344,6 +344,7 @@ void NdsLoader::Load(BootMode bootMode) if (isHomebrew) { InsertArgv(); + HandleHomebrewPatching(); } HandleDldiPatching(); @@ -429,6 +430,22 @@ void NdsLoader::InsertArgv() HOMEBREW_ARGV->length = argSize; } +void NdsLoader::HandleHomebrewPatching() +{ + sendToArm9(IPC_COMMAND_ARM9_APPLY_HOMEBREW_PATCHES); + sendToArm9(16 * 1024); // required dldi space + void* dldiSpace = (void*)receiveFromArm9(); + char* launcherPath = (char*)receiveFromArm9(); + if (dldiSpace != nullptr) + { + dldi_copyTo(dldiSpace); + } + if (launcherPath != nullptr) + { + strncpy(launcherPath, _launcherPath, 256); + } +} + void NdsLoader::ApplyArm7Patches() { sendToArm9(IPC_COMMAND_ARM9_APPLY_ARM7_PATCHES); diff --git a/arm7/source/loader/NdsLoader.h b/arm7/source/loader/NdsLoader.h index 14bd17a..aec06e8 100644 --- a/arm7/source/loader/NdsLoader.h +++ b/arm7/source/loader/NdsLoader.h @@ -26,6 +26,13 @@ public: _savePath = savePath; } + /// @brief Sets the \p launcherPath to use. + /// @param launcherPath The launcher path to use. + void SetLauncherPath(const TCHAR* launcherPath) + { + _launcherPath = launcherPath; + } + /// @brief Sets the argv arguments to pass to the rom. /// @param arguments The argv arguments. /// @param argumentsLength The length of the argv arguments. @@ -41,8 +48,9 @@ public: private: FIL _romFile; - const TCHAR* _romPath; - const TCHAR* _savePath; + const TCHAR* _romPath = nullptr; + const TCHAR* _savePath = nullptr; + const TCHAR* _launcherPath = nullptr; u32 _argumentsLength = 0; const char* _arguments = nullptr; nds_header_twl_t _romHeader; @@ -71,6 +79,7 @@ private: char driveLetter, const char* deviceName, const char* path, u8 flags, u8 accessRights); void SetupDsiDeviceList(); void InsertArgv(); + void HandleHomebrewPatching(); bool TrySetupDsiWareSave(); bool TryDecryptSecureArea(); ConsoleRegion GetRomRegion(u32 gameCode); diff --git a/arm7/source/loader/dldiHeader.h b/arm7/source/loader/dldiHeader.h index e10f4c3..cee06be 100644 --- a/arm7/source/loader/dldiHeader.h +++ b/arm7/source/loader/dldiHeader.h @@ -1,5 +1,4 @@ #pragma once -#include "common.h" #define DLDI_MAGIC 0xBF8DA5ED #define DLDI_DRIVER_MAGIC_NONE 0x49444C44 diff --git a/arm7/source/main.cpp b/arm7/source/main.cpp index 3607f96..3f0c8ce 100644 --- a/arm7/source/main.cpp +++ b/arm7/source/main.cpp @@ -222,6 +222,7 @@ extern "C" void loaderMain() sLoader.SetRomPath(gLoaderHeader.loadParams.romPath); handleSavePath(); sLoader.SetArguments(gLoaderHeader.loadParams.arguments, gLoaderHeader.loadParams.argumentsLength); + sLoader.SetLauncherPath(gLoaderHeader.launcherPath); sLoader.Load(BootMode::Normal); } diff --git a/arm9/source/main.cpp b/arm9/source/main.cpp index 422d4ed..10e331e 100644 --- a/arm9/source/main.cpp +++ b/arm9/source/main.cpp @@ -27,6 +27,8 @@ #include "errorDisplay/ErrorDisplay.h" #include "LoaderInfo.h" #include "jumpToArm9EntryPoint.h" +#include "patches/homebrew/BootstubPatchCode.h" +#include "HomebrewBootstub.h" #define HANDSHAKE_PART0 0xA #define HANDSHAKE_PART1 0xB @@ -231,6 +233,37 @@ static void handleBootCommand() bootArm9(); } +static void handleApplyHomebrewPatches(u32 dldiRequiredSpace) +{ + // Insert bootstub + PatchHeap patchHeap; + PatchCodeCollection patchCodeCollection; + void* patchSpace = (void*)&HOMEBREW_BOOTSTUB[1]; + auto romHeader = (const nds_header_ntr_t*)TWL_SHARED_MEMORY->ntrSharedMem.romHeader; + if (!romHeader->IsTwlRom()) + { + patchSpace = (u8*)patchSpace - 0x2F00000 + 0x2300000; + } + patchHeap.AddFreeSpace(patchSpace, 32 * 1024 - sizeof(homebrew_bootstub_t)); + + void* dldi = patchHeap.Alloc(dldiRequiredSpace); + char* launcherPath = (char*)patchHeap.Alloc(256); + + auto bootstubPatchCode = patchCodeCollection.AddUniquePatchCode( + patchHeap, dldi, launcherPath, &sLoaderInfo, + sLoaderPlatform->CreateSdReadPatchCode(patchCodeCollection, patchHeap)); + + HOMEBREW_BOOTSTUB->bootSig = HOMEBREW_BOOTSTUB_BOOTSIG; + HOMEBREW_BOOTSTUB->arm9Reboot = (void*)bootstubPatchCode->GetArm9RebootFunction(); + HOMEBREW_BOOTSTUB->arm7Reboot = (void*)bootstubPatchCode->GetArm7RebootFunction(); + HOMEBREW_BOOTSTUB->bootSize = 32 * 1024 - sizeof(homebrew_bootstub_t); + + patchCodeCollection.CopyAllToTarget(); + + ipc_sendWordDirect((u32)dldi); + ipc_sendWordDirect((u32)launcherPath); +} + static void handleArm7Command(u32 command) { switch (command) @@ -295,6 +328,12 @@ static void handleArm7Command(u32 command) handleBootCommand(); break; } + case IPC_COMMAND_ARM9_APPLY_HOMEBREW_PATCHES: + { + u32 dldiRequiredSpace = receiveFromArm7(); + handleApplyHomebrewPatches(dldiRequiredSpace); + break; + } } } diff --git a/arm9/source/patches/homebrew/BootstubPatchCode.h b/arm9/source/patches/homebrew/BootstubPatchCode.h new file mode 100644 index 0000000..79dc93b --- /dev/null +++ b/arm9/source/patches/homebrew/BootstubPatchCode.h @@ -0,0 +1,38 @@ +#pragma once +#include "sections.h" +#include "LoaderInfo.h" +#include "patches/PatchCode.h" + +DEFINE_SECTION_SYMBOLS(patch_bootstub); + +extern IReadSectorsPatchCode::ReadSectorsFunc patch_bootstub_arm9reboot_readSdSectors_address; +extern const void* patch_bootstub_arm9reboot_dldi_address; +extern const char* patch_bootstub_arm9reboot_launcher_path; +extern loader_info_t patch_bootstub_arm9reboot_loader_info; + +extern "C" void patch_bootstub_arm9reboot(void); +extern "C" void patch_bootstub_arm7reboot(void); + +class BootstubPatchCode : public PatchCode +{ +public: + BootstubPatchCode(PatchHeap& patchHeap, void* dldi, const char* launcherPath, const loader_info_t* loaderInfo, + const IReadSectorsPatchCode* readSectorsPatchCode) + : PatchCode(SECTION_START(patch_bootstub), SECTION_SIZE(patch_bootstub), patchHeap) + { + patch_bootstub_arm9reboot_dldi_address = dldi; + patch_bootstub_arm9reboot_readSdSectors_address = readSectorsPatchCode->GetReadSectorsFunction(); + patch_bootstub_arm9reboot_launcher_path = launcherPath; + patch_bootstub_arm9reboot_loader_info = *loaderInfo; + } + + const void* GetArm9RebootFunction() const + { + return GetAddressAtTarget((void*)patch_bootstub_arm9reboot); + } + + const void* GetArm7RebootFunction() const + { + return GetAddressAtTarget((void*)patch_bootstub_arm7reboot); + } +}; diff --git a/arm9/source/patches/homebrew/BootstubPatchCode.s b/arm9/source/patches/homebrew/BootstubPatchCode.s new file mode 100644 index 0000000..994f273 --- /dev/null +++ b/arm9/source/patches/homebrew/BootstubPatchCode.s @@ -0,0 +1,237 @@ +.syntax unified +.section "patch_bootstub", "ax" +.cpu arm946e-s +.arm + +.global patch_bootstub_arm9reboot +.type patch_bootstub_arm9reboot, %function +patch_bootstub_arm9reboot: + // disable irqs + ldr r0,= 0x04000208 + strb r0, [r0] + + // sync with arm7 + // make arm7 jump to arm7_after_arm9_sync + ldr r0,= 0x02FFFE34 + adr r1, arm7_after_arm9_sync + str r1, [r0] + + ldr r0,= 0x04000180 + ldr r1,= 0x0C04000C // LIBNDS_RESET_CODE + str r1, [r0, #8] + + // wait for 1 from arm7 +1: + ldrb r1, [r0] + cmp r1, #1 + bne 1b + + // send 1 to arm7 + ldr r1,= 0x100 + strh r1, [r0] + + // wait for 0 from arm7 +1: + ldrb r1, [r0] + cmp r1, #0 + bne 1b + + // send 0 to arm7 + strh r1, [r0] + +.type arm9_after_arm7_sync, %function +arm9_after_arm7_sync: + // disable irqs + ldr r0,= 0x04000208 + strb r0, [r0] + + // map vram ABCD to lcdc + mov r0, #0x04000000 + ldr r1,= 0x80808080 + str r1, [r0, #0x240] + + ldr r0,= 0x04000204 + ldrh r1, [r0] + bic r1, r1, #0x880 // map slot 1 and 2 to arm9 + strh r1, [r0] + + ldr r9, patch_bootstub_arm9reboot_readSdSectors_address + ldr sp,= 0x02000000 + (16 * 1024) // the platform code needs a valid stack pointer + + // load pico loader arm9 + adr r5, patch_bootstub_arm9reboot_loader_info + ldmia r5!, {r4,r6,r7} // clusterShift, database, clusterMap[0] + mov r7, #0x06800000 + mov r11, pc + b loadData + + // load pico loader arm7 + adr r5, patch_bootstub_arm9reboot_loader_info + 52 + ldr r7,= 0x06840000 + mov r11, pc + b loadData + + ldr r7,= 0x06840000 + adr r5, patch_bootstub_arm9reboot_loader_info + ldrh r0, [r5, #2] // loader_info_t::picoLoaderBootDrive + strh r0, [r7, #8] // pload_header7_t::bootDrive + + ldr r0, patch_bootstub_arm9reboot_dldi_address + str r0, [r7, #4] + + // set loadParams->romPath + ldr r0, patch_bootstub_arm9reboot_launcher_path + mov r1, #256 + add r3, r7, #0xC +1: + ldr r2, [r0], #4 + str r2, [r3], #4 + subs r1, r1, #4 + bne 1b + + // map vram CD to arm7 + ldr r0,= 0x04000240 + ldr r1,= 0x8A82 + strh r1, [r0, #2] + + // start arm7 by sending 3 + ldr r0,= 0x04000180 + mov r1, #0x300 + strh r1, [r0] + + // invalidate icache + mov r0, #0 + mcr p15, 0, r0, c7, c5, 0 + + mov pc, #0x06800000 + +loadData_loop: + sub r3, r3, #2 + add r0, r6, r3, lsl r4 // start sector + mov r1, r7 // dst + mov r2, r2, lsl r4 // sector count + add r7, r7, r2, lsl #9 + + blx r9 // read sectors + +loadData: + ldmia r5!, {r2, r3} // ncl, startSector + cmp r2, #0 + bne loadData_loop + bx r11 + +.balign 4 + +.pool + +.global patch_bootstub_arm9reboot_readSdSectors_address +patch_bootstub_arm9reboot_readSdSectors_address: + .word 0 + +.global patch_bootstub_arm9reboot_dldi_address +patch_bootstub_arm9reboot_dldi_address: + .word 0 + +.global patch_bootstub_arm9reboot_launcher_path +patch_bootstub_arm9reboot_launcher_path: + .word 0 + +.global patch_bootstub_arm9reboot_loader_info +patch_bootstub_arm9reboot_loader_info: + .space 0x70 // sizeof(loader_info_t) + +.balign 4 + +.cpu arm7tdmi + +.global patch_bootstub_arm7reboot +.type patch_bootstub_arm7reboot, %function +patch_bootstub_arm7reboot: + // disable irqs + mov r0, #0x04000000 + strb r0, [r0, #0x208] + + bl copy_arm7_code + + // sync with arm9 + // make arm9 jump to arm9_after_arm7_sync + ldr r0,= 0x02FFFE24 + adr r1, arm9_after_arm7_sync + str r1, [r0] + + ldr r0,= 0x04000180 + ldr r1,= 0x0C04000C // LIBNDS_RESET_CODE + str r1, [r0, #8] + + mov pc, #0x03800000 // arm7 private ram + +.type arm7_after_arm9_sync, %function +arm7_after_arm9_sync: + // disable irqs + mov r0, #0x04000000 + strb r0, [r0, #0x208] + + bl copy_arm7_code + mov r0, #0x03800000 + add r0, r0, #(arm7_iwram_region_after_arm9_sync - arm7_iwram_region) + bx r0 + +copy_arm7_code: + adr r0, arm7_iwram_region + adr r1, arm7_iwram_region_end + mov r2, #0x03800000 // arm7 private ram +1: + cmp r0, r1 + ldrne r3, [r0], #4 + strne r3, [r2], #4 + bne 1b +2: + bx lr + +.balign 4 +.pool + +// Code loaded to arm7 private wram to avoid main memory contention +arm7_iwram_region: + // wait for 1 from arm9 +1: + ldrb r1, [r0] + cmp r1, #1 + bne 1b + + // send 1 to arm9 + ldr r1,= 0x100 + strh r1, [r0] + + // wait for 0 from arm9 +1: + ldrb r1, [r0] + cmp r1, #0 + bne 1b + + // send 0 to arm9 + strh r1, [r0] + +arm7_iwram_region_after_arm9_sync: + ldr r0,= 0x04000180 + + // wait for 3 from arm9 +1: + ldrb r1, [r0] + cmp r1, #3 + bne 1b + + // boot pico loader arm7 + mov r0, #0x06000000 + ldr r0, [r0] + bx r0 + +.balign 4 +.pool +.balign 4 + +arm7_iwram_region_end: + +.balign 4 + +.end \ No newline at end of file diff --git a/common/HomebrewBootstub.h b/common/HomebrewBootstub.h new file mode 100644 index 0000000..d6a167c --- /dev/null +++ b/common/HomebrewBootstub.h @@ -0,0 +1,13 @@ +#pragma once + +#define HOMEBREW_BOOTSTUB_BOOTSIG 0x62757473746F6F62ULL + +struct homebrew_bootstub_t +{ + u64 bootSig; + void* arm9Reboot; + void* arm7Reboot; + u32 bootSize; +}; + +#define HOMEBREW_BOOTSTUB ((homebrew_bootstub_t*)0x02FF4000) diff --git a/common/ipcCommands.h b/common/ipcCommands.h index 50bbccf..4328988 100644 --- a/common/ipcCommands.h +++ b/common/ipcCommands.h @@ -12,3 +12,4 @@ #define IPC_COMMAND_ARM9_DISPLAY_ERROR 0xB #define IPC_COMMAND_ARM9_SWITCH_TO_DS_MODE 0xD #define IPC_COMMAND_ARM9_BOOT 0xF +#define IPC_COMMAND_ARM9_APPLY_HOMEBREW_PATCHES 0x10 diff --git a/include/picoLoader7.h b/include/picoLoader7.h index 375975f..11877c2 100644 --- a/include/picoLoader7.h +++ b/include/picoLoader7.h @@ -1,7 +1,10 @@ #pragma once /// @brief The Pico Loader API version supported by this header file. -#define PICO_LOADER_API_VERSION 1 +#define PICO_LOADER_API_VERSION 2 + +/// @brief The minimum Pico Loader API version that supports the launcherPath setting. +#define PICO_LOADER_LAUNCHER_PATH_MIN_API_VERSION 2 /// @brief Enum to specify the drive to boot from. typedef enum @@ -52,4 +55,22 @@ typedef struct /// @brief The load params, see \see pload_params_t. pload_params_t loadParams; + + // === api version 2 === + + /// @brief The path of the rom to return to when exiting an application. + char launcherPath[256]; } pload_header7_t; + +#ifdef __cplusplus +extern "C" { +#endif + +static inline bool pload_supportsLauncherPath(const pload_header7_t* header) +{ + return header->apiVersion >= PICO_LOADER_LAUNCHER_PATH_MIN_API_VERSION; +} + +#ifdef __cplusplus +} +#endif