Initial commit

This commit is contained in:
Gericom
2025-11-22 11:08:28 +01:00
commit 9cf3ffbfcf
358 changed files with 58350 additions and 0 deletions

BIN
arm9/data/font.nft2 Normal file

Binary file not shown.

245
arm9/loader9.ld Normal file
View File

@@ -0,0 +1,245 @@
/*--------------------------------------------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public License,
v. 2.0. If a copy of the MPL was not distributed with this file, You can
obtain one at https://mozilla.org/MPL/2.0/.
--------------------------------------------------------------------------------*/
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
MEMORY
{
itcm : ORIGIN = 0x01000000, LENGTH = 32K
vram : ORIGIN = 0x06800000, LENGTH = 128K
dtcm : ORIGIN = 0x07800000, LENGTH = 16K
}
PHDRS
{
main PT_LOAD FLAGS(7);
itcm PT_LOAD FLAGS(7);
dtcm PT_LOAD FLAGS(7);
}
__heap_end = ORIGIN(vram) + LENGTH(vram);
SECTIONS
{
.crt0 :
{
__text_start = . ;
KEEP (*(.crt0))
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram :main = 0xff
.text : /* ALIGN (4): */
{
*(EXCLUDE_FILE(*.itcm* *.vectors* *.twl*) .text)
*(EXCLUDE_FILE(*.itcm* *.vectors* *.twl*) .stub)
*(EXCLUDE_FILE(*.itcm* *.vectors* *.twl*) .text.*)
KEEP (*(SORT_NONE(.init)))
*(.text.*)
/* __patch_cardireadcard_start = .; */
/* KEEP (*(.patch_cardireadcard)) */
/* __patch_cardireadcard_end = .; */
*(.stub)
/* .gnu.warning sections are handled specially by elf32.em. */
*(.gnu.warning)
*(.gnu.linkonce.t*)
__glue_start = ABSOLUTE(.);
*(.glue_7)
*(.glue_7t)
__glue_end = ABSOLUTE(.);
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram :main = 0xff
.fini :
{
KEEP (*(.fini))
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram :main =0xff
__text_end = . ;
.rodata :
{
*(.rodata)
*all.rodata*(*)
*(.roda)
*(.rodata.*)
*(.gnu.linkonce.r*)
SORT(CONSTRUCTORS)
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram :main = 0xff
.ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >vram :main
__exidx_start = .;
.ARM.exidx : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) } >vram :main
__exidx_end = .;
/* Ensure the __preinit_array_start label is properly aligned. We
could instead move the label definition inside the section, but
the linker would then create the section even if it turns out to
be empty, which isn't pretty. */
. = ALIGN(32 / 8);
.init_array :
{
PROVIDE (__preinit_array_start = .);
PROVIDE (__bothinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE (__preinit_array_end = .);
PROVIDE (__init_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
PROVIDE (__init_array_end = .);
PROVIDE (__bothinit_array_end = .);
} >vram :main
.fini_array :
{
PROVIDE (__fini_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
/* Required by pico-exitprocs.c. */
KEEP (*(.fini_array*))
PROVIDE (__fini_array_end = .);
} >vram :main
.ctors :
{
/* gcc uses crtbegin.o to find the start of the constructors, so
we make sure it is first. Because this is a wildcard, it
doesn't matter if the user does not actually link against
crtbegin.o; the linker won't look for a file to match a
wildcard. The wildcard also means that it doesn't matter which
directory crtbegin.o is in. */
KEEP (*crtbegin.o(.ctors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram :main = 0xff
.dtors :
{
KEEP (*crtbegin.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram :main = 0xff
.eh_frame :
{
KEEP (*(.eh_frame))
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram :main = 0xff
.gcc_except_table :
{
*(.gcc_except_table)
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram :main = 0xff
.jcr : { KEEP (*(.jcr)) } >vram = 0
__got_start = . ;
.got :
{
*(.got.plt)
*(.got)
*(.rel.got)
} >vram :main = 0
__got_end = . ;
.data ALIGN(4) : {
__data_start = ABSOLUTE(.);
*(.data)
*(.data.*)
*(.gnu.linkonce.d*)
CONSTRUCTORS
. = ALIGN(4);
__data_end = ABSOLUTE(.) ;
} >vram :main = 0xff
__data_end = . ;
__bss_vma = . ;
.dtcm :
{
__dtcm_lma = LOADADDR(.dtcm);
__dtcm_start = ABSOLUTE(.);
*(.dtcm)
*(.dtcm.*)
. = ALIGN(4);
__dtcm_end = ABSOLUTE(.);
} >dtcm AT>vram :dtcm = 0xff
.itcm :
{
__itcm_lma = LOADADDR(.itcm);
__itcm_start = ABSOLUTE(.);
*(.itcm)
*.itcm*(.text .stub .text.*)
. = ALIGN(4);
__itcm_end = ABSOLUTE(.);
} >itcm AT>vram :itcm = 0xff
.bss __bss_vma (NOLOAD):
{
__bss_start = ABSOLUTE(.);
__bss_start__ = ABSOLUTE(.);
*(.dynbss)
*(.gnu.linkonce.b*)
*(.bss*)
*(COMMON)
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >vram :NONE
__bss_end = . ;
__bss_end__ = . ;
__heap_start = . ;
_end = . ;
__end__ = . ;
PROVIDE (end = _end);
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
/* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to the beginning
of the section so we begin them at 0. */
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* SGI/MIPS DWARF 2 extensions */
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
.stack 0x80000 : { _stack = .; *(.stack) }
/* These must appear regardless of . */
}

146
arm9/source/Arm7Patcher.cpp Normal file
View File

@@ -0,0 +1,146 @@
#include "common.h"
#include <libtwl/mem/memNtrWram.h>
#include "ModuleParamsLocator.h"
#include "SdkVersion.h"
#include "sharedMemory.h"
#include "gameCode.h"
#include "cache.h"
#include "errorDisplay/ErrorDisplay.h"
#include "patches/PatchCollection.h"
#include "patches/PatchContext.h"
#include "patches/platform/LoaderPlatform.h"
#include "patches/arm7/sdk2to4/CardiTaskThreadPatch.h"
#include "patches/arm7/sdk5/CardiDoTaskFromArm9Patch.h"
#include "patches/arm7/OsGetInitArenaLoPatch.h"
#include "patches/arm7/DisableArm7WramClearPatch.h"
#include "patches/arm7/sdk5/Sdk5DsiSdCardRedirectPatch.h"
#include "patches/arm7/PokemonDownloaderArm7Patch.h"
#include "Arm7Patcher.h"
void* Arm7Patcher::ApplyPatches(const LoaderPlatform* loaderPlatform) const
{
auto romHeader = (const nds_header_ntr_t*)TWL_SHARED_MEMORY->ntrSharedMem.romHeader;
auto twlRomHeader = (const nds_header_twl_t*)TWL_SHARED_MEMORY->twlRomHeader;
ModuleParamsLocator moduleParamsLocator;
auto moduleParams = moduleParamsLocator.FindModuleParams(romHeader);
SdkVersion sdkVersion = moduleParams ? moduleParams->sdkVersion : 0u;
if (!moduleParams && romHeader->gameCode == GAMECODE("AS2E"))
{
// Spider-Man 2 (USA) is probably the only game without module params
sdkVersion = 0x02004F50;
}
PatchCollection patchCollection;
LOG_DEBUG("Arm7 region: 0x%x - 0x%x\n", romHeader->arm7LoadAddress, romHeader->arm7LoadAddress + romHeader->arm7Size);
PatchContext patchContext
{
(void*)romHeader->arm7LoadAddress,
romHeader->arm7Size,
(romHeader->IsTwlRom()) ? (void*)twlRomHeader->arm7iLoadAddress : nullptr,
(romHeader->IsTwlRom()) ? twlRomHeader->arm7iSize : 0,
sdkVersion,
romHeader->gameCode,
loaderPlatform
};
void* patchSpaceStart = nullptr;
if (sdkVersion != 0)
{
mem_setNtrWramMapping(MEM_NTR_WRAM_ARM9, MEM_NTR_WRAM_ARM9);
auto arm7ArenaPatch = new OsGetInitArenaLoPatch();
patchCollection.AddPatch(arm7ArenaPatch);
if (romHeader->unitCode == 0) // seems only present on NITRO, not on HYBRID or LIMITED
patchCollection.AddPatch(new DisableArm7WramClearPatch());
if (sdkVersion.IsTwlSdk())
{
if (gIsDsiMode && romHeader->IsTwlRom() && twlRomHeader->IsDsiWare())
{
patchCollection.AddPatch(new Sdk5DsiSdCardRedirectPatch());
}
else
{
patchCollection.AddPatch(new CardiDoTaskFromArm9Patch());
}
}
else
{
patchCollection.AddPatch(new CardiTaskThreadPatch());
}
if (*(vu32*)0x02FFF00C == GAMECODE("ADAJ") &&
romHeader->arm9LoadAddress == 0x02004000 &&
romHeader->arm9EntryAddress == 0x02004800)
{
// pokemon downloader
patchCollection.AddPatch(new PokemonDownloaderArm7Patch());
}
if (arm7ArenaPatch->FindPatchTarget(patchContext))
{
const u32 arm7PatchSpaceSize = 0x800;
void* privateWramHeapStart = arm7ArenaPatch->GetArm7PrivateWramArenaLo();
if (0x0380F780 - (u32)privateWramHeapStart - 0x2100 >= arm7PatchSpaceSize)
{
patchSpaceStart = privateWramHeapStart;
arm7ArenaPatch->SetArm7PrivateWramArenaLo((u8*)patchSpaceStart + arm7PatchSpaceSize);
}
else
{
LOG_DEBUG("Arm 7 patches placed in main memory\n");
u32 mainMemoryArenaLo = (u32)arm7ArenaPatch->GetMainMemoryArenaLo();
if (gIsDsiMode && romHeader->unitCode == 0)
{
patchSpaceStart = (void*)(mainMemoryArenaLo & ~0xC00000); // 0x023...
}
else
{
patchSpaceStart = (void*)(mainMemoryArenaLo | 0x800000); // make sure it ends up in the right place while in 16MB mode
}
arm7ArenaPatch->SetMainMemoryArenaLo((u8*)mainMemoryArenaLo + arm7PatchSpaceSize);
}
patchContext.GetPatchHeap().AddFreeSpace(patchSpaceStart, arm7PatchSpaceSize);
}
// The arm7 patcher uses the fact that the ntr wram is mirrored over the entire 03 region.
// Since the reserved patch space is smaller than the ntr wram, it will never overlap.
// As a result, the patcher can use arm7 addresses for placing patches.
// The arm7 will copy the patch data to the actual arm7 location afterwards.
// If in DSi mode and the rom is a DSi rom, temporarily disable twl wram to let ntr wram cover the entire 03 region.
u32 mbk6 = 0;
u32 mbk7 = 0;
u32 mbk8 = 0;
if (gIsDsiMode && romHeader->IsTwlRom())
{
mbk6 = REG_MBK6;
mbk7 = REG_MBK7;
mbk8 = REG_MBK8;
REG_MBK6 = 0;
REG_MBK7 = 0;
REG_MBK8 = 0;
}
if (!patchCollection.TryPerformPatches(patchContext))
{
ErrorDisplay().PrintError("Failed to apply arm7 patches.");
}
dc_flushAll();
dc_drainWriteBuffer();
ic_invalidateAll();
// If in DSi mode and the rom is a DSi rom, restore twl wram.
if (gIsDsiMode && romHeader->IsTwlRom())
{
REG_MBK6 = mbk6;
REG_MBK7 = mbk7;
REG_MBK8 = mbk8;
}
mem_setNtrWramMapping(MEM_NTR_WRAM_ARM7, MEM_NTR_WRAM_ARM7);
}
else
{
LOG_DEBUG("Module params not found!\n");
}
return (u32)patchSpaceStart < 0x03000000 ? nullptr : patchSpaceStart;
}

13
arm9/source/Arm7Patcher.h Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
class LoaderPlatform;
/// @brief Class for patching the arm7 of retail roms.
class Arm7Patcher
{
public:
/// @brief Applies arm7 patches using the given \p loaderPlatform.
/// @param loaderPlatform The loader platform to use.
/// @return A pointer to the patch space in IWRAM, or \c nullptr if the patches have been placed in main memory.
void* ApplyPatches(const LoaderPlatform* loaderPlatform) const;
};

View File

@@ -0,0 +1,179 @@
#include "common.h"
#include <libtwl/gfx/gfx3d.h>
#include <libtwl/gfx/gfxWindow.h>
#include <libtwl/rtos/rtosIrq.h>
#include <libtwl/ipc/ipcFifo.h>
#include <libtwl/ipc/ipcSync.h>
#include <libtwl/timer/timer.h>
#include <libtwl/dma/dmaNitro.h>
#include <libtwl/dma/dmaTwl.h>
#include <libtwl/mem/memVram.h>
#include <libtwl/card/card.h>
#include "Arm9IoRegisterClearer.h"
void Arm9IoRegisterClearer::ClearNtrIoRegisters(bool isSdkResetSystem) const
{
REG_IME = 0;
REG_IE = 0;
// Important! Make sure that all sources of interrupts are disabled as much as possible.
// It is possible to accidentally trigger an irq too early in games if bits are set in REG_IF
// while it doesn't expect that.
ClearGraphicsRegisters(isSdkResetSystem); // vblank, hblank, vcount, gxfifo
ClearTimerRegisters(); // timer 0, timer 1, timer 2, timer 3
ClearNtrDmaRegisters(); // dma 0, dma 1, dma 2, dma 3
REG_MCCNT0 = 0; // rom transfer
ipc_disableRecvFifoNotEmptyIrq();
ipc_disableSendFifoEmptyIrq();
ipc_disableArm7Irq();
ipc_clearSendFifo();
ipc_disableFifo();
REG_KEYCNT = 0;
}
void Arm9IoRegisterClearer::ClearTwlIoRegisters() const
{
REG_NDMA0SAD = 0;
REG_NDMA0DAD = 0;
REG_NDMA0TCNT = 0;
REG_NDMA0WCNT = 0;
REG_NDMA0BCNT = 0;
REG_NDMA0FDATA = 0;
REG_NDMA0CNT = 0;
REG_NDMA1SAD = 0;
REG_NDMA1DAD = 0;
REG_NDMA1TCNT = 0;
REG_NDMA1WCNT = 0;
REG_NDMA1BCNT = 0;
REG_NDMA1FDATA = 0;
REG_NDMA1CNT = 0;
REG_NDMA2SAD = 0;
REG_NDMA2DAD = 0;
REG_NDMA2TCNT = 0;
REG_NDMA2WCNT = 0;
REG_NDMA2BCNT = 0;
REG_NDMA2FDATA = 0;
REG_NDMA2CNT = 0;
REG_NDMA3SAD = 0;
REG_NDMA3DAD = 0;
REG_NDMA3TCNT = 0;
REG_NDMA3WCNT = 0;
REG_NDMA3BCNT = 0;
REG_NDMA3FDATA = 0;
REG_NDMA3CNT = 0;
}
void Arm9IoRegisterClearer::ClearGraphicsRegisters(bool isSdkResetSystem) const
{
REG_POWERCNT = 0x820F;
REG_DISPSTAT = 0; // vblank, hblank, vcount
if (!isSdkResetSystem)
{
REG_DISPCNT = 0;
REG_DISPCNT_SUB = 0;
REG_BG0CNT = 0;
REG_BG0CNT_SUB = 0;
REG_BG1CNT = 0;
REG_BG1CNT_SUB = 0;
REG_BG2CNT = 0;
REG_BG2CNT_SUB = 0;
REG_BG3CNT = 0;
REG_BG3CNT_SUB = 0;
REG_BG0HOFS = 0;
REG_BG0HOFS_SUB = 0;
REG_BG0VOFS = 0;
REG_BG0VOFS_SUB = 0;
REG_BG1HOFS = 0;
REG_BG1HOFS_SUB = 0;
REG_BG1VOFS = 0;
REG_BG1VOFS_SUB = 0;
REG_BG2HOFS = 0;
REG_BG2HOFS_SUB = 0;
REG_BG2VOFS = 0;
REG_BG2VOFS_SUB = 0;
REG_BG3HOFS = 0;
REG_BG3HOFS_SUB = 0;
REG_BG3VOFS = 0;
REG_BG3VOFS_SUB = 0;
REG_BG2PA = 0;
REG_BG2PB = 0;
REG_BG2PC = 0;
REG_BG2PD = 0;
REG_BG3PA = 0;
REG_BG3PB = 0;
REG_BG3PC = 0;
REG_BG3PD = 0;
REG_BG2PA_SUB = 0;
REG_BG2PB_SUB = 0;
REG_BG2PC_SUB = 0;
REG_BG2PD_SUB = 0;
REG_BG3PA_SUB = 0;
REG_BG3PB_SUB = 0;
REG_BG3PC_SUB = 0;
REG_BG3PD_SUB = 0;
gfx_setWindow0(0, 0, 0, 0);
gfx_setWindow1(0, 0, 0, 0);
gfx_setSubWindow0(0, 0, 0, 0);
gfx_setSubWindow1(0, 0, 0, 0);
REG_WININ = 0;
REG_WININ_SUB = 0;
REG_WINOUT = 0;
REG_WINOUT_SUB = 0;
REG_MOSAIC = 0;
REG_MOSAIC_SUB = 0;
REG_BLDCNT = 0;
REG_BLDCNT_SUB = 0;
REG_BLDALPHA = 0;
REG_BLDALPHA_SUB = 0;
REG_BLDY = 0;
REG_BLDY_SUB = 0;
REG_MASTER_BRIGHT = 0;
REG_MASTER_BRIGHT_SUB = 0;
}
REG_DISP3DCNT = 0;
REG_DISPCAPCNT = 0;
REG_GXSTAT = 0; // gxfifo
REG_CLEAR_COLOR = 0;
REG_CLEAR_DEPTH = 0x7FFF;
REG_DISP_1DOT_DEPTH = 0x7FFF;
REG_CLRIMAGE_OFFSET = 0;
REG_FOG_COLOR = 0;
REG_FOG_OFFSET = 0;
REG_ALPHA_TEST_REF = 0;
// VRAM A used for arm9 code
mem_setVramBMapping(MEM_VRAM_AB_NONE);
// VRAM C used for arm7 code
mem_setVramDMapping(MEM_VRAM_D_NONE);
mem_setVramEMapping(MEM_VRAM_E_NONE);
mem_setVramFMapping(MEM_VRAM_FG_NONE);
mem_setVramGMapping(MEM_VRAM_FG_NONE);
mem_setVramHMapping(MEM_VRAM_H_NONE);
mem_setVramIMapping(MEM_VRAM_I_NONE);
}
void Arm9IoRegisterClearer::ClearTimerRegisters() const
{
REG_TM0CNT_H = 0; // timer 0
REG_TM0CNT_L = 0;
REG_TM1CNT_H = 0; // timer 1
REG_TM1CNT_L = 0;
REG_TM2CNT_H = 0; // timer 2
REG_TM2CNT_L = 0;
REG_TM3CNT_H = 0; // timer 3
REG_TM3CNT_L = 0;
}
void Arm9IoRegisterClearer::ClearNtrDmaRegisters() const
{
REG_DMA0CNT = 0; // dma 0
REG_DMA0SAD = 0;
REG_DMA0DAD = 0;
REG_DMA1CNT = 0; // dma 1
REG_DMA1SAD = 0;
REG_DMA1DAD = 0;
REG_DMA2CNT = 0; // dma 2
REG_DMA2SAD = 0;
REG_DMA2DAD = 0;
REG_DMA3CNT = 0; // dma 3
REG_DMA3SAD = 0;
REG_DMA3DAD = 0;
}

View File

@@ -0,0 +1,19 @@
#pragma once
/// @brief Class for clearing the arm9 IO registers.
class Arm9IoRegisterClearer
{
public:
/// @brief Clears the arm9 ntr IO registers.
/// When \p isSdkResetSystem is \c true, some IO registers are not cleared to avoid graphical glitches.
/// @param isSdkResetSystem \c true if the clear is performed context of OS_ResetSystem, or \c false otherwise.
void ClearNtrIoRegisters(bool isSdkResetSystem) const;
/// @brief Clears the arm9 twl IO registers.
void ClearTwlIoRegisters() const;
private:
void ClearGraphicsRegisters(bool isSdkResetSystem) const;
void ClearTimerRegisters() const;
void ClearNtrDmaRegisters() const;
};

336
arm9/source/Arm9Patcher.cpp Normal file
View File

@@ -0,0 +1,336 @@
#include "common.h"
#include "ModuleParamsLocator.h"
#include "SdkVersion.h"
#include "patches/PatchCollection.h"
#include "patches/PatchContext.h"
#include "sharedMemory.h"
#include "patches/arm9/sdk2to4/CardiReadCardPatch.h"
#include "patches/arm9/sdk2to4/CardiTryReadCardDmaPatch.h"
#include "patches/arm9/sdk5/CardiIsRomDmaAvailablePatch.h"
#include "patches/arm9/sdk5/CardiReadCardWithHashInternalAsyncPatch.h"
#include "patches/arm9/sdk5/CardiReadRomWithCpuPatch.h"
#include "patches/arm9/CardiReadRomIdCorePatch.h"
#include "patches/arm9/OSResetSystemPatch.h"
#include "patches/arm9/PokemonDownloaderArm9Patch.h"
#include "patches/arm9/OverlayPatches/FsStartOverlayHookPatch.h"
#include "patches/arm9/OverlayPatches/DSProtectPatches/DSProtectOverlayPatch.h"
#include "patches/arm9/OverlayPatches/PokemonBw1/PokemonBw1IrApPatch.h"
#include "patches/arm9/OverlayPatches/PokemonBw2/PokemonBw2IrApPatch.h"
#include "patches/arm9/OverlayPatches/GoldenSunDarkDawn/GoldenSunDarkDawnOverlayHookPatch.h"
#include "SecureSysCallsUnusedSpaceLocator.h"
#include "fastSearch.h"
#include "gameCode.h"
#include "cache.h"
#include "ApList.h"
#include "patches/platform/LoaderPlatform.h"
#include "errorDisplay/ErrorDisplay.h"
#include "Arm9Patcher.h"
#define PARENT_SECTION_START 0x02001000
#define PARENT_SECTION_END 0x02003000
#define REQUIRED_PATCH_HEAP_SPACE 0x500
typedef void (*uncompress_func_t)(void* compressedEnd);
static const u32 sMiiUncompressBackwardPatternOld[] = { 0xE3500000, 0x0A000025, 0xE92D00F0, 0xE9100006 }; // mkds beta; version 0x2012774
static const u32 sMiiUncompressBackwardPatternOld2[] = { 0xE3500000, 0x0A00002B, 0xE92D00F0, 0xE9100006 }; // asterix & obelix xxl 2; version 0x3017531
static const u32 sMiiUncompressBackwardPattern[] = { 0xE3500000, 0x0A000027, 0xE92D00F0, 0xE9100006 }; // mkds
static const u32 sMiiUncompressBackwardPatternHybrid[] = { 0xE3500000, 0x0A000029, 0xE92D01F0, 0xE9100006 };
void Arm9Patcher::ApplyPatches(const LoaderPlatform* loaderPlatform, const ApListEntry* apListEntry,
bool isCloneBootRom, const loader_info_t* loaderInfo) const
{
auto romHeader = (const nds_header_ntr_t*)TWL_SHARED_MEMORY->ntrSharedMem.romHeader;
auto twlRomHeader = (const nds_header_twl_t*)TWL_SHARED_MEMORY->twlRomHeader;
ModuleParamsLocator moduleParamsLocator;
auto moduleParams = moduleParamsLocator.FindModuleParams(romHeader);
u32 arm9Size = romHeader->arm9Size;
u32 arm9iSize = romHeader->IsTwlRom() ? twlRomHeader->arm9iSize : 0;
SdkVersion sdkVersion = moduleParams ? moduleParams->sdkVersion : 0u;
u32 compressedEnd = 0;
PatchCollection patchCollection;
if (moduleParams)
{
LOG_DEBUG("Module params found at 0x%p\n", moduleParams);
LOG_DEBUG("Sdk version: 0x%x\n", moduleParams->sdkVersion);
const u32* miiUncompressBackward = nullptr;
if (moduleParams->compressedEnd)
{
const u32* miiUncompressBackwardPattern;
if (sdkVersion <= 0x2017532)
miiUncompressBackwardPattern = sMiiUncompressBackwardPatternOld;
else
miiUncompressBackwardPattern = sMiiUncompressBackwardPattern;
miiUncompressBackward = fastSearch16((const u32*)romHeader->arm9LoadAddress, 0x1000, miiUncompressBackwardPattern);
if (!sdkVersion.IsTwlSdk() && !miiUncompressBackward)
miiUncompressBackward = fastSearch16((const u32*)romHeader->arm9LoadAddress, 0x1000, sMiiUncompressBackwardPatternOld2);
if (sdkVersion.IsTwlSdk() && !miiUncompressBackward)
miiUncompressBackward = fastSearch16((const u32*)romHeader->arm9LoadAddress, 0x1000, sMiiUncompressBackwardPatternHybrid);
if (miiUncompressBackward)
{
arm9Size = moduleParams->compressedEnd + *(u32*)(moduleParams->compressedEnd - 4) - romHeader->arm9LoadAddress;
((uncompress_func_t)miiUncompressBackward)((void*)moduleParams->compressedEnd);
compressedEnd = moduleParams->compressedEnd;
moduleParams->compressedEnd = 0;
}
else
{
LOG_DEBUG("MIi_UncompressBackward not found\n");
}
}
if (gIsDsiMode && romHeader->IsTwlRom())
{
auto arm9iModuleParams = (module_params_twl_t*)(romHeader->arm9LoadAddress + twlRomHeader->arm9iModuleParamsAddress);
if (arm9iModuleParams->magicBigEndian == MODULE_PARAMS_TWL_MAGIC_BE &&
arm9iModuleParams->magicLittleEndian == MODULE_PARAMS_TWL_MAGIC_LE)
{
if (arm9iModuleParams->compressedEnd)
{
LOG_DEBUG("Compressed arm9i found\n");
if (miiUncompressBackward)
{
arm9iSize = arm9iModuleParams->compressedEnd + *(u32*)(arm9iModuleParams->compressedEnd - 4) - twlRomHeader->arm9iLoadAddress;
((uncompress_func_t)miiUncompressBackward)((void*)arm9iModuleParams->compressedEnd);
arm9iModuleParams->compressedEnd = 0;
LOG_DEBUG("Decompressed arm9i\n");
}
else
{
LOG_DEBUG("Could not decompress arm9i\n");
}
}
}
}
}
else
{
LOG_DEBUG("Module params not found!\n");
if (romHeader->gameCode == GAMECODE("AS2E"))
{
// Spider-Man 2 (USA) is probably the only game without module params
sdkVersion = 0x02004F50;
}
}
LOG_DEBUG("Arm9 region: 0x%x - 0x%x\n", romHeader->arm9LoadAddress, romHeader->arm9LoadAddress + arm9Size);
PatchContext patchContext
{
(void*)romHeader->arm9LoadAddress,
arm9Size,
romHeader->IsTwlRom() ? (void*)twlRomHeader->arm9iLoadAddress : nullptr,
arm9iSize,
sdkVersion,
romHeader->gameCode,
loaderPlatform
};
if (sdkVersion != 0)
{
if (*(vu32*)0x02FFF00C == GAMECODE("ADAJ") &&
romHeader->arm9LoadAddress == 0x02004000 &&
romHeader->arm9EntryAddress == 0x02004800)
{
// pokemon downloader
patchContext.GetPatchHeap().AddFreeSpace((void*)0x023FF160, 0x6A0);
patchCollection.AddPatch(new PokemonDownloaderArm9Patch(loaderInfo));
}
else
{
u32 availableParentSize = 0;
if (isCloneBootRom)
{
availableParentSize = GetAvailableParentSectionSpace();
LOG_DEBUG("0x%X bytes available in .parent section\n", availableParentSize);
}
if (availableParentSize >= REQUIRED_PATCH_HEAP_SPACE)
{
patchContext.GetPatchHeap().AddFreeSpace(
(void*)(PARENT_SECTION_END - REQUIRED_PATCH_HEAP_SPACE),
REQUIRED_PATCH_HEAP_SPACE);
LOG_DEBUG("Placing patches in .parent section\n");
}
else
{
SecureSysCallsUnusedSpaceLocator secureSysCallsUnusedSpaceLocator;
secureSysCallsUnusedSpaceLocator.FindUnusedSpace(romHeader, patchContext.GetPatchHeap());
}
}
if (sdkVersion.IsTwlSdk())
{
if (!(romHeader->IsTwlRom() && twlRomHeader->IsDsiWare()))
{
// if ((romHeader->unitCode & 3) != 3)
{
patchCollection.AddPatch(new CardiIsRomDmaAvailablePatch());
}
patchCollection.AddPatch(new CardiReadRomWithCpuPatch());
if (gIsDsiMode && (romHeader->unitCode & 2))
{
patchCollection.AddPatch(new CardiReadCardWithHashInternalAsyncPatch());
}
}
}
else
{
patchCollection.AddPatch(new CardiReadCardPatch());
patchCollection.AddPatch(new CardiTryReadCardDmaPatch());
}
patchCollection.AddPatch(new CardiReadRomIdCorePatch());
patchCollection.AddPatch(new OSResetSystemPatch(loaderInfo));
OverlayHookPatch* overlayHookPatch;
if (romHeader->gameCode == GAMECODE("BO5P") ||
romHeader->gameCode == GAMECODE("BO5E") ||
romHeader->gameCode == GAMECODE("BO5J"))
{
overlayHookPatch = new GoldenSunDarkDawnOverlayHookPatch();
overlayHookPatch->AddOverlayPatch(new DSProtectOverlayPatch(334, 0, DSProtectVersion::v2_01, ~0u));
overlayHookPatch->AddOverlayPatch(new DSProtectOverlayPatch(335, 0, DSProtectVersion::v2_01s, ~0u));
}
else
{
overlayHookPatch = new FsStartOverlayHookPatch();
if (apListEntry)
{
u32 regularOverlayId = apListEntry->GetRegularOverlayId();
if (regularOverlayId != AP_LIST_OVERLAY_ID_INVALID)
{
if (regularOverlayId == AP_LIST_OVERLAY_ID_STATIC_ARM9)
{
LOG_WARNING("Patching DSProtect in main memory currently not supported\n");
}
else
{
overlayHookPatch->AddOverlayPatch(new DSProtectOverlayPatch(
regularOverlayId, apListEntry->GetRegularOffset(),
apListEntry->GetDSProtectVersion(), apListEntry->GetDSProtectFunctionMask()));
}
}
u32 sOverlayId = apListEntry->GetSOverlayId();
if (sOverlayId != AP_LIST_OVERLAY_ID_INVALID)
{
if (sOverlayId == AP_LIST_OVERLAY_ID_STATIC_ARM9)
{
LOG_WARNING("Patching DSProtect in main memory currently not supported\n");
}
else
{
auto version = apListEntry->GetDSProtectVersion();
if (version < DSProtectVersion::v2_00s)
{
version = (DSProtectVersion)((u32)version - (u32)DSProtectVersion::v2_00 + (u32)DSProtectVersion::v2_00s);
}
overlayHookPatch->AddOverlayPatch(new DSProtectOverlayPatch(
sOverlayId, apListEntry->GetSOffset(), version, ~0u));
}
}
}
switch (romHeader->gameCode)
{
// Pokemon Black & White
case GAMECODE("IRAD"):
case GAMECODE("IRAF"):
case GAMECODE("IRAI"):
case GAMECODE("IRAJ"):
case GAMECODE("IRAK"):
case GAMECODE("IRAO"):
case GAMECODE("IRAS"):
case GAMECODE("IRBD"):
case GAMECODE("IRBF"):
case GAMECODE("IRBI"):
case GAMECODE("IRBJ"):
case GAMECODE("IRBK"):
case GAMECODE("IRBO"):
case GAMECODE("IRBS"):
{
overlayHookPatch->AddOverlayPatch(new PokemonBw1IrApPatch());
break;
}
// Pokemon Black & White 2
// todo: IRDJ and IREJ have two revisions and the first one seems to be different
case GAMECODE("IRDD"):
case GAMECODE("IRDF"):
case GAMECODE("IRDI"):
case GAMECODE("IRDK"):
case GAMECODE("IRDO"):
case GAMECODE("IRDS"):
case GAMECODE("IRED"):
case GAMECODE("IREF"):
case GAMECODE("IREI"):
case GAMECODE("IREK"):
case GAMECODE("IREO"):
case GAMECODE("IRES"):
{
overlayHookPatch->AddOverlayPatch(new PokemonBw2IrApPatch());
break;
}
}
}
patchCollection.AddPatch(overlayHookPatch);
if (moduleParams && compressedEnd != 0)
{
AddRestoreCompressedEndPatch(
patchContext,
romHeader->arm9AutoLoadDoneHookAddress,
&moduleParams->compressedEnd,
compressedEnd);
}
}
if (!patchCollection.TryPerformPatches(patchContext))
{
ErrorDisplay().PrintError("Failed to apply arm9 patches.");
}
dc_flushAll();
dc_drainWriteBuffer();
ic_invalidateAll();
}
void Arm9Patcher::AddRestoreCompressedEndPatch(PatchContext& patchContext,
u32 arm9AutoLoadDoneHookAddress, u32* moduleParamsCompressedEnd, u32 originalCompressedEndValue) const
{
// Restore compressedEnd after first boot.
// This is necessary to not break cloneboot.
const u32 compressedEndFixCode[] =
{
0xE59F0014, // ldr r0,= moduleParamsCompressedEnd
0xE59F1014, // ldr r1,= originalCompressedEndValue
0xE5801000, // str r1, [r0]
0xE59F0010, // ldr r0,= arm9AutoLoadDoneHookAddress
0xE59F1000, // ldr r1, ret
0xE5801000, // str r1, [r0]
0xE12FFF1E, // ret: bx lr
(u32)moduleParamsCompressedEnd,
originalCompressedEndValue,
arm9AutoLoadDoneHookAddress
};
void* fixDst = patchContext.GetPatchHeap().Alloc(sizeof(compressedEndFixCode));
memcpy(fixDst, compressedEndFixCode, sizeof(compressedEndFixCode));
*(u32*)arm9AutoLoadDoneHookAddress = 0xEA000000u | ((((int)fixDst - (int)arm9AutoLoadDoneHookAddress - 8) >> 2) & 0xFFFFFF);
}
u32 Arm9Patcher::GetAvailableParentSectionSpace() const
{
u32 availableParentSize = 0;
for (u32 ptr = PARENT_SECTION_END; ptr > PARENT_SECTION_START; ptr -= 32)
{
u32* segment = (u32*)(ptr - 32);
if (segment[0] != 0 || segment[1] != 0 || segment[2] != 0 || segment[3] != 0 ||
segment[4] != 0 || segment[5] != 0 || segment[6] != 0 || segment[7] != 0)
{
break;
}
availableParentSize += 32;
}
return availableParentSize;
}

24
arm9/source/Arm9Patcher.h Normal file
View File

@@ -0,0 +1,24 @@
#pragma once
#include "LoaderInfo.h"
class ApListEntry;
class LoaderPlatform;
class PatchContext;
/// @brief Class for patching the arm9 of retail roms.
class Arm9Patcher
{
public:
/// @brief Applies arm9 patches using the given \p loaderPlatform.
/// @param loaderPlatform The loader platform to use.
/// @param apListEntry The AP list entry for the rom being loaded, or \c nullptr if there is none.
/// @param isCloneBootRom \c true if the rom being loaded is a clone boot rom, or \c false otherwise.
/// @param loaderInfo The loader info to use.
void ApplyPatches(const LoaderPlatform* loaderPlatform, const ApListEntry* apListEntry,
bool isCloneBootRom, const loader_info_t* loaderInfo) const;
private:
void AddRestoreCompressedEndPatch(PatchContext& patchContext,
u32 arm9AutoLoadDoneHookAddress, u32* moduleParamsCompressedEnd, u32 originalCompressedEndValue) const;
u32 GetAvailableParentSectionSpace() const;
};

View File

@@ -0,0 +1,59 @@
#include "common.h"
#include "ModuleParamsLocator.h"
module_params_ntr_t* ModuleParamsLocator::FindModuleParams(const nds_header_ntr_t* romHeader)
{
//todo: static footer
module_params_ntr_t* moduleParams = nullptr;
if (romHeader->arm9ModuleParamsAddress != 0)
{
moduleParams = (module_params_ntr_t*)(romHeader->arm9LoadAddress + romHeader->arm9ModuleParamsAddress);
if (moduleParams->magicBigEndian != MODULE_PARAMS_NTR_MAGIC_BE ||
moduleParams->magicLittleEndian != MODULE_PARAMS_NTR_MAGIC_LE)
{
// try again by subtracting arm9 rom address
moduleParams = (module_params_ntr_t*)((u8*)moduleParams - romHeader->arm9RomOffset);
if (moduleParams->magicBigEndian != MODULE_PARAMS_NTR_MAGIC_BE ||
moduleParams->magicLittleEndian != MODULE_PARAMS_NTR_MAGIC_LE)
{
LOG_DEBUG("Module params not found at header specified offset 0x%x\n", romHeader->arm9ModuleParamsAddress);
moduleParams = nullptr;
}
}
}
else
{
LOG_DEBUG("Header specifies no module params address\n");
}
if (!moduleParams)
{
// try searching for the module params as not all roms have the address in the header
// it should be within the first 0x1000 bytes of the arm9
for (u32 i = 0; i < 0x1000; i += 4)
{
u32* ptr = (u32*)(romHeader->arm9LoadAddress + i);
if (ptr[0] == MODULE_PARAMS_NTR_MAGIC_BE && ptr[1] == MODULE_PARAMS_NTR_MAGIC_LE)
{
moduleParams = (module_params_ntr_t*)((u8*)ptr + 8 - sizeof(module_params_ntr_t));
break;
}
}
}
if (!moduleParams)
{
// as a last resort, scan the entire arm9 binary
for (u32 i = 0; i < romHeader->arm9Size; i += 4)
{
u32* ptr = (u32*)(romHeader->arm9LoadAddress + i);
if (ptr[0] == MODULE_PARAMS_NTR_MAGIC_BE && ptr[1] == MODULE_PARAMS_NTR_MAGIC_LE)
{
moduleParams = (module_params_ntr_t*)((u8*)ptr + 8 - sizeof(module_params_ntr_t));
break;
}
}
}
return moduleParams;
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include "ndsHeader.h"
#include "moduleParams.h"
/// @brief Class for finding the module params of a retail arm9.
class ModuleParamsLocator
{
public:
/// @brief Searches for the module params of the arm9 of the rom with the given \p romHeader.
/// @param romHeader The header of the rom.
/// @return A pointer to the found module params, or \c nullptr if the module params could not be found.
module_params_ntr_t* FindModuleParams(const nds_header_ntr_t* romHeader);
};

View File

@@ -0,0 +1,136 @@
#include "common.h"
#include "gameCode.h"
#include <algorithm>
#include "patches/PatchHeap.h"
#include "thumbInstructions.h"
#include "SecureSysCallsUnusedSpaceLocator.h"
#define THUMB_MOVS_R0_R1 THUMB_MOVS_REG(0, 1)
#define THUMB_MOVS_R2_0 THUMB_MOVS_IMM(2, 0)
static const u16 sSvcSoftResetPattern[] = { THUMB_SVC(0), THUMB_BX_LR };
static const u16 sSvcWaitByLoopPattern[] = { THUMB_SVC(3), THUMB_BX_LR };
static const u16 sSvcWaitIntrPattern[] = { THUMB_MOVS_R2_0, THUMB_SVC(4), THUMB_BX_LR };
static const u16 sSvcWaitVBlankIntrPattern[] = { THUMB_MOVS_R2_0, THUMB_SVC(5), THUMB_BX_LR };
static const u16 sSvcHaltPattern[] = { THUMB_SVC(6), THUMB_BX_LR };
static const u16 sSvcDivPattern[] = { THUMB_SVC(9), THUMB_BX_LR };
static const u16 sSvcDivRemPattern[] = { THUMB_SVC(9), THUMB_MOVS_R0_R1, THUMB_BX_LR };
static const u16 sSvcCpuSetPattern[] = { THUMB_SVC(0xB), THUMB_BX_LR };
static const u16 sSvcCpuSetFastPattern[] = { THUMB_SVC(0xC), THUMB_BX_LR };
static const u16 sSvcSqrtPattern[] = { THUMB_SVC(0xD), THUMB_BX_LR };
static const u16 sSvcGetCrc16Pattern[] = { THUMB_SVC(0xE), THUMB_BX_LR };
static const u16 sSvcIsMainMemExpandedPattern[] = { THUMB_SVC(0xF), THUMB_BX_LR };
static const u16 sSvcUnpackBitsPattern[] = { THUMB_SVC(0x10), THUMB_BX_LR };
static const u16 sSvcUncompressLz8Pattern[] = { THUMB_SVC(0x11), THUMB_BX_LR };
static const u16 sSvcUncompressLz16FromDevicePattern[] = { THUMB_SVC(0x12), THUMB_BX_LR };
static const u16 sSvcUncompressHuffmanFromDevicePattern[] = { THUMB_SVC(0x13), THUMB_BX_LR };
static const u16 sSvcUncompressRl8Pattern[] = { THUMB_SVC(0x14), THUMB_BX_LR };
static const u16 sSvcUncompressRl16FromDevicePattern[] = { THUMB_SVC(0x15), THUMB_BX_LR };
struct svc_pattern_t
{
const u16* pattern;
u32 length;
};
static const std::array<const svc_pattern_t, 18> sSvcPatterns
{
svc_pattern_t { sSvcSoftResetPattern, sizeof(sSvcSoftResetPattern) },
svc_pattern_t { sSvcWaitByLoopPattern, sizeof(sSvcWaitByLoopPattern) },
svc_pattern_t { sSvcWaitIntrPattern, sizeof(sSvcWaitIntrPattern) },
svc_pattern_t { sSvcWaitVBlankIntrPattern, sizeof(sSvcWaitVBlankIntrPattern) },
svc_pattern_t { sSvcHaltPattern, sizeof(sSvcHaltPattern) },
svc_pattern_t { sSvcDivPattern, sizeof(sSvcDivPattern) },
svc_pattern_t { sSvcDivRemPattern, sizeof(sSvcDivRemPattern) },
svc_pattern_t { sSvcCpuSetPattern, sizeof(sSvcCpuSetPattern) },
svc_pattern_t { sSvcCpuSetFastPattern, sizeof(sSvcCpuSetFastPattern) },
svc_pattern_t { sSvcSqrtPattern, sizeof(sSvcSqrtPattern) },
svc_pattern_t { sSvcGetCrc16Pattern, sizeof(sSvcGetCrc16Pattern) },
svc_pattern_t { sSvcIsMainMemExpandedPattern, sizeof(sSvcIsMainMemExpandedPattern) },
svc_pattern_t { sSvcUnpackBitsPattern, sizeof(sSvcUnpackBitsPattern) },
svc_pattern_t { sSvcUncompressLz8Pattern, sizeof(sSvcUncompressLz8Pattern) },
svc_pattern_t { sSvcUncompressLz16FromDevicePattern, sizeof(sSvcUncompressLz16FromDevicePattern) },
svc_pattern_t { sSvcUncompressHuffmanFromDevicePattern, sizeof(sSvcUncompressHuffmanFromDevicePattern) },
svc_pattern_t { sSvcUncompressRl8Pattern, sizeof(sSvcUncompressRl8Pattern) },
svc_pattern_t { sSvcUncompressRl16FromDevicePattern, sizeof(sSvcUncompressRl16FromDevicePattern) }
};
const u16* SecureSysCallsUnusedSpaceLocator::FindPattern(const u16* data, u32 length, const u16* pattern, u32 patternLength) const
{
length >>= 1;
patternLength >>= 1;
for (u32 i = 0; i < length - patternLength; i++)
{
bool ok = true;
for (u32 j = 0; j < patternLength; j++)
{
if (data[i + j] != pattern[j])
{
ok = false;
break;
}
}
if (ok)
return &data[i];
}
return nullptr;
}
void SecureSysCallsUnusedSpaceLocator::FindUnusedSpace(const nds_header_ntr_t* romHeader, PatchHeap& patchHeap) const
{
if (romHeader->arm9RomOffset != 0x4000)
return;
u32 secureStart = romHeader->arm9LoadAddress;
if (*(u32*)(secureStart + 0x800) == 0x4770DF00)
{
// secure area for development purposes has this area empty
patchHeap.AddFreeSpace((void*)secureStart, 0x800);
LOG_DEBUG("Added free space starting at 0x%x with size 0x%x\n", secureStart, 0x800);
return;
}
std::array<svc_pattern_t, sSvcPatterns.size()> patternLocations;
for (u32 i = 0; i < sSvcPatterns.size(); i++)
{
patternLocations[i].pattern = FindPattern((const u16*)secureStart, 0x800, sSvcPatterns[i].pattern, sSvcPatterns[i].length);
patternLocations[i].length = sSvcPatterns[i].length;
}
std::qsort(patternLocations.begin(), patternLocations.size(), sizeof(svc_pattern_t), [](const void* a, const void* b)
{
const auto cmp = static_cast<const svc_pattern_t*>(a)->pattern <=> static_cast<const svc_pattern_t*>(b)->pattern;
if (cmp < 0)
{
return -1;
}
if (cmp > 0)
{
return 1;
}
return 0;
});
u32 current = secureStart;
for (u32 i = 0; i < sSvcPatterns.size(); i++)
{
if (!patternLocations[i].pattern)
continue;
u32 freeSpaceEnd = (u32)patternLocations[i].pattern & ~3;
if (current < freeSpaceEnd)
{
u32 freeSpace = freeSpaceEnd - current;
patchHeap.AddFreeSpace((void*)current, freeSpace);
LOG_DEBUG("Added free space starting at 0x%x with size 0x%x\n", current, freeSpace);
}
current = ((u32)patternLocations[i].pattern + patternLocations[i].length + 3) & ~3;
}
if (current < secureStart + 0x800)
{
u32 freeSpace = secureStart + 0x800 - current;
patchHeap.AddFreeSpace((void*)current, freeSpace);
LOG_DEBUG("Added free space starting at 0x%x with size 0x%x\n", current, freeSpace);
}
}

View File

@@ -0,0 +1,18 @@
#pragma once
#include "ndsHeader.h"
class PatchHeap;
/// @brief Class for finding the unused space between the system calls in the arm9 secure area.
class SecureSysCallsUnusedSpaceLocator
{
public:
/// @brief Searches for the unused space between the system calls in the arm9 secure area of the rom
/// with the given \p romHeader and adds the unused space to the given \p patchHeap.
/// @param romHeader The rom header.
/// @param patchHeap The patch heap to add the unused space to.
void FindUnusedSpace(const nds_header_ntr_t* romHeader, PatchHeap& patchHeap) const;
private:
const u16* FindPattern(const u16* data, u32 length, const u16* pattern, u32 patternLength) const;
};

9
arm9/source/arm9Clock.h Normal file
View File

@@ -0,0 +1,9 @@
#pragma once
enum class ScfgArm9Clock
{
Nitro67MHz = 0,
Twl134MHz = 1
};
extern "C" void scfg_setArm9Clock(ScfgArm9Clock arm9Clock);

32
arm9/source/arm9Clock.s Normal file
View File

@@ -0,0 +1,32 @@
.section ".itcm", "ax"
.arm
#define REG_SCFG_CLK 0x04004004
#define SCFG_CLK_CPU_SPEED 1
.global scfg_setArm9Clock
.type scfg_setArm9Clock, %function
scfg_setArm9Clock:
ldr r3,= REG_SCFG_CLK
ldrh r2, [r3]
and r1, r2, #SCFG_CLK_CPU_SPEED
cmp r1, r0
bxeq lr // requested speed already set
mrs r12, cpsr
orr r1, r12, #0xC0 // disable irq and fiq
msr cpsr, r1
bic r2, r2, #SCFG_CLK_CPU_SPEED
orr r2, r2, r0
strh r2, [r3]
// allow the clock switch to stabilize
mov r0, #8
1:
subs r0, r0, #1
bne 1b
msr cpsr, r12
bx lr

26
arm9/source/cache.h Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/// @brief Invalidates the entire instruction cache.
extern void ic_invalidateAll(void);
/// @brief Drains the write buffer.
extern void dc_drainWriteBuffer(void);
/// @brief Invalidates the entire data cache.
extern void dc_invalidateAll(void);
/// @brief Flushes the entire data cache.
extern void dc_flushAll(void);
/// @brief Invalidates the data cache in the given range.
/// @param ptr A pointer to the memory block to invalidate. Should be 32-byte aligned.
/// @param byteCount The number of bytes to invalidate. Will be rounded up to 32-byte multiples.
extern void dc_invalidateRange(void* ptr, u32 byteCount);
#ifdef __cplusplus
}
#endif

64
arm9/source/cache.s Normal file
View File

@@ -0,0 +1,64 @@
.text
.arm
// ARM DDI 0201D, page 3-11
.global dc_flushAll
.type dc_flushAll, %function
dc_flushAll:
// Temp register to set to 0. Needed for write buffer drain
mov r3, #0
// Initialize segment counter outer_loop
mov r1, #0
outer_loop:
// Initialize line counter inner_loop
mov r0, #0
inner_loop:
orr r2, r1, r0 // Generate segment and line address
mcr p15, 0, r3, c7, c10, 4 // Drain write buffer. See errata ARM946-PRDC-000592 5.0, section 4.8
mcr p15, 0, r2, c7, c14, 2 // Clean and flush the line
add r0, r0, #0x20 // Increment to next line
cmp r0, #0x400 // (data cache size / entries)
bne inner_loop
add r1, r1, #0x40000000 // Increment segment counter
cmp r1, #0x0
bne outer_loop
bx lr
.global dc_invalidateRange
.type dc_invalidateRange, %function
dc_invalidateRange:
add r1, r1, r0
bic r0, r0, #0x1F
1:
mcr p15, 0, r0, c7, c6, 1
add r0, r0, #32
cmp r0, r1
blt 1b
bx lr
.global dc_drainWriteBuffer
.type dc_drainWriteBuffer, %function
dc_drainWriteBuffer:
mov r0, #0
mcr p15, 0, r0, c7, c10, 4
bx lr
.global dc_invalidateAll
.type dc_invalidateAll, %function
dc_invalidateAll:
mov r0, #0
mcr p15, 0, r0, c7, c6, 0
bx lr
.global ic_invalidateAll
.type ic_invalidateAll, %function
ic_invalidateAll:
mov r0, #0
mcr p15, 0, r0, c7, c5, 0
bx lr
.pool
.end

22
arm9/source/common.h Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include <nds.h>
extern u16 gIsDsiMode;
#ifdef __cplusplus
#include "globalHeap.h"
#include "logger/ILogger.h"
extern ILogger* gLogger;
#define MAX_COMPILED_LOG_LEVEL LogLevel::All
#define LOG_FATAL(...) if (LogLevel::Fatal < MAX_COMPILED_LOG_LEVEL) gLogger->Log(LogLevel::Fatal, __VA_ARGS__)
#define LOG_ERROR(...) if (LogLevel::Error < MAX_COMPILED_LOG_LEVEL) gLogger->Log(LogLevel::Error, __VA_ARGS__)
#define LOG_WARNING(...) if (LogLevel::Warning < MAX_COMPILED_LOG_LEVEL) gLogger->Log(LogLevel::Warning, __VA_ARGS__)
#define LOG_INFO(...) if (LogLevel::Info < MAX_COMPILED_LOG_LEVEL) gLogger->Log(LogLevel::Info, __VA_ARGS__)
#define LOG_DEBUG(...) if (LogLevel::Debug < MAX_COMPILED_LOG_LEVEL) gLogger->Log(LogLevel::Debug, __VA_ARGS__)
#define LOG_TRACE(...) if (LogLevel::Trace < MAX_COMPILED_LOG_LEVEL) gLogger->Log(LogLevel::Trace, __VA_ARGS__)
#endif

118
arm9/source/crt0.s Normal file
View File

@@ -0,0 +1,118 @@
.section ".crt0", "ax"
.arm
.global _start
.type _start, %function
_start:
// disable irqs
ldr r0,= 0x04000208
strb r0, [r0]
// configure cp15
// disable itcm, dtcm, caches and mpu
ldr r0,= 0x00002078
mcr p15, 0, r0, c1, c0
mov r0, #0
// invalidate entire icache
mcr p15, 0, r0, c7, c5, 0
// invalidate entire dcache
mcr p15, 0, r0, c7, c6, 0
// drain write buffer
mcr p15, 0, r0, c7, c10, 4
// move dtcm in place
ldr r0,= __dtcm_start + 0xA
mcr p15, 0, r0, c9, c1, 0
// setup itcm to cover the first 32MB of memory
mov r0, #0x20
mcr p15, 0, r0, c9, c1, 1
// mpu region 0: IO, Palette, VRAM, OAM (64 MB)
ldr r0,= ((1 | (25 << 1)) + 0x04000000)
mcr p15, 0, r0, c6, c0, 0
// mpu region 1: Main Memory + TWL WRAM (32 MB)
ldr r0,= ((1 | (24 << 1)) + 0x02000000)
mcr p15, 0, r0, c6, c1, 0
// mpu region 2: GBA slot
ldr r0,= ((1 | (24 << 1)) + 0x08000000)
mcr p15, 0, r0, c6, c2, 0
// mpu region 3: Disabled
mov r0, #0
mcr p15, 0, r0, c6, c3, 0
// mpu region 4: Disabled
mov r0, #0
mcr p15, 0, r0, c6, c4, 0
// mpu region 5: ITCM (32 KB)
ldr r0,= ((1 | (14 << 1)) + __itcm_start)
mcr p15, 0, r0, c6, c5, 0
// mpu region 6: DTCM (16 KB)
ldr r0,= ((1 | (13 << 1)) + __dtcm_start)
mcr p15, 0, r0, c6, c6, 0
// mpu region 7: LCDC VRAM A (128 KB)
ldr r0,= (1 | (16 << 1) | 0x06800000)
mcr p15, 0, r0, c6, c7, 0
// data permissions
ldr r0,= 0x33300333
mcr p15, 0, r0, c5, c0, 2
// code permissions
ldr r0,= 0x30300330
mcr p15, 0, r0, c5, c0, 3
// dcache
ldr r0,= 0b10000010
mcr p15, 0, r0, c2, c0, 0
// icache
ldr r0,= 0b10000010
mcr p15, 0, r0, c2, c0, 1
// write buffer
ldr r0,= 0b10000010
mcr p15, 0, r0, c3, c0, 0
// turn back on itcm, dtcm, cache and mpu
// keep data cache off
ldr r0,= 0x00057079 //0x0005707D
mcr p15, 0, r0, c1, c0
// copy itcm in place
ldr r0,= __itcm_lma
ldr r2,= __itcm_start
ldr r1,= __itcm_end
subs r1, r1, r2
beq itcm_done
1:
ldmia r0!, {r3-r10}
stmia r2!, {r3-r10}
subs r1, #0x20
bgt 1b
itcm_done:
// copy dtcm in place
ldr r0,= __dtcm_lma
ldr r2,= __dtcm_start
ldr r1,= __dtcm_end
subs r1, r1, r2
beq dtcm_done
1:
ldmia r0!, {r3-r10}
stmia r2!, {r3-r10}
subs r1, #0x20
bgt 1b
dtcm_done:
// clear bss
ldr r0,= __bss_start
ldr r1,= __bss_end
cmp r0, r1
beq bss_done
mov r2, #0
1:
str r2, [r0], #4
cmp r0, r1
bne 1b
bss_done:
msr cpsr_c, #0x13 // svc
ldr sp,= __dtcm_start + 0x3FC0
msr cpsr_c, #0x12 // irq
ldr sp,= __dtcm_start + 0x3F80
msr cpsr_c, #0x1F // sys
ldr sp,= __dtcm_start + 0x2F7C
b loaderMain
.pool
.end

View File

@@ -0,0 +1,48 @@
#include "common.h"
#include <libtwl/mem/memVram.h>
#include <libtwl/gfx/gfx.h>
#include <libtwl/gfx/gfxStatus.h>
#include <libtwl/gfx/gfxPalette.h>
#include <libtwl/gfx/gfxBackground.h>
#include "nitroFont2.h"
#include "font_nft2.h"
#include "ErrorDisplay.h"
void ErrorDisplay::PrintError(const char* errorString)
{
mem_setVramEMapping(MEM_VRAM_E_MAIN_BG_00000);
auto textBuffer = (u8*)0x02100000;
memset(textBuffer, 0, 256 * 192);
nft2_unpack((nft2_header_t*)font_nft2);
nft2_string_render_params_t renderParams =
{
x: 0,
y: 0,
width: 256,
height: 192
};
nft2_renderString((const nft2_header_t*)font_nft2, errorString, textBuffer, 256, &renderParams);
memcpy((void*)GFX_BG_MAIN, textBuffer, 256 * 192);
while (gfx_getVCount() != 191);
while (gfx_getVCount() == 191);
// 4 bit grayscale palette
for (int i = 0; i < 16; i++)
{
int gray = i * 2 + (i == 0 ? 0 : 1);
GFX_PLTT_BG_MAIN[i] = gray | (gray << 5) | (gray << 10);
}
REG_BG3PA = 256;
REG_BG3PB = 0;
REG_BG3PC = 0;
REG_BG3PD = 256;
REG_BG3X = 0;
REG_BG3Y = 0;
REG_BG3CNT = (1 << 7) | (1 << 14);
REG_BLDCNT = 0;
REG_DISPCNT = 3 | (1 << 11) | (1 << 16);
REG_MASTER_BRIGHT = 0;
GFX_PLTT_BG_SUB[0] = 0;
REG_MASTER_BRIGHT_SUB = 0x8010;
REG_DISPCNT_SUB = 0x10000;
while (true);
}

View File

@@ -0,0 +1,11 @@
#pragma once
/// @brief Class for displaying a critical error message.
class ErrorDisplay
{
public:
/// @brief Displays the given \p errorString and loops.
/// @note This function does not return.
/// @param errorString The error string to display.
void PrintError(const char* errorString);
};

View File

@@ -0,0 +1,104 @@
#include "common.h"
#include "nitroFont2.h"
bool nft2_unpack(nft2_header_t* font)
{
if (font->signature != NFT2_SIGNATURE)
return false;
font->glyphInfoPtr = (const nft2_glyph_t*)((u32)font + (u32)font->glyphInfoPtr);
font->charMapPtr = (const nft2_char_map_entry_t*)((u32)font + (u32)font->charMapPtr);
font->glyphDataPtr = (const u8*)((u32)font + (u32)font->glyphDataPtr);
return true;
}
int nft2_findGlyphIdxForCharacter(const nft2_header_t* font, u16 character)
{
const nft2_char_map_entry_t* charMapEntry = font->charMapPtr;
while (charMapEntry->count > 0)
{
if (charMapEntry->startChar <= character && character < charMapEntry->startChar + charMapEntry->count)
return charMapEntry->glyphs[character - charMapEntry->startChar];
charMapEntry = (const nft2_char_map_entry_t*)((u32)charMapEntry + 4 + 2 * charMapEntry->count);
}
return 0;
}
static inline void renderGlyph(const nft2_header_t* font, const nft2_glyph_t* glyph,
int xPos, int yPos, int width, int height, u8* dst, u32 stride)
{
int yOffset = glyph->spacingTop;
u32 xStart = xPos < 0 ? -xPos : 0;
u32 yStart = yPos + yOffset < 0 ? -(yPos + yOffset) : 0;
int xEnd = glyph->glyphWidth;
if (xPos + xEnd > width)
{
// by returning we only render complete glyphs
return;
// old code for rendering partial glyphs
// xEnd = width - xPos;
}
int yEnd = glyph->glyphHeight;
if (yPos + yOffset + yEnd > height)
yEnd = height - (yPos + yOffset); // allow partial glyphs in the vertical direction
const u8* glyphData = &font->glyphDataPtr[glyph->dataOffset];
glyphData += yStart * ((glyph->glyphWidth + 1) >> 1);
for (int y = yStart; y < yEnd; y++)
{
for (int x = xStart; x < xEnd; x++)
{
u32 data = glyphData[x >> 1];
if ((x & 1) == 0)
data &= 0xF;
else
data >>= 4;
if (data == 0)
continue;
u32 finalX = x + xPos;
u32 finalY = y + yPos + yOffset;
dst[finalY * stride + finalX] = data;
}
glyphData += (glyph->glyphWidth + 1) >> 1;
}
}
ITCM_CODE void nft2_renderString(const nft2_header_t* font, const char* string, u8* dst, u32 stride,
nft2_string_render_params_t* renderParams)
{
int xPos = renderParams->x;
int yPos = renderParams->y;
u32 textWidth = 0;
while (true)
{
char c = *string++;
if (c == 0)
break;
if (c == '\n')
{
xPos = renderParams->x;
yPos += font->ascend + font->descend + 1;
if (yPos >= (int)renderParams->height)
break;
continue;
}
int glyphIdx = nft2_findGlyphIdxForCharacter(font, c);
const nft2_glyph_t* glyph = &font->glyphInfoPtr[glyphIdx];
xPos += glyph->spacingLeft;
renderGlyph(font, glyph, xPos, yPos, renderParams->width, renderParams->height, dst, stride);
xPos += glyph->glyphWidth;
if (xPos > (int)textWidth)
textWidth = xPos;
xPos += glyph->spacingRight;
}
renderParams->textWidth = textWidth;
}

View File

@@ -0,0 +1,62 @@
#pragma once
#define NFT2_SIGNATURE 0x3254464E
struct nft2_glyph_t
{
u32 dataOffset : 24;
u32 glyphWidth : 8;
s8 spacingLeft;
s8 spacingRight;
u8 glyphHeight;
s8 spacingTop;
};
struct nft2_char_map_entry_t
{
u16 count;
u16 startChar;
u16 glyphs[1];
};
struct nft2_header_t
{
u32 signature;
const nft2_glyph_t* glyphInfoPtr;
const nft2_char_map_entry_t* charMapPtr;
const u8* glyphDataPtr;
u8 ascend;
u8 descend;
u16 glyphCount;
};
struct nft2_string_render_params_t
{
int x;
int y;
u32 width;
u32 height;
u32 textWidth;
};
/// @brief Prepares the ntf2 data of the given \p font for runtime use.
/// Call this method once after loading a font file.
/// @param font The font to prepare.
/// @return True if preparing was successful, or false otherwise.
bool nft2_unpack(nft2_header_t* font);
/// @brief Finds the glyph index in the given \p font that corresponds to the given \p character.
/// @param font The font the find the glyph index in.
/// @param character The character to find the glyph index for.
/// @return The glyph index if found, or 0 otherwise.
int nft2_findGlyphIdxForCharacter(const nft2_header_t* font, u16 character);
/// @brief Renders the given \p string with the given \p font to the \p dst buffer
/// with the given \p stride and \p renderParams.
/// @param font The font to use.
/// @param string The string to render.
/// @param dst The destination buffer.
/// @param stride The stride of the destination buffer.
/// @param renderParams The render params.
void nft2_renderString(const nft2_header_t* font, const char* string, u8* dst,
u32 stride, nft2_string_render_params_t* renderParams);

3
arm9/source/fastClear.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
extern "C" void fastClear(void* dst, u32 length);

22
arm9/source/fastClear.s Normal file
View File

@@ -0,0 +1,22 @@
.section ".itcm", "ax"
.arm
// r0: dst
// r1: size (multiple of 32)
.global fastClear
.type fastClear, %function
fastClear:
push {r4-r8,lr}
mov r2, #0
mov r3, #0
mov r4, #0
mov r5, #0
mov r6, #0
mov r7, #0
mov r8, #0
mov lr, #0
1:
subs r1, r1, #32
stmgeia r0!, {r2-r8,lr}
bgt 1b
pop {r4-r8,pc}

3
arm9/source/fastSearch.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
extern "C" const u32* fastSearch16(const u32* data, u32 length, const u32* pattern);

141
arm9/source/fastSearch.s Normal file
View File

@@ -0,0 +1,141 @@
.section ".itcm", "ax"
.arm
// r0: data
// r1: size
// r2: pattern (4 words, each word should be unique)
.global fastSearch16
.type fastSearch16, %function
fastSearch16:
push {r4-r11,lr}
add r1, r1, r0
sub r1, r1, #44 // 32 bytes to load at once + 12 bytes after it
ldmia r2, {r2-r5}
1:
ldmia r0!, {r6-r12,lr}
cmp r6, r2
cmpne r7, r2
cmpne r8, r2
cmpne r9, r2
cmpne r10, r2
cmpne r11, r2
cmpne r12, r2
cmpne lr, r2
beq fastSearch16_firstWordMatch
fastSearch16_continueFastSearch:
cmp r0, r1
ble 1b
// only need to handle the last couple of words now
add r1, #(44 - 16)
2:
ldr r6, [r0], #4
cmp r6, r2
beq fastSearch16_firstWordMatchLast
fastSearch16_continueLastSearch:
cmp r0, r1
ble 2b
mov r0, #0
pop {r4-r11,pc}
fastSearch16_firstWordMatchLast:
ldr r6, [r0], #4
cmp r6, r3
ldreq r6, [r0], #4
cmpeq r6, r4
ldreq r6, [r0], #4
cmpeq r6, r5
bne fastSearch16_continueLastSearch
sub r0, r0, #16
pop {r4-r11,pc}
fastSearch16_firstWordMatch:
cmp r6, r2
beq fastSearch16_firstWordMatch_r6
cmpne r7, r2
beq fastSearch16_firstWordMatch_r7
cmpne r8, r2
beq fastSearch16_firstWordMatch_r8
cmpne r9, r2
beq fastSearch16_firstWordMatch_r9
cmpne r10, r2
beq fastSearch16_firstWordMatch_r10
cmpne r11, r2
beq fastSearch16_firstWordMatch_r11
cmpne r12, r2
beq fastSearch16_firstWordMatch_r12
fastSearch16_firstWordMatch_lr:
ldr r6, [r0], #4
cmp r6, r3
ldreq r6, [r0], #4
cmpeq r6, r4
ldreq r6, [r0], #4
cmpeq r6, r5
bne fastSearch16_continueFastSearch
sub r0, r0, #16
pop {r4-r11,pc}
fastSearch16_firstWordMatch_r6:
sub r0, r0, #16
cmp r7, r3
cmpeq r8, r4
cmpeq r9, r5
bne fastSearch16_continueFastSearch
sub r0, r0, #16
pop {r4-r11,pc}
fastSearch16_firstWordMatch_r7:
sub r0, r0, #12
cmp r8, r3
cmpeq r9, r4
cmpeq r10, r5
bne fastSearch16_continueFastSearch
sub r0, r0, #16
pop {r4-r11,pc}
fastSearch16_firstWordMatch_r8:
sub r0, r0, #8
cmp r9, r3
cmpeq r10, r4
cmpeq r11, r5
bne fastSearch16_continueFastSearch
sub r0, r0, #16
pop {r4-r11,pc}
fastSearch16_firstWordMatch_r9:
sub r0, r0, #4
cmp r10, r3
cmpeq r11, r4
cmpeq r12, r5
bne fastSearch16_continueFastSearch
sub r0, r0, #16
pop {r4-r11,pc}
fastSearch16_firstWordMatch_r10:
cmp r11, r3
cmpeq r12, r4
cmpeq lr, r5
bne fastSearch16_continueFastSearch
sub r0, r0, #16
pop {r4-r11,pc}
fastSearch16_firstWordMatch_r11:
cmp r12, r3
cmpeq lr, r4
ldreq r6, [r0], #4
cmpeq r6, r5
bne fastSearch16_continueFastSearch
sub r0, r0, #16
pop {r4-r11,pc}
fastSearch16_firstWordMatch_r12:
cmp lr, r3
ldreq r6, [r0], #4
cmpeq r6, r4
ldreq r6, [r0], #4
cmpeq r6, r5
bne fastSearch16_continueFastSearch
sub r0, r0, #16
pop {r4-r11,pc}

102
arm9/source/globalHeap.cpp Normal file
View File

@@ -0,0 +1,102 @@
#include "common.h"
#include <string.h>
#include "core/heap/tlsf.h"
#include "globalHeap.h"
static tlsf_t sHeap;
extern "C" void* malloc(size_t size)
{
return tlsf_malloc(sHeap, size);
}
extern "C" void* _malloc_r(struct _reent *, size_t size)
{
return malloc(size);
}
extern "C" void free(void* ptr)
{
tlsf_free(sHeap, ptr);
}
extern "C" void _free_r(struct _reent *, void* ptr)
{
free(ptr);
}
extern "C" void* realloc(void* ptr, size_t size)
{
return tlsf_realloc(sHeap, ptr, size);
}
extern "C" void* memalign(size_t alignment, size_t size)
{
return tlsf_memalign(sHeap, alignment, size);
}
void* operator new(std::size_t blocksize)
{
return malloc(blocksize);
}
void* operator new(std::size_t size, std::align_val_t al)
{
return memalign(static_cast<std::size_t>(al), size);
}
void* operator new[](std::size_t blocksize)
{
return malloc(blocksize);
}
void* operator new[](std::size_t size, std::align_val_t al)
{
return memalign(static_cast<std::size_t>(al), size);
}
void operator delete(void* ptr)
{
return free(ptr);
}
void operator delete(void* ptr, std::align_val_t align)
{
return free(ptr);
}
void operator delete(void* ptr, std::size_t size, std::align_val_t align)
{
return free(ptr);
}
void operator delete[](void* ptr)
{
return free(ptr);
}
void operator delete[](void* ptr, std::align_val_t align)
{
return free(ptr);
}
void operator delete[](void* ptr, std::size_t size, std::align_val_t align)
{
return free(ptr);
}
extern u8 __heap_start;
extern u8 __heap_end;
[[gnu::target("thumb"), gnu::optimize("Os")]]
void heap_init()
{
u32 heapStart = (u32)&__heap_start;
heapStart = (heapStart + 31) & ~31;
u32 heapEnd = (u32)&__heap_end;
heapEnd = heapEnd & ~31;
u32 tlsfSize = tlsf_size();
memset((void*)heapStart, 0, tlsfSize);
memset((u8*)heapStart + tlsfSize, 0xA5, heapEnd - heapStart - tlsfSize);
sHeap = tlsf_create_with_pool((void*)heapStart, heapEnd - heapStart);
}

16
arm9/source/globalHeap.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
void* operator new(std::size_t blocksize) noexcept;
void* operator new(std::size_t size, std::align_val_t al) noexcept;
void* operator new[](std::size_t blocksize) noexcept;
void* operator new[](std::size_t size, std::align_val_t al) noexcept;
void operator delete(void* ptr) noexcept;
void operator delete(void* ptr, std::align_val_t align) noexcept;
void operator delete(void* ptr, std::size_t size, std::align_val_t align) noexcept;
void operator delete[](void* ptr) noexcept;
void operator delete[](void* ptr, std::align_val_t align) noexcept;
void operator delete[](void* ptr, std::size_t size, std::align_val_t align) noexcept;
constexpr std::align_val_t cache_align { 32 };
void heap_init();

View File

@@ -0,0 +1,22 @@
#pragma once
#include "logger/IOutputStream.h"
#define REG_NOCASH_STRING_OUT (*(vu32*)0x04FFFA10)
#define REG_NOCASH_CHAR_OUT (*(vu32*)0x04FFFA1C)
class NocashOutputStream : public IOutputStream
{
public:
void Write(const char* str) override
{
// melon ds doesn't support string addresses in itcm and dtcm
char c;
while ((c = *str++) != 0)
{
REG_NOCASH_CHAR_OUT = c;
}
// REG_NOCASH_STRING_OUT = (u32)str;
}
void Flush() override { }
};

View File

@@ -0,0 +1,25 @@
#pragma once
#include "core/mini-printf.h"
#include "logger/ILogger.h"
#include "logger/IOutputStream.h"
class PlainLogger : public ILogger
{
LogLevel _maxLogLevel;
IOutputStream* _outputStream;
// char _logBuffer[512];
public:
PlainLogger(LogLevel maxLogLevel, IOutputStream* outputStream)
: _maxLogLevel(maxLogLevel), _outputStream(outputStream) { }
void LogV(LogLevel level, const char* fmt, va_list vlist) override
{
if (level > _maxLogLevel)
return;
char logBuffer[128];
mini_vsnprintf(logBuffer, sizeof(logBuffer), fmt, vlist);
_outputStream->Write(logBuffer);
}
};

352
arm9/source/main.cpp Normal file
View File

@@ -0,0 +1,352 @@
#include "common.h"
#include "ApList.h"
#include <libtwl/gfx/gfx3d.h>
#include <libtwl/gfx/gfx3dCmd.h>
#include <libtwl/gfx/gfxOam.h>
#include <libtwl/gfx/gfxPalette.h>
#include <libtwl/gfx/gfxStatus.h>
#include <libtwl/ipc/ipcFifo.h>
#include <libtwl/ipc/ipcSync.h>
#include <libtwl/mem/memNtrWram.h>
#include <libtwl/mem/memTwlWram.h>
#include <libtwl/mem/memVram.h>
#include "fastClear.h"
#include "cache.h"
#include "sharedMemory.h"
#include "ndsHeader.h"
#include "logger/NocashOutputStream.h"
#include "logger/PicoAgbAdapterOutputStream.h"
#include "logger/PlainLogger.h"
#include "ipcCommands.h"
#include "Arm9IoRegisterClearer.h"
#include "Arm9Patcher.h"
#include "Arm7Patcher.h"
#include "patches/platform/LoaderPlatform.h"
#include "patches/platform/LoaderPlatformFactory.h"
#include "arm9Clock.h"
#include "errorDisplay/ErrorDisplay.h"
#include "LoaderInfo.h"
typedef void (*entrypoint_t)(void);
#define HANDSHAKE_PART0 0xA
#define HANDSHAKE_PART1 0xB
#define HANDSHAKE_PART2 0xC
#define HANDSHAKE_PART3 0xD
static NocashOutputStream/*PicoAgbAdapterOutputStream*/ sNocashOutput;
static PlainLogger sPlainLogger { LogLevel::All, &sNocashOutput };
ILogger* gLogger = &sPlainLogger;
static ApListEntry sApListEntry;
static LoaderPlatform* sLoaderPlatform;
static u32 sRomDirSector;
static u32 sRomDirSectorOffset;
static u16 sIsCloneBootRom;
static loader_info_t sLoaderInfo;
u16 gIsDsiMode;
// Dummy symbol to allow linking C++ applications. This is only needed to handle
// dynamic shared objects (.so), but they don't exist on the NDS.
void *__dso_handle;
static u32 receiveFromArm7()
{
while (ipc_isRecvFifoEmpty());
return ipc_recvWordDirect();
}
extern "C" void __libc_init_array();
static void clearGraphicsMemory()
{
// VRAM A used for arm9 code
mem_setVramBMapping(MEM_VRAM_AB_LCDC);
// VRAM C and D used for arm7 code
mem_setVramEMapping(MEM_VRAM_E_LCDC);
mem_setVramFMapping(MEM_VRAM_FG_LCDC);
mem_setVramGMapping(MEM_VRAM_FG_LCDC);
mem_setVramHMapping(MEM_VRAM_H_LCDC);
mem_setVramIMapping(MEM_VRAM_I_LCDC);
fastClear((void*)0x06820000, 0x20000); // VRAM B
fastClear((void*)0x06880000, 0x24000);
fastClear((void*)GFX_PLTT_BG_MAIN, 512);
fastClear((void*)GFX_PLTT_BG_SUB, 512);
fastClear((void*)GFX_PLTT_OBJ_MAIN, 512);
fastClear((void*)GFX_PLTT_OBJ_SUB, 512);
fastClear((void*)GFX_OAM_MAIN, 1024);
fastClear((void*)GFX_OAM_SUB, 1024);
// clear the vertex and polygon ram of the 3d engine
gx_init();
gx_swapBuffers(GX_XLU_SORT_AUTO, GX_DEPTH_MODE_Z);
gx_swapBuffers(GX_XLU_SORT_AUTO, GX_DEPTH_MODE_Z);
}
[[gnu::noinline, gnu::section(".itcm")]]
static void bootArm9()
{
mem_setVramAMapping(MEM_VRAM_AB_LCDC);
fastClear((void*)0x06800000, 0x20000); // VRAM A
mem_setVramAMapping(MEM_VRAM_AB_NONE);
// By now it should be safe to unmap the arm7 memory
mem_setVramCMapping(MEM_VRAM_C_LCDC);
mem_setVramDMapping(MEM_VRAM_D_LCDC);
fastClear((void*)0x06840000, 0x40000); // VRAM C and D
mem_setVramCMapping(MEM_VRAM_C_NONE);
mem_setVramDMapping(MEM_VRAM_D_NONE);
if (((REG_SCFG_EXT >> 14) & 3) == 0)
{
// When switched to DS mode, disable vram extensions
REG_SCFG_EXT &= ~(1 << 13);
}
while (gfx_getVCount() != 191);
while (gfx_getVCount() == 191);
REG_IF = ~0u; // final clear of REG_IF bits
auto romHeader = (const nds_header_ntr_t*)TWL_SHARED_MEMORY->ntrSharedMem.romHeader;
((entrypoint_t)romHeader->arm9EntryAddress)();
}
static void handleInitializeSdCardCommand()
{
REG_EXMEMCNT &= ~0x0880; // map ds and gba slot to arm9
bool result = sLoaderPlatform->InitializeSdCard();
REG_EXMEMCNT |= 0x0880; // map ds and gba slot to arm7
ipc_sendWordDirect(result);
}
static void handleWramConfigCommand()
{
REG_WRAMCNT = receiveFromArm7();
REG_MBK6 = receiveFromArm7();
REG_MBK7 = receiveFromArm7();
REG_MBK8 = receiveFromArm7();
REG_MBK1 = receiveFromArm7();
REG_MBK2 = receiveFromArm7();
REG_MBK3 = receiveFromArm7();
REG_MBK4 = receiveFromArm7();
REG_MBK5 = receiveFromArm7();
ipc_sendWordDirect(1);
}
static void handleClearMainMemCommand()
{
fastClear((void*)0x02000000, 0x400000);
dc_flushAll();
dc_drainWriteBuffer();
dc_invalidateAll();
ic_invalidateAll();
ipc_sendWordDirect(1);
}
static void handleApplyArm9PatchesCommand()
{
Arm9Patcher().ApplyPatches(
sLoaderPlatform,
sApListEntry.GetGameCode() == 0 ? nullptr : &sApListEntry,
sIsCloneBootRom,
&sLoaderInfo);
ipc_sendWordDirect(1);
}
static void handleApplyArm7PatchesCommand()
{
void* patchSpaceStart = Arm7Patcher().ApplyPatches(sLoaderPlatform);
ipc_sendWordDirect((u32)patchSpaceStart);
}
static void handleSetAPInfoCommand()
{
((u32*)&sApListEntry)[0] = receiveFromArm7();
((u32*)&sApListEntry)[1] = receiveFromArm7();
((u32*)&sApListEntry)[2] = receiveFromArm7();
((u32*)&sApListEntry)[3] = receiveFromArm7();
}
static void handleSetRomFileInfoCommand()
{
sRomDirSector = receiveFromArm7();
sRomDirSectorOffset = receiveFromArm7();
sIsCloneBootRom = receiveFromArm7();
}
static void handleInitializeLoaderInfoCommand()
{
dc_invalidateRange(TWL_SHARED_MEMORY->ntrSharedMem.cardRomHeader, sizeof(loader_info_t));
memcpy(&sLoaderInfo, TWL_SHARED_MEMORY->ntrSharedMem.cardRomHeader, sizeof(loader_info_t));
dc_flushAll();
dc_drainWriteBuffer();
ipc_sendWordDirect(1);
}
static void handleGetSdFunctionsCommand()
{
mem_setNtrWramMapping(MEM_NTR_WRAM_ARM9, MEM_NTR_WRAM_ARM9);
PatchHeap patchHeap;
PatchCodeCollection patchCodeCollection;
patchHeap.AddFreeSpace((void*)0x037F8020, 16 * 1024);
{
auto sdReadPatchCode = sLoaderPlatform->CreateSdReadPatchCode(patchCodeCollection, patchHeap);
auto sdWritePatchCode = sLoaderPlatform->CreateSdWritePatchCode(patchCodeCollection, patchHeap);
*(vu32*)0x037F8000 = (u32)sdReadPatchCode->GetSdReadFunction();
*(vu32*)0x037F8004 = (u32)sdWritePatchCode->GetSdWriteFunction();
patchCodeCollection.CopyAllToTarget();
}
dc_flushAll();
dc_drainWriteBuffer();
mem_setNtrWramMapping(MEM_NTR_WRAM_ARM7, MEM_NTR_WRAM_ARM7);
ipc_sendWordDirect(1);
}
[[gnu::noinline, gnu::section(".itcm")]]
static void handleSwitchToDSModeCommand()
{
Arm9IoRegisterClearer().ClearTwlIoRegisters();
scfg_setArm9Clock(ScfgArm9Clock::Nitro67MHz);
REG_SCFG_EXT = 0x83000000u | (1 << 13); // keep vram extensions on until the very end
ipc_sendWordDirect(1);
}
[[gnu::noinline, gnu::section(".itcm")]]
static void handleBootCommand()
{
bool isSdkResetSystem = receiveFromArm7() != 0;
REG_EXMEMCNT &= ~0x0880; // map ds and gba slot to arm9
sLoaderPlatform->PrepareRomBoot(sRomDirSector, sRomDirSectorOffset);
Arm9IoRegisterClearer().ClearNtrIoRegisters(isSdkResetSystem);
auto ntrRomHeader = (const nds_header_ntr_t*)TWL_SHARED_MEMORY->ntrSharedMem.romHeader;
if (ntrRomHeader->IsTwlRom())
{
if (gIsDsiMode)
{
Arm9IoRegisterClearer().ClearTwlIoRegisters();
REG_SCFG_EXT = 0x8307F100;
scfg_setArm9Clock(ScfgArm9Clock::Twl134MHz);
REG_SCFG_CLK = 0x87;
REG_SCFG_RST = 1;
}
auto twlRomHeader = (const nds_header_twl_t*)TWL_SHARED_MEMORY->twlRomHeader;
if (twlRomHeader->IsDsiWare())
{
REG_EXMEMCNT |= 0x0880; // map ds and gba slot to arm7
}
}
bootArm9();
}
static void handleArm7Command(u32 command)
{
switch (command)
{
case IPC_COMMAND_ARM9_INITIALIZE_SD_CARD:
{
handleInitializeSdCardCommand();
break;
}
case IPC_COMMAND_ARM9_WRAM_CONFIG:
{
handleWramConfigCommand();
break;
}
case IPC_COMMAND_ARM9_CLEAR_MAIN_MEM:
{
handleClearMainMemCommand();
break;
}
case IPC_COMMAND_ARM9_APPLY_PATCHES:
{
handleApplyArm9PatchesCommand();
break;
}
case IPC_COMMAND_ARM9_APPLY_ARM7_PATCHES:
{
handleApplyArm7PatchesCommand();
break;
}
case IPC_COMMAND_ARM9_SET_AP_INFO:
{
handleSetAPInfoCommand();
break;
}
case IPC_COMMAND_ARM9_SET_ROM_FILE_INFO:
{
handleSetRomFileInfoCommand();
break;
}
case IPC_COMMAND_ARM9_INITIALIZE_LOADER_INFO:
{
handleInitializeLoaderInfoCommand();
break;
}
case IPC_COMMAND_ARM9_GET_SD_FUNCTIONS:
{
handleGetSdFunctionsCommand();
break;
}
case IPC_COMMAND_ARM9_DISPLAY_ERROR:
{
ErrorDisplay().PrintError((const char*)0x02000000);
break;
}
case IPC_COMMAND_ARM9_SWITCH_TO_DS_MODE:
{
handleSwitchToDSModeCommand();
break;
}
case IPC_COMMAND_ARM9_BOOT:
{
handleBootCommand();
break;
}
}
}
extern "C" void loaderMain()
{
__libc_init_array();
clearGraphicsMemory();
while (ipc_getArm7SyncBits() != HANDSHAKE_PART0);
ipc_setArm9SyncBits(HANDSHAKE_PART0);
while (ipc_getArm7SyncBits() != HANDSHAKE_PART1);
ipc_setArm9SyncBits(HANDSHAKE_PART1);
REG_EXMEMCNT |= 0x8880; // everything to arm7
// REG_EXMEMCNT &= ~0xFF;
mem_setNtrWramMapping(MEM_NTR_WRAM_ARM7, MEM_NTR_WRAM_ARM7);
ipc_clearSendFifo();
ipc_ackFifoError();
ipc_disableRecvFifoNotEmptyIrq();
ipc_enableFifo();
while (ipc_getArm7SyncBits() != HANDSHAKE_PART2);
ipc_setArm9SyncBits(HANDSHAKE_PART2);
while (ipc_getArm7SyncBits() != HANDSHAKE_PART3);
ipc_setArm9SyncBits((*(vu32*)0x04004000) & 3);
gIsDsiMode = ((*(vu32*)0x04004000) & 3) == 1;
heap_init();
sLoaderPlatform = LoaderPlatformFactory().CreateLoaderPlatform();
sRomDirSector = 0;
sRomDirSectorOffset = 0;
sIsCloneBootRom = false;
LOG_DEBUG("Pico Loader ARM9 started\n");
while (true)
{
if (ipc_isRecvFifoEmpty())
continue;
u32 word = ipc_recvWordDirect();
handleArm7Command(word);
}
}

View File

@@ -0,0 +1,28 @@
#pragma once
#include "SdkVersion.h"
/// @brief Class representing a function signature with a minimum and maximum SDK version.
class FunctionSignature
{
public:
consteval FunctionSignature(const u32 pattern[4], SdkVersion minSdkVersion, SdkVersion maxSdkVersion)
: _pattern { pattern[0], pattern[1], pattern[2], pattern[3] }
, _minSdkVersion(minSdkVersion), _maxSdkVersion(maxSdkVersion) { }
/// @brief Returns the pattern of the function signature.
/// @return The pattern of the function signature.
constexpr const u32* GetPattern() const { return _pattern; }
/// @brief Returns the minimum SDK version of this function signature.
/// @return The minimum SDK version of this function signature.
constexpr SdkVersion GetMinimumSdkVersion() const { return _minSdkVersion; }
/// @brief Returns the maximum SDK version of this function signature.
/// @return The maximum SDK version of this function signature.
constexpr SdkVersion GetMaximumSdkVersion() const { return _maxSdkVersion; }
private:
const u32 _pattern[4];
const SdkVersion _minSdkVersion;
const SdkVersion _maxSdkVersion;
};

View File

@@ -0,0 +1,20 @@
#pragma once
#include "sections.h"
#include "SectorRemapPatchCode.h"
DEFINE_SECTION_SYMBOLS(offsettosectorremap);
extern "C" u32 offset_to_sector_remap(u32 romOffset);
class OffsetToSectorRemapPatchCode : public SectorRemapPatchCode
{
public:
explicit OffsetToSectorRemapPatchCode(PatchHeap& patchHeap)
: SectorRemapPatchCode(SECTION_START(offsettosectorremap), SECTION_SIZE(offsettosectorremap), patchHeap)
{ }
const void* GetRemapFunction() const override
{
return GetAddressAtTarget((void*)offset_to_sector_remap);
}
};

View File

@@ -0,0 +1,22 @@
.cpu arm7tdmi
.section "offsettosectorremap", "ax"
.syntax unified
.thumb
// r0 = rom offset
// returns sd sector in r0
// returns remaining sectors in cluster in lr
.global offset_to_sector_remap
.type offset_to_sector_remap, %function
offset_to_sector_remap:
push {lr}
lsrs r0, r0, #9 // byte address to sector address
movs r1, #1
mov lr, r1 // read at most 1 sector at a time
pop {pc}
.balign 4
.pool
.end

View File

@@ -0,0 +1,21 @@
#pragma once
#include "PatchContext.h"
/// @brief Abstract class representing a patch intended for the arm9 or arm7 binary.
class Patch
{
public:
/// @brief Next patch in the singly linked list.
Patch* next = nullptr;
/// @brief Tries to find the patch target in the region corresponding
/// to the given patchContext.
/// @param patchContext The patch context to use.
/// @return True if the patch target was found, or false otherwise.
virtual bool FindPatchTarget(PatchContext& patchContext) = 0;
/// @brief Applies the patch to the target that was previously found
/// with FindPatchTarget.
/// @param patchContext The patch context to use.
virtual void ApplyPatch(PatchContext& patchContext) = 0;
};

View File

@@ -0,0 +1,47 @@
#pragma once
#include <string.h>
#include "PatchHeap.h"
/// @brief Class representing a block of patch code to be inserted in a \see PatchHeap.
class PatchCode
{
public:
/// @brief Constructs a PatchCode instance for the given block of code
/// to be inserted in the given patchHeap.
/// @param code The start of the block of code.
/// @param size The size of the block of code.
/// @param patchHeap The patch heap to use.
PatchCode(const void* code, u32 size, PatchHeap& patchHeap)
: _code(code), _size(size), _targetAddress(patchHeap.Alloc(size)) { }
~PatchCode()
{
LOG_FATAL("Patch code must not be deleted.\n");
while (1);
}
/// @brief Converts a pointer inside the original code block
/// to a pointer at the target location in the patch heap.
/// @param ptr The pointer to convert.
/// @return The converted pointer.
const void* GetAddressAtTarget(const void* ptr) const
{
return (const void*)((u32)ptr - (u32)_code + (u32)_targetAddress);
}
/// @brief Copies the patch code to the target address.
void CopyToTarget() const
{
memcpy(_targetAddress, _code, _size);
}
protected:
/// @brief The start of the code block.
const void* const _code;
/// @brief The size of the code block.
const u32 _size;
/// @brief The target address for the code block in the patch heap.
void* const _targetAddress;
};

View File

@@ -0,0 +1,56 @@
#include "common.h"
#include "PatchCodeCollection.h"
void PatchCodeCollection::AddUniquePatchCode(PatchCode* patchCode)
{
for (auto& entry : _patchCodeEntries)
{
if (entry.type == 0)
{
entry.type = 0xFFFFFFFFu;
entry.patchCode = patchCode;
return;
}
}
LOG_FATAL("Out of patch code entries.\n");
while (true);
}
PatchCodeCollection::PatchCodeEntry& PatchCodeCollection::GetOrAddSharedPatchCode(u32 id)//, create_patch_code_func_t factoryFunction)
{
PatchCodeEntry* firstFreeEntry = nullptr;
for (auto& entry : _patchCodeEntries)
{
if (entry.type == 0)
{
firstFreeEntry = &entry;
break;
}
else if (entry.type == id)
{
return entry;
}
}
if (firstFreeEntry == nullptr)
{
LOG_FATAL("Out of patch code entries.\n");
while (true);
}
return *firstFreeEntry;
}
void PatchCodeCollection::CopyAllToTarget() const
{
for (auto& entry : _patchCodeEntries)
{
if (entry.type == 0)
{
break;
}
entry.patchCode->CopyToTarget();
}
}

View File

@@ -0,0 +1,77 @@
#pragma once
#include <array>
#include <concepts>
#include "PatchCode.h"
#include "PatchHeap.h"
/// @brief Class representing a collection of patch codes.
class PatchCodeCollection
{
private:
using create_patch_code_func_t = PatchCode* (*)(PatchCodeCollection& patchCodeCollection);
public:
PatchCodeCollection()
{
_patchCodeEntries.fill({ 0, nullptr });
}
PatchCodeCollection(const PatchCodeCollection&) = delete;
/// @brief Adds a unique patch code to the collection. If the same patch code is added another time, it will be duplicated.
/// @tparam T The type of patch code.
/// @tparam ...Args The argument types for the patch code constructor.
/// @param ...args The arguments for the patch code constructor.
/// @return A pointer to the patch code.
template <typename T, typename... Args> requires std::derived_from<T, PatchCode>
const T* AddUniquePatchCode(Args&&... args)
{
T* patchCode = new T(std::forward<Args>(args)...);
AddUniquePatchCode(patchCode);
return patchCode;
}
/// @brief Gets or adds a shared patch code from/to the collection.
/// If the same patch code is requested another time, the same instance will be returned.
/// @tparam T The type of patch code.
/// @param factoryFunction A factory function that produces a pointer to the patch code class (T*).
/// @return A pointer to the patch code.
template <typename T> requires std::derived_from<std::remove_pointer_t<std::invoke_result_t<T>>, PatchCode>
auto GetOrAddSharedPatchCode(T factoryFunction)
// return type of the factory function converted from a pointer to a pointer to const
-> std::add_pointer_t<std::add_const_t<std::remove_pointer_t<decltype(factoryFunction())>>>
{
u32 id = GetPatchCodeId<std::remove_pointer_t<decltype(factoryFunction())>>();
auto& entry = GetOrAddSharedPatchCode(id);
if (entry.type == 0)
{
entry.type = id; // claim this slot before calling the factory function
entry.patchCode = factoryFunction();
}
return static_cast<decltype(factoryFunction())>(entry.patchCode);
}
/// @brief Copies all patch codes to their target location.
void CopyAllToTarget() const;
private:
struct PatchCodeEntry
{
u32 type;
PatchCode* patchCode;
};
std::array<PatchCodeEntry, 64> _patchCodeEntries;
void AddUniquePatchCode(PatchCode* patchCode);
PatchCodeEntry& GetOrAddSharedPatchCode(u32 id);
template <class T>
static u32 GetPatchCodeId()
{
static const char typeId = 0;
return (u32)&typeId;
}
};

View File

@@ -0,0 +1,28 @@
#include "common.h"
#include "PatchContext.h"
#include "PatchCollection.h"
bool PatchCollection::TryPerformPatches(PatchContext& patchContext)
{
LOG_DEBUG("PatchCollection::PerformPatches\n");
Patch* cur = _head;
while (cur)
{
if (!cur->FindPatchTarget(patchContext))
{
LOG_DEBUG("Patch target not found!\n");
return false;
}
cur = cur->next;
}
cur = _head;
while (cur)
{
cur->ApplyPatch(patchContext);
cur = cur->next;
}
patchContext.GetPatchCodeCollection().CopyAllToTarget();
return true;
}

View File

@@ -0,0 +1,32 @@
#pragma once
#include "Patch.h"
/// @brief Class containing a linked list of patches.
class PatchCollection
{
public:
/// @brief Adds the given \p patch to the collection.
/// @param patch The patch to add.
void AddPatch(Patch* patch)
{
LOG_DEBUG("PatchCollection::AddPatch\n");
if (!_head)
{
_head = patch;
}
if (_tail)
{
_tail->next = patch;
}
_tail = patch;
}
/// @brief Tries to perform all patches in this collection.
/// @param patchContext The patch context to use.
/// @return \c true when all patches were successfully performed, or \c false otherwise.
bool TryPerformPatches(PatchContext& patchContext);
private:
Patch* _head = nullptr;
Patch* _tail = nullptr;
};

View File

@@ -0,0 +1,31 @@
#include "common.h"
#include "fastSearch.h"
#include "platform/LoaderPlatform.h"
#include "PatchContext.h"
u32* PatchContext::FindPattern32(const u32* pattern, u32 byteLength) const
{
#ifdef LIBTWL_ARM9
if (byteLength == 16)
{
return (u32*)fastSearch16((const u32*)_data, _dataSize, pattern);
}
#endif
return nullptr;
}
u32* PatchContext::FindPattern32Twl(const u32* pattern, u32 byteLength) const
{
if (!_twlData || _twlDataSize == 0)
{
return nullptr;
}
#ifdef LIBTWL_ARM9
if (byteLength == 16)
{
return (u32*)fastSearch16((const u32*)_twlData, _twlDataSize, pattern);
}
#endif
return nullptr;
}

View File

@@ -0,0 +1,61 @@
#pragma once
#include <memory>
#include "SdkVersion.h"
#include "PatchHeap.h"
#include "PatchCodeCollection.h"
class LoaderPlatform;
/// @brief Class providing a context for performing patches on retail rom arm7 or arm9.
class PatchContext
{
public:
PatchContext(void* data, u32 dataSize, void* twlData, u32 twlDataSize,
SdkVersion sdkVersion, u32 gameCode, const LoaderPlatform* loaderPlatform)
: _data(data), _dataSize(dataSize), _twlData(twlData), _twlDataSize(twlDataSize)
, _sdkVersion(sdkVersion), _gameCode(gameCode), _loaderPlatform(loaderPlatform) { }
/// @brief Tries to find the given \p pattern of the given \p byteLength in the ntr region.
/// @param pattern The pattern to find.
/// @param byteLength The length of the pattern.
/// @return A pointer to the first location where the pattern was found, or \c nullptr if the pattern was not found.
u32* FindPattern32(const u32* pattern, u32 byteLength) const;
/// @brief Tries to find the given \p pattern of the given \p byteLength in the twl region.
/// @param pattern The pattern to find.
/// @param byteLength The length of the pattern.
/// @return A pointer to the first location where the pattern was found, or \c nullptr if the pattern was not found.
u32* FindPattern32Twl(const u32* pattern, u32 byteLength) const;
/// @brief Returns the patch heap of this context.
/// @return The patch heap of this context.
constexpr PatchHeap& GetPatchHeap() { return _patchHeap; }
/// @brief Returns the patch code collection of this context.
/// @return The patch code collection of this context.
constexpr PatchCodeCollection& GetPatchCodeCollection() { return _patchCodeCollection; }
/// @brief Returns the SDK version of the rom that is being patched.
/// @return The SDK version of the rom that is being patched.
constexpr SdkVersion GetSdkVersion() const { return _sdkVersion; }
/// @brief Returns the game code of the rom that is being patched.
/// @return The game code of the rom that is being patched.
constexpr u32 GetGameCode() const { return _gameCode; }
/// @brief Returns the loader platform that should be used for the patches.
/// @return The loader platform that should be used for the patches.
constexpr const LoaderPlatform* GetLoaderPlatform() { return _loaderPlatform; }
private:
void* _data;
u32 _dataSize;
void* _twlData;
u32 _twlDataSize;
SdkVersion _sdkVersion;
u32 _gameCode;
PatchHeap _patchHeap;
PatchCodeCollection _patchCodeCollection;
const LoaderPlatform* _loaderPlatform;
};

View File

@@ -0,0 +1,100 @@
#include "common.h"
#include "PatchHeap.h"
PatchHeap::PatchHeap()
: _freeBlocks(nullptr)
{
_unusedBlockPool = &_blocks[0];
for (u32 i = 0; i < _blocks.size() - 1; i++)
_blocks[i].next = &_blocks[i + 1];
_blocks[_blocks.size() - 1].next = nullptr;
}
void PatchHeap::AddFreeSpace(void* block, u32 size)
{
PatchHeapBlock* cur = _freeBlocks;
bool added = false;
while (cur)
{
if ((u8*)cur->block + cur->size == block)
{
cur->size += size;
added = true;
break;
}
else if ((u8*)block + size == cur->block)
{
cur->block = block;
cur->size += size;
added = true;
break;
}
cur = cur->next;
}
if (added)
return;
PatchHeapBlock* heapBlock = _unusedBlockPool;
_unusedBlockPool = _unusedBlockPool->next;
heapBlock->block = block;
heapBlock->size = size;
heapBlock->next = _freeBlocks;
_freeBlocks = heapBlock;
}
void* PatchHeap::Alloc(u32 size)
{
PatchHeapBlock* prev = nullptr;
PatchHeapBlock* cur = _freeBlocks;
PatchHeapBlock* bestBlock = nullptr;
PatchHeapBlock* bestBlockPrev = nullptr;
while (cur)
{
if (cur->size >= size && (!bestBlock || cur->size < bestBlock->size))
{
bestBlock = cur;
bestBlockPrev = prev;
if (bestBlock->size == size)
break;
}
prev = cur;
cur = cur->next;
}
if (!bestBlock)
{
LOG_FATAL("No space found to put patch of size 0x%x\n", size);
while (1);
return nullptr;
}
void* result = bestBlock->block;
if (bestBlock->size == size)
{
if (bestBlockPrev)
bestBlockPrev->next = bestBlock->next;
if (_freeBlocks == bestBlock)
_freeBlocks = bestBlock->next;
bestBlock->next = _unusedBlockPool;
_unusedBlockPool = bestBlock;
}
else
{
bestBlock->block = (u8*)bestBlock->block + size;
bestBlock->size -= size;
}
LOG_DEBUG("Allocated 0x%p, size = 0x%X\n", result, size);
return result;
}

View File

@@ -0,0 +1,34 @@
#pragma once
#include <array>
/// @brief Class implementing a heap intended for patch code.
/// The heap consists of a linked list of blocks of free
/// space. It is possible to add many small pieces.
class PatchHeap
{
public:
PatchHeap();
/// @brief Adds a block of free space to the patch heap.
/// @param block The start of the block.
/// @param size The size of the block.
void AddFreeSpace(void* block, u32 size);
/// @brief Allocated a block of the given size from the patch heap.
/// @param size The size to allocate.
/// @return A pointer to the allocated block if successful, or nullptr otherwise.
void* Alloc(u32 size);
private:
struct PatchHeapBlock
{
PatchHeapBlock* next;
void* block;
u32 size;
};
std::array<PatchHeapBlock, 64> _blocks;
PatchHeapBlock* _unusedBlockPool;
PatchHeapBlock* _freeBlocks;
};

View File

@@ -0,0 +1,11 @@
#pragma once
#include "PatchCode.h"
class SectorRemapPatchCode : public PatchCode
{
public:
SectorRemapPatchCode(const void* code, u32 size, PatchHeap& patchHeap)
: PatchCode(code, size, patchHeap) { }
virtual const void* GetRemapFunction() const = 0;
};

View File

@@ -0,0 +1,15 @@
#pragma once
#include "sections.h"
DEFINE_SECTION_SYMBOLS(patch_carditaskthread);
extern "C" void __patch_carditaskthread_entry();
extern "C" void __patch_carditaskthread_entry_sdk4();
extern u16 __patch_carditaskthread_mov_cardicommon_to_r4;
extern u16 __patch_carditaskthread_mov_command_to_r0;
extern u32 __patch_carditaskthread_failoffset;
extern u32 __patch_carditaskthread_successoffset;
extern u32 __patch_carditaskthread_readsave_asm_address;
extern u32 __patch_carditaskthread_writesave_asm_address;

View File

@@ -0,0 +1,123 @@
.cpu arm7tdmi
.section "patch_carditaskthread", "ax"
.syntax unified
.thumb
.global __patch_carditaskthread_entry_sdk4
.type __patch_carditaskthread_entry_sdk4, %function
__patch_carditaskthread_entry_sdk4:
push {r4,lr}
beq end_success
b __patch_carditaskthread_mov_cardicommon_to_r4
.global __patch_carditaskthread_entry
.type __patch_carditaskthread_entry, %function
__patch_carditaskthread_entry:
push {r4,lr}
.global __patch_carditaskthread_mov_cardicommon_to_r4
__patch_carditaskthread_mov_cardicommon_to_r4:
mov r4, r9
.global __patch_carditaskthread_mov_command_to_r0
__patch_carditaskthread_mov_command_to_r0:
ldr r0, [r4, #4] // r0 = command
cmp r0, #2
beq identify_backup
cmp r0, #6
beq read_backup
cmp r0, #7
beq write_backup
cmp r0, #8
beq write_backup
cmp r0, #9
beq verify_backup
// erase
cmp r0, #10
beq end_success
cmp r0, #11
beq end_success
cmp r0, #12
beq end_success
cmp r0, #15
beq end_success
end_fail:
ldr r0, __patch_carditaskthread_failoffset
b end
end_success:
ldr r0, __patch_carditaskthread_successoffset
end:
pop {r4}
pop {r3}
adds r3, r0
bx r3
identify_backup:
b end_success
read_backup:
ldr r0, [r4]
movs r1, #0
str r1, [r0] // result
ldr r1, [r0, #0xC] // src
ldr r2, [r0, #0x10] // dst
ldr r3, [r0, #0x14] // len
cmp r3, #0
beq end_success
ldr r0, __patch_carditaskthread_readsave_asm_address
bl blx_r0
b end_success
write_backup:
ldr r0, [r4]
movs r1, #0
str r1, [r0] // result
ldr r1, [r0, #0xC] // src
ldr r2, [r0, #0x10] // dst
ldr r3, [r0, #0x14] // len
cmp r3, #0
beq end_success
ldr r0, __patch_carditaskthread_writesave_asm_address
bl blx_r0
b end_success
verify_backup:
ldr r0, [r4]
movs r1, #0
str r1, [r0] // result
b end_success
blx_r0:
bx r0
.balign 4
.global __patch_carditaskthread_failoffset
__patch_carditaskthread_failoffset:
.word 0
.global __patch_carditaskthread_successoffset
__patch_carditaskthread_successoffset:
.word 4
.global __patch_carditaskthread_readsave_asm_address
__patch_carditaskthread_readsave_asm_address:
.word 0
.global __patch_carditaskthread_writesave_asm_address
__patch_carditaskthread_writesave_asm_address:
.word 0
.pool
.end

View File

@@ -0,0 +1,39 @@
#include "common.h"
#include "../PatchContext.h"
#include "sharedMemory.h"
#include "ndsHeader.h"
#include "DisableArm7WramClearPatch.h"
bool DisableArm7WramClearPatch::FindPatchTarget(PatchContext& patchContext)
{
auto romHeader = (const nds_header_ntr_t*)TWL_SHARED_MEMORY->ntrSharedMem.romHeader;
_clearEndPool = nullptr;
for (u32 i = 0; i < 0x100; i += 4)
{
u32* ptr = (u32*)(romHeader->arm7LoadAddress + i);
if (*ptr == 0x380FF00)
{
_clearEndPool = ptr;
break;
}
}
if (_clearEndPool)
{
LOG_DEBUG("Arm7 wram clear end pool value found at 0x%p\n", _clearEndPool);
}
else
{
LOG_WARNING("Arm7 wram clear end pool value not found\n");
}
return _clearEndPool != nullptr;
}
void DisableArm7WramClearPatch::ApplyPatch(PatchContext& patchContext)
{
if (!_clearEndPool)
return;
*_clearEndPool = 0;
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include "../Patch.h"
/// @brief Arm7 patch to disabling clearing of wram.
class DisableArm7WramClearPatch : public Patch
{
public:
bool FindPatchTarget(PatchContext& patchContext) override;
void ApplyPatch(PatchContext& patchContext) override;
private:
u32* _clearEndPool = nullptr;
};

View File

@@ -0,0 +1,97 @@
#include "common.h"
#include "../PatchContext.h"
#include "OsGetInitArenaLoPatch.h"
static const u32 sOSGetInitArenaLoPatternOld[] = { 0xE3A0050Eu, 0xE59F1014u, 0xE351050Eu, 0x81A00001u }; // -0x34
static const u32 sOSGetInitArenaLoPattern[] = { 0xE59F1018u, 0xE3A0050Eu, 0xE351050Eu, 0x81A00001u }; // -0x34
static const u32 sOSGetInitArenaLoPattern2[] = { 0xE59F101Cu, 0xE3A0050Eu, 0xE351050Eu, 0x81A00001u }; // -0x3C
static const u32 sOSGetInitArenaLoPatternThumb[] = { 0xD0042801u, 0xD0042807u, 0xD0092808u, 0x4809E00Fu };
static const u32 sOSGetInitArenaLoPatternThumbSdk50[] = { 0x2801B508u, 0x2807D004u, 0x2808D006u, 0xE010D00Au };
bool OsGetInitArenaLoPatch::FindPatchTarget(PatchContext& patchContext)
{
if (_osGetInitArenaLo)
return true;
_osGetInitArenaLo = patchContext.FindPattern32(sOSGetInitArenaLoPattern, sizeof(sOSGetInitArenaLoPattern));
if (!_osGetInitArenaLo)
_osGetInitArenaLo = patchContext.FindPattern32(sOSGetInitArenaLoPatternOld, sizeof(sOSGetInitArenaLoPatternOld));
if (_osGetInitArenaLo)
{
_osGetInitArenaLo -= 13;
_wramBssOffset = 0x54;
}
else
{
_osGetInitArenaLo = patchContext.FindPattern32(sOSGetInitArenaLoPattern2, sizeof(sOSGetInitArenaLoPattern2));
if (_osGetInitArenaLo)
{
_osGetInitArenaLo -= 15;
_wramBssOffset = 0x60;
}
else
{
_osGetInitArenaLo = patchContext.FindPattern32(sOSGetInitArenaLoPatternThumb, sizeof(sOSGetInitArenaLoPatternThumb));
if (_osGetInitArenaLo)
{
_wramBssOffset = 0x38;
}
else
{
_osGetInitArenaLo = patchContext.FindPattern32(sOSGetInitArenaLoPatternThumbSdk50, sizeof(sOSGetInitArenaLoPatternThumbSdk50));
if (_osGetInitArenaLo)
{
_wramBssOffset = 0x40;
}
else
{
_osGetInitArenaLo = patchContext.FindPattern32Twl(sOSGetInitArenaLoPattern, sizeof(sOSGetInitArenaLoPattern));
if (!_osGetInitArenaLo)
_osGetInitArenaLo = patchContext.FindPattern32Twl(sOSGetInitArenaLoPatternOld, sizeof(sOSGetInitArenaLoPatternOld));
if (_osGetInitArenaLo)
{
_osGetInitArenaLo -= 13;
_wramBssOffset = 0x54;
}
else
{
_osGetInitArenaLo = patchContext.FindPattern32Twl(sOSGetInitArenaLoPattern2, sizeof(sOSGetInitArenaLoPattern2));
if (_osGetInitArenaLo)
{
_osGetInitArenaLo -= 15;
_wramBssOffset = 0x60;
}
else
{
_osGetInitArenaLo = patchContext.FindPattern32Twl(sOSGetInitArenaLoPatternThumb, sizeof(sOSGetInitArenaLoPatternThumb));
if (_osGetInitArenaLo)
{
_wramBssOffset = 0x38;
}
}
}
}
}
}
}
if (_osGetInitArenaLo)
{
LOG_DEBUG("Found OS_GetInitArenaLo at %p\n", _osGetInitArenaLo);
_wramBssEnd = (void*)*(u32*)((u8*)_osGetInitArenaLo + _wramBssOffset);
LOG_DEBUG("Bss end: 0x%p\n", _wramBssEnd);
_mainMemoryArenaLo = (void*)*(u32*)((u8*)_osGetInitArenaLo + _wramBssOffset - 4);
LOG_DEBUG("Main memory arena lo: 0x%p\n", _mainMemoryArenaLo);
}
else
{
LOG_WARNING("OS_GetInitArenaLo not found\n");
}
return _osGetInitArenaLo != nullptr;
}
void OsGetInitArenaLoPatch::ApplyPatch(PatchContext& patchContext)
{
if (!_osGetInitArenaLo)
return;
*(u32*)((u8*)_osGetInitArenaLo + _wramBssOffset) = (u32)_wramBssEnd;
*(u32*)((u8*)_osGetInitArenaLo + _wramBssOffset - 4) = (u32)_mainMemoryArenaLo;
}

View File

@@ -0,0 +1,21 @@
#pragma once
#include "../Patch.h"
/// @brief Arm7 patch to get and update the arena low addresses for wram and main memory.
class OsGetInitArenaLoPatch : public Patch
{
public:
bool FindPatchTarget(PatchContext& patchContext) override;
void ApplyPatch(PatchContext& patchContext) override;
void* GetArm7PrivateWramArenaLo() const { return _wramBssEnd; }
void SetArm7PrivateWramArenaLo(void* wramBssEnd) { _wramBssEnd = wramBssEnd; }
void* GetMainMemoryArenaLo() const { return _mainMemoryArenaLo; }
void SetMainMemoryArenaLo(void* mainMemoryArenaLo) { _mainMemoryArenaLo = mainMemoryArenaLo; }
private:
u32* _osGetInitArenaLo = nullptr;
u32 _wramBssOffset = 0;
void* _wramBssEnd = nullptr;
void* _mainMemoryArenaLo = nullptr;
};

View File

@@ -0,0 +1,27 @@
#include "common.h"
#include "../PatchContext.h"
#include "PokemonDownloaderArm7PatchAsm.h"
#include "PokemonDownloaderArm7Patch.h"
static const u32 sBootFunctionPattern[] = { 0xE92D4070u, 0xE59F0098u, 0xE5904004u, 0xE3540000u };
bool PokemonDownloaderArm7Patch::FindPatchTarget(PatchContext& patchContext)
{
_bootFunction = patchContext.FindPattern32(sBootFunctionPattern, sizeof(sBootFunctionPattern));
return _bootFunction != nullptr;
}
void PokemonDownloaderArm7Patch::ApplyPatch(PatchContext& patchContext)
{
if (!_bootFunction)
{
return;
}
auto patchCode = patchContext.GetPatchCodeCollection().AddUniquePatchCode<PokemonDownloaderArm7PatchCode>(
patchContext.GetPatchHeap());
_bootFunction[0] = 0xE59F0000; // ldr r0,= address
_bootFunction[1] = 0xE12FFF10; // bx r0
_bootFunction[2] = (u32)patchCode->GetBoot7Function(); // address
}

View File

@@ -0,0 +1,14 @@
#pragma once
#include "../Patch.h"
#include "LoaderInfo.h"
/// @brief Arm7 patch to make the Pokemon downloader reboot into Pico Loader.
class PokemonDownloaderArm7Patch : public Patch
{
public:
bool FindPatchTarget(PatchContext& patchContext) override;
void ApplyPatch(PatchContext& patchContext) override;
private:
u32* _bootFunction = nullptr;
};

View File

@@ -0,0 +1,19 @@
#pragma once
#include "../PatchCode.h"
#include "sections.h"
DEFINE_SECTION_SYMBOLS(patch_pokemondownloader7);
extern "C" void patch_pokemondownloader7_entry(void);
class PokemonDownloaderArm7PatchCode : public PatchCode
{
public:
explicit PokemonDownloaderArm7PatchCode(PatchHeap& patchHeap)
: PatchCode(SECTION_START(patch_pokemondownloader7), SECTION_SIZE(patch_pokemondownloader7), patchHeap) { }
const void* GetBoot7Function() const
{
return GetAddressAtTarget((void*)patch_pokemondownloader7_entry);
}
};

View File

@@ -0,0 +1,25 @@
.cpu arm7tdmi
.section "patch_pokemondownloader7", "ax"
.syntax unified
.thumb
.global patch_pokemondownloader7_entry
.type patch_pokemondownloader7_entry, %function
patch_pokemondownloader7_entry:
ldr r0,= 0x04000180 // REG_IPC_SYNC
movs r1, #1
strb r1, [r0, #1]
1:
ldrb r7, [r0] // ipc sync
cmp r7, #1
bne 1b // while ipc sync from arm9 is not 1
ldr r0,= 0x06000000
ldr r0, [r0]
bx r0
.balign 4
.pool
.end

View File

@@ -0,0 +1,31 @@
#pragma once
#include "sections.h"
#include "../PatchCode.h"
#include "../SectorRemapPatchCode.h"
#include "../platform/SdReadPatchCode.h"
DEFINE_SECTION_SYMBOLS(readsave);
extern "C" void readsave_asm(u32, u32 saveSrc, void* memoryDst, u32 byteLength);
extern u32 readsave_tmpBufferPtr;
extern u32 readsave_save_offset_to_sd_sector_asm_address;
extern u32 readsave_sdread_asm_address;
class ReadSavePatchCode : public PatchCode
{
public:
ReadSavePatchCode(PatchHeap& patchHeap, const SectorRemapPatchCode* sectorRemapPatchCode,
const SdReadPatchCode* sdReadPatchCode, void* tmpBuffer)
: PatchCode(SECTION_START(readsave), SECTION_SIZE(readsave), patchHeap)
{
readsave_save_offset_to_sd_sector_asm_address = (u32)sectorRemapPatchCode->GetRemapFunction();
readsave_sdread_asm_address = (u32)sdReadPatchCode->GetSdReadFunction();
readsave_tmpBufferPtr = (u32)tmpBuffer;
}
const void* GetReadSaveFunction() const
{
return GetAddressAtTarget((void*)readsave_asm);
}
};

View File

@@ -0,0 +1,75 @@
.cpu arm7tdmi
.section "readsave", "ax"
.syntax unified
.thumb
// r1 = save src
// r2 = memory dst
// r3 = byte length
.global readsave_asm
.type readsave_asm, %function
readsave_asm:
push {r4,r5,r6,r7,lr}
movs r4, r3 // remaining bytes
movs r5, r1
movs r6, r2
1:
movs r0, r5 // will be rounded down by function
ldr r3, readsave_save_offset_to_sd_sector_asm_address
bl blx_r3
cmp r0, #0
beq end // out of bounds
ldr r1, readsave_tmpBufferPtr
movs r2, #1 // single sector
lsls r7, r2, #9 // 512
ldr r3, readsave_sdread_asm_address
bl blx_r3
// copy bytes
lsls r2, r5, #23
lsrs r2, r2, #23 // r2 = byte offset in sector
subs r0, r6, r2
subs r7, r7, r2 // remaining in sector
cmp r7, r4 // if remaining in sector > requested read length
bls 2f
movs r7, r4 // clamp to requested read length
2:
subs r4, r7
adds r5, r7
adds r6, r7
ldr r1, readsave_tmpBufferPtr
3:
ldrb r3, [r1, r2]
strb r3, [r0, r2]
adds r2, #1
subs r7, #1
bne 3b
cmp r4, #0
bne 1b
end:
pop {r4,r5,r6,r7,pc}
blx_r3:
bx r3
.balign 4
.global readsave_tmpBufferPtr
readsave_tmpBufferPtr:
.word 0
.global readsave_save_offset_to_sd_sector_asm_address
readsave_save_offset_to_sd_sector_asm_address:
.word 0
.global readsave_sdread_asm_address
readsave_sdread_asm_address:
.word 0
.pool
.end

View File

@@ -0,0 +1,25 @@
#pragma once
#include "sections.h"
#include "../SectorRemapPatchCode.h"
#include "fileInfo.h"
DEFINE_SECTION_SYMBOLS(saveoffsettosdsector);
extern "C" u32 save_offset_to_sd_sector_asm(u32 saveOffset);
extern u32 saveoffsettosdsector_fatDataPtr;
class SaveOffsetToSdSectorPatchCode : public SectorRemapPatchCode
{
public:
SaveOffsetToSdSectorPatchCode(PatchHeap& patchHeap, const save_file_info_t* fatDataPtr)
: SectorRemapPatchCode(SECTION_START(saveoffsettosdsector), SECTION_SIZE(saveoffsettosdsector), patchHeap)
{
saveoffsettosdsector_fatDataPtr = (u32)fatDataPtr;
}
const void* GetRemapFunction() const override
{
return GetAddressAtTarget((void*)save_offset_to_sd_sector_asm);
}
};

View File

@@ -0,0 +1,50 @@
.cpu arm7tdmi
.section "saveoffsettosdsector", "ax"
.syntax unified
.thumb
// r0 = save offset
// returns sd sector in r0
// returns cluster shift in r1
// returns remaining sectors in cluster in lr
.global save_offset_to_sd_sector_asm
.type save_offset_to_sd_sector_asm, %function
save_offset_to_sd_sector_asm:
push {r4,r5,lr}
retry:
ldr r5, saveoffsettosdsector_fatDataPtr
ldmia r5!, {r1,r2,r3,r4} // clusterShift, database, clusterMask, clusterMap[0]
lsrs r0, r0, #9
adds r4, r3, #1
ands r3, r0
subs r4, r3
mov lr, r4 // remaining sectors in cluster
adds r3, r2
lsrs r0, r1 // cl
1:
ldmia r5!, {r2,r4} // ncl, startSector
cmp r2, #0
beq out_of_bounds
subs r0, r2 // cl -= ncl
bcs 1b
adds r0, r2
adds r0, r4 // + startSector
subs r0, #2 // - 2
lsls r0, r1
adds r0, r3 // r0 = src sector
pop {r4,r5,pc}
out_of_bounds:
movs r0, #0
pop {r4,r5,pc}
.balign 4
.global saveoffsettosdsector_fatDataPtr
saveoffsettosdsector_fatDataPtr:
.word 0x027FFA00
.pool
.end

View File

@@ -0,0 +1,34 @@
#pragma once
#include "sections.h"
#include "../PatchCode.h"
#include "../SectorRemapPatchCode.h"
#include "../platform/SdReadPatchCode.h"
#include "../platform/SdWritePatchCode.h"
DEFINE_SECTION_SYMBOLS(writesave);
extern "C" void writesave_asm(u32, const void* memorySrc, u32 saveDst, u32 byteLength);
extern u32 writesave_tmpBufferPtr;
extern u32 writesave_save_offset_to_sd_sector_asm_address;
extern u32 writesave_sdread_asm_address;
extern u32 writesave_sdwrite_asm_address;
class WriteSavePatchCode : public PatchCode
{
public:
WriteSavePatchCode(PatchHeap& patchHeap, const SectorRemapPatchCode* sectorRemapPatchCode,
const SdReadPatchCode* sdReadPatchCode, const SdWritePatchCode* sdWritePatchCode, void* tmpBuffer)
: PatchCode(SECTION_START(writesave), SECTION_SIZE(writesave), patchHeap)
{
writesave_save_offset_to_sd_sector_asm_address = (u32)sectorRemapPatchCode->GetRemapFunction();
writesave_sdread_asm_address = (u32)sdReadPatchCode->GetSdReadFunction();
writesave_sdwrite_asm_address = (u32)sdWritePatchCode->GetSdWriteFunction();
writesave_tmpBufferPtr = (u32)tmpBuffer;
}
const void* GetWriteSaveFunction() const
{
return GetAddressAtTarget((void*)writesave_asm);
}
};

View File

@@ -0,0 +1,86 @@
.cpu arm7tdmi
.section "writesave", "ax"
.syntax unified
.thumb
// r1 = memory src
// r2 = save dst
// r3 = byte length
.global writesave_asm
.type writesave_asm, %function
writesave_asm:
push {r4,r5,r6,r7,lr}
movs r4, r3 // remaining bytes
movs r5, r2
movs r6, r1
1:
movs r0, r5 // will be rounded down by function
ldr r3, writesave_save_offset_to_sd_sector_asm_address
bl blx_r3
cmp r0, #0
beq end // out of bounds
push {r0}
ldr r1, writesave_tmpBufferPtr
movs r2, #1 // single sector
lsls r7, r2, #9 // 512
ldr r3, writesave_sdread_asm_address
bl blx_r3
// copy bytes
lsls r2, r5, #23
lsrs r2, r2, #23 // r2 = byte offset in sector
subs r0, r6, r2
subs r7, r7, r2 // remaining in sector
cmp r7, r4 // if remaining in sector > requested read length
bls 2f
movs r7, r4 // clamp to requested read length
2:
subs r4, r7
adds r5, r7
adds r6, r7
ldr r1, writesave_tmpBufferPtr
3:
ldrb r3, [r0, r2]
strb r3, [r1, r2]
adds r2, #1
subs r7, #1
bne 3b
pop {r0}
movs r2, #1 // single sector
ldr r3, writesave_sdwrite_asm_address
bl blx_r3
cmp r4, #0
bne 1b
end:
pop {r4,r5,r6,r7,pc}
blx_r3:
bx r3
.balign 4
.global writesave_tmpBufferPtr
writesave_tmpBufferPtr:
.word 0
.global writesave_save_offset_to_sd_sector_asm_address
writesave_save_offset_to_sd_sector_asm_address:
.word 0
.global writesave_sdread_asm_address
writesave_sdread_asm_address:
.word 0
.global writesave_sdwrite_asm_address
writesave_sdwrite_asm_address:
.word 0
.pool
.end

View File

@@ -0,0 +1,285 @@
#include "common.h"
#include <algorithm>
#include <array>
#include "fileInfo.h"
#include "patches/PatchContext.h"
#include "patches/arm7/ReadSaveAsm.h"
#include "patches/arm7/WriteSaveAsm.h"
#include "patches/FunctionSignature.h"
#include "patches/platform/LoaderPlatform.h"
#include "patches/arm7/SaveOffsetToSdSectorAsm.h"
#include "patches/OffsetToSectorRemapAsm.h"
#include "patches/arm7/CardiTaskThreadPatchAsm.h"
#include "thumbInstructions.h"
#include "CardiTaskThreadPatch.h"
static constexpr auto sSignaturesArm = std::to_array<const FunctionSignature>
({
{ (const u32[]) { 0xE92D4FF0u, 0xE24DD004u, 0xE59FA108u, 0xE28A5040u }, 0x020029A0, 0x02005015 },
{ (const u32[]) { 0xE92D4FF0u, 0xE24DD004u, 0xE59FA10Cu, 0xE28A5040u }, 0x02004F50, 0x02017532 },
{ (const u32[]) { 0xE92D4FF0u, 0xE24DD004u, 0xE59F91DCu, 0xE2895044u }, 0x02017532, 0x02017532 },
{ (const u32[]) { 0xE92D4FF0u, 0xE24DD004u, 0xE59F91E0u, 0xE2895044u }, 0x02017532, 0x02027534 },
{ (const u32[]) { 0xE92D4FF0u, 0xE24DD004u, 0xE59F91E0u, 0xE2895048u }, 0x030028A0, 0x03004E86 },
{ (const u32[]) { 0xE92D4FF0u, 0xE24DD004u, 0xE59F91D4u, 0xE2895048u }, 0x03004F4C, 0x03017534 },
{ (const u32[]) { 0xE92D4FF0u, 0xE24DD004u, 0xE59F91F8u, 0xE2895048u }, 0x03017530, 0x04017530 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFEADCu }, 0x04002774, 0x04017533 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFEACEu }, 0x04007531, 0x04007531 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFEABFu }, 0x04007530, 0x04007531 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFEAAAu }, 0x04017530, 0x04017533 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFEA94u }, 0x04017530, 0x04017533 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFEA8Du }, 0x04017531, 0x04017533 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFEA77u }, 0x04017533, 0x04017533 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4224u, 0xE3A05000u, 0xEBFFEA70u }, 0x04027530, 0x04027539 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4224u, 0xE3A05000u, 0xEBFFEA8Du }, 0x04027530, 0x04027539 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4224u, 0xE3A05000u, 0xEBFFEA6Cu }, 0x04027531, 0x04027531 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4224u, 0xE3A05000u, 0xEBFFEA77u }, 0x04027531, 0x04027539 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4224u, 0xE3A05000u, 0xEBFFEA5Au }, 0x04027533, 0x04027533 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFFAD7u }, 0x03017531, 0x03017531 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFFAA5u }, 0x03027531, 0x03027531 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4200u, 0xE3A05000u, 0xEBFFEADBu }, 0x03027531, 0x03027531 },
{ (const u32[]) { 0xE92D41F0u, 0xE59F4224u, 0xE3A05000u, 0xEBFFEA68u }, 0x04027533, 0x04027533 }
});
static constexpr auto sSignaturesThumb = std::to_array<const FunctionSignature>
({
{ (const u32[]) { 0xB081B5F0u, 0x1C2E4D2Du, 0x27103640u, 0xF7FC2400u }, 0x02004FB0, 0x02004FB4 },
{ (const u32[]) { 0xB081B5F0u, 0x1C2E4D2Bu, 0x27103640u, 0xF7FC2400u }, 0x02007531, 0x02017532 },
{ (const u32[]) { 0xB081B5F0u, 0x1C264C57u, 0x27103644u, 0xF7FC2500u }, 0x02027532, 0x02027535 },
{ (const u32[]) { 0xB081B5F0u, 0x1C264C55u, 0x27103648u, 0xF7FC2500u }, 0x03007530, 0x03012776 },
{ (const u32[]) { 0xB081B5F0u, 0x1C264C5Cu, 0x27033648u, 0xF7FC2500u }, 0x03017531, 0x03027532 },
{ (const u32[]) { 0x4C5CB5F8u, 0xF7FC2500u, 0x1C26FAB3u, 0x36481C07u }, 0x04007531, 0x04007531 },
{ (const u32[]) { 0x4C5CB5F8u, 0xF7FC2500u, 0x1C26FA97u, 0x36481C07u }, 0x04007531, 0x04007531 },
{ (const u32[]) { 0x4C5CB5F8u, 0xF7FC2500u, 0x1C26FA4Fu, 0x36481C07u }, 0x04017530, 0x04017533 },
{ (const u32[]) { 0x4C5CB5F8u, 0xF7FC2500u, 0x1C26FA23u, 0x36481C07u }, 0x04017531, 0x04017531 },
{ (const u32[]) { 0x4D62B5F8u, 0xF7FC2400u, 0x1C2EFA2Du, 0x36481C07u }, 0x04027531, 0x04027539 }
});
bool CardiTaskThreadPatch::FindPatchTarget(PatchContext& patchContext)
{
for (const auto& signature : sSignaturesArm)
{
if (CheckSignature(patchContext, signature))
{
if (signature.GetPattern()[2] == 0xE59F91DCu)
{
_peach = true;
}
else if (signature.GetPattern()[3] == 0xEBFFFAD7u
|| signature.GetPattern()[3] == 0xEBFFFAA5u
|| signature.GetPattern()[3] == 0xEBFFEADBu)
{
_pokemonDownloader = true;
}
break;
}
}
if (!_cardiTaskThread)
{
for (const auto& signature : sSignaturesThumb)
{
if (CheckSignature(patchContext, signature))
{
_thumb = true;
break;
}
}
}
if (!_cardiTaskThread)
{
LOG_FATAL("CARDi_TaskThread not found\n");
}
return _cardiTaskThread != nullptr;
}
void CardiTaskThreadPatch::ApplyPatch(PatchContext& patchContext)
{
if (!_cardiTaskThread)
return;
//0xA4 = ADDLS PC, PC, R0,LSL#2
u32 patch1Size = SECTION_SIZE(patch_carditaskthread);
void* patch1Address = patchContext.GetPatchHeap().Alloc(patch1Size);
auto loaderPlatform = patchContext.GetLoaderPlatform();
const SdReadPatchCode* readPatchCode;
const SdWritePatchCode* writePatchCode;
const SectorRemapPatchCode* sectorRemapPatchCode;
readPatchCode = loaderPlatform->CreateSdReadPatchCode(
patchContext.GetPatchCodeCollection(), patchContext.GetPatchHeap());
writePatchCode = loaderPlatform->CreateSdWritePatchCode(
patchContext.GetPatchCodeCollection(), patchContext.GetPatchHeap());
sectorRemapPatchCode = patchContext.GetPatchCodeCollection().AddUniquePatchCode<SaveOffsetToSdSectorPatchCode>
(
patchContext.GetPatchHeap(),
(const save_file_info_t*)((u32)SHARED_SAVE_FILE_INFO - 0x02F00000 + 0x02700000)
);
void* tmpBuffer = patchContext.GetPatchHeap().Alloc(512);
auto readSavePatchCode = patchContext.GetPatchCodeCollection().AddUniquePatchCode<ReadSavePatchCode>
(
patchContext.GetPatchHeap(),
sectorRemapPatchCode,
readPatchCode,
tmpBuffer
);
auto writeSavePatchCode = patchContext.GetPatchCodeCollection().AddUniquePatchCode<WriteSavePatchCode>
(
patchContext.GetPatchHeap(),
sectorRemapPatchCode,
readPatchCode,
writePatchCode,
tmpBuffer
);
__patch_carditaskthread_readsave_asm_address = (u32)readSavePatchCode->GetReadSaveFunction();
__patch_carditaskthread_writesave_asm_address = (u32)writeSavePatchCode->GetWriteSaveFunction();
u32 entryAddress;
u32 patchOffset;
if (_thumb)
{
if (patchContext.GetSdkVersion() >= 0x4027531)
{
patchOffset = 0x90;
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_MOVS_REG(THUMB_R4, THUMB_R5);
__patch_carditaskthread_successoffset = *(u16*)((u8*)_cardiTaskThread + patchOffset + 0x0C) + 9;
__patch_carditaskthread_failoffset = *(u16*)((u8*)_cardiTaskThread + patchOffset + 0x14) + 9;
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x00) = THUMB_LDR_PC_IMM(THUMB_R1, 4);
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x02) = THUMB_MOV_HIREG(THUMB_HI_LR, THUMB_HI_PC);
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x04) = THUMB_BX(THUMB_R1);
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x08) = entryAddress;
}
else if (patchContext.GetSdkVersion() >= 0x4007530)
{
patchOffset = 0x90;
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_NOP;
__patch_carditaskthread_successoffset = *(u16*)((u8*)_cardiTaskThread + patchOffset + 0x0C) + 9;
__patch_carditaskthread_failoffset = *(u16*)((u8*)_cardiTaskThread + patchOffset + 0x14) + 9;
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x00) = THUMB_LDR_PC_IMM(THUMB_R1, 4);
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x02) = THUMB_MOV_HIREG(THUMB_HI_LR, THUMB_HI_PC);
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x04) = THUMB_BX(THUMB_R1);
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x08) = entryAddress;
}
else if (patchContext.GetSdkVersion() >= 0x3027531)
{
patchOffset = 0x98;
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_NOP;
__patch_carditaskthread_successoffset = *(u16*)((u8*)_cardiTaskThread + patchOffset + 0x0E) + 8;
__patch_carditaskthread_failoffset = *(u16*)((u8*)_cardiTaskThread + patchOffset + 0x18) + 8;
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x00) = THUMB_LDR_PC_IMM(THUMB_R1, 4);
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x02) = THUMB_MOV_HIREG(THUMB_HI_LR, THUMB_HI_PC);
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x04) = THUMB_BX(THUMB_R1);
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x08) = entryAddress;
}
else if (patchContext.GetSdkVersion() >= 0x3012776
|| (patchContext.GetSdkVersion() >= 0x2027532 && patchContext.GetSdkVersion() <= 0x2027535))
{
patchOffset = 0x82;
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_NOP;
__patch_carditaskthread_successoffset = *(u16*)((u8*)_cardiTaskThread + patchOffset + 0x0E) + 8;
__patch_carditaskthread_failoffset = *(u16*)((u8*)_cardiTaskThread + patchOffset + 0x18) + 8;
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x00) = THUMB_LDR_PC_IMM(THUMB_R1, 4);
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x02) = THUMB_MOV_HIREG(THUMB_HI_LR, THUMB_HI_PC);
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x04) = THUMB_BX(THUMB_R1);
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x06) = entryAddress;
}
else if (patchContext.GetSdkVersion() >= 0x2004FB0)
{
patchOffset = patchContext.GetSdkVersion() >= 0x2007531 ? 0x60 : 0x64;
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_MOVS_REG(THUMB_R4, THUMB_R5);
__patch_carditaskthread_failoffset = 0;
__patch_carditaskthread_successoffset = 0;
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x00) = THUMB_LDR_PC_IMM(THUMB_R1, 0);
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x02) = 0xE001; // jump over entryAddress
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x04) = entryAddress;
*(u16*)((u8*)_cardiTaskThread + patchOffset + 0x08) = THUMB_NOP;
}
else
{
LOG_FATAL("Unsupported thumb CARDi_TaskThread\n");
while(1);
}
}
else
{
if (patchContext.GetSdkVersion() < 0x2020000 && !_peach)
{
// uses a table dispatch
//0xA4 = ldr r1,= table
__patch_carditaskthread_failoffset = 4;
__patch_carditaskthread_successoffset = 4;
if (patchContext.GetSdkVersion() >= 0x2005015)//0x2007532)//0x2012774)
patchOffset = 0xA4;
else
patchOffset = 0xA0;
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_MOV_HIREG(THUMB_R4, THUMB_HI_R10);
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x00) = 0xe59f0004; // ldr r0,= entryAddress
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x04) = 0xe1a0e00f; // mov lr, pc
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x08) = 0xe12fff10; // bx r0
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x0C) = entryAddress;
}
else
{
if (patchContext.GetSdkVersion() >= 0x4027531)
{
patchOffset = 0xA8;
entryAddress = (u32)&__patch_carditaskthread_entry_sdk4 - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_NOP;
}
else if (patchContext.GetSdkVersion() >= 0x4007530 || _pokemonDownloader)
{
patchOffset = 0xA8;
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_NOP;
}
else if (patchContext.GetSdkVersion() >= 0x3017531)
{
patchOffset = 0xB4;
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_MOV_HIREG(THUMB_R4, THUMB_HI_R9);
}
else
{
patchOffset = 0x9C;
entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_MOV_HIREG(THUMB_R4, THUMB_HI_R9);
}
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x00) = 0xe59f000C; // ldr r0,= entryAddress
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x04) = 0xe1a0e00f; // mov lr, pc
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x08) = 0xe12fff10; // bx r0
*(u32*)((u8*)_cardiTaskThread + patchOffset + 0x14) = entryAddress;
}
}
memcpy(patch1Address, SECTION_START(patch_carditaskthread), patch1Size);
}
bool CardiTaskThreadPatch::CheckSignature(const PatchContext& patchContext, const FunctionSignature& signature)
{
if (patchContext.GetSdkVersion() >= signature.GetMinimumSdkVersion() &&
patchContext.GetSdkVersion() <= signature.GetMaximumSdkVersion())
{
_cardiTaskThread = patchContext.FindPattern32(signature.GetPattern(), 16);
if (_cardiTaskThread)
{
LOG_DEBUG("CARDi_TaskThread found at 0x%p\n", _cardiTaskThread);
return true;
}
}
return false;
}

