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

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

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