treat empty mkw save as missing

first-run format zero-fills rksys.dat before any real save; a quit before
the first save left an all-zero file that read back as corrupt and trapped
the user in a delete/recreate loop. read opens now treat an all-zero
rksys.dat as absent (a real save always begins with the RKSD0006 header),
so the game recreates it from scratch. also ignore native build output.
This commit is contained in:
JGM01
2026-09-05 22:48:05 -04:00
parent 56db6ba641
commit c95f1c95b7
6 changed files with 63 additions and 4 deletions
+2
View File
@@ -26,6 +26,8 @@ Code.pul
/build/
/build-*/
/native-build/
/native-build-macos/
/local-products/
/dist/
/out/
[Bb]in/
+4 -4
View File
@@ -93,6 +93,9 @@ extern "C" int32_t NANDOpen_HLE(uint32_t pathPtr, uint32_t fileInfoPtr, uint32_t
const std::filesystem::path hostPath = TranslateNandPath(path);
if (NandIgnoreUninitializedSaveRead("NANDOpen", hostPath, mode))
return NAND_RESULT_NOEXISTS;
// Existing-file write opens go through a shadow copy seeded from the original, so a
// crash between NANDWrite and NANDClose cannot leave a torn file (the game patches
// sub-ranges, e.g. ghost saves at a non-zero offset). New files still create in place.
@@ -136,10 +139,7 @@ extern "C" int32_t NANDOpen_HLE(uint32_t pathPtr, uint32_t fileInfoPtr, uint32_t
}
}
const char* fopenMode = "rb";
if (mode == 1) fopenMode = "rb";
else if (mode == 2) fopenMode = "r+b";
else if (mode == 3) fopenMode = "r+b";
const char* fopenMode = (mode == 2 || mode == 3) ? "r+b" : "rb";
FILE* file = NandFopen(hostPath, fopenMode);
if (!file && mode >= 2) {
+2
View File
@@ -411,6 +411,8 @@ extern "C" int32_t NANDSafeOpen_HLE(uint32_t pathPtr, uint32_t fileInfoPtr, uint
if (mode == 1) {
// Read-only safe open reads the original in place; the library builds no scratch
// copy for this case.
if (NandIgnoreUninitializedSaveRead("NANDSafeOpen", hostPath, mode))
return NAND_RESULT_NOEXISTS;
FILE* file = NandFopen(hostPath, "rb");
if (!file && IsFaceLibResourcePath(path) && SeedFaceLibResource(hostPath)) {
file = NandFopen(hostPath, "rb");
+33
View File
@@ -411,6 +411,39 @@ bool IsFaceLibResourcePath(const char* path) {
return std::strcmp(path, "/shared2/menu/FaceLib/RFL_Res.dat") == 0;
}
bool NandSystemSaveIsUninitialized(const std::filesystem::path& hostPath) {
if (!IsNandSystemSavePath(hostPath))
return false;
std::error_code ec;
if (!std::filesystem::is_regular_file(hostPath, ec) || ec)
return false;
std::ifstream in(hostPath, std::ios::binary);
if (!in)
return false;
char block[4096];
while (in) {
in.read(block, sizeof(block));
const std::streamsize got = in.gcount();
for (std::streamsize i = 0; i < got; ++i)
if (block[i] != 0)
return false;
}
return true;
}
bool NandIgnoreUninitializedSaveRead(const char* who,
const std::filesystem::path& hostPath, int mode) {
if (mode != 1 || !NandSystemSaveIsUninitialized(hostPath))
return false;
LogNandWarning(who, "ignoring uninitialized system save '%s' (no save committed yet)",
HostPathText(hostPath).c_str());
return true;
}
// Create directories recursively
bool CreateDirectoryPath(const std::filesystem::path& path) {
if (path.empty()) {
+19
View File
@@ -56,6 +56,25 @@ constexpr uint32_t kNandTitleIdLo = 0x524D4350; // "RMCP" fallback
void LogNandError(const char* func, const char* fmt, ...);
void LogNandWarning(const char* func, const char* fmt, ...);
// The Mario Kart Wii system save (rksys.dat) and its ".nandsafe.tmp" write shadows.
inline bool IsNandSystemSavePath(const std::filesystem::path& path) {
const std::string name = path.filename().string();
return name == "rksys.dat" || name.rfind("rksys.dat", 0) == 0;
}
// True when a system save exists on the host but holds no committed save yet: the game
// zero-fills rksys.dat during its first-run "format save data" step and only writes the
// real database (which always begins with the RKSD0006 header) once it actually saves.
// An all-zero file therefore contains nothing worth loading; read it as absent so the
// game recreates its save instead of entering the corrupt-save recovery loop.
bool NandSystemSaveIsUninitialized(const std::filesystem::path& hostPath);
// Read-open helper: logs and returns true when a read of this system save should see
// "no save". Centralizes the mode == 1 guard and warning so the NAND, NANDSafe and IOS
// open paths share one branch and message.
bool NandIgnoreUninitializedSaveRead(const char* who, const std::filesystem::path& hostPath,
int mode);
// ============================================================================
// File Descriptor Management
// ============================================================================
+3
View File
@@ -391,6 +391,9 @@ extern "C" int32_t NAND_IOS_Open_HLE(uint32_t pathPtr, uint32_t mode) {
// It's a NAND file path
const std::filesystem::path hostPath = TranslateNandPath(path);
if (NandIgnoreUninitializedSaveRead("IOS_Open", hostPath, mode))
return ISFS_ENOENT;
// Seed FaceLib resources before the existence check so every open mode can
// still find them on a fresh managed NAND.