Added support for nand saving in Face Training and Nintendo DS Guide (fixes #6), calculate nand save size from header, fixed Band Brothers save initialization

This commit is contained in:
Gericom
2025-12-28 17:34:06 +01:00
parent 6d12399ba4
commit 9f6311014d
10 changed files with 216 additions and 41 deletions

View File

@@ -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;
}
}

View File

@@ -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;
};

View File

@@ -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);

View File

@@ -56,7 +56,6 @@ private:
void ClearMainMemory();
void CreateRomClusterTable();
bool TryLoadRomHeader(u32 romOffset);
void HandleCardSave();
void HandleAntiPiracy();
void RemapWram();
bool TryLoadArm9();