mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-09-11 01:23:15 -04:00
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:
@@ -26,6 +26,8 @@ Code.pul
|
||||
/build/
|
||||
/build-*/
|
||||
/native-build/
|
||||
/native-build-macos/
|
||||
/local-products/
|
||||
/dist/
|
||||
/out/
|
||||
[Bb]in/
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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
|
||||
// ============================================================================
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user