View File

@@ -0,0 +1,20 @@
#pragma once
#include "patches/Patch.h"
class FunctionSignature;
/// @brief Arm7 patch for redirecting save reads and writes on SDK 2-4.
class CardiTaskThreadPatch : public Patch
{
public:
bool FindPatchTarget(PatchContext& patchContext) override;
void ApplyPatch(PatchContext& patchContext) override;
private:
u32* _cardiTaskThread = nullptr;
u16 _thumb = false;
u16 _peach = false;
u16 _pokemonDownloader = false;
bool CheckSignature(const PatchContext& patchContext, const FunctionSignature& signature);
};

View File

@@ -0,0 +1,139 @@
#include "common.h"
#include "patches/PatchContext.h"
#include "thumbInstructions.h"
#include "fileInfo.h"
#include "patches/arm7/ReadSaveAsm.h"
#include "patches/arm7/WriteSaveAsm.h"
#include "patches/arm7/SaveOffsetToSdSectorAsm.h"
#include "patches/platform/LoaderPlatform.h"
#include "patches/OffsetToSectorRemapAsm.h"
#include "patches/arm7/CardiTaskThreadPatchAsm.h"
#include "CardiDoTaskFromArm9Patch.h"
static const u32 sCARDiDoTaskFromARM9Pattern5007538[] = { 0xE92D4038u, 0xE59F4190u, 0xE3A01001u, 0xE5943000u }; // this one has branches instead of an offset table
static const u32 sCARDiDoTaskFromARM9Pattern5017537[] = { 0xE92D4070u, 0xE59F5168u, 0xE3A01001u, 0xE5953000u };
static const u32 sCARDiDoTaskFromARM9Pattern5057530[] = { 0xE92D4070u, 0xE59F5174u, 0xE3A01001u, 0xE5953000u };
static const u32 sCARDiDoTaskFromARM9Pattern5017537Thumb[] = { 0x4C3FB538u, 0x68214A3Fu, 0x23016E92u, 0x40936D88u };
static const u32 sCARDiDoTaskFromARM9Pattern5037531Thumb[] = { 0x4C3EB538u, 0x68214A3Eu, 0x23016E92u, 0x40936D88u };
static const u32 sCARDiDoTaskFromARM9Pattern5057530Thumb[] = { 0x4C40B538u, 0x68214A40u, 0x23016E92u, 0x40936D88u };
void CardiDoTaskFromArm9Patch::TryPattern(PatchContext& patchContext, const u32* pattern)
{
_cardiDoTaskFromArm9 = patchContext.FindPattern32(pattern, 16);
if (_cardiDoTaskFromArm9)
{
_foundPattern = pattern;
}
}
bool CardiDoTaskFromArm9Patch::FindPatchTarget(PatchContext& patchContext)
{
if (patchContext.GetSdkVersion() <= 0x5027535)
TryPattern(patchContext, sCARDiDoTaskFromARM9Pattern5007538);
if (!_cardiDoTaskFromArm9 && patchContext.GetSdkVersion() >= 0x5017537 && patchContext.GetSdkVersion() <= 0x5057534)
TryPattern(patchContext, sCARDiDoTaskFromARM9Pattern5017537);
if (!_cardiDoTaskFromArm9 && patchContext.GetSdkVersion() >= 0x5057530)
TryPattern(patchContext, sCARDiDoTaskFromARM9Pattern5057530);
if (!_cardiDoTaskFromArm9)
{
TryPattern(patchContext, sCARDiDoTaskFromARM9Pattern5057530Thumb);
if (!_cardiDoTaskFromArm9)
TryPattern(patchContext, sCARDiDoTaskFromARM9Pattern5037531Thumb);
if (!_cardiDoTaskFromArm9)
TryPattern(patchContext, sCARDiDoTaskFromARM9Pattern5017537Thumb);
if (_cardiDoTaskFromArm9)
{
_thumb = true;
}
}
if (_cardiDoTaskFromArm9)
{
LOG_DEBUG("CARDi_DoTaskFromARM9 found at 0x%p\n", _cardiDoTaskFromArm9);
}
return _cardiDoTaskFromArm9 != nullptr;
}
void CardiDoTaskFromArm9Patch::ApplyPatch(PatchContext& patchContext)
{
if (!_cardiDoTaskFromArm9)
return;
u32 patch1Size = SECTION_SIZE(patch_carditaskthread);
void* patch1Address = patchContext.GetPatchHeap().Alloc(patch1Size);
auto loaderPlatform = patchContext.GetLoaderPlatform();
auto readPatchCode = loaderPlatform->CreateSdReadPatchCode(
patchContext.GetPatchCodeCollection(), patchContext.GetPatchHeap());
auto writePatchCode = loaderPlatform->CreateSdWritePatchCode(
patchContext.GetPatchCodeCollection(), patchContext.GetPatchHeap());
auto sectorRemapPatchCode = patchContext.GetPatchCodeCollection().AddUniquePatchCode<SaveOffsetToSdSectorPatchCode>(
patchContext.GetPatchHeap(), SHARED_SAVE_FILE_INFO);
void* tmpBuffer = patchContext.GetPatchHeap().Alloc(512);
auto readSavePatchCode = patchContext.GetPatchCodeCollection().AddUniquePatchCode<ReadSavePatchCode>(
patchContext.GetPatchHeap(),
sectorRemapPatchCode,
readPatchCode,
tmpBuffer
);
auto writeSavePatchCode = patchContext.GetPatchCodeCollection().AddUniquePatchCode<WriteSavePatchCode>(
patchContext.GetPatchHeap(),
sectorRemapPatchCode,
readPatchCode,
writePatchCode,
tmpBuffer
);
__patch_carditaskthread_readsave_asm_address = (u32)readSavePatchCode->GetReadSaveFunction();
__patch_carditaskthread_writesave_asm_address = (u32)writeSavePatchCode->GetWriteSaveFunction();
u32 patchOffset;
if (_thumb)
{
patchOffset = _foundPattern == sCARDiDoTaskFromARM9Pattern5057530Thumb ? 0x3A : 0x36;
u32 entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_NOP;
__patch_carditaskthread_mov_command_to_r0 = THUMB_NOP;
__patch_carditaskthread_successoffset = *(u16*)((u8*)_cardiDoTaskFromArm9 + patchOffset + 0x0C) + 9;
__patch_carditaskthread_failoffset = *(u16*)((u8*)_cardiDoTaskFromArm9 + patchOffset + 0x14) + 9;
*(u16*)((u8*)_cardiDoTaskFromArm9 + patchOffset + 0x00) = THUMB_LDR_PC_IMM(THUMB_R1, 4);
*(u16*)((u8*)_cardiDoTaskFromArm9 + patchOffset + 0x02) = THUMB_MOV_HIREG(THUMB_HI_LR, THUMB_HI_PC);
*(u16*)((u8*)_cardiDoTaskFromArm9 + patchOffset + 0x04) = THUMB_BX(THUMB_R1);
*(u32*)((u8*)_cardiDoTaskFromArm9 + patchOffset + 0x06) = entryAddress;
}
else
{
if (_foundPattern == sCARDiDoTaskFromARM9Pattern5007538)
{
// 0x4C = cmp r0, #0xF
patchOffset = 0x4C;
__patch_carditaskthread_successoffset = 4;
__patch_carditaskthread_failoffset = ((*(u32*)((u8*)_cardiDoTaskFromArm9 + patchOffset + 0x08) & 0xFFFFFF) << 2) + 4;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_NOP; // already in r4
__patch_carditaskthread_mov_command_to_r0 = THUMB_NOP;
}
else
{
// 0x58 = add r0, pc, r0, lsl #1
if (_foundPattern == sCARDiDoTaskFromARM9Pattern5057530)
patchOffset = 0x58;
else
patchOffset = 0x54;
__patch_carditaskthread_successoffset = *(u16*)((u8*)_cardiDoTaskFromArm9 + patchOffset + 0x0C) + 4;
__patch_carditaskthread_failoffset = *(u16*)((u8*)_cardiDoTaskFromArm9 + patchOffset + 0x14) + 4;
__patch_carditaskthread_mov_cardicommon_to_r4 = THUMB_MOVS_REG(THUMB_R4, THUMB_R5);
__patch_carditaskthread_mov_command_to_r0 = THUMB_NOP;
}
u32 entryAddress = (u32)&__patch_carditaskthread_entry - (u32)SECTION_START(patch_carditaskthread) + (u32)patch1Address;
*(u32*)((u8*)_cardiDoTaskFromArm9 + patchOffset + 0x00) = 0xe59f1004; // ldr r1,= entryAddress
*(u32*)((u8*)_cardiDoTaskFromArm9 + patchOffset + 0x04) = 0xe1a0e00f; // mov lr, pc
*(u32*)((u8*)_cardiDoTaskFromArm9 + patchOffset + 0x08) = 0xe12fff11; // bx r1
*(u32*)((u8*)_cardiDoTaskFromArm9 + patchOffset + 0x0C) = entryAddress;
}
memcpy(patch1Address, SECTION_START(patch_carditaskthread), patch1Size);
}

