mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-09-27 15:21:35 -04:00
83463764b8
* Fix NAND moves across mount points * Harden cross-mount NAND move fallback * Preserve directory copy on NAND move cleanup failure * Clarify NAND move cleanup behavior * Use exclusive scratch paths for NAND moves * Copy NAND move directories into reserved destination * Publish NAND move directories without replacement
590 lines
23 KiB
C++
590 lines
23 KiB
C++
// NAND/ISFS HLE: the synchronous RVL NAND* library.
|
|
//
|
|
// Shared state and helpers live in nand_internal.h.
|
|
|
|
#include "nand_internal.h"
|
|
|
|
#include <atomic>
|
|
#include <cerrno>
|
|
|
|
#ifdef __linux__
|
|
#include <linux/fs.h>
|
|
#include <sys/syscall.h>
|
|
#endif
|
|
|
|
// ============================================================================
|
|
// Local helpers
|
|
// ============================================================================
|
|
|
|
// Unchecked NUL-terminated write into guest memory. The RVL NAND library hands
|
|
// out fixed-size caller buffers and never tells us how big they are, so this
|
|
// deliberately matches the library's own unbounded copy.
|
|
static void WriteGuestNandPath(uint32_t address, const std::string& value) {
|
|
for (size_t i = 0; i < value.size(); ++i) {
|
|
Memory::Write8(address + static_cast<uint32_t>(i), static_cast<uint8_t>(value[i]));
|
|
}
|
|
Memory::Write8(address + static_cast<uint32_t>(value.size()), 0);
|
|
}
|
|
|
|
// NANDGetHomeDir and NANDGetCurrentDir both answer with the title's data
|
|
// directory; the SDK keeps no separate working directory for us to track.
|
|
static int32_t WriteNandDataDir(uint32_t outPathPtr) {
|
|
if (!outPathPtr) {
|
|
return NAND_RESULT_INVALID;
|
|
}
|
|
WriteGuestNandPath(outPathPtr, CurrentNandDataDir());
|
|
return NAND_RESULT_OK;
|
|
}
|
|
|
|
// fileInfoPtr -> fd -> live handle. Logs and returns null when the guest hands
|
|
// us a descriptor that was never opened (or was already closed).
|
|
static FileHandle* ResolveNandFileHandle(const char* who, uint32_t fileInfoPtr) {
|
|
const int32_t fd = static_cast<int32_t>(Memory::Read32(fileInfoPtr));
|
|
FileHandle* handle = GetHandle(fd);
|
|
if (!handle || !handle->file) {
|
|
LogNandError(who, "invalid fd=%d", fd);
|
|
return nullptr;
|
|
}
|
|
return handle;
|
|
}
|
|
|
|
// ============================================================================
|
|
// The synchronous RVL NAND* library
|
|
// ============================================================================
|
|
|
|
static bool RenameNoReplace(const std::filesystem::path& from,
|
|
const std::filesystem::path& to,
|
|
std::error_code& error) {
|
|
#ifdef _WIN32
|
|
if (MoveFileExW(from.c_str(), to.c_str(), MOVEFILE_WRITE_THROUGH)) {
|
|
error.clear();
|
|
return true;
|
|
}
|
|
error = std::error_code(static_cast<int>(GetLastError()), std::system_category());
|
|
return false;
|
|
#elif defined(__linux__)
|
|
const int result = syscall(SYS_renameat2, AT_FDCWD, from.c_str(), AT_FDCWD, to.c_str(), RENAME_NOREPLACE);
|
|
if (result == 0) {
|
|
error.clear();
|
|
return true;
|
|
}
|
|
error = std::error_code(errno, std::generic_category());
|
|
return false;
|
|
#else
|
|
(void)from;
|
|
(void)to;
|
|
error = std::make_error_code(std::errc::operation_not_supported);
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
extern "C" int32_t NANDInit_HLE(void) {
|
|
// Initialize ISFS
|
|
ISFS_OpenLib_Initialize(&GetPersistentCpuContext());
|
|
|
|
// NANDHomeDir is at 0x80346D20 (from ESP_GetDataDir output)
|
|
WriteGuestNandPath(0x80346D20, CurrentNandDataDir());
|
|
|
|
// Mark NAND as initialized (0x80386848 = 2)
|
|
Memory::Write32(0x80386848, 2);
|
|
|
|
return NAND_RESULT_OK;
|
|
}
|
|
PPC_NATIVE_OVERRIDE(8019E18C, NANDInit_HLE, int32_t, (void), ());
|
|
|
|
extern "C" int32_t NANDGetCurrentDir_HLE(uint32_t outPathPtr) {
|
|
return WriteNandDataDir(outPathPtr);
|
|
}
|
|
PPC_NATIVE_OVERRIDE(8019E390, NANDGetCurrentDir_HLE, int32_t, (uint32_t outPathPtr), (outPathPtr));
|
|
|
|
extern "C" int32_t NANDCheck_HLE(uint32_t blockSize, uint32_t blockCount, uint32_t outResults) {
|
|
const int32_t result = NandCheckContract::WriteHealthyResult(
|
|
outResults,
|
|
[](uint32_t address, size_t length) { return Memory::Contains(address, length); },
|
|
[](uint32_t address, uint32_t value) {
|
|
try {
|
|
Memory::Write32(address, value);
|
|
return true;
|
|
} catch (const Memory::AccessViolation&) {
|
|
return false;
|
|
}
|
|
});
|
|
if (result != NAND_RESULT_OK) {
|
|
LogNandError("NANDCheck",
|
|
"FAILED: invalid four-byte result buffer 0x%08X (blockSize=%u blockCount=%u)",
|
|
outResults, blockSize, blockCount);
|
|
}
|
|
return result;
|
|
}
|
|
PPC_NATIVE_OVERRIDE(8019EAD0, NANDCheck_HLE, int32_t, (uint32_t blockSize, uint32_t blockCount, uint32_t outResults), (blockSize, blockCount, outResults));
|
|
|
|
extern "C" int32_t NANDOpen_HLE(uint32_t pathPtr, uint32_t fileInfoPtr, uint32_t mode) {
|
|
const char* path = pathPtr ? (const char*)Memory::GetPointer(pathPtr) : nullptr;
|
|
if (!path || !fileInfoPtr) {
|
|
LogNandError("NANDOpen", "invalid params: path=%p fileInfo=0x%08X", path, fileInfoPtr);
|
|
return NAND_RESULT_INVALID;
|
|
}
|
|
|
|
const std::filesystem::path hostPath = TranslateNandPath(path);
|
|
|
|
if (const auto result = NandCheckSystemSaveRead("NANDOpen", hostPath, mode))
|
|
return *result;
|
|
|
|
// 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.
|
|
if ((mode == 2 || mode == 3) && PathExists(hostPath) && !IsDirectory(hostPath)) {
|
|
if (IsHostPathOpen(hostPath)) {
|
|
// Another live handle already refers to this file. A shadow would hide the
|
|
// writes from that handle, so stay in place for this open.
|
|
LogNandWarning("NANDOpen", "WARNING: '%s' already has a live handle, writing in place",
|
|
HostPathText(hostPath).c_str());
|
|
} else {
|
|
const std::filesystem::path tempPath = SafeTempPathFor(hostPath);
|
|
if (DiscardStaleSafeTemp(tempPath)) {
|
|
std::error_code ec;
|
|
std::filesystem::copy_file(hostPath, tempPath,
|
|
std::filesystem::copy_options::overwrite_existing, ec);
|
|
if (ec) {
|
|
LogNandWarning("NANDOpen", "WARNING: could not seed shadow '%s' (%s), writing in place",
|
|
HostPathText(tempPath).c_str(), ec.message().c_str());
|
|
NandRemove(tempPath);
|
|
} else {
|
|
FILE* shadow = NandFopen(tempPath, "r+b");
|
|
if (!shadow) {
|
|
LogNandWarning("NANDOpen", "WARNING: could not open shadow '%s', writing in place",
|
|
HostPathText(tempPath).c_str());
|
|
NandRemove(tempPath);
|
|
} else {
|
|
const int32_t shadowFd = AllocateFd(tempPath, shadow, static_cast<int32_t>(mode));
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_fdMutex);
|
|
auto it = g_fileHandles.find(shadowFd);
|
|
if (it != g_fileHandles.end()) {
|
|
it->second.safeCommitPath = hostPath;
|
|
}
|
|
}
|
|
Memory::Write32(fileInfoPtr, static_cast<uint32_t>(shadowFd));
|
|
Memory::Write8(fileInfoPtr + 0x8a, NAND_OPEN_FLAG_OPEN);
|
|
return NAND_RESULT_OK;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const char* fopenMode = "rb";
|
|
if (mode == 1) fopenMode = "rb";
|
|
else if (mode == 2) fopenMode = "r+b";
|
|
else if (mode == 3) fopenMode = "r+b";
|
|
|
|
FILE* file = NandFopen(hostPath, fopenMode);
|
|
if (!file && mode >= 2) {
|
|
// Try creating for write modes
|
|
file = NandFopen(hostPath, "w+b");
|
|
}
|
|
|
|
// Create parent directories and retry
|
|
if (!file && CreateParentDirectories(hostPath)) {
|
|
file = NandFopen(hostPath, mode >= 2 ? "w+b" : "rb");
|
|
}
|
|
|
|
if (!file) {
|
|
if (IsFaceLibResourcePath(path) && SeedFaceLibResource(hostPath)) {
|
|
file = NandFopen(hostPath, fopenMode);
|
|
}
|
|
if (!file) {
|
|
LogNandError("NANDOpen", "FAILED to open");
|
|
return NAND_RESULT_NOEXISTS;
|
|
}
|
|
}
|
|
|
|
int32_t fd = AllocateFd(hostPath, file, mode);
|
|
Memory::Write32(fileInfoPtr, static_cast<uint32_t>(fd));
|
|
Memory::Write8(fileInfoPtr + 0x8a, NAND_OPEN_FLAG_OPEN);
|
|
return NAND_RESULT_OK;
|
|
}
|
|
PPC_NATIVE_OVERRIDE(8019C800, NANDOpen_HLE, int32_t, (uint32_t pathPtr, uint32_t fileInfoPtr, uint32_t mode), (pathPtr, fileInfoPtr, mode));
|
|
|
|
extern "C" int32_t NANDClose_HLE(uint32_t fileInfoPtr) {
|
|
if (!fileInfoPtr) {
|
|
return NAND_RESULT_INVALID;
|
|
}
|
|
|
|
uint8_t openFlag = Memory::Read8(fileInfoPtr + 0x8a);
|
|
if (openFlag == NAND_OPEN_FLAG_SAFE_OPEN || openFlag == NAND_OPEN_FLAG_SAFE_OPEN_ASYNC) {
|
|
// Console behaviour (nandClose @ 0x8019CA80): only openFlag==1 is accepted, a safe
|
|
// handle falls through to -8. It is neither closed nor committed here.
|
|
LogNandWarning("NANDClose", "WARNING: refusing safe-open handle (flag=%u), NANDSafeClose is required", openFlag);
|
|
return NAND_RESULT_INVALID;
|
|
}
|
|
if (openFlag != NAND_OPEN_FLAG_OPEN) {
|
|
LogNandWarning("NANDClose", "file not open (flag=%u)", openFlag);
|
|
return NAND_RESULT_INVALID;
|
|
}
|
|
|
|
int32_t fd = static_cast<int32_t>(Memory::Read32(fileInfoPtr));
|
|
|
|
// Flush, fsync and (for shadowed write handles) atomically publish. A failure here is
|
|
// reported to the guest instead of being swallowed, so NandUtil_close surfaces it as a
|
|
// save error rather than a silent success on top of a half-written file.
|
|
const int32_t result = CommitAndCloseFd("NANDClose", fd, /*missingFdIsError=*/false);
|
|
if (result != NAND_RESULT_OK) {
|
|
return result;
|
|
}
|
|
|
|
Memory::Write8(fileInfoPtr + 0x8a, NAND_OPEN_FLAG_CLOSED); // Mark as closed
|
|
return NAND_RESULT_OK;
|
|
}
|
|
PPC_NATIVE_OVERRIDE(8019CA80, NANDClose_HLE, int32_t, (uint32_t fileInfoPtr), (fileInfoPtr));
|
|
|
|
extern "C" int32_t NANDRead_HLE(uint32_t fileInfoPtr, uint32_t bufferPtr, uint32_t length) {
|
|
if (!fileInfoPtr) {
|
|
return NAND_RESULT_INVALID;
|
|
}
|
|
|
|
FileHandle* handle = ResolveNandFileHandle("NANDRead", fileInfoPtr);
|
|
if (!handle) {
|
|
return NAND_RESULT_INVALID;
|
|
}
|
|
|
|
uint8_t* buffer = (uint8_t*)Memory::GetPointer(bufferPtr);
|
|
if (!buffer) {
|
|
return NAND_RESULT_INVALID;
|
|
}
|
|
|
|
size_t bytesRead = std::fread(buffer, 1, length, handle->file);
|
|
return static_cast<int32_t>(bytesRead);
|
|
}
|
|
PPC_NATIVE_OVERRIDE(8019B7A4, NANDRead_HLE, int32_t, (uint32_t fileInfoPtr, uint32_t bufferPtr, uint32_t length), (fileInfoPtr, bufferPtr, length));
|
|
|
|
extern "C" int32_t NANDWrite_HLE(uint32_t fileInfoPtr, uint32_t bufferPtr, uint32_t length) {
|
|
if (!fileInfoPtr) {
|
|
return NAND_RESULT_INVALID;
|
|
}
|
|
|
|
FileHandle* handle = ResolveNandFileHandle("NANDWrite", fileInfoPtr);
|
|
if (!handle) {
|
|
return NAND_RESULT_INVALID;
|
|
}
|
|
|
|
const uint8_t* buffer = (const uint8_t*)Memory::GetPointer(bufferPtr);
|
|
if (!buffer) {
|
|
return NAND_RESULT_INVALID;
|
|
}
|
|
|
|
size_t bytesWritten = std::fwrite(buffer, 1, length, handle->file);
|
|
std::fflush(handle->file);
|
|
return static_cast<int32_t>(bytesWritten);
|
|
}
|
|
PPC_NATIVE_OVERRIDE(8019B884, NANDWrite_HLE, int32_t, (uint32_t fileInfoPtr, uint32_t bufferPtr, uint32_t length), (fileInfoPtr, bufferPtr, length));
|
|
|
|
extern "C" int32_t NANDSeek_HLE(uint32_t fileInfoPtr, int32_t offset, int32_t whence) {
|
|
if (!fileInfoPtr) {
|
|
return NAND_RESULT_INVALID;
|
|
}
|
|
|
|
FileHandle* handle = ResolveNandFileHandle("NANDSeek", fileInfoPtr);
|
|
if (!handle) {
|
|
return NAND_RESULT_INVALID;
|
|
}
|
|
|
|
if (std::fseek(handle->file, offset, NandSeekOrigin(whence)) != 0) {
|
|
return NAND_RESULT_UNKNOWN;
|
|
}
|
|
|
|
return static_cast<int32_t>(std::ftell(handle->file));
|
|
}
|
|
PPC_NATIVE_OVERRIDE(8019B964, NANDSeek_HLE, int32_t, (uint32_t fileInfoPtr, int32_t offset, int32_t whence), (fileInfoPtr, offset, whence));
|
|
|
|
extern "C" int32_t NANDGetLength_HLE(uint32_t fileInfoPtr, uint32_t outLengthPtr) {
|
|
if (!fileInfoPtr || !outLengthPtr) {
|
|
return NAND_RESULT_INVALID;
|
|
}
|
|
|
|
FileHandle* handle = ResolveNandFileHandle("NANDGetLength", fileInfoPtr);
|
|
if (!handle) {
|
|
return NAND_RESULT_INVALID;
|
|
}
|
|
|
|
const NandFileExtent extent = NandProbeFileExtent(handle->file);
|
|
Memory::Write32(outLengthPtr, static_cast<uint32_t>(extent.size));
|
|
return NAND_RESULT_OK;
|
|
}
|
|
PPC_NATIVE_OVERRIDE(8019BF4C, NANDGetLength_HLE, int32_t, (uint32_t fileInfoPtr, uint32_t outLengthPtr), (fileInfoPtr, outLengthPtr));
|
|
|
|
extern "C" int32_t NANDCreate_HLE(uint32_t pathPtr, uint32_t perm, uint32_t attr) {
|
|
const char* path = pathPtr ? (const char*)Memory::GetPointer(pathPtr) : nullptr;
|
|
if (!path) {
|
|
return NAND_RESULT_INVALID;
|
|
}
|
|
|
|
const std::filesystem::path hostPath = TranslateNandPath(path);
|
|
CreateParentDirectories(hostPath);
|
|
|
|
// Check if file already exists
|
|
if (PathExists(hostPath)) {
|
|
return NAND_RESULT_EXISTS;
|
|
}
|
|
|
|
// Create empty file
|
|
FILE* f = NandFopen(hostPath, "wb");
|
|
if (!f) {
|
|
return NAND_RESULT_UNKNOWN;
|
|
}
|
|
std::fclose(f);
|
|
|
|
return NAND_RESULT_OK;
|
|
}
|
|
PPC_NATIVE_OVERRIDE(8019B43C, NANDCreate_HLE, int32_t, (uint32_t pathPtr, uint32_t perm, uint32_t attr), (pathPtr, perm, attr));
|
|
|
|
extern "C" int32_t NANDDelete_HLE(uint32_t pathPtr) {
|
|
const char* path = pathPtr ? (const char*)Memory::GetPointer(pathPtr) : nullptr;
|
|
if (!path) {
|
|
return NAND_RESULT_INVALID;
|
|
}
|
|
|
|
const std::filesystem::path hostPath = TranslateNandPath(path);
|
|
|
|
if (!PathExists(hostPath)) {
|
|
return NAND_RESULT_NOEXISTS;
|
|
}
|
|
|
|
if (NandRemove(hostPath)) {
|
|
return NAND_RESULT_OK;
|
|
}
|
|
|
|
return NAND_RESULT_UNKNOWN;
|
|
}
|
|
PPC_NATIVE_OVERRIDE(8019B59C, NANDDelete_HLE, int32_t, (uint32_t pathPtr), (pathPtr));
|
|
|
|
extern "C" int32_t NANDCreateDir_HLE(uint32_t pathPtr, uint32_t perm, uint32_t attr) {
|
|
const char* path = pathPtr ? (const char*)Memory::GetPointer(pathPtr) : nullptr;
|
|
if (!path) {
|
|
return NAND_RESULT_INVALID;
|
|
}
|
|
|
|
const std::filesystem::path hostPath = TranslateNandPath(path);
|
|
|
|
if (PathExists(hostPath)) {
|
|
if (IsDirectory(hostPath)) {
|
|
return NAND_RESULT_OK; // Already exists
|
|
}
|
|
return NAND_RESULT_EXISTS;
|
|
}
|
|
|
|
if (CreateDirectoryPath(hostPath)) {
|
|
return NAND_RESULT_OK;
|
|
}
|
|
|
|
return NAND_RESULT_UNKNOWN;
|
|
}
|
|
PPC_NATIVE_OVERRIDE(8019BBE0, NANDCreateDir_HLE, int32_t, (uint32_t pathPtr, uint32_t perm, uint32_t attr), (pathPtr, perm, attr));
|
|
|
|
extern "C" int32_t NANDMove_HLE(uint32_t srcPathPtr, uint32_t dstPathPtr) {
|
|
// A cross-mount move is implemented as several host operations. Keep two
|
|
// guest moves from interleaving those operations and corrupting recovery.
|
|
static std::mutex moveMutex;
|
|
std::lock_guard<std::mutex> lock(moveMutex);
|
|
|
|
const char* srcPath = srcPathPtr ? (const char*)Memory::GetPointer(srcPathPtr) : nullptr;
|
|
const char* dstPath = dstPathPtr ? (const char*)Memory::GetPointer(dstPathPtr) : nullptr;
|
|
|
|
if (!srcPath || !dstPath) {
|
|
return NAND_RESULT_INVALID;
|
|
}
|
|
|
|
const std::filesystem::path srcHost = TranslateNandPath(srcPath);
|
|
const std::filesystem::path dstDirectoryHost = TranslateNandPath(dstPath);
|
|
const std::filesystem::path srcName = srcHost.filename();
|
|
if (srcName.empty()) {
|
|
return NAND_RESULT_INVALID;
|
|
}
|
|
|
|
// RVL SDK nandMove always appends the source entry's relative name to the
|
|
// second argument. The latter is a directory, not a complete destination
|
|
// filename (for example /tmp/banner.bin -> <title home>/banner.bin).
|
|
const std::filesystem::path dstHost = dstDirectoryHost / srcName;
|
|
|
|
if (!PathExists(srcHost)) {
|
|
return NAND_RESULT_NOEXISTS;
|
|
}
|
|
if (!IsDirectory(dstDirectoryHost)) {
|
|
return NAND_RESULT_NOEXISTS;
|
|
}
|
|
if (PathExists(dstHost)) {
|
|
return NAND_RESULT_EXISTS;
|
|
}
|
|
|
|
std::error_code ec;
|
|
std::filesystem::rename(srcHost, dstHost, ec);
|
|
if (!ec) {
|
|
return NAND_RESULT_OK;
|
|
}
|
|
|
|
// Flatpak can expose the managed NAND and an external Riivolution save
|
|
// directory as separate mounts. Linux cannot rename across mounts, but
|
|
// nandMove must still work for files such as banner.bin. Preserve the
|
|
// operation's semantics with a copy followed by source removal.
|
|
if (ec == std::errc::cross_device_link) {
|
|
static std::atomic<uint64_t> moveSequence{0};
|
|
#ifdef _WIN32
|
|
const auto processId = GetCurrentProcessId();
|
|
#else
|
|
const auto processId = getpid();
|
|
#endif
|
|
std::filesystem::path scratchHost;
|
|
std::error_code scratchEc;
|
|
for (unsigned attempt = 0; attempt < 128; ++attempt) {
|
|
const auto name = ".nandmove-" + std::to_string(processId) + "-" +
|
|
std::to_string(moveSequence.fetch_add(1)) + "-" +
|
|
std::to_string(attempt);
|
|
const auto candidate = dstDirectoryHost / name;
|
|
scratchEc.clear();
|
|
if (std::filesystem::create_directory(candidate, scratchEc)) {
|
|
scratchHost = candidate;
|
|
break;
|
|
}
|
|
if (scratchEc && scratchEc != std::errc::file_exists) {
|
|
LogNandError("NANDMove", "failed to claim temporary directory '%s': %s",
|
|
HostPathText(candidate).c_str(), scratchEc.message().c_str());
|
|
return NAND_RESULT_UNKNOWN;
|
|
}
|
|
}
|
|
if (scratchHost.empty()) {
|
|
LogNandError("NANDMove", "could not claim a unique temporary directory");
|
|
return NAND_RESULT_UNKNOWN;
|
|
}
|
|
|
|
const bool sourceIsDirectory = IsDirectory(srcHost);
|
|
const std::filesystem::path tempHost = scratchHost / srcName;
|
|
const auto cleanupScratch = [&]() {
|
|
std::error_code cleanupEc;
|
|
std::filesystem::remove_all(scratchHost, cleanupEc);
|
|
if (cleanupEc) {
|
|
LogNandError("NANDMove", "failed to clean up temporary directory '%s': %s",
|
|
HostPathText(scratchHost).c_str(), cleanupEc.message().c_str());
|
|
}
|
|
};
|
|
|
|
std::error_code copyEc;
|
|
if (sourceIsDirectory) {
|
|
std::filesystem::copy(srcHost, tempHost,
|
|
std::filesystem::copy_options::recursive, copyEc);
|
|
} else {
|
|
std::filesystem::copy_file(srcHost, tempHost, copyEc);
|
|
}
|
|
|
|
if (copyEc) {
|
|
LogNandError("NANDMove", "cross-mount copy failed: %s", copyEc.message().c_str());
|
|
cleanupScratch();
|
|
return NAND_RESULT_UNKNOWN;
|
|
}
|
|
|
|
std::error_code publishEc;
|
|
if (sourceIsDirectory) {
|
|
RenameNoReplace(tempHost, dstHost, publishEc);
|
|
} else {
|
|
// link(2) and CreateHardLink do not replace an existing destination,
|
|
// unlike rename(2) on POSIX. Both paths are already on the target
|
|
// filesystem, so the link is a no-replace publication operation.
|
|
std::filesystem::create_hard_link(tempHost, dstHost, publishEc);
|
|
}
|
|
if (publishEc) {
|
|
LogNandError("NANDMove", "failed to publish cross-mount copy: %s",
|
|
publishEc.message().c_str());
|
|
cleanupScratch();
|
|
return NAND_RESULT_UNKNOWN;
|
|
}
|
|
cleanupScratch();
|
|
|
|
std::error_code removeEc;
|
|
std::filesystem::remove_all(srcHost, removeEc);
|
|
if (!removeEc) {
|
|
LogNandWarning("NANDMove", "used copy/remove fallback across mounts");
|
|
return NAND_RESULT_OK;
|
|
}
|
|
|
|
// Keep the source as the authoritative copy when cleanup fails. The
|
|
// destination was published atomically on its own mount; regular files
|
|
// are rolled back below, while directories keep the complete copy when
|
|
// their source removal was only partial. Cross-mount moves cannot
|
|
// provide crash-atomicity, so this is best effort.
|
|
LogNandError("NANDMove", "copy succeeded but source removal failed: %s",
|
|
removeEc.message().c_str());
|
|
if (sourceIsDirectory) {
|
|
// remove_all may have removed only part of a directory tree. Keep
|
|
// the complete published copy rather than rolling it back to a
|
|
// partially deleted source.
|
|
LogNandWarning("NANDMove", "preserving published directory copy after partial source removal");
|
|
} else {
|
|
std::error_code rollbackEc;
|
|
std::filesystem::remove_all(dstHost, rollbackEc);
|
|
if (rollbackEc) {
|
|
LogNandError("NANDMove", "failed to roll back destination '%s': %s",
|
|
HostPathText(dstHost).c_str(), rollbackEc.message().c_str());
|
|
}
|
|
}
|
|
return NAND_RESULT_UNKNOWN;
|
|
}
|
|
|
|
LogNandError("NANDMove", "FAILED error=%d message='%s'", ec.value(), ec.message().c_str());
|
|
return NAND_RESULT_UNKNOWN;
|
|
}
|
|
PPC_NATIVE_OVERRIDE(8019BEE8, NANDMove_HLE, int32_t, (uint32_t srcPathPtr, uint32_t dstPathPtr), (srcPathPtr, dstPathPtr));
|
|
|
|
extern "C" int32_t NANDGetStatus_HLE(uint32_t pathPtr, uint32_t outStatusPtr) {
|
|
const char* path = pathPtr ? (const char*)Memory::GetPointer(pathPtr) : nullptr;
|
|
if (!path || !outStatusPtr) {
|
|
return NAND_RESULT_INVALID;
|
|
}
|
|
|
|
const std::filesystem::path hostPath = TranslateNandPath(path);
|
|
|
|
if (!PathExists(hostPath)) {
|
|
return NAND_RESULT_NOEXISTS;
|
|
}
|
|
|
|
// NANDStatus structure - fill with fake values
|
|
// This is typically used to check permissions and file type
|
|
Memory::Write32(outStatusPtr, 0); // Magic/type
|
|
Memory::Write32(outStatusPtr + 4, 0); // Permissions
|
|
|
|
return NAND_RESULT_OK;
|
|
}
|
|
PPC_NATIVE_OVERRIDE(8019C380, NANDGetStatus_HLE, int32_t, (uint32_t pathPtr, uint32_t outStatusPtr), (pathPtr, outStatusPtr));
|
|
|
|
extern "C" int32_t NANDGetType_HLE(uint32_t pathPtr, uint32_t outTypePtr) {
|
|
const char* path = pathPtr ? (const char*)Memory::GetPointer(pathPtr) : nullptr;
|
|
if (!path || !outTypePtr) {
|
|
return NAND_RESULT_INVALID;
|
|
}
|
|
|
|
const std::filesystem::path hostPath = TranslateNandPath(path);
|
|
|
|
if (!PathExists(hostPath)) {
|
|
return NAND_RESULT_NOEXISTS;
|
|
}
|
|
|
|
// 1 = file, 2 = directory
|
|
uint8_t type = IsDirectory(hostPath) ? 2 : 1;
|
|
Memory::Write8(outTypePtr, type);
|
|
return NAND_RESULT_OK;
|
|
}
|
|
PPC_NATIVE_OVERRIDE(8019E770, NANDGetType_HLE, int32_t, (uint32_t pathPtr, uint32_t outTypePtr), (pathPtr, outTypePtr));
|
|
|
|
// ============================================================================
|
|
// contentFastOpenNAND/contentReadNAND/contentCloseNAND: NAND-installed content (WADs) never
|
|
// exists here, everything comes from the DVD image, so refusing is the correct SDK answer.
|
|
// ============================================================================
|
|
|
|
extern "C" int32_t contentFastOpenNAND_HLE(uint32_t contentId, uint32_t outHandlePtr) {
|
|
return ISFS_ENOENT;
|
|
}
|
|
PPC_NATIVE_OVERRIDE(8015BC80, contentFastOpenNAND_HLE, int32_t, (uint32_t contentId, uint32_t outHandlePtr), (contentId, outHandlePtr));
|
|
|
|
extern "C" int32_t contentReadNAND_HLE(uint32_t handlePtr, uint32_t buffer, uint32_t length, uint32_t outReadPtr) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
PPC_NATIVE_OVERRIDE(8015BCF8, contentReadNAND_HLE, int32_t, (uint32_t handlePtr, uint32_t buffer, uint32_t length, uint32_t outReadPtr), (handlePtr, buffer, length, outReadPtr));
|