diff --git a/README.md b/README.md index 7634042..65b2a9c 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,105 @@ protection since it won't interfere with the system. As a bonus, if you sell/trade you console in the future and the new owner uses the official installer, they'll be protected from bricks. +## Patches applied to unlaunch +The installer ships with 2 binary patches, one is an "ahestetic" one to enable +the dsi H&S screen and sound. +The other is a mandatory one, required to make unlaunch properly use the tmd that +was installed in the HNAA folder, otherwise it would attempt to save its settings +to random fat blocks, since it assumes that unlaunch is installed in the blocks right +after the title.tmd associated with the launcher read from HWINFO_S. +More specifically, the sound and splash patch modifies the arm7 instruction of unlaunch at +address 0x1308 (in the then relocated code it is run at 0x23fe038). The patched instruction +is a `bl` to the function patching a second binary blob of the launcher, and is replaced with +a nop. +The other patch, modifies the code responsible for reading the launcher title id from HWINFO. +The original code is as follow +``` +@ this loads in r0 the buffer containing the contents read +@ from HWINFO_S, offsetted at the position of the title id +FC 05 9F E5 ldr r0, [pc, #0x5fc] +FC 15 9F E5 ldr r1, [pc, #0x5fc] +@ This reads the title id and then stores it to the address +@ pointed by r1, which is then used across the whole program +00 00 90 E5 ldr r0, [r0] +00 00 81 E5 str r0, [r1] +F4 05 9F E5 ldr r0, [pc, #0x5f4] +9d 01 00 EB bl #0x602a3c4 +C5 FD FF EB bl #0x602946c +EC 05 9F E5 ldr r0, [pc, #0x5ec] +00 00 90 E5 ldr r0, [r0] +E8 15 9F E5 ldr r1, [pc, #0x5e8] +66 00 00 EB bl #0x6029f00 +E4 05 9F E5 ldr r0, [pc, #0x5e4] +E4 15 9F E5 ldr r1, [pc, #0x5e4] +14 20 A0 E3 mov r2, #0x14 +0C 09 00 EB bl #0x602c1a8 +DC 05 9F E5 ldr r0, [pc, #0x5dc] +91 01 00 EB bl #0x602a3c4 +BA FD FF EB bl #0x602946c +D4 05 9F E5 ldr r0, [pc, #0x5d4] +00 00 90 E5 ldr r0, [r0] +D0 15 9F E5 ldr r1, [pc, #0x5d0] +02 2C A0 E3 mov r2, #0x200 +5B 00 00 EB bl #0x6029f04 +C8 15 9F E5 ldr r1, [pc, #0x5c8] +E4 01 91 E5 ldr r0, [r1, #0x1e4] +72 08 00 EB bl #0x602bf6c +C0 15 9F E5 ldr r1, [pc, #0x5c0] +19 00 00 EB bl #0x6029e10 +00 00 A0 E3 mov r0, #0 +01 00 C1 E4 strb r0, [r1], #1 +B4 05 9F E5 ldr r0, [pc, #0x5b4] +93 01 D0 E5 ldrb r0, [r0, #0x193] +01 00 C1 E4 strb r0, [r1], #1 +FF 9F BD E8 pop {r0, r1, r2, r3, r4, r5, r6, r7, r8, sb, sl, fp, ip, pc} +``` +The patched code is +``` +@ Instead of reading the title id from the HWINFO_S buffer, +@ read the hardcoded value placed at the bottom. +@ all the relative addresses have been incremented by 4 to account +@ for the shift of the instructions (same for the relative bl instructions) +7C 00 9F E5 ldr r0, [pc, #0x7c] +FC 15 9F E5 ldr r1, [pc, #0x5fc] +00 00 81 E5 str r0, [r1] +F8 05 9F E5 ldr r0, [pc, #0x5f8] +9D 01 00 EB bl #0x602a3c4 +C6 FD FF EB bl #0x602946c +F0 05 9F E5 ldr r0, [pc, #0x5f0] +00 00 90 E5 ldr r0, [r0] +EC 15 9F E5 ldr r1, [pc, #0x5ec] +67 00 00 EB bl #0x6029f00 +E8 05 9F E5 ldr r0, [pc, #0x5e8] +E8 15 9F E5 ldr r1, [pc, #0x5e8] +14 20 A0 E3 mov r2, #0x14 +0D 09 00 EB bl #0x602c1a8 +E0 05 9F E5 ldr r0, [pc, #0x5e0] +92 01 00 EB bl #0x602a3c4 +BB FD FF EB bl #0x602946c +D8 05 9F E5 ldr r0, [pc, #0x5d8] +00 00 90 E5 ldr r0, [r0] +D4 15 9F E5 ldr r1, [pc, #0x5d4] +02 2C A0 E3 mov r2, #0x200 +5C 00 00 EB bl #0x6029f04 +CC 15 9F E5 ldr r1, [pc, #0x5cc] +E8 01 91 E5 ldr r0, [r1, #0x1e8] +73 08 00 EB bl #0x602bf6c +C4 15 9F E5 ldr r1, [pc, #0x5c4] +1A 00 00 EB bl #0x6029e10 +00 00 A0 E3 mov r0, #0 +01 00 C1 E4 strb r0, [r1], #1 +B8 05 9F E5 ldr r0, [pc, #0x5b8] +97 01 D0 E5 ldrb r0, [r0, #0x197] +01 00 C1 E4 strb r0, [r1], #1 +FF 9F BD E8 pop {r0, r1, r2, r3, r4, r5, r6, r7, r8, sb, sl, fp, ip, pc} +41 41 4E 48 .word 0x484e4141 +``` +The patched code is found at address `0x60a7` in the decompressed binary, and is loaded into +ram at address `0x6029d38`. + +The lzss compressed arm7 payload is found at offset `0x8580` for unlaunch 2.0 with a length of `0x67FD`. + ## Credits - [DevkitPro](https://devkitpro.org/): devkitARM and libnds - [Martin Korth (nocash)](https://problemkaputt.de): diff --git a/arm9/src/main.cpp b/arm9/src/main.cpp index bd2a114..848ecc0 100644 --- a/arm9/src/main.cpp +++ b/arm9/src/main.cpp @@ -90,7 +90,7 @@ static void setupScreens() static int mainMenu(const consoleInfo& info, int cursor) { - const auto tidPatchesSupported = (foundUnlaunchInstallerVersion == v1_9 || foundUnlaunchInstallerVersion == v2_0) && isLauncherVersionSupported; + const auto tidPatchesSupported = (foundUnlaunchInstallerVersion == v2_0) && isLauncherVersionSupported; //top screen clearScreen(&topScreen); @@ -352,12 +352,13 @@ void loadUnlaunchInstaller() { } void loadUnlaunchInstallerPatch() { - if (fileExists("sd:/unlaunch-patch.bin")) { - splashSoundBinaryPatchPath = "sd:/unlaunch-patch.bin"; + if (fileExists("sd:/sound-and-splash-patch.bin")) { + splashSoundBinaryPatchPath = "sd:/sound-and-splash-patch.bin"; + } else if(fileExists("nitro:/sound-and-splash-patch.bin")) { + splashSoundBinaryPatchPath = "nitro:/sound-and-splash-patch.bin"; } - else if(fileExists("nitro:/unlaunch-patch.bin")) - { - splashSoundBinaryPatchPath = "nitro:/unlaunch-patch.bin"; + if(!fileExists("nitro:/force-hnaa-patch.bin")) { + throw std::runtime_error(std::format("Failed to find hnaa patch ({})", "nitro:/force-hnaa-patch.bin")); } } @@ -657,7 +658,7 @@ void doMainMenu(consoleInfo& info) { { break; } - if(advancedOptionsUnlocked && (foundUnlaunchInstallerVersion == v1_9 || foundUnlaunchInstallerVersion == v2_0)) { + if(advancedOptionsUnlocked && (foundUnlaunchInstallerVersion == v2_0)) { disableAllPatches = !disableAllPatches; } break; @@ -700,11 +701,12 @@ int main(int argc, char **argv) } loadUnlaunchInstaller(); - loadUnlaunchInstallerPatch(); - - consoleInfo info; try { + loadUnlaunchInstallerPatch(); + + consoleInfo info; + retrieveInstalledLauncherInfo(info); checkNocashFooter(info); diff --git a/arm9/src/unlaunch.cpp b/arm9/src/unlaunch.cpp index 45e81c4..79495fb 100644 --- a/arm9/src/unlaunch.cpp +++ b/arm9/src/unlaunch.cpp @@ -30,19 +30,19 @@ constexpr std::array knownUnlaunchHashes{ /*"0525b28cc59b6f7fc00ad592aebadd7257bf7efb"_sha1, // v1.5: blacklisted, doesn't like this install method*/ /*"9470a51fde188235052b119f6bfabf6689cb2343"_sha1, // v1.6: blacklisted, doesn't like this install method*/ /*"672c11eb535b97b0d32ff580d314a2ad6411d5fe"_sha1, // v1.7: blacklisted, doesn't like this install method*/ - "b76c2b1722e769c6c0b4b3d4bc73250e41993229"_sha1, // v1.8 - "f3eb41cba136a3477523155f8b05df14917c55f4"_sha1, // v1.9 + /*"b76c2b1722e769c6c0b4b3d4bc73250e41993229"_sha1, // v1.8: blacklisted, the HNAA patch is only done for 2.0 */ + /*"f3eb41cba136a3477523155f8b05df14917c55f4"_sha1, // v1.9: blacklisted, the HNAA patch is only done for 2.0 */ "15f4a36251d1408d71114019b2825fe8f5b4c8cc"_sha1, // v2.0 }; constexpr std::array gifOffsets{ - std::make_pair(0x48d4, 0x8540), /* 1.8 */ - std::make_pair(0x48c8, 0x8534), /* 1.9 */ + /* std::make_pair(0x48d4, 0x8540),*/ /* 1.8 */ + /* std::make_pair(0x48c8, 0x8534),*/ /* 1.9 */ std::make_pair(0x48f0, 0x855c), /* 2.0 */ }; constexpr std::array blockAllPatchesOffset{ - 0xae74, /* 1.9 */ + /* 0xae74, */ /* 1.9 */ 0xae91, /* 2.0 */ }; @@ -476,55 +476,56 @@ static bool patchCustomBackground(const char* customBackgroundPath) return true; } +static bool applyBinaryPatch(const char* path) +{ + static constexpr auto lzssCompressedBinaryOffset = 0x8580; + static constexpr auto lzssCompressedBinarySize = 0x67FD; + auto* patch = fopen(path, "rb"); + if(!patch) + { + messageBox("\x1B[31mError:\x1B[33m Failed to open the patch.\n"); + return false; + } + auto patchSize = getFileSize(patch); + if(patchSize > lzssCompressedBinarySize) + { + messageBox("\x1B[31mError:\x1B[33m Patch is too big.\n"); + fclose(patch); + return false; + } + if (fread((unlaunchInstallerBuffer + 520) + lzssCompressedBinaryOffset, 1, patchSize, patch) != patchSize) + { + messageBox("\x1B[31mError:\x1B[33m Failed to read patch.\n"); + fclose(patch); + return false; + } + fclose(patch); + return true; +} + static bool patchUnlaunchInstaller(bool disableAllPatches, const char* splashSoundBinaryPatchPath, const char* customBackgroundPath) { tonccpy(unlaunchInstallerBuffer, ogUnlaunchInstallerBuffer, sizeof(unlaunchInstallerBuffer)); - if(disableAllPatches) - { - if(installerVersion == v1_8) - { - messageBox("\x1B[31mError:\x1B[33m Unlaunch 1.8 can't be patched\n"); - return false; - } - // change launcher TID from ANH to SAN so that unlaunch doesn't realize it's booting the launcher - auto patchOffset = blockAllPatchesOffset[installerVersion - 1]; - const char newID[]{'S','A','N'}; - memcpy((unlaunchInstallerBuffer + 520) + patchOffset, newID, 3); - } - else if (splashSoundBinaryPatchPath) - { - if(installerVersion != v2_0) - { - messageBox("\x1B[31mError:\x1B[33m The splash and sound patch is\n" - "only for unlaunch 2.0\n"); - return false; - } - static constexpr auto patchOffset = 0x8580; - static constexpr auto patchSectionSize = 0x67FD; - auto* patch = fopen(splashSoundBinaryPatchPath, "rb"); - if(!patch) - { - messageBox("\x1B[31mError:\x1B[33m Failed to open the splash and\n" - "sound patch is.\n"); - return false; - } - auto patchSize = getFileSize(patch); - if(patchSize > patchSectionSize) - { - messageBox("\x1B[31mError:\x1B[33m Splash and sound patch is too\n" - "big.\n"); - fclose(patch); - return false; - } - if (fread((unlaunchInstallerBuffer + 520) + patchOffset, 1, patchSize, patch) != patchSize) - { - messageBox("\x1B[31mError:\x1B[33m Failed to read splash and sound\n" - "patch.\n"); - fclose(patch); - return false; - } - fclose(patch); - } + if (splashSoundBinaryPatchPath) + { + iprintf("Applying splash and sound patch\n"); + if(!applyBinaryPatch(splashSoundBinaryPatchPath)) + { + return false; + } + } else { + if(disableAllPatches) { + // change launcher TID from ANH to SAN so that unlaunch doesn't realize it's booting the launcher + auto patchOffset = blockAllPatchesOffset[installerVersion]; + const char newID[]{'S','A','N'}; + memcpy((unlaunchInstallerBuffer + 520) + patchOffset, newID, 3); + } + iprintf("Applying HNAA patch\n"); + if(!applyBinaryPatch("nitro:/force-hnaa-patch.bin")) + { + return false; + } + } if(!patchCustomBackground(customBackgroundPath)) { return false; @@ -543,8 +544,6 @@ UNLAUNCH_VERSION loadUnlaunchInstaller(std::string_view path) } std::array unlaunchVersionStrings{ - "v1.8", - "v1.9", "v2.0", "INVALID", }; diff --git a/arm9/src/unlaunch.h b/arm9/src/unlaunch.h index b179d2b..f2443a1 100644 --- a/arm9/src/unlaunch.h +++ b/arm9/src/unlaunch.h @@ -5,8 +5,6 @@ #include "consoleInfo.h" typedef enum UNLAUNCH_VERSION { - v1_8, - v1_9, v2_0, INVALID, } UNLAUNCH_VERSION; diff --git a/nitrofiles/force-hnaa-patch.bin b/nitrofiles/force-hnaa-patch.bin new file mode 100644 index 0000000..2715023 Binary files /dev/null and b/nitrofiles/force-hnaa-patch.bin differ diff --git a/nitrofiles/unlaunch-patch.bin b/nitrofiles/sound-and-splash-patch.bin similarity index 100% rename from nitrofiles/unlaunch-patch.bin rename to nitrofiles/sound-and-splash-patch.bin