From fee3207b33fd0b33b74ca757111df299cedbaa63 Mon Sep 17 00:00:00 2001 From: Mow <32942550+taxicat1@users.noreply.github.com> Date: Tue, 13 Jan 2026 15:47:35 -0500 Subject: [PATCH] Patch checksum AP in Last Window: The Secret of Cape West (#104) --- arm9/source/Arm9Patcher.cpp | 7 ++++ .../patches/arm9/LastWindowCrcPatch.cpp | 18 +++++++++ arm9/source/patches/arm9/LastWindowCrcPatch.h | 40 +++++++++++++++++++ .../patches/arm9/LastWindowCrcPatchCode.h | 20 ++++++++++ .../patches/arm9/LastWindowCrcPatchCode.s | 30 ++++++++++++++ 5 files changed, 115 insertions(+) create mode 100644 arm9/source/patches/arm9/LastWindowCrcPatch.cpp create mode 100644 arm9/source/patches/arm9/LastWindowCrcPatch.h create mode 100644 arm9/source/patches/arm9/LastWindowCrcPatchCode.h create mode 100644 arm9/source/patches/arm9/LastWindowCrcPatchCode.s diff --git a/arm9/source/Arm9Patcher.cpp b/arm9/source/Arm9Patcher.cpp index 36cb2b7..cbb3839 100644 --- a/arm9/source/Arm9Patcher.cpp +++ b/arm9/source/Arm9Patcher.cpp @@ -13,6 +13,7 @@ #include "patches/arm9/OSResetSystemPatch.h" #include "patches/arm9/PokemonDownloaderArm9Patch.h" #include "patches/arm9/DSProtectArm9Patch.h" +#include "patches/arm9/LastWindowCrcPatch.h" #include "patches/arm9/NandSave/FaceTrainingNandSavePatch.h" #include "patches/arm9/NandSave/JamWithTheBandNandSavePatch.h" #include "patches/arm9/NandSave/NintendoDSGuideNandSavePatch.h" @@ -404,6 +405,12 @@ void Arm9Patcher::AddGameSpecificPatches( patchCollection.AddPatch(new NintendoDSGuideNandSavePatch()); break; } + // Last Window: The Secret of Cape West + case GAMECODE("YLUP"): + { + patchCollection.AddPatch(new LastWindowCrcPatch()); + break; + } // Rabbids Go Home case GAMECODE("VRGE"): case GAMECODE("VRGV"): diff --git a/arm9/source/patches/arm9/LastWindowCrcPatch.cpp b/arm9/source/patches/arm9/LastWindowCrcPatch.cpp new file mode 100644 index 0000000..ea16582 --- /dev/null +++ b/arm9/source/patches/arm9/LastWindowCrcPatch.cpp @@ -0,0 +1,18 @@ +#include "common.h" +#include "patches/PatchContext.h" +#include "LastWindowCrcPatchCode.h" +#include "LastWindowCrcPatch.h" + +bool LastWindowCrcPatch::FindPatchTarget(PatchContext& patchContext) +{ + _getCrc16 = (u32*)0x020304B4; + return true; +} + +void LastWindowCrcPatch::ApplyPatch(PatchContext& patchContext) +{ + auto patchCode = patchContext.GetPatchCodeCollection().AddUniquePatchCode(patchContext.GetPatchHeap()); + u32 patchAddr = (u32)patchCode->GetLastWindowCrcFunction(); + *_getCrc16 = MakeBlxCall(patchAddr); +} + diff --git a/arm9/source/patches/arm9/LastWindowCrcPatch.h b/arm9/source/patches/arm9/LastWindowCrcPatch.h new file mode 100644 index 0000000..2444ede --- /dev/null +++ b/arm9/source/patches/arm9/LastWindowCrcPatch.h @@ -0,0 +1,40 @@ +#pragma once +#include "../Patch.h" + +/// @brief Arm9 patch for intercepting calls to SVC_GetCRC16 in The Last Window: The Secret of Cape West +/// @details +/// This game has DS Protect 1.27, which is patched by the regular DS Protect patches, but also has +/// an extra layer of CRC checks that must match or the game will fail to boot. +/// +/// It first runs DS Protect NotA1 at startup, and then does a CRC of most of DS Protect. This can +/// tell which DS Protect functions were run, because at decrypt-run-encrypt, the key gets changed. +/// So the CRC is sensitive not only to code modifications, but also to which encrypted functions +/// were run, and how many times they were run. +/// +/// The current DS Protect patch modifies NotA1, causes one of its subfunction to run twice, and +/// the other subfunction to not be run, which obviously breaks the CRC. +/// +/// Another region is also CRCed, from 0202DB34 to 02030334. What resides here is unknown, it does not +/// contain any commonly modified functions such as AP or card reading. +/// +/// To make things more annoying, the memory location of the correct CRC is randomized, and the code +/// that checks the CRC resides in a compressed overlay which is loaded into ITCM. This same function +/// also receives several dummy checks that are expected to fail, and so cannot be patched to always +/// report the CRC matches. +/// +/// Instead, the function call that calculates the CRC is shimmed and if its arguments match +/// a problematic CRC calculation, the expected answer is returned instead. +class LastWindowCrcPatch : public Patch +{ +public: + bool FindPatchTarget(PatchContext& patchContext) override; + void ApplyPatch(PatchContext& patchContext) override; + +private: + u32* _getCrc16 = nullptr; + + u32 MakeBlxCall(u32 patchAddr) const + { + return 0xFA000000 | (((patchAddr - (u32)_getCrc16 - 8) >> 2) & 0xFFFFFF); + } +}; diff --git a/arm9/source/patches/arm9/LastWindowCrcPatchCode.h b/arm9/source/patches/arm9/LastWindowCrcPatchCode.h new file mode 100644 index 0000000..cef6802 --- /dev/null +++ b/arm9/source/patches/arm9/LastWindowCrcPatchCode.h @@ -0,0 +1,20 @@ +#pragma once +#include "../PatchCode.h" +#include "sections.h" + +DEFINE_SECTION_SYMBOLS(patch_lastwindowcrc); + +extern "C" void patch_lastwindowcrc_entry(void); + +class LastWindowCrcPatchCode : public PatchCode +{ +public: + LastWindowCrcPatchCode(PatchHeap& patchHeap) + : PatchCode(SECTION_START(patch_lastwindowcrc), SECTION_SIZE(patch_lastwindowcrc), patchHeap) + { } + + const void* GetLastWindowCrcFunction() const + { + return GetAddressAtTarget((void*)patch_lastwindowcrc_entry); + } +}; diff --git a/arm9/source/patches/arm9/LastWindowCrcPatchCode.s b/arm9/source/patches/arm9/LastWindowCrcPatchCode.s new file mode 100644 index 0000000..444b378 --- /dev/null +++ b/arm9/source/patches/arm9/LastWindowCrcPatchCode.s @@ -0,0 +1,30 @@ +.cpu arm946e-s +.section "patch_lastwindowcrc", "ax" +.syntax unified + +.thumb +.global patch_lastwindowcrc_entry +.type patch_lastwindowcrc_entry, %function +patch_lastwindowcrc_entry: + // r0=crc_start, r1=datap, r2=size + ldr r3, =0x0207D4F4 // CRC of DS Protect region + cmp r1, r3 + beq crc_dsprotect + + ldr r3, =0x0202DB34 // CRC of other code region (unknown) + cmp r1, r3 + beq crc_unk + + swi #0xE // Normal CRC if address does not match (never hit?) + bx lr + +crc_dsprotect: + ldr r0, =0x67D7 + bx lr + +crc_unk: + ldr r0, =0x0D1E + bx lr + +.pool +.end