Merge pull request #97 from LNH-team/feature/homebrew-return-to-loader

Homebrew return to loader
This commit is contained in:
Gericom
2026-01-11 13:24:04 +01:00
committed by GitHub
12 changed files with 390 additions and 16 deletions

View File

@@ -95,3 +95,8 @@ bool dldi_patchTo(dldi_header_t* stub)
{ {
return sDldiDriver.PatchTo(stub); return sDldiDriver.PatchTo(stub);
} }
void dldi_copyTo(void* target)
{
memcpy(target, sDldiBuffer, sizeof(sDldiBuffer));
}

View File

@@ -3,6 +3,7 @@
bool dldi_init(); bool dldi_init();
bool dldi_patchTo(dldi_header_t* stub); bool dldi_patchTo(dldi_header_t* stub);
void dldi_copyTo(void* target);
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {

View File

@@ -352,6 +352,7 @@ void NdsLoader::Load(BootMode bootMode)
if (isHomebrew) if (isHomebrew)
{ {
InsertArgv(); InsertArgv();
HandleHomebrewPatching();
} }
HandleDldiPatching(); HandleDldiPatching();
@@ -437,6 +438,26 @@ void NdsLoader::InsertArgv()
HOMEBREW_ARGV->length = argSize; HOMEBREW_ARGV->length = argSize;
} }
void NdsLoader::HandleHomebrewPatching()
{
if (_launcherPath != nullptr && _launcherPath[0] != 0)
{
sendToArm9(IPC_COMMAND_ARM9_SETUP_HOMEBREW_BOOTSTUB);
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);
launcherPath[255] = 0;
}
}
}
void NdsLoader::ApplyArm7Patches() void NdsLoader::ApplyArm7Patches()
{ {
sendToArm9(IPC_COMMAND_ARM9_APPLY_ARM7_PATCHES); sendToArm9(IPC_COMMAND_ARM9_APPLY_ARM7_PATCHES);

View File

@@ -26,6 +26,13 @@ public:
_savePath = savePath; _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. /// @brief Sets the argv arguments to pass to the rom.
/// @param arguments The argv arguments. /// @param arguments The argv arguments.
/// @param argumentsLength The length of the argv arguments. /// @param argumentsLength The length of the argv arguments.
@@ -41,8 +48,9 @@ public:
private: private:
FIL _romFile; FIL _romFile;
const TCHAR* _romPath; const TCHAR* _romPath = nullptr;
const TCHAR* _savePath; const TCHAR* _savePath = nullptr;
const TCHAR* _launcherPath = nullptr;
u32 _argumentsLength = 0; u32 _argumentsLength = 0;
const char* _arguments = nullptr; const char* _arguments = nullptr;
nds_header_twl_t _romHeader; nds_header_twl_t _romHeader;
@@ -71,6 +79,7 @@ private:
char driveLetter, const char* deviceName, const char* path, u8 flags, u8 accessRights); char driveLetter, const char* deviceName, const char* path, u8 flags, u8 accessRights);
void SetupDsiDeviceList(); void SetupDsiDeviceList();
void InsertArgv(); void InsertArgv();
void HandleHomebrewPatching();
bool TrySetupDsiWareSave(); bool TrySetupDsiWareSave();
bool TryDecryptSecureArea(); bool TryDecryptSecureArea();
void HandleIQueRegionFreePatching(); void HandleIQueRegionFreePatching();

View File

@@ -1,5 +1,4 @@
#pragma once #pragma once
#include "common.h"
#define DLDI_MAGIC 0xBF8DA5ED #define DLDI_MAGIC 0xBF8DA5ED
#define DLDI_DRIVER_MAGIC_NONE 0x49444C44 #define DLDI_DRIVER_MAGIC_NONE 0x49444C44

View File

@@ -222,6 +222,7 @@ extern "C" void loaderMain()
sLoader.SetRomPath(gLoaderHeader.loadParams.romPath); sLoader.SetRomPath(gLoaderHeader.loadParams.romPath);
handleSavePath(); handleSavePath();
sLoader.SetArguments(gLoaderHeader.loadParams.arguments, gLoaderHeader.loadParams.argumentsLength); sLoader.SetArguments(gLoaderHeader.loadParams.arguments, gLoaderHeader.loadParams.argumentsLength);
sLoader.SetLauncherPath(gLoaderHeader.v2.launcherPath);
sLoader.Load(BootMode::Normal); sLoader.Load(BootMode::Normal);
} }

View File