View File

@@ -0,0 +1,17 @@
#pragma once
#include "patches/Patch.h"
/// @brief Arm7 patch for redirecting save reads and writes on SDK 5.
class CardiDoTaskFromArm9Patch : public Patch
{
public:
bool FindPatchTarget(PatchContext& patchContext) override;
void ApplyPatch(PatchContext& patchContext) override;
private:
u32* _cardiDoTaskFromArm9 = nullptr;
const u32* _foundPattern = nullptr;
u16 _thumb = false;
void TryPattern(PatchContext& patchContext, const u32* pattern);
};

View File

@@ -0,0 +1,14 @@
#pragma once
#include "sections.h"
DEFINE_SECTION_SYMBOLS(patch_cardidotaskfromarm9);
extern "C" void __patch_cardidotaskfromarm9_entry();
extern u32 __patch_cardidotaskfromarm9_failoffset;
extern u32 __patch_cardidotaskfromarm9_successoffset;
extern u16 __patch_cardidotaskfromarm9_move_statereg;
extern u32 __patch_cardidotaskfromarm9_readsave_asm_address;
extern u32 __patch_cardidotaskfromarm9_writesave_asm_address;

View File

@@ -0,0 +1,82 @@
#include "common.h"
#include "patches/platform/LoaderPlatform.h"
#include "Sdk5DsiSdCardRedirectPatchAsm.h"
#include "Sdk5DsiSdCardRedirectPatch.h"
static const u32 sAttachFunctionPattern[] = { 0xE92D4018u, 0xE24DDF5Du, 0xE24DDB01u, 0xE59FE050u };
static const u32 sAttachFunctionPatternThumb[] = { 0xB0FFB518u, 0xB0DFB0FFu, 0x4A0E490Du, 0x64CA4469u };
bool Sdk5DsiSdCardRedirectPatch::FindPatchTarget(PatchContext& patchContext)
{
_attachFunction = patchContext.FindPattern32Twl(sAttachFunctionPattern, sizeof(sAttachFunctionPattern));
if (!_attachFunction)
{
_attachFunction = patchContext.FindPattern32Twl(sAttachFunctionPatternThumb, sizeof(sAttachFunctionPatternThumb));
if (_attachFunction)
{
_thumb = true;
}
}
if (_attachFunction)
{
LOG_DEBUG("Found FATFSi_sdmcRtfsAttach at %p\n", _attachFunction);
}
else
{
LOG_WARNING("FATFSi_sdmcRtfsAttach not found\n");
}
return _attachFunction != nullptr;
}
static u32 getArmBlAddress(const u32* instructionPointer)
{
u32 blInstruction = *instructionPointer;
return (u32)instructionPointer + 8 + ((int)((blInstruction & 0xFFFFFF) << 8) >> 6);
}
static u32 getThumbBlAddress(const u32* instructionPointer)
{
u32 blInstruction1 = ((u16*)instructionPointer)[0];
u32 blInstruction2 = ((u16*)instructionPointer)[1];
return (u32)instructionPointer + 5 + ((int)((((blInstruction1 & 0x7FF) << 11) | (blInstruction2 & 0x7FF)) << 10) >> 9);
}
void Sdk5DsiSdCardRedirectPatch::ApplyPatch(PatchContext& patchContext)
{
if (!_attachFunction)
return;
u32 getDriveStructAddress;
if (_thumb)
{
getDriveStructAddress = getThumbBlAddress((u32*)((u8*)_attachFunction - 0x20));
}
else
{
getDriveStructAddress = getArmBlAddress((u32*)((u8*)_attachFunction - 0x20));
}
auto sdRead = patchContext.GetLoaderPlatform()->CreateSdReadPatchCode(
patchContext.GetPatchCodeCollection(), patchContext.GetPatchHeap());
auto sdWrite = patchContext.GetLoaderPlatform()->CreateSdWritePatchCode(
patchContext.GetPatchCodeCollection(), patchContext.GetPatchHeap());
auto patch = patchContext.GetPatchCodeCollection().AddUniquePatchCode<Sdk5DsiSdCardRedirectPatchCode>
(
patchContext.GetPatchHeap(),
sdRead,
sdWrite,
getDriveStructAddress
);
if (_thumb)
{
*(u32*)((u8*)_attachFunction + 0x44) = (u32)patch->GetIoFunction();
*(u32*)((u8*)_attachFunction + 0x48) = (u32)patch->GetControlFunction();
}
else
{
*(u32*)((u8*)_attachFunction + 0x64) = (u32)patch->GetIoFunction();
*(u32*)((u8*)_attachFunction + 0x68) = (u32)patch->GetControlFunction();
}
}

