From 6d12399ba48d52c55a021675823e81fd00c6547b Mon Sep 17 00:00:00 2001 From: Gericom Date: Sun, 28 Dec 2025 13:18:30 +0100 Subject: [PATCH] Added support for NAND saving in WarioWare D.I.Y. and Jam with the Band (#6) Also improved speed of creating save files --- arm7/source/loader/CardSaveArranger.cpp | 34 ++++++-- arm7/source/loader/DsiWareSaveArranger.cpp | 22 +++-- arm7/source/loader/SaveList.h | 4 +- arm9/source/Arm9Patcher.cpp | 16 ++++ .../{arm7 => }/SaveOffsetToSdSectorAsm.h | 2 +- .../{arm7 => }/SaveOffsetToSdSectorAsm.s | 0 .../arm7/sdk2to4/CardiTaskThreadPatch.cpp | 12 +-- .../arm7/sdk5/CardiDoTaskFromArm9Patch.cpp | 3 +- .../NandSave/JamWithTheBandNandSavePatch.cpp | 57 +++++++++++++ .../NandSave/JamWithTheBandNandSavePatch.h | 12 +++ .../patches/arm9/NandSave/ReadNandSaveAsm.h | 29 +++++++ .../patches/arm9/NandSave/ReadNandSaveAsm.s | 57 +++++++++++++ .../NandSave/WarioWareDiyNandSavePatch.cpp | 85 +++++++++++++++++++ .../arm9/NandSave/WarioWareDiyNandSavePatch.h | 15 ++++ .../patches/arm9/NandSave/WriteNandSaveAsm.h | 29 +++++++ .../patches/arm9/NandSave/WriteNandSaveAsm.s | 57 +++++++++++++ common/CardSaveType.h | 2 +- 17 files changed, 409 insertions(+), 27 deletions(-) rename arm9/source/patches/{arm7 => }/SaveOffsetToSdSectorAsm.h (95%) rename arm9/source/patches/{arm7 => }/SaveOffsetToSdSectorAsm.s (100%) create mode 100644 arm9/source/patches/arm9/NandSave/JamWithTheBandNandSavePatch.cpp create mode 100644 arm9/source/patches/arm9/NandSave/JamWithTheBandNandSavePatch.h create mode 100644 arm9/source/patches/arm9/NandSave/ReadNandSaveAsm.h create mode 100644 arm9/source/patches/arm9/NandSave/ReadNandSaveAsm.s create mode 100644 arm9/source/patches/arm9/NandSave/WarioWareDiyNandSavePatch.cpp create mode 100644 arm9/source/patches/arm9/NandSave/WarioWareDiyNandSavePatch.h create mode 100644 arm9/source/patches/arm9/NandSave/WriteNandSaveAsm.h create mode 100644 arm9/source/patches/arm9/NandSave/WriteNandSaveAsm.s diff --git a/arm7/source/loader/CardSaveArranger.cpp b/arm7/source/loader/CardSaveArranger.cpp index 4299c41..c2b6ba9 100644 --- a/arm7/source/loader/CardSaveArranger.cpp +++ b/arm7/source/loader/CardSaveArranger.cpp @@ -1,4 +1,5 @@ #include "common.h" +#include #include #include "SaveList.h" #include "SaveListFactory.h" @@ -9,10 +10,13 @@ #define DEFAULT_SAVE_SIZE (512 * 1024) #define SAVE_FILL_VALUE 0xFF +static const u8 sNandSaveId[16] = { 0xEC, 0x00, 0x9E, 0xA1, 0x51, 0x65, 0x34, 0x35, 0x30, 0x35, 0x30, 0x31, 0x19, 0x19, 0x02, 0x0A }; + bool CardSaveArranger::SetupCardSave(u32 gameCode, const TCHAR* savePath) const { SaveList* saveList = SaveListFactory().CreateFromFile(SAVE_LIST_PATH); u32 saveSize = DEFAULT_SAVE_SIZE; + auto saveType = CardSaveType::None; if (saveList) { const auto saveListEntry = saveList->FindEntry(gameCode); @@ -23,6 +27,7 @@ bool CardSaveArranger::SetupCardSave(u32 gameCode, const TCHAR* savePath) const } else { + saveType = saveListEntry->GetSaveType(); saveSize = saveListEntry->GetSaveSize(); saveListEntry->Dump(); } @@ -50,8 +55,9 @@ bool CardSaveArranger::SetupCardSave(u32 gameCode, const TCHAR* savePath) const return false; } - auto ffBuffer = std::make_unique(512); - memset(ffBuffer.get(), SAVE_FILL_VALUE, 512); + const u32 ffBufferSize = 32 * 1024; + auto ffBuffer = std::make_unique(ffBufferSize); + memset(ffBuffer.get(), SAVE_FILL_VALUE, ffBufferSize); u32 offset = initialSize; // Align to 512 bytes @@ -68,17 +74,33 @@ bool CardSaveArranger::SetupCardSave(u32 gameCode, const TCHAR* savePath) const offset += remainingTo512; } - // Write in 512-byte blocks + // Write in up to 32kb blocks while (offset < saveSize) { + u32 bytesToWrite = std::min(saveSize - offset, ffBufferSize); UINT bytesWritten = 0; - if (f_write(file.get(), ffBuffer.get(), 512, &bytesWritten) != FR_OK || - bytesWritten != 512) + if (f_write(file.get(), ffBuffer.get(), bytesToWrite, &bytesWritten) != FR_OK || + bytesWritten != bytesToWrite) { LOG_FATAL("Failed to expand save file\n"); return false; } - offset += 512; + offset += bytesToWrite; + } + + if (saveType == CardSaveType::Nand) + { + // NAND save games have a read-only NAND save id at the end of their save area. + // Jam with the Band checks this id and errors if it cannot find it. + // See https://github.com/melonDS-emu/melonDS/blob/3a3388c4c50e8735af125c1af4d89e457f5e9035/src/NDSCart.cpp#L1067 + UINT bytesWritten = 0; + if (f_lseek(file.get(), saveSize - 0x800) != FR_OK || + f_write(file.get(), sNandSaveId, sizeof(sNandSaveId), &bytesWritten) != FR_OK || + bytesWritten != sizeof(sNandSaveId)) + { + LOG_FATAL("Failed to write NAND save id\n"); + return false; + } } } diff --git a/arm7/source/loader/DsiWareSaveArranger.cpp b/arm7/source/loader/DsiWareSaveArranger.cpp index 8e5ba85..0a9198f 100644 --- a/arm7/source/loader/DsiWareSaveArranger.cpp +++ b/arm7/source/loader/DsiWareSaveArranger.cpp @@ -1,5 +1,6 @@ #include "common.h" #include +#include #include #include "DsiWareSaveArranger.h" @@ -70,7 +71,7 @@ bool DsiWareSaveArranger::SetupDsiWareSaveFile(const TCHAR* savePath, u32 saveSi if (f_lseek(file.get(), saveSize) != FR_OK || f_lseek(file.get(), 0) != FR_OK) { - LOG_FATAL("Failed to create private save file\n"); + LOG_FATAL("Failed to create DSiWare save file\n"); return false; } @@ -80,21 +81,28 @@ bool DsiWareSaveArranger::SetupDsiWareSaveFile(const TCHAR* savePath, u32 saveSi if (f_write(file.get(), fatHeader.get(), sizeof(fat_header_t), &bytesWritten) != FR_OK || bytesWritten != sizeof(fat_header_t)) { - LOG_FATAL("Failed to format private save file\n"); + LOG_FATAL("Failed to format DSiWare save file\n"); return false; } - memset(fatHeader.get(), 0, 512); + fatHeader.reset(); - while (f_tell(file.get()) < saveSize) + const u32 clearBufferSize = 32 * 1024; + auto clearBuffer = std::make_unique(clearBufferSize); + memset(clearBuffer.get(), 0, clearBufferSize); + + u32 offset = f_tell(file.get()); + while (offset < saveSize) { + u32 bytesToWrite = std::min(saveSize - offset, clearBufferSize); bytesWritten = 0; - if (f_write(file.get(), fatHeader.get(), 512, &bytesWritten) != FR_OK || - bytesWritten != 512) + if (f_write(file.get(), clearBuffer.get(), bytesToWrite, &bytesWritten) != FR_OK || + bytesWritten != bytesToWrite) { - LOG_FATAL("Failed to format private save file\n"); + LOG_FATAL("Failed to format DSiWare save file\n"); return false; } + offset += bytesToWrite; } } diff --git a/arm7/source/loader/SaveList.h b/arm7/source/loader/SaveList.h index d566298..5b6de36 100644 --- a/arm7/source/loader/SaveList.h +++ b/arm7/source/loader/SaveList.h @@ -7,13 +7,13 @@ class SaveListEntry { u32 gameCode; - u8 saveType; // see CardSaveType + CardSaveType saveType; u8 saveSize; // 0 or 1 << x u8 reserved[2]; // for possible future use public: u32 GetGameCode() const { return gameCode; } - CardSaveType GetSaveType() const { return static_cast(saveType); } + CardSaveType GetSaveType() const { return saveType; } u32 GetSaveSize() const { return saveSize == 0 ? 0 : (1u << saveSize); } void Dump() const diff --git a/arm9/source/Arm9Patcher.cpp b/arm9/source/Arm9Patcher.cpp index 8c1d4f3..12e47c4 100644 --- a/arm9/source/Arm9Patcher.cpp +++ b/arm9/source/Arm9Patcher.cpp @@ -13,6 +13,8 @@ #include "patches/arm9/OSResetSystemPatch.h" #include "patches/arm9/PokemonDownloaderArm9Patch.h" #include "patches/arm9/DSProtectArm9Patch.h" +#include "patches/arm9/NandSave/JamWithTheBandNandSavePatch.h" +#include "patches/arm9/NandSave/WarioWareDiyNandSavePatch.h" #include "patches/arm9/OverlayPatches/FsStartOverlayHookPatch.h" #include "patches/arm9/OverlayPatches/DSProtectPatches/DSProtectOverlayPatch.h" #include "patches/arm9/OverlayPatches/DSProtectPatches/DSProtectPuyoPuyo7Patch.h" @@ -374,6 +376,20 @@ void Arm9Patcher::AddGameSpecificPatches( overlayHookPatch->AddOverlayPatch(new PokemonIrApPatch(PokemonIrVersion::Bw2)); break; } + // WarioWare: D.I.Y. + case GAMECODE("UORE"): + case GAMECODE("UORP"): + case GAMECODE("UORJ"): + { + patchCollection.AddPatch(new WarioWareDiyNandSavePatch()); + break; + } + // Jam with the Band + case GAMECODE("UXBP"): + { + patchCollection.AddPatch(new JamWithTheBandNandSavePatch()); + break; + } } } diff --git a/arm9/source/patches/arm7/SaveOffsetToSdSectorAsm.h b/arm9/source/patches/SaveOffsetToSdSectorAsm.h similarity index 95% rename from arm9/source/patches/arm7/SaveOffsetToSdSectorAsm.h rename to arm9/source/patches/SaveOffsetToSdSectorAsm.h index 7bbc47b..de22ee1 100644 --- a/arm9/source/patches/arm7/SaveOffsetToSdSectorAsm.h +++ b/arm9/source/patches/SaveOffsetToSdSectorAsm.h @@ -1,6 +1,6 @@ #pragma once #include "sections.h" -#include "../SectorRemapPatchCode.h" +#include "SectorRemapPatchCode.h" #include "fileInfo.h" DEFINE_SECTION_SYMBOLS(saveoffsettosdsector); diff --git a/arm9/source/patches/arm7/SaveOffsetToSdSectorAsm.s b/arm9/source/patches/SaveOffsetToSdSectorAsm.s similarity index 100% rename from arm9/source/patches/arm7/SaveOffsetToSdSectorAsm.s rename to arm9/source/patches/SaveOffsetToSdSectorAsm.s diff --git a/arm9/source/patches/arm7/sdk2to4/CardiTaskThreadPatch.cpp b/arm9/source/patches/arm7/sdk2to4/CardiTaskThreadPatch.cpp index 6b9a136..b5bdac3 100644 --- a/arm9/source/patches/arm7/sdk2to4/CardiTaskThreadPatch.cpp +++ b/arm9/source/patches/arm7/sdk2to4/CardiTaskThreadPatch.cpp @@ -8,8 +8,7 @@ #include "patches/arm7/VerifySaveAsm.h" #include "patches/FunctionSignature.h" #include "patches/platform/LoaderPlatform.h" -#include "patches/arm7/SaveOffsetToSdSectorAsm.h" -#include "patches/OffsetToSectorRemapAsm.h" +#include "patches/SaveOffsetToSdSectorAsm.h" #include "patches/arm7/CardiTaskThreadPatchAsm.h" #include "thumbInstructions.h" #include "CardiTaskThreadPatch.h" @@ -95,14 +94,11 @@ void CardiTaskThreadPatch::ApplyPatch(PatchContext& patchContext) 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( + auto readPatchCode = loaderPlatform->CreateSdReadPatchCode( patchContext.GetPatchCodeCollection(), patchContext.GetPatchHeap()); - writePatchCode = loaderPlatform->CreateSdWritePatchCode( + auto writePatchCode = loaderPlatform->CreateSdWritePatchCode( patchContext.GetPatchCodeCollection(), patchContext.GetPatchHeap()); - sectorRemapPatchCode = patchContext.GetPatchCodeCollection().AddUniquePatchCode + auto sectorRemapPatchCode = patchContext.GetPatchCodeCollection().AddUniquePatchCode ( patchContext.GetPatchHeap(), (const save_file_info_t*)((u32)SHARED_SAVE_FILE_INFO - 0x02F00000 + 0x02700000) diff --git a/arm9/source/patches/arm7/sdk5/CardiDoTaskFromArm9Patch.cpp b/arm9/source/patches/arm7/sdk5/CardiDoTaskFromArm9Patch.cpp index 9f9fcf2..105f1d0 100644 --- a/arm9/source/patches/arm7/sdk5/CardiDoTaskFromArm9Patch.cpp +++ b/arm9/source/patches/arm7/sdk5/CardiDoTaskFromArm9Patch.cpp @@ -5,9 +5,8 @@ #include "patches/arm7/ReadSaveAsm.h" #include "patches/arm7/WriteSaveAsm.h" #include "patches/arm7/VerifySaveAsm.h" -#include "patches/arm7/SaveOffsetToSdSectorAsm.h" +#include "patches/SaveOffsetToSdSectorAsm.h" #include "patches/platform/LoaderPlatform.h" -#include "patches/OffsetToSectorRemapAsm.h" #include "patches/arm7/CardiTaskThreadPatchAsm.h" #include "CardiDoTaskFromArm9Patch.h" diff --git a/arm9/source/patches/arm9/NandSave/JamWithTheBandNandSavePatch.cpp b/arm9/source/patches/arm9/NandSave/JamWithTheBandNandSavePatch.cpp new file mode 100644 index 0000000..61eb75d --- /dev/null +++ b/arm9/source/patches/arm9/NandSave/JamWithTheBandNandSavePatch.cpp @@ -0,0 +1,57 @@ +#include "common.h" +#include "gameCode.h" +#include "patches/platform/LoaderPlatform.h" +#include "patches/SaveOffsetToSdSectorAsm.h" +#include "ReadNandSaveAsm.h" +#include "WriteNandSaveAsm.h" +#include "JamWithTheBandNandSavePatch.h" + +// This code was based on nds-bootstrap: +// https://github.com/DS-Homebrew/nds-bootstrap/blob/89f27d1392a68436695d0050992ee84258ef41bc/retail/bootloader/source/arm7/patch_arm9.c#L2531 + +bool JamWithTheBandNandSavePatch::FindPatchTarget(PatchContext& patchContext) +{ + return true; +} + +void JamWithTheBandNandSavePatch::ApplyPatch(PatchContext& patchContext) +{ + auto sectorRemapPatchCode = patchContext.GetPatchCodeCollection().AddUniquePatchCode + ( + patchContext.GetPatchHeap(), + (const save_file_info_t*)((u32)SHARED_SAVE_FILE_INFO - 0x02F00000 + 0x02700000) + ); + auto loaderPlatform = patchContext.GetLoaderPlatform(); + auto readNandSavePatchCode = patchContext.GetPatchCodeCollection().AddUniquePatchCode + ( + patchContext.GetPatchHeap(), + sectorRemapPatchCode, + loaderPlatform->CreateSdReadPatchCode(patchContext.GetPatchCodeCollection(), patchContext.GetPatchHeap()) + ); + auto writeNandSavePatchCode = patchContext.GetPatchCodeCollection().AddUniquePatchCode + ( + patchContext.GetPatchHeap(), + sectorRemapPatchCode, + loaderPlatform->CreateSdWritePatchCode(patchContext.GetPatchCodeCollection(), patchContext.GetPatchHeap()) + ); + + // u32 nandInit(void* data) + *(u32*)0x020613CC = 0xE3A00001; // mov r0, #1 + *(u32*)0x020613D0 = 0xE12FFF1E; // bx lr + + // u32 nandResume(void) + *(u32*)0x02061A4C = 0xE3A00000; // mov r0, #0 + *(u32*)0x02061A50 = 0xE12FFF1E; // bx lr + + // u32 nandError(void) + *(u32*)0x02061C24 = 0xE3A00000; // mov r0, #0 + *(u32*)0x02061C28 = 0xE12FFF1E; // bx lr + + // u32 nandWrite(const void* memory, u32 flash, u32 size, u32 dmaChannel) + *(u32*)0x0206176C = 0xE51FF004; // ldr pc,= patch_writeNandSave + *(u32*)0x02061770 = (u32)writeNandSavePatchCode->GetWriteNandSaveFunction(); + + // u32 nandRead(void* memory, u32 flash, u32 size, u32 dmaChannel) + *(u32*)0x02061AC4 = 0xE51FF004; // ldr pc,= patch_readNandSave + *(u32*)0x02061AC8 = (u32)readNandSavePatchCode->GetReadNandSaveFunction(); +} diff --git a/arm9/source/patches/arm9/NandSave/JamWithTheBandNandSavePatch.h b/arm9/source/patches/arm9/NandSave/JamWithTheBandNandSavePatch.h new file mode 100644 index 0000000..d5606cf --- /dev/null +++ b/arm9/source/patches/arm9/NandSave/JamWithTheBandNandSavePatch.h @@ -0,0 +1,12 @@ +#pragma once +#include "patches/Patch.h" + +class FunctionSignature; + +/// @brief Arm9 patch to redirect Jam with the Band nand saving to the SD card. +class JamWithTheBandNandSavePatch : public Patch +{ +public: + bool FindPatchTarget(PatchContext& patchContext) override; + void ApplyPatch(PatchContext& patchContext) override; +}; diff --git a/arm9/source/patches/arm9/NandSave/ReadNandSaveAsm.h b/arm9/source/patches/arm9/NandSave/ReadNandSaveAsm.h new file mode 100644 index 0000000..213a22a --- /dev/null +++ b/arm9/source/patches/arm9/NandSave/ReadNandSaveAsm.h @@ -0,0 +1,29 @@ +#pragma once +#include "patches/PatchCode.h" +#include "sections.h" +#include "patches/SaveOffsetToSdSectorAsm.h" +#include "patches/platform/SdReadPatchCode.h" + +DEFINE_SECTION_SYMBOLS(patch_readnandsave); + +extern "C" bool patch_readNandSave(void* dst, u32 nandByteOffset, u32 byteLength, u32 dmaChannel); + +extern u32 patch_readNandSave_save_offset_to_sd_sector_asm_address; +extern u32 patch_readNandSave_sdread_asm_address; + +class ReadNandSavePatchCode : public PatchCode +{ +public: + ReadNandSavePatchCode(PatchHeap& patchHeap, const SaveOffsetToSdSectorPatchCode* saveOffsetToSdSectorPatchCode, + const SdReadPatchCode* sdReadPatchCode) + : PatchCode(SECTION_START(patch_readnandsave), SECTION_SIZE(patch_readnandsave), patchHeap) + { + patch_readNandSave_save_offset_to_sd_sector_asm_address = (u32)saveOffsetToSdSectorPatchCode->GetRemapFunction(); + patch_readNandSave_sdread_asm_address = (u32)sdReadPatchCode->GetSdReadFunction(); + } + + const void* GetReadNandSaveFunction() const + { + return GetAddressAtTarget((void*)patch_readNandSave); + } +}; diff --git a/arm9/source/patches/arm9/NandSave/ReadNandSaveAsm.s b/arm9/source/patches/arm9/NandSave/ReadNandSaveAsm.s new file mode 100644 index 0000000..774bcf8 --- /dev/null +++ b/arm9/source/patches/arm9/NandSave/ReadNandSaveAsm.s @@ -0,0 +1,57 @@ +.cpu arm946e-s +.section "patch_readnandsave", "ax" +.syntax unified +.thumb + +// r0 = memory dst +// r1 = nand byte offset +// r2 = byte length +// returns r0 = bool success +.global patch_readNandSave +.type patch_readNandSave, %function +patch_readNandSave: + push {r0,r1,r2,r4,r5,r6,lr} + pop {r4,r5,r6} + lsrs r6, r6, #9 // remaining number of sectors = byte length / 512 +loop: + // while remaining number of sectors > 0 + cmp r6, #0 + beq end + + movs r0, r5 // nand byte offset + ldr r3, patch_readNandSave_save_offset_to_sd_sector_asm_address + blx r3 + + mov r2, lr // sectors to read + // if sectors to read > remaining number of sectors + cmp r2, r6 + bls 1f + movs r2, r6 // sectors to read = remaining number of sectors +1: + subs r6, r2 // remaining number of sectors -= sectors to read + movs r1, r4 // memory dst + lsls r3, r2, #9 + adds r4, r3 // memory dst += sectors to read * 512 + adds r5, r3 // nand byte offset += sectors to read * 512 + + ldr r3, patch_readNandSave_sdread_asm_address + blx r3 + + b loop + +end: + movs r0, #1 + pop {r4,r5,r6,pc} + +.balign 4 +.pool + +.global patch_readNandSave_save_offset_to_sd_sector_asm_address +patch_readNandSave_save_offset_to_sd_sector_asm_address: + .word 0 + +.global patch_readNandSave_sdread_asm_address +patch_readNandSave_sdread_asm_address: + .word 0 + +.end diff --git a/arm9/source/patches/arm9/NandSave/WarioWareDiyNandSavePatch.cpp b/arm9/source/patches/arm9/NandSave/WarioWareDiyNandSavePatch.cpp new file mode 100644 index 0000000..121d1f7 --- /dev/null +++ b/arm9/source/patches/arm9/NandSave/WarioWareDiyNandSavePatch.cpp @@ -0,0 +1,85 @@ +#include "common.h" +#include "gameCode.h" +#include "patches/platform/LoaderPlatform.h" +#include "patches/SaveOffsetToSdSectorAsm.h" +#include "ReadNandSaveAsm.h" +#include "WriteNandSaveAsm.h" +#include "WarioWareDiyNandSavePatch.h" + +// This code was based on nds-bootstrap: +// https://github.com/DS-Homebrew/nds-bootstrap/blob/89f27d1392a68436695d0050992ee84258ef41bc/retail/bootloader/source/arm7/patch_arm9.c#L2531 + +bool WarioWareDiyNandSavePatch::FindPatchTarget(PatchContext& patchContext) +{ + _sdPatchEntry = nullptr; + switch (patchContext.GetGameCode()) + { + case GAMECODE("UORE"): + { + _sdPatchEntry = (u8*)0x02002C04; + break; + } + case GAMECODE("UORP"): + { + _sdPatchEntry = (u8*)0x02002CA4; + break; + } + case GAMECODE("UORJ"): + { + _sdPatchEntry = (u8*)0x02002BE4; + break; + } + } + + return _sdPatchEntry != nullptr; +} + +void WarioWareDiyNandSavePatch::ApplyPatch(PatchContext& patchContext) +{ + if (_sdPatchEntry == nullptr) + { + return; + } + + auto sectorRemapPatchCode = patchContext.GetPatchCodeCollection().AddUniquePatchCode + ( + patchContext.GetPatchHeap(), + (const save_file_info_t*)((u32)SHARED_SAVE_FILE_INFO - 0x02F00000 + 0x02700000) + ); + auto loaderPlatform = patchContext.GetLoaderPlatform(); + auto readNandSavePatchCode = patchContext.GetPatchCodeCollection().AddUniquePatchCode + ( + patchContext.GetPatchHeap(), + sectorRemapPatchCode, + loaderPlatform->CreateSdReadPatchCode(patchContext.GetPatchCodeCollection(), patchContext.GetPatchHeap()) + ); + auto writeNandSavePatchCode = patchContext.GetPatchCodeCollection().AddUniquePatchCode + ( + patchContext.GetPatchHeap(), + sectorRemapPatchCode, + loaderPlatform->CreateSdWritePatchCode(patchContext.GetPatchCodeCollection(), patchContext.GetPatchHeap()) + ); + + // u32 nandInit(void* data) + *(u32*)(_sdPatchEntry + 0x50C) = 0xE3A00001; // mov r0, #1 + *(u32*)(_sdPatchEntry + 0x510) = 0xE12FFF1E; // bx lr + + // u32 nandWait(void) + *(u32*)(_sdPatchEntry + 0xC9C) = 0xE12FFF1E; // bx lr + + // u32 nandState(void) + *(u32*)(_sdPatchEntry + 0xEB0) = 0xE3A00003; // mov r0, #3 + *(u32*)(_sdPatchEntry + 0xEB4) = 0xE12FFF1E; // bx lr + + // u32 nandError(void) + *(u32*)(_sdPatchEntry + 0xEC8) = 0xE3A00000; // mov r0, #0 + *(u32*)(_sdPatchEntry + 0xECC) = 0xE12FFF1E; // bx lr + + // u32 nandWrite(void* memory, u32 flash, u32 size, u32 dmaChannel) + *(u32*)(_sdPatchEntry + 0x958) = 0xE51FF004; // ldr pc,= patch_writeNandSave + *(u32*)(_sdPatchEntry + 0x95C) = (u32)writeNandSavePatchCode->GetWriteNandSaveFunction(); + + // u32 nandRead(void* memory, u32 flash, u32 size, u32 dmaChannel) + *(u32*)(_sdPatchEntry + 0xD24) = 0xE51FF004; // ldr pc,= patch_readNandSave + *(u32*)(_sdPatchEntry + 0xD28) = (u32)readNandSavePatchCode->GetReadNandSaveFunction(); +} diff --git a/arm9/source/patches/arm9/NandSave/WarioWareDiyNandSavePatch.h b/arm9/source/patches/arm9/NandSave/WarioWareDiyNandSavePatch.h new file mode 100644 index 0000000..08946c5 --- /dev/null +++ b/arm9/source/patches/arm9/NandSave/WarioWareDiyNandSavePatch.h @@ -0,0 +1,15 @@ +#pragma once +#include "patches/Patch.h" + +class FunctionSignature; + +/// @brief Arm9 patch to redirect WarioWare D.I.Y. nand saving to the SD card. +class WarioWareDiyNandSavePatch : public Patch +{ +public: + bool FindPatchTarget(PatchContext& patchContext) override; + void ApplyPatch(PatchContext& patchContext) override; + +private: + u8* _sdPatchEntry = nullptr; +}; diff --git a/arm9/source/patches/arm9/NandSave/WriteNandSaveAsm.h b/arm9/source/patches/arm9/NandSave/WriteNandSaveAsm.h new file mode 100644 index 0000000..21e1116 --- /dev/null +++ b/arm9/source/patches/arm9/NandSave/WriteNandSaveAsm.h @@ -0,0 +1,29 @@ +#pragma once +#include "patches/PatchCode.h" +#include "sections.h" +#include "patches/SaveOffsetToSdSectorAsm.h" +#include "patches/platform/SdWritePatchCode.h" + +DEFINE_SECTION_SYMBOLS(patch_writenandsave); + +extern "C" bool patch_writeNandSave(const void* src, u32 nandByteOffset, u32 byteLength, u32 dmaChannel); + +extern u32 patch_writeNandSave_save_offset_to_sd_sector_asm_address; +extern u32 patch_writeNandSave_sdwrite_asm_address; + +class WriteNandSavePatchCode : public PatchCode +{ +public: + WriteNandSavePatchCode(PatchHeap& patchHeap, const SaveOffsetToSdSectorPatchCode* saveOffsetToSdSectorPatchCode, + const SdWritePatchCode* sdWritePatchCode) + : PatchCode(SECTION_START(patch_writenandsave), SECTION_SIZE(patch_writenandsave), patchHeap) + { + patch_writeNandSave_save_offset_to_sd_sector_asm_address = (u32)saveOffsetToSdSectorPatchCode->GetRemapFunction(); + patch_writeNandSave_sdwrite_asm_address = (u32)sdWritePatchCode->GetSdWriteFunction(); + } + + const void* GetWriteNandSaveFunction() const + { + return GetAddressAtTarget((void*)patch_writeNandSave); + } +}; diff --git a/arm9/source/patches/arm9/NandSave/WriteNandSaveAsm.s b/arm9/source/patches/arm9/NandSave/WriteNandSaveAsm.s new file mode 100644 index 0000000..0e08c3b --- /dev/null +++ b/arm9/source/patches/arm9/NandSave/WriteNandSaveAsm.s @@ -0,0 +1,57 @@ +.cpu arm946e-s +.section "patch_writenandsave", "ax" +.syntax unified +.thumb + +// r0 = memory src +// r1 = nand byte offset +// r2 = byte length +// returns r0 = bool success +.global patch_writeNandSave +.type patch_writeNandSave, %function +patch_writeNandSave: + push {r0,r1,r2,r4,r5,r6,lr} + pop {r4,r5,r6} + lsrs r6, r6, #9 // remaining number of sectors = byte length / 512 +loop: + // while remaining number of sectors > 0 + cmp r6, #0 + beq end + + movs r0, r5 // nand byte offset + ldr r3, patch_writeNandSave_save_offset_to_sd_sector_asm_address + blx r3 + + mov r2, lr // sectors to read + // if sectors to read > remaining number of sectors + cmp r2, r6 + bls 1f + movs r2, r6 // sectors to read = remaining number of sectors +1: + subs r6, r2 // remaining number of sectors -= sectors to read + movs r1, r4 // memory src + lsls r3, r2, #9 + adds r4, r3 // memory src += sectors to read * 512 + adds r5, r3 // nand byte offset += sectors to read * 512 + + ldr r3, patch_writeNandSave_sdwrite_asm_address + blx r3 + + b loop + +end: + movs r0, #1 + pop {r4,r5,r6,pc} + +.balign 4 +.pool + +.global patch_writeNandSave_save_offset_to_sd_sector_asm_address +patch_writeNandSave_save_offset_to_sd_sector_asm_address: + .word 0 + +.global patch_writeNandSave_sdwrite_asm_address +patch_writeNandSave_sdwrite_asm_address: + .word 0 + +.end diff --git a/common/CardSaveType.h b/common/CardSaveType.h index 928c8c6..f4d864a 100644 --- a/common/CardSaveType.h +++ b/common/CardSaveType.h @@ -1,7 +1,7 @@ #pragma once /// @brief Enum representing the save type. -enum class CardSaveType +enum class CardSaveType : u8 { /// @brief The game has no save. None = 0,