Added support for NAND saving in WarioWare D.I.Y. and Jam with the Band (#6)

Also improved speed of creating save files
This commit is contained in:
Gericom
2025-12-28 13:18:30 +01:00
parent 64e020182a
commit 6d12399ba4
17 changed files with 409 additions and 27 deletions

View File

@@ -1,4 +1,5 @@
#include "common.h"
#include <algorithm>
#include <string.h>
#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<u8[]>(512);
memset(ffBuffer.get(), SAVE_FILL_VALUE, 512);
const u32 ffBufferSize = 32 * 1024;
auto ffBuffer = std::make_unique<u8[]>(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<u32>(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;
}
}
}

View File

@@ -1,5 +1,6 @@
#include "common.h"
#include <string.h>
#include <algorithm>
#include <memory>
#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<u8[]>(clearBufferSize);
memset(clearBuffer.get(), 0, clearBufferSize);
u32 offset = f_tell(file.get());
while (offset < saveSize)
{
u32 bytesToWrite = std::min<u32>(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;
}
}

View File

@@ -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<CardSaveType>(saveType); }
CardSaveType GetSaveType() const { return saveType; }
u32 GetSaveSize() const { return saveSize == 0 ? 0 : (1u << saveSize); }
void Dump() const