View File

@@ -0,0 +1,14 @@
#pragma once
#include "patches/Patch.h"
/// @brief Arm7 patch for redirecting access to the DSi sd card to the flashcard sd card.
class Sdk5DsiSdCardRedirectPatch : public Patch
{
public:
bool FindPatchTarget(PatchContext& patchContext) override;
void ApplyPatch(PatchContext& patchContext) override;
private:
u32* _attachFunction = nullptr;
u16 _thumb = false;
};

View File

@@ -0,0 +1,37 @@
#pragma once
#include "patches/PatchCode.h"
#include "sections.h"
#include "patches/platform/SdReadPatchCode.h"
#include "patches/platform/SdWritePatchCode.h"
DEFINE_SECTION_SYMBOLS(patch_dsisdredirect);
extern "C" bool __patch_dsisdredirect_io(u32 driveNumber, u32 startSector, void* buffer, u32 sectorCount, bool isRead);
extern "C" u32 __patch_dsisdredirect_control(u32 driveNumber, u32 command, void* argumentBuffer);
extern u32 __patch_dsisdredirect_io_readsd_asm_address;
extern u32 __patch_dsisdredirect_io_writesd_asm_address;
extern u32 __patch_dsisdredirect_control_get_drive_struct_address;
class Sdk5DsiSdCardRedirectPatchCode : public PatchCode
{
public:
Sdk5DsiSdCardRedirectPatchCode(PatchHeap& patchHeap, const SdReadPatchCode* sdReadPatchCode,
const SdWritePatchCode* sdWritePatchCode, u32 getDriveStructAddress)
: PatchCode(SECTION_START(patch_dsisdredirect), SECTION_SIZE(patch_dsisdredirect), patchHeap)
{
__patch_dsisdredirect_io_readsd_asm_address = (u32)sdReadPatchCode->GetSdReadFunction();
__patch_dsisdredirect_io_writesd_asm_address = (u32)sdWritePatchCode->GetSdWriteFunction();
__patch_dsisdredirect_control_get_drive_struct_address = getDriveStructAddress;
}
const void* GetIoFunction() const
{
return GetAddressAtTarget((void*)__patch_dsisdredirect_io);
}
const void* GetControlFunction() const
{
return GetAddressAtTarget((void*)__patch_dsisdredirect_control);
}
};

