diff --git a/arm7/source/loader/CardSaveArranger.cpp b/arm7/source/loader/CardSaveArranger.cpp index c2b6ba9..9a0d41f 100644 --- a/arm7/source/loader/CardSaveArranger.cpp +++ b/arm7/source/loader/CardSaveArranger.cpp @@ -4,34 +4,51 @@ #include "SaveList.h" #include "SaveListFactory.h" #include "fileInfo.h" +#include "gameCode.h" #include "CardSaveArranger.h" #define SAVE_LIST_PATH "/_pico/savelist.bin" #define DEFAULT_SAVE_SIZE (512 * 1024) #define SAVE_FILL_VALUE 0xFF +#define NTR_NAND_BLOCK_SIZE 0x20000 +#define TWL_NAND_BLOCK_SIZE 0x80000 +#define NAND_RW_REGION_END 0x07A00000 -static const u8 sNandSaveId[16] = { 0xEC, 0x00, 0x9E, 0xA1, 0x51, 0x65, 0x34, 0x35, 0x30, 0x35, 0x30, 0x31, 0x19, 0x19, 0x02, 0x0A }; +static const u8 sBandBrothersSaveId[12] = { 0x48, 0x8A, 0x00, 0x00, 0x42, 0x42, 0x44, 0x58, 0x31, 0x32, 0x33, 0x34 }; +static const u8 sJamWithTheBandSaveId[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 +bool CardSaveArranger::SetupCardSave(const nds_header_ntr_t* header, const TCHAR* savePath) const { - SaveList* saveList = SaveListFactory().CreateFromFile(SAVE_LIST_PATH); - u32 saveSize = DEFAULT_SAVE_SIZE; auto saveType = CardSaveType::None; - if (saveList) + u32 saveSize = DEFAULT_SAVE_SIZE; + if (header->nandBackupRegionStart != 0) { - const auto saveListEntry = saveList->FindEntry(gameCode); - if (!saveListEntry) + saveType = CardSaveType::Nand; + u32 blockSize = header->IsTwlRom() ? TWL_NAND_BLOCK_SIZE : NTR_NAND_BLOCK_SIZE; + u32 nandBackupRegionStart = header->nandBackupRegionStart * blockSize; + saveSize = NAND_RW_REGION_END - nandBackupRegionStart; + LOG_DEBUG("NAND save. Size: 0x%X.", saveSize); + } + else + { + SaveList* saveList = SaveListFactory().CreateFromFile(SAVE_LIST_PATH); + if (saveList) { - LOG_WARNING("Game code %c%c%c%c not found in save list\n", - gameCode & 0xFF, (gameCode >> 8) & 0xFF, (gameCode >> 16) & 0xFF, gameCode >> 24); + const auto saveListEntry = saveList->FindEntry(header->gameCode); + if (!saveListEntry) + { + LOG_WARNING("Game code %c%c%c%c not found in save list\n", + header->gameCode & 0xFF, (header->gameCode >> 8) & 0xFF, + (header->gameCode >> 16) & 0xFF, header->gameCode >> 24); + } + else + { + saveType = saveListEntry->GetSaveType(); + saveSize = saveListEntry->GetSaveSize(); + saveListEntry->Dump(); + } + delete saveList; } - else - { - saveType = saveListEntry->GetSaveType(); - saveSize = saveListEntry->GetSaveSize(); - saveListEntry->Dump(); - } - delete saveList; } if (saveSize == 0) { @@ -88,17 +105,29 @@ bool CardSaveArranger::SetupCardSave(u32 gameCode, const TCHAR* savePath) const offset += bytesToWrite; } - if (saveType == CardSaveType::Nand) + // Additional save initialization + if (header->gameCode == GAMECODE("AXBJ")) { - // 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 + // Write save id for Band Brothers + UINT bytesWritten = 0; + if (f_lseek(file.get(), 0) != FR_OK || + f_write(file.get(), sBandBrothersSaveId, sizeof(sBandBrothersSaveId), &bytesWritten) != FR_OK || + bytesWritten != sizeof(sBandBrothersSaveId)) + { + LOG_FATAL("Failed to write Band Brothers save id\n"); + return false; + } + } + else if (header->gameCode == GAMECODE("UXBP")) + { + // Write save id for Jam with the Band + // See also 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)) + f_write(file.get(), sJamWithTheBandSaveId, sizeof(sJamWithTheBandSaveId), &bytesWritten) != FR_OK || + bytesWritten != sizeof(sJamWithTheBandSaveId)) { - LOG_FATAL("Failed to write NAND save id\n"); + LOG_FATAL("Failed to write Jam with the Band save id\n"); return false; } } diff --git a/arm7/source/loader/CardSaveArranger.h b/arm7/source/loader/CardSaveArranger.h index 5e218ff..f8b5e90 100644 --- a/arm7/source/loader/CardSaveArranger.h +++ b/arm7/source/loader/CardSaveArranger.h @@ -1,12 +1,13 @@ #pragma once +#include "ndsHeader.h" /// @brief Class for setting up the save file for retail card roms. class CardSaveArranger { public: /// @brief Sets up the save file at \p savePath for a retail card rom with the given \p gameCode. - /// @param gameCode The game code of the retail card rom. + /// @param header The header of the retail card rom. /// @param savePath The desired save file path. /// @return \c true when setting up the save was successful, or \c false otherwise. - bool SetupCardSave(u32 gameCode, const TCHAR* savePath) const; + bool SetupCardSave(const nds_header_ntr_t* header, const TCHAR* savePath) const; }; diff --git a/arm7/source/loader/NdsLoader.cpp b/arm7/source/loader/NdsLoader.cpp index 61a2489..7709061 100644 --- a/arm7/source/loader/NdsLoader.cpp +++ b/arm7/source/loader/NdsLoader.cpp @@ -205,7 +205,11 @@ void NdsLoader::Load(BootMode bootMode) { if (bootMode != BootMode::SdkResetSystem) { - HandleCardSave(); + if (!CardSaveArranger().SetupCardSave(&_romHeader, _savePath)) + { + ErrorDisplay().PrintError("Failed to setup save file."); + return; + } } HandleAntiPiracy(); @@ -608,14 +612,6 @@ bool NdsLoader::TryLoadRomHeader(u32 romOffset) return true; } -void NdsLoader::HandleCardSave() -{ - if (!CardSaveArranger().SetupCardSave(_romHeader.gameCode, _savePath)) - { - ErrorDisplay().PrintError("Failed to setup save file."); - } -} - void NdsLoader::HandleAntiPiracy() { auto apList = ApListFactory().CreateFromFile(AP_LIST_PATH); diff --git a/arm7/source/loader/NdsLoader.h b/arm7/source/loader/NdsLoader.h index 416faa4..14bd17a 100644 --- a/arm7/source/loader/NdsLoader.h +++ b/arm7/source/loader/NdsLoader.h @@ -56,7 +56,6 @@ private: void ClearMainMemory(); void CreateRomClusterTable(); bool TryLoadRomHeader(u32 romOffset); - void HandleCardSave(); void HandleAntiPiracy(); void RemapWram(); bool TryLoadArm9(); diff --git a/arm9/source/Arm9Patcher.cpp b/arm9/source/Arm9Patcher.cpp index 12e47c4..f69d8d0 100644 --- a/arm9/source/Arm9Patcher.cpp +++ b/arm9/source/Arm9Patcher.cpp @@ -13,7 +13,9 @@ #include "patches/arm9/OSResetSystemPatch.h" #include "patches/arm9/PokemonDownloaderArm9Patch.h" #include "patches/arm9/DSProtectArm9Patch.h" +#include "patches/arm9/NandSave/FaceTrainingNandSavePatch.h" #include "patches/arm9/NandSave/JamWithTheBandNandSavePatch.h" +#include "patches/arm9/NandSave/NintendoDSGuideNandSavePatch.h" #include "patches/arm9/NandSave/WarioWareDiyNandSavePatch.h" #include "patches/arm9/OverlayPatches/FsStartOverlayHookPatch.h" #include "patches/arm9/OverlayPatches/DSProtectPatches/DSProtectOverlayPatch.h" @@ -390,6 +392,18 @@ void Arm9Patcher::AddGameSpecificPatches( patchCollection.AddPatch(new JamWithTheBandNandSavePatch()); break; } + // Face Training + case GAMECODE("USKV"): + { + patchCollection.AddPatch(new FaceTrainingNandSavePatch()); + break; + } + // Nintendo DS Guide + case GAMECODE("UGDA"): + { + patchCollection.AddPatch(new NintendoDSGuideNandSavePatch()); + break; + } } } diff --git a/arm9/source/patches/arm9/NandSave/FaceTrainingNandSavePatch.cpp b/arm9/source/patches/arm9/NandSave/FaceTrainingNandSavePatch.cpp new file mode 100644 index 0000000..7ac99f1 --- /dev/null +++ b/arm9/source/patches/arm9/NandSave/FaceTrainingNandSavePatch.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 "FaceTrainingNandSavePatch.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 FaceTrainingNandSavePatch::FindPatchTarget(PatchContext& patchContext) +{ + return true; +} + +void FaceTrainingNandSavePatch::ApplyPatch(PatchContext& patchContext) +{ + auto sectorRemapPatchCode = patchContext.GetPatchCodeCollection().AddUniquePatchCode + ( + patchContext.GetPatchHeap(), + SHARED_SAVE_FILE_INFO + ); + 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*)0x020E2AEC = 0xE3A00001; // mov r0, #1 + *(u32*)0x020E2AF0 = 0xE12FFF1E; // bx lr + + // u32 nandResume(void) + *(u32*)0x020E2EC0 = 0xE3A00000; // mov r0, #0 + *(u32*)0x020E2EC4 = 0xE12FFF1E; // bx lr + + // u32 nandError(void) + *(u32*)0x020E3150 = 0xE3A00000; // mov r0, #0 + *(u32*)0x020E3154 = 0xE12FFF1E; // bx lr + + // u32 nandWrite(const void* memory, u32 flash, u32 size, u32 dmaChannel) + *(u32*)0x020E2BF0 = 0xE51FF004; // ldr pc,= patch_writeNandSave + *(u32*)0x020E2BF4 = (u32)writeNandSavePatchCode->GetWriteNandSaveFunction(); + + // u32 nandRead(void* memory, u32 flash, u32 size, u32 dmaChannel) + *(u32*)0x020E2F3C = 0xE51FF004; // ldr pc,= patch_readNandSave + *(u32*)0x020E2F40 = (u32)readNandSavePatchCode->GetReadNandSaveFunction(); +} diff --git a/arm9/source/patches/arm9/NandSave/FaceTrainingNandSavePatch.h b/arm9/source/patches/arm9/NandSave/FaceTrainingNandSavePatch.h new file mode 100644 index 0000000..7dddb25 --- /dev/null +++ b/arm9/source/patches/arm9/NandSave/FaceTrainingNandSavePatch.h @@ -0,0 +1,12 @@ +#pragma once +#include "patches/Patch.h" + +class FunctionSignature; + +/// @brief Arm9 patch to redirect Face Training nand saving to the SD card. +class FaceTrainingNandSavePatch : public Patch +{ +public: + bool FindPatchTarget(PatchContext& patchContext) override; + void ApplyPatch(PatchContext& patchContext) override; +}; diff --git a/arm9/source/patches/arm9/NandSave/NintendoDSGuideNandSavePatch.cpp b/arm9/source/patches/arm9/NandSave/NintendoDSGuideNandSavePatch.cpp new file mode 100644 index 0000000..1a3e377 --- /dev/null +++ b/arm9/source/patches/arm9/NandSave/NintendoDSGuideNandSavePatch.cpp @@ -0,0 +1,61 @@ +#include "common.h" +#include "gameCode.h" +#include "patches/platform/LoaderPlatform.h" +#include "patches/SaveOffsetToSdSectorAsm.h" +#include "ReadNandSaveAsm.h" +#include "WriteNandSaveAsm.h" +#include "NintendoDSGuideNandSavePatch.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 NintendoDSGuideNandSavePatch::FindPatchTarget(PatchContext& patchContext) +{ + return true; +} + +void NintendoDSGuideNandSavePatch::ApplyPatch(PatchContext& patchContext) +{ + auto sectorRemapPatchCode = patchContext.GetPatchCodeCollection().AddUniquePatchCode + ( + patchContext.GetPatchHeap(), + SHARED_SAVE_FILE_INFO + ); + 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*)0x02009298 = 0xE3A00001; // mov r0, #1 + *(u32*)0x0200929C = 0xE12FFF1E; // bx lr + + // u32 nandResume(void) + *(u32*)0x020098C8 = 0xE3A00000; // mov r0, #0 + *(u32*)0x020098CC = 0xE12FFF1E; // bx lr + + // u32 nandState(void) + *(u32*)0x02009AA8 = 0xE1A00000; //nop + *(u32*)0x02009AB0 = 0x06600000; + + // u32 nandError(void) + *(u32*)0x02009AB4 = 0xE3A00000; // mov r0, #0 + *(u32*)0x02009AB8 = 0xE12FFF1E; // bx lr + + // u32 nandWrite(const void* memory, u32 flash, u32 size, u32 dmaChannel) + *(u32*)0x0200961C = 0xE51FF004; // ldr pc,= patch_writeNandSave + *(u32*)0x02009620 = (u32)writeNandSavePatchCode->GetWriteNandSaveFunction(); + + // u32 nandRead(void* memory, u32 flash, u32 size, u32 dmaChannel) + *(u32*)0x02009940 = 0xE51FF004; // ldr pc,= patch_readNandSave + *(u32*)0x02009944 = (u32)readNandSavePatchCode->GetReadNandSaveFunction(); +} diff --git a/arm9/source/patches/arm9/NandSave/NintendoDSGuideNandSavePatch.h b/arm9/source/patches/arm9/NandSave/NintendoDSGuideNandSavePatch.h new file mode 100644 index 0000000..fbd48f1 --- /dev/null +++ b/arm9/source/patches/arm9/NandSave/NintendoDSGuideNandSavePatch.h @@ -0,0 +1,12 @@ +#pragma once +#include "patches/Patch.h" + +class FunctionSignature; + +/// @brief Arm9 patch to redirect Nintendo DS Guide nand saving to the SD card. +class NintendoDSGuideNandSavePatch : public Patch +{ +public: + bool FindPatchTarget(PatchContext& patchContext) override; + void ApplyPatch(PatchContext& patchContext) override; +}; diff --git a/data/savelist.csv b/data/savelist.csv index dd3bca1..601a2b3 100644 --- a/data/savelist.csv +++ b/data/savelist.csv @@ -5455,13 +5455,7 @@ UBRE;flash;0x40000 UBRJ;flash;0x40000 UBRP;flash;0x40000 UEIJ;eeprom;0x2000 -UGDA;none;0x0 UNSJ;flash;0x40000 -UORE;nand;0x1000000 -UORJ;nand;0x1000000 -UORP;nand;0x1000000 -USKV;nand;0x4000000 -UXBP;nand;0x800000 UZCJ;flash;0x2000000 UZPD;flash;0x20000 UZPF;flash;0x20000