@@ -27,6 +27,8 @@
#include "errorDisplay/ErrorDisplay.h" #include "errorDisplay/ErrorDisplay.h"
#include "LoaderInfo.h" #include "LoaderInfo.h"
#include "jumpToArm9EntryPoint.h" #include "jumpToArm9EntryPoint.h"
#include "patches/homebrew/BootstubPatchCode.h"
#include "HomebrewBootstub.h"
#define HANDSHAKE_PART0 0xA #define HANDSHAKE_PART0 0xA
#define HANDSHAKE_PART1 0xB #define HANDSHAKE_PART1 0xB
@@ -231,6 +233,36 @@ static void handleBootCommand()
bootArm9(); bootArm9();
} }
static void handleSetupHomebrewBootstub(u32 dldiRequiredSpace)
{
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<BootstubPatchCode>(
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) static void handleArm7Command(u32 command)
{ {
switch (command) switch (command)
@@ -295,6 +327,12 @@ static void handleArm7Command(u32 command)
handleBootCommand(); handleBootCommand();
break; break;
} }
case IPC_COMMAND_ARM9_SETUP_HOMEBREW_BOOTSTUB:
{
u32 dldiRequiredSpace = receiveFromArm7();
handleSetupHomebrewBootstub(dldiRequiredSpace);
break;
}
} }
} }

View File

@@ -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);
}
};

View File

@@ -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

13
common/HomebrewBootstub.h Normal file
View File

@@ -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)

View File

@@ -1,14 +1,15 @@
#pragma once #pragma once
#define IPC_COMMAND_ARM9_WRAM_CONFIG 1 #define IPC_COMMAND_ARM9_WRAM_CONFIG 1
#define IPC_COMMAND_ARM9_CLEAR_MAIN_MEM 2 #define IPC_COMMAND_ARM9_CLEAR_MAIN_MEM 2
#define IPC_COMMAND_ARM9_APPLY_PATCHES 3 #define IPC_COMMAND_ARM9_APPLY_PATCHES 3
#define IPC_COMMAND_ARM9_APPLY_ARM7_PATCHES 4 #define IPC_COMMAND_ARM9_APPLY_ARM7_PATCHES 4
#define IPC_COMMAND_ARM9_SET_AP_INFO 5 #define IPC_COMMAND_ARM9_SET_AP_INFO 5
#define IPC_COMMAND_ARM9_SET_ROM_FILE_INFO 6 #define IPC_COMMAND_ARM9_SET_ROM_FILE_INFO 6
#define IPC_COMMAND_ARM9_INITIALIZE_SD_CARD 8 #define IPC_COMMAND_ARM9_INITIALIZE_SD_CARD 8
#define IPC_COMMAND_ARM9_INITIALIZE_LOADER_INFO 9 #define IPC_COMMAND_ARM9_INITIALIZE_LOADER_INFO 9
#define IPC_COMMAND_ARM9_GET_SD_FUNCTIONS 0xA #define IPC_COMMAND_ARM9_GET_SD_FUNCTIONS 0xA
#define IPC_COMMAND_ARM9_DISPLAY_ERROR 0xB #define IPC_COMMAND_ARM9_DISPLAY_ERROR 0xB
#define IPC_COMMAND_ARM9_SWITCH_TO_DS_MODE 0xD #define IPC_COMMAND_ARM9_SWITCH_TO_DS_MODE 0xD
#define IPC_COMMAND_ARM9_BOOT 0xF #define IPC_COMMAND_ARM9_BOOT 0xF
#define IPC_COMMAND_ARM9_SETUP_HOMEBREW_BOOTSTUB 0x10

View File

@@ -1,7 +1,7 @@
#pragma once #pragma once
/// @brief The Pico Loader API version supported by this header file. /// @brief The Pico Loader API version supported by this header file.
#define PICO_LOADER_API_VERSION 1 #define PICO_LOADER_API_VERSION 2
/// @brief Enum to specify the drive to boot from. /// @brief Enum to specify the drive to boot from.
typedef enum typedef enum
@@ -35,6 +35,14 @@ typedef struct
char arguments[256]; char arguments[256];
} pload_params_t; } pload_params_t;
/// @brief Struct representing the API version 2 part of the header of picoLoader7.bin.
typedef struct
{
/// @brief The path of the rom to return to when exiting an application.
/// When this path is not set, no bootstub will be patched into homebrew applications.
char launcherPath[256];
} pload_header7_v2_t;
/// @brief Struct representing the header of picoLoader7.bin. /// @brief Struct representing the header of picoLoader7.bin.
typedef struct typedef struct
{ {
@@ -52,4 +60,7 @@ typedef struct
/// @brief The load params, see \see pload_params_t. /// @brief The load params, see \see pload_params_t.
pload_params_t loadParams; pload_params_t loadParams;
/// @brief The API version 2 part of the header. Only access this when \see apiVersion >= 2.
pload_header7_v2_t v2;
} pload_header7_t; } pload_header7_t;