diff --git a/arm7/source/loader/NdsLoader.cpp b/arm7/source/loader/NdsLoader.cpp index 9578e7f..d3b346c 100644 --- a/arm7/source/loader/NdsLoader.cpp +++ b/arm7/source/loader/NdsLoader.cpp @@ -522,7 +522,13 @@ void NdsLoader::HandleHomebrewPatching() void NdsLoader::ApplyArm7Patches() { sendToArm9(IPC_COMMAND_ARM9_APPLY_ARM7_PATCHES); + sendToArm9(_cheats ? _cheats->length : 0); void* patchSpaceStart = (void*)receiveFromArm9(); + void* cheatsPtr = (void*)receiveFromArm9(); + if (cheatsPtr != nullptr && _cheats != nullptr) + { + memcpy(cheatsPtr, _cheats, _cheats->length); + } if (patchSpaceStart) { u32 mbk6 = 0; diff --git a/arm7/source/loader/NdsLoader.h b/arm7/source/loader/NdsLoader.h index d51c674..3cf2497 100644 --- a/arm7/source/loader/NdsLoader.h +++ b/arm7/source/loader/NdsLoader.h @@ -42,6 +42,13 @@ public: _argumentsLength = argumentsLength; } + /// @brief Sets the cheats to apply to the rom. + /// @param cheats The cheats. + void SetCheats(const pload_cheats_t* cheats) + { + _cheats = cheats; + } + /// @brief Loads the rom according to the specified \p bootMode. /// @param bootMode The boot mode. void Load(BootMode bootMode); @@ -53,6 +60,7 @@ private: const TCHAR* _launcherPath = nullptr; u32 _argumentsLength = 0; const char* _arguments = nullptr; + const pload_cheats_t* _cheats = nullptr; nds_header_twl_t _romHeader; DsiWareSaveResult _dsiwareSaveResult; diff --git a/arm7/source/main.cpp b/arm7/source/main.cpp index be03045..7683786 100644 --- a/arm7/source/main.cpp +++ b/arm7/source/main.cpp @@ -219,10 +219,19 @@ extern "C" void loaderMain() } else { + pload_cheats_t* cheats = nullptr; + if (gLoaderHeader.v3.cheats != nullptr && gLoaderHeader.v3.cheats->numberOfCheats != 0) + { + // Copy cheats to vram + cheats = (pload_cheats_t*)malloc(gLoaderHeader.v3.cheats->length); + memcpy(cheats, gLoaderHeader.v3.cheats, gLoaderHeader.v3.cheats->length); + } + sLoader.SetRomPath(gLoaderHeader.loadParams.romPath); handleSavePath(); sLoader.SetArguments(gLoaderHeader.loadParams.arguments, gLoaderHeader.loadParams.argumentsLength); sLoader.SetLauncherPath(gLoaderHeader.v2.launcherPath); + sLoader.SetCheats(cheats); sLoader.Load(BootMode::Normal); } diff --git a/arm9/source/Arm7Patcher.cpp b/arm9/source/Arm7Patcher.cpp index 3aeca80..334559d 100644 --- a/arm9/source/Arm7Patcher.cpp +++ b/arm9/source/Arm7Patcher.cpp @@ -15,10 +15,12 @@ #include "patches/arm7/DisableArm7WramClearPatch.h" #include "patches/arm7/sdk5/Sdk5DsiSdCardRedirectPatch.h" #include "patches/arm7/PokemonDownloaderArm7Patch.h" +#include "patches/arm7/cheats/CheatEnginePatch.h" #include "Arm7Patcher.h" -void* Arm7Patcher::ApplyPatches(const LoaderPlatform* loaderPlatform) const +void* Arm7Patcher::ApplyPatches(const LoaderPlatform* loaderPlatform, u32 cheatsLength, void*& cheatsPtr) const { + cheatsPtr = nullptr; 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; @@ -91,10 +93,18 @@ void* Arm7Patcher::ApplyPatches(const LoaderPlatform* loaderPlatform) const patchCollection.AddPatch(new PokemonDownloaderArm7Patch()); } + CheatEnginePatch* cheatEnginePatch = nullptr; + if (cheatsLength > 0) + { + cheatEnginePatch = new CheatEnginePatch(); + patchCollection.AddPatch(cheatEnginePatch); + } + if (arm7ArenaPatch->FindPatchTarget(patchContext)) { const u32 arm7PatchSpaceSize = 0x800; void* privateWramHeapStart = arm7ArenaPatch->GetArm7PrivateWramArenaLo(); + u32 mainMemoryArenaLo = (u32)arm7ArenaPatch->GetMainMemoryArenaLo(); if (0x0380F780 - (u32)privateWramHeapStart - 0x2100 >= arm7PatchSpaceSize) { patchSpaceStart = privateWramHeapStart; @@ -103,7 +113,6 @@ void* Arm7Patcher::ApplyPatches(const LoaderPlatform* loaderPlatform) const 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... @@ -112,8 +121,24 @@ void* Arm7Patcher::ApplyPatches(const LoaderPlatform* loaderPlatform) const { patchSpaceStart = (void*)(mainMemoryArenaLo | 0x800000); // make sure it ends up in the right place while in 16MB mode } - arm7ArenaPatch->SetMainMemoryArenaLo((u8*)mainMemoryArenaLo + arm7PatchSpaceSize); + mainMemoryArenaLo += arm7PatchSpaceSize; } + if (cheatsLength > 0) + { + void* cheats; + if (gIsDsiMode && romHeader->unitCode == 0) + { + cheats = (void*)(mainMemoryArenaLo & ~0xC00000); // 0x023... + } + else + { + cheats = (void*)(mainMemoryArenaLo | 0x800000); // make sure it ends up in the right place while in 16MB mode + } + cheatsPtr = cheats; + cheatEnginePatch->SetCheats(cheats); + mainMemoryArenaLo += cheatsLength; + } + arm7ArenaPatch->SetMainMemoryArenaLo((void*)mainMemoryArenaLo); patchContext.GetPatchHeap().AddFreeSpace(patchSpaceStart, arm7PatchSpaceSize); } diff --git a/arm9/source/Arm7Patcher.h b/arm9/source/Arm7Patcher.h index a765f21..ce87d21 100644 --- a/arm9/source/Arm7Patcher.h +++ b/arm9/source/Arm7Patcher.h @@ -8,6 +8,8 @@ class Arm7Patcher public: /// @brief Applies arm7 patches using the given \p loaderPlatform. /// @param loaderPlatform The loader platform to use. + /// @param cheatsLength The length of the cheats data, or zero when there are no cheats. + /// @param cheatsPtr Pointer to where the cheats need to be stored, or \c nullptr when there are no cheats. /// @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; + void* ApplyPatches(const LoaderPlatform* loaderPlatform, u32 cheatsLength, void*& cheatsPtr) const; }; diff --git a/arm9/source/main.cpp b/arm9/source/main.cpp index 3de1841..f8577c4 100644 --- a/arm9/source/main.cpp +++ b/arm9/source/main.cpp @@ -155,10 +155,12 @@ static void handleApplyArm9PatchesCommand() ipc_sendWordDirect(1); } -static void handleApplyArm7PatchesCommand() +static void handleApplyArm7PatchesCommand(u32 cheatsLength) { - void* patchSpaceStart = Arm7Patcher().ApplyPatches(sLoaderPlatform); + void* cheats = nullptr; + void* patchSpaceStart = Arm7Patcher().ApplyPatches(sLoaderPlatform, cheatsLength, cheats); ipc_sendWordDirect((u32)patchSpaceStart); + ipc_sendWordDirect((u32)cheats); } static void handleSetAPInfoCommand() @@ -289,7 +291,8 @@ static void handleArm7Command(u32 command) } case IPC_COMMAND_ARM9_APPLY_ARM7_PATCHES: { - handleApplyArm7PatchesCommand(); + u32 cheatsLength = receiveFromArm7(); + handleApplyArm7PatchesCommand(cheatsLength); break; } case IPC_COMMAND_ARM9_SET_AP_INFO: diff --git a/arm9/source/patches/arm7/cheats/CheatEnginePatch.cpp b/arm9/source/patches/arm7/cheats/CheatEnginePatch.cpp new file mode 100644 index 0000000..3365e75 --- /dev/null +++ b/arm9/source/patches/arm7/cheats/CheatEnginePatch.cpp @@ -0,0 +1,125 @@ +#include "common.h" +#include "CheatEnginePatchCode.h" +#include "CheatEnginePatch.h" + +// sdk2-4 +static const u32 sVBlankIntrPatternArm0[] = { 0xE92D4000u, 0xE24DD004u, 0xE59F0018u, 0xE5900000u }; +static const u32 sVBlankIntrPatternArm1[] = { 0xE92D4008u, 0xE59F0014u, 0xE5900000u, 0xE3500000u }; +static const u32 sVBlankIntrPatternThumb0[] = { 0x00000000u, 0xB081B500u, 0x68004804u, 0xD0012800u }; // +4 +static const u32 sVBlankIntrPatternThumb1[] = { 0x46C04770u, 0x027FFE1Du, 0x4804B508u, 0x28006800u }; // +8 + +bool CheatEnginePatch::FindPatchTarget(PatchContext& patchContext) +{ + _vblankIrqHandler = patchContext.FindPattern32(sVBlankIntrPatternArm0, sizeof(sVBlankIntrPatternArm0)); + if (_vblankIrqHandler) + { + _foundPattern = sVBlankIntrPatternArm0; + } + if (!_vblankIrqHandler) + { + _vblankIrqHandler = patchContext.FindPattern32(sVBlankIntrPatternArm1, sizeof(sVBlankIntrPatternArm1)); + if (_vblankIrqHandler) + { + _foundPattern = sVBlankIntrPatternArm1; + } + } + if (!_vblankIrqHandler) + { + _vblankIrqHandler = patchContext.FindPattern32(sVBlankIntrPatternThumb0, sizeof(sVBlankIntrPatternThumb0)); + if (_vblankIrqHandler) + { + _foundPattern = sVBlankIntrPatternThumb0; + _vblankIrqHandler += 1; + } + } + if (!_vblankIrqHandler) + { + _vblankIrqHandler = patchContext.FindPattern32(sVBlankIntrPatternThumb1, sizeof(sVBlankIntrPatternThumb1)); + if (_vblankIrqHandler) + { + _foundPattern = sVBlankIntrPatternThumb1; + _vblankIrqHandler += 2; + } + } + + if (_vblankIrqHandler) + { + LOG_DEBUG("ARM7 VBlankIntr found at 0x%p\n", _vblankIrqHandler); + } + + return _vblankIrqHandler != nullptr; +} + +void CheatEnginePatch::ApplyPatch(PatchContext& patchContext) +{ + if (!_vblankIrqHandler || !_cheats) + return; + + auto cheatEnginePatchCode = patchContext.GetPatchCodeCollection().AddUniquePatchCode + ( + patchContext.GetPatchHeap(), + _cheats + ); + + if (_foundPattern == sVBlankIntrPatternArm0) + { + // push {lr} + // sub sp, sp, #4 + // ldr r0,= + // ldr r0, [r0] + // cmp r0, #0 + // beq 1f + // bl + // 1: + // add sp, sp, #4 + // pop {lr} + // bx lr + _vblankIrqHandler[7] = 0xE59F0000; // ldr r0,= address + _vblankIrqHandler[8] = 0xE12FFF10; // bx r0 + _vblankIrqHandler[9] = (u32)cheatEnginePatchCode->GetCheatEngineFunction(); // address + } + else if (_foundPattern == sVBlankIntrPatternArm1) + { + // push {r3,lr} + // ldr r0,= + // ldr r0, [r0] + // cmp r0, #0 + // beq 1f + // bl + // 1: + // pop {r3,lr} + // bx lr + } + else if (_foundPattern == sVBlankIntrPatternThumb0) + { + // push {lr} + // sub sp, sp, #4 + // ldr r0,= + // ldr r0, [r0] + // cmp r0, #0 + // beq 1f + // bl + // 1: + // add sp, sp, #4 + // pop {r3} + // bx r3 + // nop + } + else if (_foundPattern == sVBlankIntrPatternThumb1) + { + // push {r3,lr} + // ldr r0,= + // ldr r0, [r0] + // cmp r0, #0 + // beq 1f + // bl + // 1: + // pop {r3} + // pop {r3} + // bx r3 + } + else + { + LOG_ERROR("ARM7 VBlankIntr signature not implemented\n"); + } +} diff --git a/arm9/source/patches/arm7/cheats/CheatEnginePatch.h b/arm9/source/patches/arm7/cheats/CheatEnginePatch.h new file mode 100644 index 0000000..b0d8790 --- /dev/null +++ b/arm9/source/patches/arm7/cheats/CheatEnginePatch.h @@ -0,0 +1,20 @@ +#pragma once +#include "patches/Patch.h" + +/// @brief Arm7 patch for injecting the cheat engine in the vblank interrupt handler. +class CheatEnginePatch : public Patch +{ +public: + bool FindPatchTarget(PatchContext& patchContext) override; + void ApplyPatch(PatchContext& patchContext) override; + + void SetCheats(const void* cheats) + { + _cheats = cheats; + } + +private: + const void* _cheats = nullptr; + u32* _vblankIrqHandler = nullptr; + const u32* _foundPattern = nullptr; +}; diff --git a/arm9/source/patches/arm7/cheats/CheatEnginePatchCode.h b/arm9/source/patches/arm7/cheats/CheatEnginePatchCode.h new file mode 100644 index 0000000..3d2be3f --- /dev/null +++ b/arm9/source/patches/arm7/cheats/CheatEnginePatchCode.h @@ -0,0 +1,24 @@ +#pragma once +#include "sections.h" +#include "patches/PatchCode.h" + +DEFINE_SECTION_SYMBOLS(patch_cheatengine); + +extern "C" void cheatengine_entry(void); + +extern const void* cheatengine_cheatsPtr; + +class CheatEnginePatchCode : public PatchCode +{ +public: + CheatEnginePatchCode(PatchHeap& patchHeap, const void* cheatsAddress) + : PatchCode(SECTION_START(patch_cheatengine), SECTION_SIZE(patch_cheatengine), patchHeap) + { + cheatengine_cheatsPtr = cheatsAddress; + } + + const void* GetCheatEngineFunction() const + { + return GetAddressAtTarget((void*)cheatengine_entry); + } +}; diff --git a/arm9/source/patches/arm7/cheats/CheatEnginePatchCode.s b/arm9/source/patches/arm7/cheats/CheatEnginePatchCode.s index e993469..0074a45 100644 --- a/arm9/source/patches/arm7/cheats/CheatEnginePatchCode.s +++ b/arm9/source/patches/arm7/cheats/CheatEnginePatchCode.s @@ -10,16 +10,22 @@ .global cheatengine_entry .type cheatengine_entry, %function cheatengine_entry: - push {r4, lr} + pop {r0, r1} + mov lr, r1 + push {r4, r5, lr} ldr r4, cheatengine_cheatsPtr + ldr r5, [r4, #4] // pload_cheats_t::numberOfCheats + adds r4, #8 entry_cheats_loop: - ldmia r4!, {r0} // r0 = cheat pointer - cmp r0, #0 - beq entry_end + subs r5, #1 + bmi entry_end + movs r0, r4 bl cheatengine_runCheat + ldmia r4!, {r0} // r0 = length of cheat in bytes (pload_cheat_t::length) + adds r4, r0 b entry_cheats_loop entry_end: - pop {r4} + pop {r4, r5} pop {r3} bx r3 @@ -38,7 +44,8 @@ cheatengine_runCheat: mov r6, r10 mov r7, r11 push {r4, r5, r6, r7} // r8, r9, r10, r11 - ldr r1, [r0] // r1 = length of cheat code + + ldmia r0!, {r1} // r1 = length of cheat code adds r1, r0 mov r8, r1 // r8 = end of cheat code mov r9, r0 // r9 = loop start @@ -389,7 +396,6 @@ FX_end: .balign 4 -// Pointer to list of pointers to cheat codes. Last pointer should be nullptr to terminate the list. .global cheatengine_cheatsPtr cheatengine_cheatsPtr: .word 0 diff --git a/include/picoLoader7.h b/include/picoLoader7.h index 859e3fc..ee2f6e4 100644 --- a/include/picoLoader7.h +++ b/include/picoLoader7.h @@ -1,7 +1,7 @@ #pragma once /// @brief The Pico Loader API version supported by this header file. -#define PICO_LOADER_API_VERSION 2 +#define PICO_LOADER_API_VERSION 3 /// @brief Enum to specify the drive to boot from. typedef enum @@ -43,6 +43,46 @@ typedef struct char launcherPath[256]; } pload_header7_v2_t; +/// @brief Struct representing a single Action Replay cheat opcode. +typedef struct +{ + /// @brief The first part of the opcode. + u32 a; + + /// @brief The second part of the opcode. + u32 b; +} pload_cheat_opcode_t; + +/// @brief Struct representing a single Action Replay cheat. +typedef struct +{ + /// @brief Length of \see opcodes in bytes. + u32 length; + + /// @brief The cheat opcodes. + pload_cheat_opcode_t opcodes[1]; +} pload_cheat_t; + +/// @brief Struct representing one or more cheats. Cheats are adjacent starting from the firstCheat field. +typedef struct +{ + /// @brief Length of this stucture (length field + numberOfCheats field + all cheats). + u32 length; + + /// @brief The number of cheats. + u32 numberOfCheats; + + /// @brief The first cheat. + pload_cheat_t firstCheat; +} pload_cheats_t; + +/// @brief Struct representing the API version 3 part of the header of picoLoader7.bin. +typedef struct +{ + /// @brief Pointer to the cheats, or \c nullptr when there are no cheats. + const pload_cheats_t* cheats; +} pload_header7_v3_t; + /// @brief Struct representing the header of picoLoader7.bin. typedef struct { @@ -63,4 +103,7 @@ typedef struct /// @brief The API version 2 part of the header. Only access this when \see apiVersion >= 2. pload_header7_v2_t v2; + + /// @brief The API version 3 part of the header. Only access this when \see apiVersion >= 3. + pload_header7_v3_t v3; } pload_header7_t;