View File

@@ -0,0 +1,80 @@
.cpu arm7tdmi
.section "patch_dsisdredirect", "ax"
.syntax unified
.thumb
// r0 = drive number
// r1 = start sector
// r2 = source/destination buffer
// r3 = sector count
// [sp] = isReading (0 = write, otherwise read)
.global __patch_dsisdredirect_io
.type __patch_dsisdredirect_io, %function
__patch_dsisdredirect_io:
push {r4,lr}
movs r0, r1
movs r1, r2
movs r2, r3
ldr r4, [sp, #8] // reading
cmp r4, #0
bne 1f
ldr r3, __patch_dsisdredirect_io_writesd_asm_address
bl blx_r3
movs r0, #1 // success
b return
1:
ldr r3, __patch_dsisdredirect_io_readsd_asm_address
bl blx_r3
movs r0, #1 // success
return:
pop {r4}
pop {r3}
blx_r3:
bx r3
// r0 = drive number
// r1 = command
// r2 = argument buffer
.global __patch_dsisdredirect_control
.type __patch_dsisdredirect_control, %function
__patch_dsisdredirect_control:
push {lr}
cmp r1, #1 // startup
bne returnZero
ldr r3, __patch_dsisdredirect_control_get_drive_struct_address
bl blx_r3
ldr r1,= 0x4B4
movs r2, #0
str r2, [r0, r1] // partition 0
subs r1, r1, #4
ldr r2, [r0, r1]
movs r3, #0x83 // valid, partitioned, inserted
orrs r2, r2, r3
str r2, [r0, r1]
returnZero:
movs r0, #0
pop {r3}
bx r3
.balign 4
.global __patch_dsisdredirect_io_readsd_asm_address
__patch_dsisdredirect_io_readsd_asm_address:
.word 0
.global __patch_dsisdredirect_io_writesd_asm_address
__patch_dsisdredirect_io_writesd_asm_address:
.word 0
.global __patch_dsisdredirect_control_get_drive_struct_address
__patch_dsisdredirect_control_get_drive_struct_address:
.word 0
.pool
.end

View File

@@ -0,0 +1,142 @@
#include "common.h"
#include "thumbInstructions.h"
#include "../PatchContext.h"
#include "../FunctionSignature.h"
#include "CardiReadRomIdCorePatchAsm.h"
#include "CardiReadRomIdCorePatch.h"
static constexpr auto sSignaturesArm = std::to_array<const FunctionSignature>
({
{ (const u32[]) { 0xE92D4000u, 0xE24DD004u, 0xE3A0032Eu, 0xE3A01000u }, 0x02004F50, 0x04017531 },
{ (const u32[]) { 0xE92D4008u, 0xE3A0032Eu, 0xE3A01000u, 0xEBFFFF0Bu }, 0x03007532, 0x03017531 },
{ (const u32[]) { 0xE92D4008u, 0xE3A0032Eu, 0xE3A01000u, 0xEBFFFF08u }, 0x03017530, 0x03017534 },
{ (const u32[]) { 0xE92D4008u, 0xE3A0032Eu, 0xE3A01000u, 0xEBFFFF07u }, 0x03027530, 0x04007531 },
{ (const u32[]) { 0xE92D4008u, 0xE3A0032Eu, 0xE3A01000u, 0xEBFFFEF2u }, 0x04007531, 0x04007531 },
{ (const u32[]) { 0xE92D4008u, 0xE3A0032Eu, 0xE3A01000u, 0xEBFFFEFDu }, 0x04007532, 0x04007532 },
{ (const u32[]) { 0xE92D4008u, 0xE3A0032Eu, 0xE3A01000u, 0xEBFFFEFBu }, 0x04017530, 0x04027539 },
{ (const u32[]) { 0xE92D4008u, 0xE3A0032Eu, 0xE3A01000u, 0xEBFFFEE6u }, 0x04017533, 0x04017533 },
{ (const u32[]) { 0xE92D4010u, 0xE3A04000u, 0xE1A01004u, 0xE3A0032Eu }, 0x04027537, 0x04027539 }
});
static constexpr auto sSignaturesThumb = std::to_array<const FunctionSignature>
({
{ (const u32[]) { 0xB081B500u, 0x2100480Bu, 0xF98AF000u, 0x6801480Au }, 0x03007530, 0x03012776 },
{ (const u32[]) { 0xB081B500u, 0x2100480Bu, 0xF990F000u, 0x6801480Au }, 0x03007532, 0x03007532 },
{ (const u32[]) { 0x202EB508u, 0x21000680u, 0xFE8CF7FFu, 0x68014809u }, 0x03017531, 0x03017531 },
{ (const u32[]) { 0xB081B500u, 0x2100480Bu, 0xF98EF000u, 0x6801480Au }, 0x03017531, 0x03017534 },
{ (const u32[]) { 0xB081B500u, 0x2100480Cu, 0xF992F000u, 0x6800480Bu }, 0x03027530, 0x03027532 },
{ (const u32[]) { 0x202EB508u, 0x21000680u, 0xFE8AF7FFu, 0x6800480Au }, 0x03027530, 0x04007531 },
{ (const u32[]) { 0x202EB508u, 0x21000680u, 0xFE74F7FFu, 0x6800480Au }, 0x04017530, 0x04027539 },
{ (const u32[]) { 0x202EB508u, 0x21000680u, 0xFE62F7FFu, 0x6800480Au }, 0x04027531, 0x04027531 },
{ (const u32[]) { 0x202EB508u, 0x21000680u, 0xFE56F7FFu, 0x6800480Au }, 0x04027537, 0x04027537 }
});
static constexpr auto sSignaturesArmSdk5 = std::to_array<const FunctionSignature>
({
{ (const u32[]) { 0xE92D4010u, 0xE3A040B8u, 0xEBFFFFD6u, 0xE3500000u }, 0x0500753B, 0x05027534 },
{ (const u32[]) { 0xE92D4008u, 0xE3A000B8u, 0xE3A01000u, 0xEBFFFFCEu }, 0x05017536, 0x05047531 },
{ (const u32[]) { 0xE92D4038u, 0xE3A050B8u, 0xEBFFFFD7u, 0xE3500000u }, 0x05017537, 0x05057535 },
{ (const u32[]) { 0xE92D4010u, 0xE3A04000u, 0xE1A01004u, 0xE3A000B8u }, 0x05037531, 0x05057535 }
});
static constexpr auto sSignaturesThumbSdk5 = std::to_array<const FunctionSignature>
({
{ (const u32[]) { 0x24B8B510u, 0xFFC4F7FFu, 0xD0002800u, 0x1C202490u }, 0x05007538, 0x05017537 },
{ (const u32[]) { 0x20B8B508u, 0xF7FF2100u, 0x480AFFABu, 0x480A6801u }, 0x05017537, 0x05057535 },
{ (const u32[]) { 0x24B8B510u, 0xFFC6F7FFu, 0xD0002800u, 0x1C202490u }, 0x05037531, 0x05057534 }
});
bool CardiReadRomIdCorePatch::FindPatchTarget(PatchContext& patchContext)
{
if (patchContext.GetSdkVersion().IsTwlSdk())
{
for (const auto& signature : sSignaturesArmSdk5)
{
if (CheckSignature(patchContext, signature))
{
break;
}
}
if (!_cardiReadRomIdCore)
{
for (const auto& signature : sSignaturesThumbSdk5)
{
if (CheckSignature(patchContext, signature))
{
_thumb = true;
break;
}
}
}
}
else
{
for (const auto& signature : sSignaturesArm)
{
if (CheckSignature(patchContext, signature))
{
break;
}
}
if (!_cardiReadRomIdCore)
{
for (const auto& signature : sSignaturesThumb)
{
if (CheckSignature(patchContext, signature))
{
_thumb = true;
break;
}
}
}
}
if (!_cardiReadRomIdCore)
{
LOG_WARNING("CARDi_ReadRomIDCore not found\n");
}
return true; //_cardiReadRomIdCore != nullptr;
}
void CardiReadRomIdCorePatch::ApplyPatch(PatchContext& patchContext)
{
if (!_cardiReadRomIdCore)
return;
auto patchCode = patchContext.GetPatchCodeCollection().AddUniquePatchCode<CardiReadRomIdCorePatchCode>
(
patchContext.GetPatchHeap(),
(const void*)(patchContext.GetSdkVersion().IsTwlSdk() ? 0x02FFFC00 : 0x027FFC00)
);
if (_thumb)
{
((u16*)_cardiReadRomIdCore)[0] = 0x4800;
((u16*)_cardiReadRomIdCore)[1] = 0x4700;
}
else
{
_cardiReadRomIdCore[0] = 0xE51FF004;
}
_cardiReadRomIdCore[1] = (u32)patchCode->GetCardiReadRomIdCoreFunction();
}
bool CardiReadRomIdCorePatch::CheckSignature(const PatchContext& patchContext, const FunctionSignature& signature)
{
if (patchContext.GetSdkVersion() >= signature.GetMinimumSdkVersion() &&
patchContext.GetSdkVersion() <= signature.GetMaximumSdkVersion())
{
_cardiReadRomIdCore = patchContext.FindPattern32(signature.GetPattern(), 16);
if (_cardiReadRomIdCore)
{
LOG_DEBUG("Found CARDi_ReadRomIDCore at %p\n", _cardiReadRomIdCore);
return true;
}
}
return false;
}

View File

@@ -0,0 +1,18 @@
#pragma once
#include "../Patch.h"
class FunctionSignature;
/// @brief Arm9 patch to avoid card id reads.
class CardiReadRomIdCorePatch : public Patch
{
public:
bool FindPatchTarget(PatchContext& patchContext) override;
void ApplyPatch(PatchContext& patchContext) override;
private:
bool CheckSignature(const PatchContext& patchContext, const FunctionSignature& signature);
u32* _cardiReadRomIdCore = nullptr;
u16 _thumb = false;
};

View File

@@ -0,0 +1,24 @@
#pragma once
#include "../PatchCode.h"
#include "sections.h"
DEFINE_SECTION_SYMBOLS(patch_cardireadromidcore);
extern "C" u32 patch_cardireadromidcore_entry(void);
extern u32 patch_cardireadromidcore_cardid_address;
class CardiReadRomIdCorePatchCode : public PatchCode
{
public:
CardiReadRomIdCorePatchCode(PatchHeap& patchHeap, const void* cardIdPointer)
: PatchCode(SECTION_START(patch_cardireadromidcore), SECTION_SIZE(patch_cardireadromidcore), patchHeap)
{
patch_cardireadromidcore_cardid_address = (u32)cardIdPointer;
}
const void* GetCardiReadRomIdCoreFunction() const
{
return GetAddressAtTarget((void*)patch_cardireadromidcore_entry);
}
};

View File

@@ -0,0 +1,21 @@
.cpu arm946e-s
.section "patch_cardireadromidcore", "ax"
.syntax unified
.thumb
.global patch_cardireadromidcore_entry
.type patch_cardireadromidcore_entry, %function
patch_cardireadromidcore_entry:
ldr r0, patch_cardireadromidcore_cardid_address
ldr r0, [r0]
bx lr
.balign 4
.global patch_cardireadromidcore_cardid_address
patch_cardireadromidcore_cardid_address:
.word 0x027FFC00
.pool
.end

View File

@@ -0,0 +1,6 @@
#pragma once
#include "sections.h"
DEFINE_SECTION_SYMBOLS(fixcp15);
extern "C" void fix_cp15_asm();

View File

@@ -0,0 +1,26 @@
.cpu arm946e-s
.section "fixcp15", "ax"
.syntax unified
.arm
// to fix access to gba slot memory
.global fix_cp15_asm
.type fix_cp15_asm, %function
fix_cp15_asm:
mrc p15, 0, r0, c2, c0, 0
bic r0, r0, #(1 << 3)
mcr p15, 0, r0, c2, c0, 0
mrc p15, 0, r0, c3, c0, 0
bic r0, r0, #(1 << 3)
mcr p15, 0, r0, c3, c0, 0
mrc p15, 0, r0, c5, c0, 2
bic r0, r0, #(0xF << 12)
orr r0, r0, #(3 << 12)
mcr p15, 0, r0, c5, c0, 2
bx lr
.balign 4
.pool
.end

View File

@@ -0,0 +1,125 @@
#include "common.h"
#include "../PatchContext.h"
#include "thumbInstructions.h"
#include "../platform/LoaderPlatform.h"
#include "OSResetSystemPatchAsm.h"
#include "OSResetSystemPatch.h"
static const u32 sOSResetSystemPatternSdk2Old[] = { 0xE59F101Cu, 0xE3A00010u, 0xE5815000u, 0xEB000005u };
static const u32 sOSResetSystemPatternSdk2[] = { 0xE59F1018u, 0xE3A00010u, 0xE5814000u, 0xEB000004u };
static const u32 sOSResetSystemPatternSdk3[] = { 0xE59F1014u, 0xE3A00010u, 0xE5814000u, 0xEBFFFFD9u };
static const u32 sOSResetSystemPatternSdk4[] = { 0xE59F103Cu, 0xE59F003Cu, 0xE5814000u, 0xEBFFFFD6u };
static const u32 sOSResetSystemPatternPokemonDownloader[] = { 0xE59F1010u, 0xE3A00010u, 0xE5814000u, 0xEBFFFFDEu };
static const u32 sOSResetSystemPatternSdk5Old[] = { 0xE1A04000u, 0xE1D100B0u, 0xE3500002u, 0x1A000000u };
static const u32 sOSResetSystemPatternSdk5New[] = { 0xE1A05000u, 0xE1D100B0u, 0xE3500002u, 0x1A000000u };
static const u32 sOSResetSystemPatternSdk5HybridOld[] = { 0xE1A04000u, 0xE1D100B0u, 0xE3500002u, 0x0A000006 };
static const u32 sOSResetSystemPatternSdk5HybridNew[] = { 0xE1A05000u, 0xE1D100B0u, 0xE3500002u, 0x0A000006 };
bool OSResetSystemPatch::FindPatchTarget(PatchContext& patchContext)
{
_osResetSystem = nullptr;
if (patchContext.GetSdkVersion().IsTwlSdk())
{
_osResetSystem = patchContext.FindPattern32(sOSResetSystemPatternSdk5Old, sizeof(sOSResetSystemPatternSdk5Old));
if (!_osResetSystem)
{
_osResetSystem = patchContext.FindPattern32(sOSResetSystemPatternSdk5New, sizeof(sOSResetSystemPatternSdk5New));
}
if (!_osResetSystem)
{
_osResetSystem = patchContext.FindPattern32(sOSResetSystemPatternSdk5HybridOld, sizeof(sOSResetSystemPatternSdk5HybridOld));
_hybrid = true;
}
if (!_osResetSystem)
{
_osResetSystem = patchContext.FindPattern32(sOSResetSystemPatternSdk5HybridNew, sizeof(sOSResetSystemPatternSdk5HybridNew));
_hybrid = true;
}
}
else
{
if (patchContext.GetSdkVersion() >= 0x4017530)
{
_osResetSystem = patchContext.FindPattern32(sOSResetSystemPatternSdk4, sizeof(sOSResetSystemPatternSdk4));
}
if (!_osResetSystem && patchContext.GetSdkVersion() >= 0x3017530)
{
_osResetSystem = patchContext.FindPattern32(sOSResetSystemPatternSdk3, sizeof(sOSResetSystemPatternSdk3));
}
if (!_osResetSystem && patchContext.GetSdkVersion() >= 0x2004F50)
{
_osResetSystem = patchContext.FindPattern32(sOSResetSystemPatternSdk2, sizeof(sOSResetSystemPatternSdk2));
}
if (!_osResetSystem && patchContext.GetSdkVersion() >= 0x2004EE9)
{
_osResetSystem = patchContext.FindPattern32(sOSResetSystemPatternSdk2Old, sizeof(sOSResetSystemPatternSdk2Old));
}
if (!_osResetSystem)
{
_osResetSystem = patchContext.FindPattern32(sOSResetSystemPatternPokemonDownloader, sizeof(sOSResetSystemPatternPokemonDownloader));
}
}
if (_osResetSystem)
{
LOG_DEBUG("Found end of OS_ResetSystem at %p\n", _osResetSystem);
}
else
{
LOG_DEBUG("OS_ResetSystem not found\n");
}
return true;
}
void OSResetSystemPatch::ApplyPatch(PatchContext& patchContext)
{
if (!_osResetSystem)
{
return;
}
u32 offset;
if (patchContext.GetSdkVersion().IsTwlSdk())
{
patch_osresetsystem_arm7Entry_address = 0x02FFFE34;
if (_hybrid)
{
offset = 0x80;
if (!gIsDsiMode)
{
patch_osresetsystem_entry_jump_to_twl_arm7_sync = THUMB_NOP;
}
}
else
{
offset = 0x44;
patch_osresetsystem_entry_jump_to_twl_arm7_sync = THUMB_NOP;
}
}
else
{
offset = 0xC;
patch_osresetsystem_entry_jump_to_twl_arm7_sync = THUMB_NOP;
}
auto loaderInfoTarget = (loader_info_t*)patchContext.GetPatchHeap().Alloc(sizeof(loader_info_t));
memcpy(loaderInfoTarget, _loaderInfo, sizeof(loader_info_t));
auto sdReadPatchCode = patchContext.GetLoaderPlatform()->CreateSdReadPatchCode(
patchContext.GetPatchCodeCollection(), patchContext.GetPatchHeap());
auto patchCodePart2 = patchContext.GetPatchCodeCollection().AddUniquePatchCode<OSResetSystemPart2PatchCode>(
patchContext.GetPatchHeap());
auto patchCode = patchContext.GetPatchCodeCollection().AddUniquePatchCode<OSResetSystemPatchCode>
(
patchContext.GetPatchHeap(),
loaderInfoTarget,
sdReadPatchCode,
patchCodePart2
);
*(u32*)((u8*)_osResetSystem + offset) = 0xE51FF004;
*(u32*)((u8*)_osResetSystem + offset + 4) = (u32)patchCode->GetOSResetSystemFunction();
}

View File

@@ -0,0 +1,19 @@
#pragma once
#include "../Patch.h"
#include "LoaderInfo.h"
/// @brief Arm9 patch to make OS_ResetSystem reboot into Pico Loader.
class OSResetSystemPatch : public Patch
{
public:
explicit OSResetSystemPatch(const loader_info_t* loaderInfo)
: _loaderInfo(loaderInfo) { }
bool FindPatchTarget(PatchContext& patchContext) override;
void ApplyPatch(PatchContext& patchContext) override;
private:
u32* _osResetSystem = nullptr;
u32 _hybrid = false;
const loader_info_t* _loaderInfo;
};

View File

@@ -0,0 +1,49 @@
#pragma once
#include "../PatchCode.h"
#include "sections.h"
#include "LoaderInfo.h"
#include "../platform/SdReadPatchCode.h"
DEFINE_SECTION_SYMBOLS(patch_osresetsystem);
DEFINE_SECTION_SYMBOLS(patch_osresetsystem_boot);
extern "C" void patch_osresetsystem_entry(void);
extern "C" void patch_osresetsystem_bootPicoLoader(void);
extern const loader_info_t* patch_osresetsystem_loader_info_address;
extern u32 patch_osresetsystem_readSdSectors_address;
extern u32 patch_osresetsystem_bootPicoLoader_address;
extern u16 patch_osresetsystem_entry_jump_to_twl_arm7_sync;
extern u32 patch_osresetsystem_arm7Entry_address;
class OSResetSystemPart2PatchCode : public PatchCode
{
public:
explicit OSResetSystemPart2PatchCode(PatchHeap& patchHeap)
: PatchCode(SECTION_START(patch_osresetsystem_boot), SECTION_SIZE(patch_osresetsystem_boot), patchHeap)
{
}
const void* GetOSResetSystemPart2Function() const
{
return GetAddressAtTarget((void*)patch_osresetsystem_bootPicoLoader);
}
};
class OSResetSystemPatchCode : public PatchCode
{
public:
OSResetSystemPatchCode(PatchHeap& patchHeap, const loader_info_t* loaderInfo,
const SdReadPatchCode* sdReadPatchCode, const OSResetSystemPart2PatchCode* part2PatchCode)
: PatchCode(SECTION_START(patch_osresetsystem), SECTION_SIZE(patch_osresetsystem), patchHeap)
{
patch_osresetsystem_loader_info_address = loaderInfo;
patch_osresetsystem_readSdSectors_address = (u32)sdReadPatchCode->GetSdReadFunction();
patch_osresetsystem_bootPicoLoader_address = (u32)part2PatchCode->GetOSResetSystemPart2Function();
}
const void* GetOSResetSystemFunction() const
{
return GetAddressAtTarget((void*)patch_osresetsystem_entry);
}
};

View File

@@ -0,0 +1,178 @@
.cpu arm946e-s
.section "patch_osresetsystem", "ax"
.syntax unified
.thumb
.global patch_osresetsystem_entry
.type patch_osresetsystem_entry, %function
patch_osresetsystem_entry:
adr r0, regIpcSync
// regIpcSync, arm7ResetCommand, regVramCntA, vramAbcdLcdcSetting, readSdSectors_address, loader_info_address, vramCLcdcAddress, patch_osresetsystem_bootPicoLoader_address
ldmia r0, {r0, r1, r2, r3, r4, r5, r6, r7}
// r9 = readSdSectors_address
mov r9, r4
mov r10, r7
// reset arm7
1:
ldr r6, [r0, #4]
lsrs r6, r6, #2 // shift bit 1 to carry
bcs 1b // while fifo full
str r1, [r0, #8]
adds r7, #(twl_arm7_sync - patch_osresetsystem_bootPicoLoader)
mov lr, pc
.global patch_osresetsystem_entry_jump_to_twl_arm7_sync
patch_osresetsystem_entry_jump_to_twl_arm7_sync:
bx r7
2:
ldrb r7, [r0] // ipc sync
cmp r7, #1
bne 2b // while ipc sync from arm7 is not 1
// map vram ABCD to LCDC
str r3, [r2]
// load pico loader arm9
ldmia r5!, {r4,r6,r7} // clusterShift, database, clusterMap[0]
ldr r7,= 0x06800000
mov r11, pc
b loadData
// load pico loader arm7
ldr r5, patch_osresetsystem_loader_info_address
adds r5, #52
ldr r7, vramCLcdcAddress
mov r11, pc
b loadData
adr r5, patch_osresetsystem_loader_info_address
bx r10
loadData_loop:
subs r3, #2
lsls r3, r4
adds r0, r3, r6 // start sector
movs r1, r7 // dst
lsls r2, r4 // sector count
lsls r3, r2, #9
adds r7, r3
blx r9 // read sectors
loadData:
ldmia r5!, {r2, r3} // ncl, startSector
cmp r2, #0
bne loadData_loop
mov pc, r11
.balign 4
regIpcSync:
.word 0x04000180
arm7ResetCommand:
.word 0x4000C
regVramCntA:
.word 0x04000240
vramAbcdLcdcSetting:
.word 0x80808080
.global patch_osresetsystem_readSdSectors_address
patch_osresetsystem_readSdSectors_address:
.word 0
.global patch_osresetsystem_loader_info_address
patch_osresetsystem_loader_info_address:
.word 0
vramCLcdcAddress:
.word 0x06840000
.global patch_osresetsystem_bootPicoLoader_address
patch_osresetsystem_bootPicoLoader_address:
.word 0
.pool
.section "patch_osresetsystem_boot", "ax"
.thumb
.global patch_osresetsystem_bootPicoLoader
.type patch_osresetsystem_bootPicoLoader, %function
patch_osresetsystem_bootPicoLoader:
// loader_info_address, vramCLcdcAddress
ldmia r5, {r3, r5}
ldrh r0, [r3, #2] // loader_info_t::picoLoaderBootDrive
strh r0, [r5, #8] // pload_header7_t::bootDrive
ldr r0, [r5] // pload_header7_t::entryPoint
// set NTR_SHARED_MEMORY->romHeader.arm7EntryAddress
ldr r4, patch_osresetsystem_arm7Entry_address
str r0, [r4]
// map vram CD to arm7
ldr r0,= 0x04000240
ldr r7,= 0x8A82
strh r7, [r0, #2]
adds r0, #(0x04000180 - 0x04000240) // REG_IPC_SYNC
movs r1, #1
strb r1, [r0, #1]
1:
ldrb r7, [r0] // ipc sync
cmp r7, #0
bne 1b // while ipc sync from arm7 is not 0
movs r1, #0
strb r1, [r0, #1]
movs r0, #0x68
lsls r0, r0, #20 // 0x06800000
bx r0
twl_arm7_sync:
ldr r7,= 0x02FFFC24
movs r1, #1
strh r1, [r7, #4]
ldrh r6, [r7, #2]
ldrh r1, [r7]
1:
adds r1, #1
strh r1, [r7]
ldrh r4, [r7, #2]
cmp r6, r4
beq 1b
adds r1, #1
strh r1, [r7]
movs r1, #3
strh r1, [r7, #4]
ldrh r6, [r7, #2]
ldrh r1, [r7]
1:
adds r1, #1
strh r1, [r7]
ldrh r4, [r7, #2]
cmp r6, r4
beq 1b
adds r1, #1
strh r1, [r7]
mov pc, lr
.balign 4
.global patch_osresetsystem_arm7Entry_address
patch_osresetsystem_arm7Entry_address:
.word 0x027FFE34
.pool
.end

View File

@@ -0,0 +1,418 @@
#include "common.h"
#include "../../../PatchContext.h"
#include "thumbInstructions.h"
#include "gameCode.h"
#include "DSProtectOverlayPatchv1Asm.h"
#include "DSProtectOverlayPatchv2Asm.h"
#include "DSProtectOverlayPatchv2sAsm.h"
#include "DSProtectOverlayPatch.h"
const void* DSProtectOverlayPatch::InsertPatch(PatchContext& patchContext)
{
if (_version <= DSProtectVersion::v1_28)
{
return InsertPatchV1(patchContext);
}
else if (_version <= DSProtectVersion::v2_05)
{
return InsertPatchV2(patchContext);
}
else if (_version <= DSProtectVersion::v2_05s)
{
return InsertPatchV2s(patchContext);
}
else
{
LOG_WARNING("Unsupported DSProtect version\n");
return next ? (const void*)next->InsertPatch(patchContext) : nullptr;
}
}
void DSProtectOverlayPatch::SetOldV1Offsets(const u16* offsets) const
{
int offsetIdx = 0;
if (_functionMask & 0b000001)
dsprotectpatchv1_stub_protectb1_offset = offsets[offsetIdx++];
else
dsprotectpatchv1_stub_protectb1_offset = 0; // does not exist
if (_functionMask & 0b000010)
dsprotectpatchv1_stub_notprotectb1_offset = offsets[offsetIdx++];
else
dsprotectpatchv1_stub_notprotectb1_offset = 0; // does not exist
if (_functionMask & 0b000100)
dsprotectpatchv1_stub_protectb2_offset = offsets[offsetIdx++];
else
dsprotectpatchv1_stub_protectb2_offset = 0; // does not exist
if (_functionMask & 0b001000)
dsprotectpatchv1_stub_notprotectb2_offset = offsets[offsetIdx++];
else
dsprotectpatchv1_stub_notprotectb2_offset = 0; // does not exist
if (_functionMask & 0b010000)
dsprotectpatchv1_stub_protectb3_offset = offsets[offsetIdx++];
else
dsprotectpatchv1_stub_protectb3_offset = 0; // does not exist
if (_functionMask & 0b100000)
dsprotectpatchv1_stub_notprotectb3_offset = offsets[offsetIdx++];
else
dsprotectpatchv1_stub_notprotectb3_offset = 0; // does not exist
}
const void* DSProtectOverlayPatch::InsertPatchV1(PatchContext& patchContext) const
{
const void* nextPatch = next ? (const void*)next->InsertPatch(patchContext) : nullptr;
dsprotectpatchv1_overlay_id = _overlayId;
dsprotectpatchv1_base_offset = _overlayOffset;
switch (_version)
{
case DSProtectVersion::v1_05:
{
static u16 v105Offsets[] = { 0x858, 0x934, 0xA10, 0xAEC, 0xBC8, 0xC94 };
SetOldV1Offsets(v105Offsets);
dsprotectpatchv1_nitro_static_init_offset = 0; // does not exist
dsprotectpatchv1_protectb1_return_value = 0;
dsprotectpatchv1_protectb2_return_value = 0;
dsprotectpatchv1_protectb3_return_value = 0;
dsprotectpatchv1_notprotectb1_return_value = 1;
dsprotectpatchv1_notprotectb2_return_value = 1;
dsprotectpatchv1_notprotectb3_return_value = 1;
// Remove xor 0x320 from the callback pointer
dsprotectpatchv1_moveCallback = 0xE230EE32; // eors lr, r0, #0x320
break;
}
case DSProtectVersion::v1_06:
{
static u16 v106Offsets[] = { 0x860, 0x938, 0xA10, 0xAE8, 0xBC0, 0xC88 };
SetOldV1Offsets(v106Offsets);
dsprotectpatchv1_nitro_static_init_offset = 0; // does not exist
dsprotectpatchv1_protectb1_return_value = 0;
dsprotectpatchv1_protectb2_return_value = 0;
dsprotectpatchv1_protectb3_return_value = 0;
dsprotectpatchv1_notprotectb1_return_value = 1;
dsprotectpatchv1_notprotectb2_return_value = 1;
dsprotectpatchv1_notprotectb3_return_value = 1;
break;
}
case DSProtectVersion::v1_08:
{
static u16 v108Offsets[] = { 0x858, 0x930, 0xA08, 0xAE0, 0xBB8, 0xC94 };
SetOldV1Offsets(v108Offsets);
dsprotectpatchv1_nitro_static_init_offset = 0; // does not exist
dsprotectpatchv1_protectb1_return_value = 0;
dsprotectpatchv1_protectb2_return_value = 0;
dsprotectpatchv1_protectb3_return_value = 0;
dsprotectpatchv1_notprotectb1_return_value = 1;
dsprotectpatchv1_notprotectb2_return_value = 1;
dsprotectpatchv1_notprotectb3_return_value = 1;
break;
}
case DSProtectVersion::v1_10:
{
static u16 v110Offsets[] = { 0x860, 0x940, 0xA20, 0xB00, 0xBE0, 0xCB0 };
SetOldV1Offsets(v110Offsets);
dsprotectpatchv1_nitro_static_init_offset = 0; // does not exist
dsprotectpatchv1_protectb1_return_value = 0;
dsprotectpatchv1_protectb2_return_value = 0;
dsprotectpatchv1_protectb3_return_value = 0;
dsprotectpatchv1_notprotectb1_return_value = 1;
dsprotectpatchv1_notprotectb2_return_value = 1;
dsprotectpatchv1_notprotectb3_return_value = 1;
break;
}
case DSProtectVersion::v1_20:
{
switch (_functionMask)
{
case 0b001111:
dsprotectpatchv1_stub_protectb1_offset = 0x864;
dsprotectpatchv1_stub_notprotectb1_offset = 0x94C;
dsprotectpatchv1_stub_protectb2_offset = 0xA34;
dsprotectpatchv1_stub_notprotectb2_offset = 0xB1C;
dsprotectpatchv1_stub_protectb3_offset = 0; // does not exist
dsprotectpatchv1_stub_notprotectb3_offset = 0; // does not exist
break;
case 0b010101:
dsprotectpatchv1_stub_protectb1_offset = 0x644;
dsprotectpatchv1_stub_notprotectb1_offset = 0; // does not exist
dsprotectpatchv1_stub_protectb2_offset = 0x72C;
dsprotectpatchv1_stub_notprotectb2_offset = 0; // does not exist
dsprotectpatchv1_stub_protectb3_offset = 0x814;
dsprotectpatchv1_stub_notprotectb3_offset = 0; // does not exist
break;
case 0b010110:
dsprotectpatchv1_stub_protectb1_offset = 0; // does not exist
dsprotectpatchv1_stub_notprotectb1_offset = 0x644;
dsprotectpatchv1_stub_protectb2_offset = 0x72C;
dsprotectpatchv1_stub_notprotectb2_offset = 0; // does not exist
dsprotectpatchv1_stub_protectb3_offset = 0x814;
dsprotectpatchv1_stub_notprotectb3_offset = 0; // does not exist
break;
case 0b011001:
dsprotectpatchv1_stub_protectb1_offset = 0x644;
dsprotectpatchv1_stub_notprotectb1_offset = 0; // does not exist
dsprotectpatchv1_stub_protectb2_offset = 0;
dsprotectpatchv1_stub_notprotectb2_offset = 0x72C;
dsprotectpatchv1_stub_protectb3_offset = 0x814;
dsprotectpatchv1_stub_notprotectb3_offset = 0; // does not exist
break;
case 0b110101:
dsprotectpatchv1_stub_protectb1_offset = 0x644;
dsprotectpatchv1_stub_notprotectb1_offset = 0; // does not exist
dsprotectpatchv1_stub_protectb2_offset = 0x72C;
dsprotectpatchv1_stub_notprotectb2_offset = 0; // does not exist
dsprotectpatchv1_stub_protectb3_offset = 0x814;
dsprotectpatchv1_stub_notprotectb3_offset = 0x8DC;
break;
case 0b111111:
dsprotectpatchv1_stub_protectb1_offset = 0x864;
dsprotectpatchv1_stub_notprotectb1_offset = 0x94C;
dsprotectpatchv1_stub_protectb2_offset = 0xA34;
dsprotectpatchv1_stub_notprotectb2_offset = 0xB1C;
dsprotectpatchv1_stub_protectb3_offset = 0xC04;
dsprotectpatchv1_stub_notprotectb3_offset = 0xCCC;
break;
default:
LOG_WARNING("Unsupported DSProtect function mask\n");
return nextPatch;
}
dsprotectpatchv1_nitro_static_init_offset = 0; // does not exist
dsprotectpatchv1_protectb1_return_value = 0;
dsprotectpatchv1_protectb2_return_value = 0;
dsprotectpatchv1_protectb3_return_value = 0;
dsprotectpatchv1_notprotectb1_return_value = 1;
dsprotectpatchv1_notprotectb2_return_value = 1;
dsprotectpatchv1_notprotectb3_return_value = 1;
break;
}
case DSProtectVersion::v1_22:
{
switch (_functionMask)
{
case 0b010101:
dsprotectpatchv1_stub_protectb1_offset = 0x64C;
dsprotectpatchv1_stub_notprotectb1_offset = 0; // does not exist
dsprotectpatchv1_stub_protectb2_offset = 0x734;
dsprotectpatchv1_stub_notprotectb2_offset = 0; // does not exist
dsprotectpatchv1_stub_protectb3_offset = 0x81C;
dsprotectpatchv1_stub_notprotectb3_offset = 0; // does not exist
break;
case 0b010110:
dsprotectpatchv1_stub_protectb1_offset = 0; // does not exist
dsprotectpatchv1_stub_notprotectb1_offset = 0x64C;
dsprotectpatchv1_stub_protectb2_offset = 0x734;
dsprotectpatchv1_stub_notprotectb2_offset = 0; // does not exist
dsprotectpatchv1_stub_protectb3_offset = 0x81C;
dsprotectpatchv1_stub_notprotectb3_offset = 0; // does not exist
break;
case 0b010111:
dsprotectpatchv1_stub_protectb1_offset = 0x754;
dsprotectpatchv1_stub_notprotectb1_offset = 0x83C;
dsprotectpatchv1_stub_protectb2_offset = 0x924;
dsprotectpatchv1_stub_notprotectb2_offset = 0; // does not exist
dsprotectpatchv1_stub_protectb3_offset = 0xA0C;
dsprotectpatchv1_stub_notprotectb3_offset = 0; // does not exist
break;
case 0b011111:
dsprotectpatchv1_stub_protectb1_offset = 0x874;
dsprotectpatchv1_stub_notprotectb1_offset = 0x95C;
dsprotectpatchv1_stub_protectb2_offset = 0xA44;
dsprotectpatchv1_stub_notprotectb2_offset = 0xB2C;
dsprotectpatchv1_stub_protectb3_offset = 0xC14;
dsprotectpatchv1_stub_notprotectb3_offset = 0; // does not exist
break;
case 0b111111:
dsprotectpatchv1_stub_protectb1_offset = 0x874;
dsprotectpatchv1_stub_notprotectb1_offset = 0x95C;
dsprotectpatchv1_stub_protectb2_offset = 0xA44;
dsprotectpatchv1_stub_notprotectb2_offset = 0xB2C;
dsprotectpatchv1_stub_protectb3_offset = 0xC14;
dsprotectpatchv1_stub_notprotectb3_offset = 0xCDC;
break;
default:
LOG_WARNING("Unsupported DSProtect function mask\n");
return nextPatch;
}
dsprotectpatchv1_nitro_static_init_offset = 0; // does not exist
dsprotectpatchv1_protectb1_return_value = 0;
dsprotectpatchv1_protectb2_return_value = 0;
dsprotectpatchv1_protectb3_return_value = 0;
dsprotectpatchv1_notprotectb1_return_value = 1;
dsprotectpatchv1_notprotectb2_return_value = 1;
dsprotectpatchv1_notprotectb3_return_value = 1;
break;
}
case DSProtectVersion::v1_23:
case DSProtectVersion::v1_23Z:
{
dsprotectpatchv1_stub_protectb1_offset = 0x410;
dsprotectpatchv1_stub_protectb2_offset = 0x500;
dsprotectpatchv1_stub_protectb3_offset = 0x5F0;
dsprotectpatchv1_stub_notprotectb1_offset = 0x488;
dsprotectpatchv1_stub_notprotectb2_offset = 0x578;
dsprotectpatchv1_stub_notprotectb3_offset = 0x668;
// this seems to be a decrypt function that is called from 5 static init functions
dsprotectpatchv1_nitro_static_init_offset = 0xD78;
dsprotectpatchv1_protectb1_return_value = 0;
dsprotectpatchv1_protectb2_return_value = 0;
dsprotectpatchv1_protectb3_return_value = 0;
dsprotectpatchv1_notprotectb1_return_value = 1;
dsprotectpatchv1_notprotectb2_return_value = 1;
dsprotectpatchv1_notprotectb3_return_value = 1;
break;
}
case DSProtectVersion::v1_25:
case DSProtectVersion::v1_26:
{
dsprotectpatchv1_stub_protectb1_offset = 0x3DC;
dsprotectpatchv1_stub_protectb2_offset = 0x4CC;
dsprotectpatchv1_stub_protectb3_offset = 0x5BC;
dsprotectpatchv1_stub_notprotectb1_offset = 0x454;
dsprotectpatchv1_stub_notprotectb2_offset = 0x544;
dsprotectpatchv1_stub_notprotectb3_offset = 0x634;
dsprotectpatchv1_nitro_static_init_offset = 0x6AC;
break;
}
case DSProtectVersion::v1_27:
{
dsprotectpatchv1_stub_protectb1_offset = 0x3A0;
dsprotectpatchv1_stub_protectb2_offset = 0x418;
dsprotectpatchv1_stub_protectb3_offset = 0x490;
dsprotectpatchv1_stub_notprotectb1_offset = 0x3DC;
dsprotectpatchv1_stub_notprotectb2_offset = 0x454;
dsprotectpatchv1_stub_notprotectb3_offset = 0x4CC;
dsprotectpatchv1_nitro_static_init_offset = 0x508;
dsprotectpatchv1_loadReturnValue = 0; // Return the return value of the callback
break;
}
case DSProtectVersion::v1_28:
{
dsprotectpatchv1_stub_protectb1_offset = 0x3A0;
dsprotectpatchv1_stub_protectb2_offset = 0x418;
dsprotectpatchv1_stub_protectb3_offset = 0x490;
dsprotectpatchv1_stub_notprotectb1_offset = 0x3DC;
dsprotectpatchv1_stub_notprotectb2_offset = 0x454;
dsprotectpatchv1_stub_notprotectb3_offset = 0x4CC;
dsprotectpatchv1_nitro_static_init_offset = 0x508;
dsprotectpatchv1_loadReturnValue = 0; // Return the return value of the callback
break;
}
default:
{
LOG_WARNING("Unsupported DSProtect version\n");
return nextPatch;
}
}
dsprotectpatchv1_nextAddress = (u32)nextPatch;
u32 patch1Size = SECTION_SIZE(dsprotectpatchv1_part1);
void* patch1Address = patchContext.GetPatchHeap().Alloc(patch1Size);
u32 patch2Size = SECTION_SIZE(dsprotectpatchv1_part2);
void* patch2Address = patchContext.GetPatchHeap().Alloc(patch2Size);
dsprotectpatchv1_part2_address = (u32)&dsprotectpatchv1_entry2 - (u32)SECTION_START(dsprotectpatchv1_part2) + (u32)patch2Address;
u32 entryAddress1 = (u32)&dsprotectpatchv1_entry1 - (u32)SECTION_START(dsprotectpatchv1_part1) + (u32)patch1Address;
memcpy(patch1Address, SECTION_START(dsprotectpatchv1_part1), patch1Size);
memcpy(patch2Address, SECTION_START(dsprotectpatchv1_part2), patch2Size);
return (const void*)entryAddress1;
}
struct ds_protect_v2_patch_t
{
u32 checksumFix;
u16 stubProtectb1Offset;
u16 stubProtectb2Offset;
u16 stubProtectb3Offset;
u16 stubProtectb4Offset;
u16 amInitOffset;
u16 storeChecksumFix;
};
static const ds_protect_v2_patch_t sDSProtectV2Patches[] =
{
{ 0x7CDBA8BD, 0x670, 0x6AC, 0x6E8, 0x724, 0x760, THUMB_STR_IMM(THUMB_R7, THUMB_R0, 0) }, // v2.00
{ 0xA6FD7CE1, 0x1120, 0x115C, 0x1198, 0x11D4, 0x1210, THUMB_STR_IMM(THUMB_R7, THUMB_R0, 0) }, // v2.01
{ 0x934DD0, 0x129C, 0x1324, 0x13AC, 0x1434, 0x14BC, THUMB_STR_IMM(THUMB_R7, THUMB_R0, 0) }, // v2.03
{ 0x32E120D5, 0x132C, 0x13D8, 0x1484, 0x1530, 0x15DC, THUMB_STR_IMM(THUMB_R7, THUMB_R0, 8) }, // v2.05
};
const void* DSProtectOverlayPatch::InsertPatchV2(PatchContext& patchContext) const
{
dsprotectpatchv2_nextAddress = next ? (u32)next->InsertPatch(patchContext) : 0u;
dsprotectpatchv2_overlay_id = _overlayId;
dsprotectpatchv2_base_offset = _overlayOffset;
const auto& patch = sDSProtectV2Patches[(u32)_version - (u32)DSProtectVersion::v2_00];
dsprotectpatchv2_checksum_fix = patch.checksumFix;
dsprotectpatchv2_stub_protectb1_offset = patch.stubProtectb1Offset;
dsprotectpatchv2_stub_protectb2_offset = patch.stubProtectb2Offset;
dsprotectpatchv2_stub_protectb3_offset = patch.stubProtectb3Offset;
dsprotectpatchv2_stub_protectb4_offset = patch.stubProtectb4Offset;
dsprotectpatchv2_am_init_offset = patch.amInitOffset;
dsprotectpatchv2_store_checksum_fix = patch.storeChecksumFix;
u32 patchSize = SECTION_SIZE(dsprotectpatchv2);
void* patchAddress = patchContext.GetPatchHeap().Alloc(patchSize);
u32 entryAddress = (u32)&dsprotectpatchv2_entry - (u32)SECTION_START(dsprotectpatchv2) + (u32)patchAddress;
memcpy(patchAddress, SECTION_START(dsprotectpatchv2), patchSize);
return (const void*)entryAddress;
}
struct ds_protect_v2s_patch_t
{
u32 checksumFix;
u16 instantDetectOffset;
u16 storeChecksumFix;
};
static const ds_protect_v2s_patch_t sDSProtectV2sPatches[] =
{
{ 0xEDEA7F01, 0x1BC, THUMB_STR_IMM(THUMB_R7, THUMB_R0, 0) }, // v2.00s
{ 0xEDEA7F1F, 0x1BC, THUMB_STR_IMM(THUMB_R7, THUMB_R0, 0) }, // v2.01s
{ 0x167325AA, 0x20C, THUMB_STR_IMM(THUMB_R7, THUMB_R0, 0) }, // v2.03s
{ 0x32E120D5, 0x230, THUMB_STR_IMM(THUMB_R7, THUMB_R0, 4) } // v2.05s
};
const void* DSProtectOverlayPatch::InsertPatchV2s(PatchContext& patchContext) const
{
dsprotectpatchv2s_nextAddress = next ? (u32)next->InsertPatch(patchContext) : 0u;
dsprotectpatchv2s_overlay_id = _overlayId;
const auto& patch = sDSProtectV2sPatches[(u32)_version - (u32)DSProtectVersion::v2_00s];
dsprotectpatchv2s_checksum_fix = patch.checksumFix;
dsprotectpatchv2s_stub_instantdetect_offset = patch.instantDetectOffset + _overlayOffset;
dsprotectpatchv2s_store_checksum_fix = patch.storeChecksumFix;
u32 patchSize = SECTION_SIZE(dsprotectpatchv2s);
void* patchAddress = patchContext.GetPatchHeap().Alloc(patchSize);
u32 entryAddress = (u32)&dsprotectpatchv2s_entry - (u32)SECTION_START(dsprotectpatchv2s) + (u32)patchAddress;
memcpy(patchAddress, SECTION_START(dsprotectpatchv2s), patchSize);
return (const void*)entryAddress;
}

View File

@@ -0,0 +1,24 @@
#pragma once
#include "../OverlayPatch.h"
#include "DSProtectVersion.h"
/// @brief Arm9 overlay patch for DS Protect.
class DSProtectOverlayPatch : public OverlayPatch
{
public:
DSProtectOverlayPatch(u32 overlayId, u32 overlayOffset, DSProtectVersion version, u32 functionMask)
: _overlayId(overlayId), _overlayOffset(overlayOffset), _version(version), _functionMask(functionMask) { }
const void* InsertPatch(PatchContext& patchContext) override;
private:
u32 _overlayId;
u32 _overlayOffset;
DSProtectVersion _version;
u32 _functionMask;
void SetOldV1Offsets(const u16* offsets) const;
const void* InsertPatchV1(PatchContext& patchContext) const;
const void* InsertPatchV2(PatchContext& patchContext) const;
const void* InsertPatchV2s(PatchContext& patchContext) const;
};

View File

@@ -0,0 +1,27 @@
#pragma once
#include "sections.h"
DEFINE_SECTION_SYMBOLS(dsprotectpatchv1_part1);
extern "C" void dsprotectpatchv1_entry1();
extern u16 dsprotectpatchv1_stub_protectb1_offset;
extern u16 dsprotectpatchv1_stub_protectb2_offset;
extern u16 dsprotectpatchv1_stub_protectb3_offset;
extern u16 dsprotectpatchv1_nitro_static_init_offset;
extern u16 dsprotectpatchv1_overlay_id;
extern u32 dsprotectpatchv1_base_offset;
extern u32 dsprotectpatchv1_part2_address;
extern u32 dsprotectpatchv1_protectb1_return_value;
extern u32 dsprotectpatchv1_protectb2_return_value;
extern u32 dsprotectpatchv1_protectb3_return_value;
DEFINE_SECTION_SYMBOLS(dsprotectpatchv1_part2);
extern "C" void dsprotectpatchv1_entry2();
extern u16 dsprotectpatchv1_stub_notprotectb1_offset;
extern u16 dsprotectpatchv1_stub_notprotectb2_offset;
extern u16 dsprotectpatchv1_stub_notprotectb3_offset;
extern u32 dsprotectpatchv1_nextAddress;
extern u32 dsprotectpatchv1_notprotectb1_return_value;
extern u32 dsprotectpatchv1_notprotectb2_return_value;
extern u32 dsprotectpatchv1_notprotectb3_return_value;
extern u32 dsprotectpatchv1_loadReturnValue;
extern u32 dsprotectpatchv1_moveCallback;

View File

@@ -0,0 +1,168 @@
.cpu arm946e-s
.syntax unified
.section "dsprotectpatchv1_part1", "ax"
.thumb
.global dsprotectpatchv1_entry1
.type dsprotectpatchv1_entry1, %function
dsprotectpatchv1_entry1:
push {r4-r7,lr}
adr r0, dsprotectpatchv1_stub_protectb1_offset
ldrh r4, [r0, #(dsprotectpatchv1_overlay_id - dsprotectpatchv1_stub_protectb1_offset)]
ldmia r5!, {r1, r2} // id, ram address
cmp r1, r4
bne continue_to_next
ldr r4, dsprotectpatchv1_base_offset
adds r2, r4 // add base offset to overlay address
ldrh r4, [r0, #(dsprotectpatchv1_nitro_static_init_offset - dsprotectpatchv1_stub_protectb1_offset)]
ldr r3,= 0xE12FFF1E // bx lr
str r3, [r4, r2]
adr r4, protectbX_patch
ldmia r4!, {r5,r6}
adds r3, r0, #(dsprotectpatchv1_nitro_static_init_offset - dsprotectpatchv1_stub_protectb1_offset)
1:
ldmia r4!, {r7}
ldrh r1, [r0]
adds r1, r2
stmia r1!, {r5,r6,r7}
adds r0, #2
cmp r0, r3
bne 1b
ldr r7, dsprotectpatchv1_part2_address
bx r7
continue_to_next:
ldr r7, dsprotectpatchv1_part2_address
adds r7, #(dsprotectpatchv1_continue_to_next - dsprotectpatchv1_entry2)
bx r7
.global dsprotectpatchv1_stub_protectb1_offset
dsprotectpatchv1_stub_protectb1_offset:
.short 0
.global dsprotectpatchv1_stub_protectb2_offset
dsprotectpatchv1_stub_protectb2_offset:
.short 0
.global dsprotectpatchv1_stub_protectb3_offset
dsprotectpatchv1_stub_protectb3_offset:
.short 0
.global dsprotectpatchv1_nitro_static_init_offset
dsprotectpatchv1_nitro_static_init_offset:
.short 0
.global dsprotectpatchv1_overlay_id
dsprotectpatchv1_overlay_id:
.short 0
.balign 4
.global dsprotectpatchv1_base_offset
dsprotectpatchv1_base_offset:
.word 0
.global dsprotectpatchv1_part2_address
dsprotectpatchv1_part2_address:
.word 0
.pool
.arm
protectbX_patch:
ldr r0, dsprotectpatchv1_protectb1_return_value
bx lr
.global dsprotectpatchv1_protectb1_return_value
dsprotectpatchv1_protectb1_return_value:
.word 1830601
.global dsprotectpatchv1_protectb2_return_value
dsprotectpatchv1_protectb2_return_value:
.word 1830203
.global dsprotectpatchv1_protectb3_return_value
dsprotectpatchv1_protectb3_return_value:
.word 1828014
.section "dsprotectpatchv1_part2", "ax"
.thumb
.global dsprotectpatchv1_entry2
.type dsprotectpatchv1_entry2, %function
dsprotectpatchv1_entry2:
adr r0, dsprotectpatchv1_stub_notprotectb1_offset
adds r1, r0, #(dsprotectpatchv1_stub_notprotectb3_offset - dsprotectpatchv1_stub_notprotectb1_offset + 2)
mov lr, r1
2:
ldrh r1, [r0]
adds r1, r2
adr r3, notprotectbX_patch
ldmia r3!, {r4,r5,r6,r7}
stmia r1!, {r4,r5,r6,r7}
ldmia r3!, {r4,r5,r6}
stmia r1!, {r4,r5,r6}
adds r0, #2
cmp r0, lr
bne 2b
ldmia r3!, {r6,r7}
adds r2, #0x18
adr r0, dsprotectpatchv1_stub_notprotectb1_offset
ldrh r1, [r0, #(dsprotectpatchv1_stub_notprotectb2_offset - dsprotectpatchv1_stub_notprotectb1_offset)]
str r6, [r1, r2]
ldrh r1, [r0, #(dsprotectpatchv1_stub_notprotectb3_offset - dsprotectpatchv1_stub_notprotectb1_offset)]
str r7, [r1, r2]
dsprotectpatchv1_continue_to_next:
ldr r0, dsprotectpatchv1_nextAddress
pop {r4-r7,pc}
.global dsprotectpatchv1_stub_notprotectb1_offset
dsprotectpatchv1_stub_notprotectb1_offset:
.short 0
.global dsprotectpatchv1_stub_notprotectb2_offset
dsprotectpatchv1_stub_notprotectb2_offset:
.short 0
.global dsprotectpatchv1_stub_notprotectb3_offset
dsprotectpatchv1_stub_notprotectb3_offset:
.short 0
.balign 4
.global dsprotectpatchv1_nextAddress
dsprotectpatchv1_nextAddress:
.word 0
.pool
.arm
notprotectbX_patch:
push {lr}
.global dsprotectpatchv1_moveCallback
dsprotectpatchv1_moveCallback:
movs lr, r0
movne r0, r1
blxne lr
.global dsprotectpatchv1_loadReturnValue
dsprotectpatchv1_loadReturnValue:
ldr r0, dsprotectpatchv1_notprotectb1_return_value
pop {pc}
.global dsprotectpatchv1_notprotectb1_return_value
dsprotectpatchv1_notprotectb1_return_value:
.word 1831551
.global dsprotectpatchv1_notprotectb2_return_value
dsprotectpatchv1_notprotectb2_return_value:
.word 1830859
.global dsprotectpatchv1_notprotectb3_return_value
dsprotectpatchv1_notprotectb3_return_value:
.word 1829648
.end

View File

@@ -0,0 +1,17 @@
#pragma once
#include "sections.h"
DEFINE_SECTION_SYMBOLS(dsprotectpatchv2);
extern "C" void dsprotectpatchv2_entry();
extern u16 dsprotectpatchv2_stub_protectb1_offset;
extern u16 dsprotectpatchv2_stub_protectb2_offset;
extern u16 dsprotectpatchv2_stub_protectb3_offset;
extern u16 dsprotectpatchv2_stub_protectb4_offset;
extern u16 dsprotectpatchv2_am_init_offset;
extern u16 dsprotectpatchv2_overlay_id;
extern u32 dsprotectpatchv2_nextAddress;
extern u32 dsprotectpatchv2_checksum_fix;
extern u32 dsprotectpatchv2_base_offset;
extern u16 dsprotectpatchv2_store_checksum_fix;

View File

@@ -0,0 +1,93 @@
.cpu arm946e-s
.section "dsprotectpatchv2", "ax"
.syntax unified
.thumb
.global dsprotectpatchv2_entry
.type dsprotectpatchv2_entry, %function
dsprotectpatchv2_entry:
push {r4-r7,lr}
ldmia r5!, {r0,r1,r2}
adds r3, r2, r1 // bss address
adr r2, dsprotectpatchv2_stub_protectb1_offset
ldrh r7, [r2, #(dsprotectpatchv2_overlay_id - dsprotectpatchv2_stub_protectb1_offset)]
cmp r0, r7
bne continue_to_next
ldr r7, [r2, #(dsprotectpatchv2_base_offset - dsprotectpatchv2_stub_protectb1_offset)]
adds r1, r7 // add base offset to overlay address
ldrh r0, [r2, #(dsprotectpatchv2_am_init_offset - dsprotectpatchv2_stub_protectb1_offset)]
ldr r7,= 0xE12FFF1E // bx lr
str r7, [r1, r0]
adr r0, stub_patch_code
ldmia r0!, {r4,r5,r6,r7}
subs r1, #4 // write from 4 bytes before the actual function address
adds r0, r2, #(dsprotectpatchv2_stub_protectb4_offset - dsprotectpatchv2_stub_protectb1_offset)
mov lr, r0
patch_loop:
ldrh r0, [r2]
adds r2, #2
adds r0, r1
stmia r0!, {r3,r4,r5,r6} // bss address + code
.global dsprotectpatchv2_store_checksum_fix
dsprotectpatchv2_store_checksum_fix:
str r7, [r0, #0] // checksum fix
cmp r2, lr
ble patch_loop
continue_to_next:
ldr r0, dsprotectpatchv2_nextAddress
pop {r4-r7,pc}
.balign 4
.global dsprotectpatchv2_stub_protectb1_offset
dsprotectpatchv2_stub_protectb1_offset:
.short 0
.global dsprotectpatchv2_stub_protectb2_offset
dsprotectpatchv2_stub_protectb2_offset:
.short 0
.global dsprotectpatchv2_stub_protectb3_offset
dsprotectpatchv2_stub_protectb3_offset:
.short 0
.global dsprotectpatchv2_stub_protectb4_offset
dsprotectpatchv2_stub_protectb4_offset:
.short 0
.global dsprotectpatchv2_am_init_offset
dsprotectpatchv2_am_init_offset:
.short 0
.global dsprotectpatchv2_overlay_id
dsprotectpatchv2_overlay_id:
.short 0
.global dsprotectpatchv2_base_offset
dsprotectpatchv2_base_offset:
.word 0
.global dsprotectpatchv2_nextAddress
dsprotectpatchv2_nextAddress:
.word 0
.pool
.arm
stub_patch_code:
ldr r12, (. - 4)
ldr r2, [r12], #4
ldr pc, [r12, r2, lsl #2]
.global dsprotectpatchv2_checksum_fix
dsprotectpatchv2_checksum_fix:
.word 0
.end

View File

@@ -0,0 +1,12 @@
#pragma once
#include "sections.h"
DEFINE_SECTION_SYMBOLS(dsprotectpatchv2s);
extern "C" void dsprotectpatchv2s_entry();
extern u32 dsprotectpatchv2s_stub_instantdetect_offset;
extern u32 dsprotectpatchv2s_overlay_id;
extern u32 dsprotectpatchv2s_nextAddress;
extern u32 dsprotectpatchv2s_checksum_fix;
extern u16 dsprotectpatchv2s_store_checksum_fix;

View File

@@ -0,0 +1,64 @@
.cpu arm946e-s
.section "dsprotectpatchv2s", "ax"
.syntax unified
.thumb
.global dsprotectpatchv2s_entry
.type dsprotectpatchv2s_entry, %function
dsprotectpatchv2s_entry:
push {r4-r7,lr}
ldmia r5!, {r0,r1,r2,r3,r4}
ldr r7, dsprotectpatchv2s_overlay_id
cmp r0, r7
bne continue_to_next
movs r3, #0
movs r7, #0
stmia r4!, {r3,r7}
stmia r4!, {r3,r7}
str r3, [r4]
adr r0, stub_patch_code
ldmia r0!, {r3,r4,r5,r6,r7}
ldr r0, dsprotectpatchv2s_stub_instantdetect_offset
adds r0, r1
stmia r0!, {r3,r4,r5,r6} // code
.global dsprotectpatchv2s_store_checksum_fix
dsprotectpatchv2s_store_checksum_fix:
str r7, [r0, #0] // checksum fix
continue_to_next:
ldr r0, dsprotectpatchv2s_nextAddress
pop {r4-r7,pc}
.balign 4
.global dsprotectpatchv2s_stub_instantdetect_offset
dsprotectpatchv2s_stub_instantdetect_offset:
.word 0
.global dsprotectpatchv2s_overlay_id
dsprotectpatchv2s_overlay_id:
.word 0
.global dsprotectpatchv2s_nextAddress
dsprotectpatchv2s_nextAddress:
.word 0
.pool
.arm
stub_patch_code:
movs r3, r0
pusheq {r0,r1,lr}
pushne {r1,r2,r3}
pop {r0,r1,pc}
.global dsprotectpatchv2s_checksum_fix
dsprotectpatchv2s_checksum_fix:
.word 0
.end

View File

@@ -0,0 +1,160 @@
#include "common.h"
#include "../../PatchContext.h"
#include "thumbInstructions.h"
#include "FsStartOverlayHookPatch.h"
static const u32 sFSStartOverlayPatternSdk4[] = { 0xE59F10D0u, 0xE1A04000u, 0xE1D100B0u, 0xE3500002u }; // -0xC
static const u32 sFSStartOverlayPatternSdk4Thumb[] = { 0x481F1C06u, 0x28028800u, 0x69E8D121u, 0x0E012700u }; // -8
static const u32 sFSStartOverlayPattern[] = { 0xE3500001u, 0x0A00001Cu, 0xE595001Cu, 0xE3A03000u }; // -0x14
static const u32 sFSStartOverlayPatternThumb[] = { 0x69E8D023u, 0x0E012600u, 0x42082002u, 0x491BD013u }; // -0x10
static const u32 sFSStartOverlayPatternThumbHybrid[] = { 0x68691C07u, 0x42814828u, 0x4828D30Cu, 0xD2094281u }; // -8
extern "C" void fsstartoverlayhook_entry();
extern u8 __fsstartoverlayhook_start[];
extern u8 __fsstartoverlayhook_end[];
extern u32 fsstartoverlayhook_dcFlushRangeOffset;
extern u32 fsstartoverlayhook_hookFuncAddress;
bool FsStartOverlayHookPatch::FindPatchTarget(PatchContext& patchContext)
{
if (!_patchHead) // no patches
return true;
if (patchContext.GetSdkVersion().GetMajor() <= 4)
{
_fsStartOverlay = patchContext.FindPattern32(sFSStartOverlayPatternSdk4, sizeof(sFSStartOverlayPatternSdk4));
if (_fsStartOverlay)
{
_fsStartOverlay -= 3;
}
else
{
_fsStartOverlay = patchContext.FindPattern32(sFSStartOverlayPatternSdk4Thumb, sizeof(sFSStartOverlayPatternSdk4Thumb));
if (_fsStartOverlay)
{
_fsStartOverlay -= 2;
_thumb = true;
}
}
}
else
{
_fsStartOverlay = patchContext.FindPattern32(sFSStartOverlayPattern, sizeof(sFSStartOverlayPattern));
if (_fsStartOverlay)
{
_fsStartOverlay -= 5;
}
else
{
_fsStartOverlay = patchContext.FindPattern32(sFSStartOverlayPatternThumb, sizeof(sFSStartOverlayPatternThumb));
if (_fsStartOverlay)
{
_fsStartOverlay -= 4;
_thumb = true;
}
else
{
_fsStartOverlay = patchContext.FindPattern32(sFSStartOverlayPatternThumbHybrid, sizeof(sFSStartOverlayPatternThumbHybrid));
if (_fsStartOverlay)
{
_fsStartOverlay -= 2;
_thumb = true;
_hybrid = true;
}
}
}
}
if (_fsStartOverlay)
{
LOG_DEBUG("FS_StartOverlay found at 0x%p\n", _fsStartOverlay);
}
else
{
LOG_WARNING("FS_StartOverlay not found\n");
}
return _fsStartOverlay != nullptr;
}
void FsStartOverlayHookPatch::ApplyPatch(PatchContext& patchContext)
{
if (!_fsStartOverlay)
return;
if (!_patchHead) // no patches
return;
const void* firstPatch = _patchHead->InsertPatch(patchContext);
if (!firstPatch)
return;
fsstartoverlayhook_hookFuncAddress = (u32)firstPatch;
u32 patchOffset;
if (_thumb)
{
u32 blDcFlushRange1;
u32 blDcFlushRange2;
if (_hybrid)
{
patchOffset = 0x8E;
blDcFlushRange1 = *(u16*)((u8*)_fsStartOverlay + 0x92);
blDcFlushRange2 = *(u16*)((u8*)_fsStartOverlay + 0x94);
}
else
{
if (patchContext.GetSdkVersion().GetMajor() <= 4)
{
patchOffset = 0x68;
blDcFlushRange1 = *(u16*)((u8*)_fsStartOverlay + 0x6C);
blDcFlushRange2 = *(u16*)((u8*)_fsStartOverlay + 0x6E);
}
else
{
patchOffset = 0x6C;
blDcFlushRange1 = *(u16*)((u8*)_fsStartOverlay + 0x70);
blDcFlushRange2 = *(u16*)((u8*)_fsStartOverlay + 0x72);
}
}
fsstartoverlayhook_dcFlushRangeOffset = ((int)((((blDcFlushRange1 & 0x7FF) << 11) | (blDcFlushRange2 & 0x7FF)) << 10) >> 9) - (_hybrid ? 5 : 1);
}
else
{
patchOffset = 0xAC;
u32 blDcFlushRange = *(u32*)((u8*)_fsStartOverlay + 0xB0);
fsstartoverlayhook_dcFlushRangeOffset = (int)((blDcFlushRange & 0xFFFFFF) << 8) >> 6;
}
u32 patch1Size = (u32)__fsstartoverlayhook_end - (u32)__fsstartoverlayhook_start;
void* patch1Address = patchContext.GetPatchHeap().Alloc(patch1Size);
u32 entryAddress = (u32)&fsstartoverlayhook_entry - (u32)__fsstartoverlayhook_start + (u32)patch1Address;
memcpy(patch1Address, __fsstartoverlayhook_start, patch1Size);
if (_thumb)
{
if (_hybrid)
{
*(u16*)((u8*)_fsStartOverlay + patchOffset + 0) = THUMB_LDR_PC_IMM(THUMB_R0, 4);
*(u16*)((u8*)_fsStartOverlay + patchOffset + 2) = THUMB_NOP;
*(u16*)((u8*)_fsStartOverlay + patchOffset + 4) = THUMB_BLX(THUMB_R0);
*(u32*)((u8*)_fsStartOverlay + patchOffset + 6) = entryAddress;
}
else
{
*(u16*)((u8*)_fsStartOverlay + patchOffset + 0) = THUMB_LDR_PC_IMM(THUMB_R0, 0);
*(u16*)((u8*)_fsStartOverlay + patchOffset + 2) = THUMB_BLX(THUMB_R0);
*(u32*)((u8*)_fsStartOverlay + patchOffset + 4) = entryAddress;
}
}
else
{
*(u32*)((u8*)_fsStartOverlay + patchOffset + 0) = 0xE59F0000; // ldr r0,= entryAddress
*(u32*)((u8*)_fsStartOverlay + patchOffset + 4) = 0xE12FFF30; // blx r0
*(u32*)((u8*)_fsStartOverlay + patchOffset + 8) = entryAddress;
}
}

View File

@@ -0,0 +1,15 @@
#pragma once
#include "OverlayHookPatch.h"
/// @brief Arm9 patch to apply patches to overlays when they are loaded.
class FsStartOverlayHookPatch : public OverlayHookPatch
{
public:
bool FindPatchTarget(PatchContext& patchContext) override;
void ApplyPatch(PatchContext& patchContext) override;
private:
u32* _fsStartOverlay = nullptr;
u16 _thumb = false;
u16 _hybrid = false;
};

View File

@@ -0,0 +1,45 @@
.cpu arm946e-s
.section ".fsstartoverlayhook", "ax"
.syntax unified
.thumb
.global __fsstartoverlayhook_start
__fsstartoverlayhook_start:
.global fsstartoverlayhook_entry
.type fsstartoverlayhook_entry, %function
fsstartoverlayhook_entry:
movs r0, #0x4
add lr, r0
ldr r2, fsstartoverlayhook_dcFlushRangeOffset
add r2, lr
push {r2,lr}
ldr r0, fsstartoverlayhook_hookFuncAddress
1:
blx r0
cmp r0, #0
bne 1b
ldr r0, [r5, #4]
ldr r1, [r5, #8]
pop {r2}
blx r2
ldr r4, [r5, #0x10]
pop {pc}
.balign 4
.global fsstartoverlayhook_dcFlushRangeOffset
fsstartoverlayhook_dcFlushRangeOffset:
.word 0
.global fsstartoverlayhook_hookFuncAddress
fsstartoverlayhook_hookFuncAddress:
.word 0
.pool
.global __fsstartoverlayhook_end
__fsstartoverlayhook_end:
.end

View File

@@ -0,0 +1,34 @@
#include "common.h"
#include "../../../PatchContext.h"
#include "GoldenSunDarkDawnOverlayHookPatchAsm.h"
#include "GoldenSunDarkDawnOverlayHookPatch.h"
static const u32 sStartOverlayPattern[] = { 0xE92D4038u, 0xE1A05000u, 0xEBFFFF92u, 0xE595101Cu };
bool GoldenSunDarkDawnOverlayHookPatch::FindPatchTarget(PatchContext& patchContext)
{
_overlayStartFunc = patchContext.FindPattern32(sStartOverlayPattern, sizeof(sStartOverlayPattern));
return _overlayStartFunc != nullptr;
}
void GoldenSunDarkDawnOverlayHookPatch::ApplyPatch(PatchContext& patchContext)
{
if (!_overlayStartFunc)
return;
if (!_patchHead) // no patches
return;
gsddoverlayhookpatch_hookFuncAddress = (u32)_patchHead->InsertPatch(patchContext);
gsddoverlayhookpatch_returnAddress = (u32)_overlayStartFunc + 0x30;
u32 patch1Size = SECTION_SIZE(gsddoverlayhookpatch);
void* patch1Address = patchContext.GetPatchHeap().Alloc(patch1Size);
u32 entryAddress = (u32)&gsddoverlayhookpatch_entry - (u32)SECTION_START(gsddoverlayhookpatch) + (u32)patch1Address;
*(u32*)((u8*)_overlayStartFunc + 0x28) = 0xE51FF004; // ldr pc,= entryAddress
*(u32*)((u8*)_overlayStartFunc + 0x2C) = entryAddress;
memcpy(patch1Address, SECTION_START(gsddoverlayhookpatch), patch1Size);
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include "../OverlayHookPatch.h"
/// @brief Arm9 patch to apply patches to overlays when they are loaded in Golden Sun Dark Dawn.
class GoldenSunDarkDawnOverlayHookPatch : public OverlayHookPatch
{
public:
bool FindPatchTarget(PatchContext& patchContext) override;
void ApplyPatch(PatchContext& patchContext) override;
private:
u32* _overlayStartFunc;
};

View File

@@ -0,0 +1,9 @@
#pragma once
#include "sections.h"
DEFINE_SECTION_SYMBOLS(gsddoverlayhookpatch);
extern "C" void gsddoverlayhookpatch_entry();
extern u32 gsddoverlayhookpatch_hookFuncAddress;
extern u32 gsddoverlayhookpatch_returnAddress;

View File

@@ -0,0 +1,34 @@
.cpu arm946e-s
.section "gsddoverlayhookpatch", "ax"
.syntax unified
.thumb
.global gsddoverlayhookpatch_entry
.type gsddoverlayhookpatch_entry, %function
gsddoverlayhookpatch_entry:
ldr r0, gsddoverlayhookpatch_hookFuncAddress
1:
blx r0
cmp r0, #0
bne 1b
ldr r3, gsddoverlayhookpatch_returnAddress
ldr r1, [r5, #8]
movs r0, #1
lsls r0, r0, #14 // r0 = 0x4000
cmp r1, r0
bx r3
.balign 4
.global gsddoverlayhookpatch_hookFuncAddress
gsddoverlayhookpatch_hookFuncAddress:
.word 0
.global gsddoverlayhookpatch_returnAddress
gsddoverlayhookpatch_returnAddress:
.word 0
.pool
.end

View File

@@ -0,0 +1,24 @@
#pragma once
#include "../../Patch.h"
#include "OverlayPatch.h"
/// @brief Abstract base class for a patch that applies patches to overlays when they are loaded.
class OverlayHookPatch : public Patch
{
public:
/// @brief Adds an overlay patch to the list.
/// @param patch The overlay pach to add.
void AddOverlayPatch(OverlayPatch* patch)
{
LOG_DEBUG("OverlayHookPatch::AddOverlayPatch\n");
if (!_patchHead)
_patchHead = patch;
if (_patchTail)
_patchTail->next = patch;
_patchTail = patch;
}
protected:
OverlayPatch* _patchHead = nullptr;
OverlayPatch* _patchTail = nullptr;
};

View File

@@ -0,0 +1,15 @@
#pragma once
class PatchContext;
/// @brief Patch to be applied when an overlay is loaded.
class OverlayPatch
{
public:
/// @brief Pointer to the next overlay patch, or \c nullptr when none.
OverlayPatch* next = nullptr;
/// @brief Inserts the patch using the given \p patchContext.
/// @param patchContext The patch context to use.
/// @return A pointer to the patch function.
virtual const void* InsertPatch(PatchContext& patchContext) = 0;
};

View File

@@ -0,0 +1,13 @@
#include "common.h"
#include "../../../PatchContext.h"
#include "PokemonBw1IrApPatchAsm.h"
#include "PokemonBw1IrApPatch.h"
const void* PokemonBw1IrApPatch::InsertPatch(PatchContext& patchContext)
{
return patchContext.GetPatchCodeCollection().AddUniquePatchCode<PokemonBw1IrApPatchCode>
(
patchContext.GetPatchHeap(),
next ? next->InsertPatch(patchContext) : nullptr
)->GetPatchFunction();
}

View File

@@ -0,0 +1,9 @@
#pragma once
#include "../OverlayPatch.h"
/// @brief Arm9 overlay patch to disable the Pokemon Black & White IR-sensor anti-piracy.
class PokemonBw1IrApPatch : public OverlayPatch
{
public:
const void* InsertPatch(PatchContext& patchContext) override;
};

View File

@@ -0,0 +1,24 @@
#pragma once
#include "sections.h"
#include "../../../PatchCode.h"
DEFINE_SECTION_SYMBOLS(pokemonbw1irappatch);
extern "C" void pokemonbw1irappatch_entry();
extern u32 pokemonbw1irappatch_nextAddress;
class PokemonBw1IrApPatchCode : public PatchCode
{
public:
PokemonBw1IrApPatchCode(PatchHeap& patchHeap, const void* nextPatch)
: PatchCode(SECTION_START(pokemonbw1irappatch), SECTION_SIZE(pokemonbw1irappatch), patchHeap)
{
pokemonbw1irappatch_nextAddress = (u32)nextPatch;
}
const void* GetPatchFunction() const
{
return GetAddressAtTarget((void*)pokemonbw1irappatch_entry);
}
};

Some files were not shown because too many files have changed in this diff Show More