mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-09-10 17:16:47 -04:00
f424536d3b
* 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. * shorten * I dont really want to change this to be honest. * extra safety --------- Co-authored-by: patchzyy <64382339+patchzyy@users.noreply.github.com>
1150 lines
43 KiB
C++
1150 lines
43 KiB
C++
// NAND/ISFS HLE: the IOS_* device layer.
|
|
//
|
|
// Shared state and helpers live in nand_internal.h.
|
|
|
|
#include "nand_internal.h"
|
|
|
|
#include "discord_presence.h"
|
|
#include "runtime_log.h"
|
|
|
|
extern "C" void OSSleepThread_HLE_801aa9b8(CpuContext* ctx);
|
|
|
|
// ============================================================================
|
|
// SHA device handles
|
|
// ============================================================================
|
|
|
|
struct ShaHandle {
|
|
CryptoPP::SHA1 hash;
|
|
uint64_t byteCount = 0;
|
|
|
|
void Restart() {
|
|
hash.Restart();
|
|
byteCount = 0;
|
|
}
|
|
};
|
|
|
|
static std::map<int32_t, ShaHandle> g_shaHandles;
|
|
static int32_t g_nextShaFd = 0x10001;
|
|
static std::mutex g_shaMutex;
|
|
|
|
static int32_t AllocateShaFd() {
|
|
std::lock_guard<std::mutex> lock(g_shaMutex);
|
|
const int32_t fd = g_nextShaFd++;
|
|
g_shaHandles.try_emplace(fd);
|
|
return fd;
|
|
}
|
|
|
|
static ShaHandle* GetShaHandle(int32_t fd) {
|
|
auto it = g_shaHandles.find(fd);
|
|
if (it == g_shaHandles.end()) {
|
|
return nullptr;
|
|
}
|
|
return &it->second;
|
|
}
|
|
|
|
static void CloseShaFd(int32_t fd) {
|
|
std::lock_guard<std::mutex> lock(g_shaMutex);
|
|
g_shaHandles.erase(fd);
|
|
}
|
|
|
|
struct ISFSFileStats {
|
|
uint32_t length; // File size in bytes
|
|
uint32_t position; // Current file position
|
|
};
|
|
|
|
// ============================================================================
|
|
// Device identifiers and ioctl commands
|
|
// ============================================================================
|
|
|
|
// Special FD for /dev/fs (the ISFS device)
|
|
static constexpr int32_t ISFS_DEV_FD = 1;
|
|
static constexpr int32_t ES_DEV_FD = 3;
|
|
static constexpr int32_t DOLPHIN_DEV_FD = 4;
|
|
static constexpr uint32_t ES_IOCTL_GETDEVICEID = 0x07;
|
|
static constexpr uint32_t ES_IOCTL_GETDEVICECERT = 0x1E;
|
|
static constexpr uint32_t ES_IOCTL_GETTITLEID = 0x20;
|
|
static constexpr uint32_t ES_IOCTL_SIGN = 0x30;
|
|
static constexpr uint32_t DOLPHIN_IOCTL_GET_ELAPSED_TIME = 0x01;
|
|
static constexpr uint32_t DOLPHIN_IOCTL_GET_VERSION = 0x02;
|
|
static constexpr uint32_t DOLPHIN_IOCTL_GET_SPEED_LIMIT = 0x03;
|
|
static constexpr uint32_t DOLPHIN_IOCTL_SET_SPEED_LIMIT = 0x04;
|
|
static constexpr uint32_t DOLPHIN_IOCTL_GET_CPU_SPEED = 0x05;
|
|
static constexpr uint32_t DOLPHIN_IOCTL_GET_REAL_PRODUCT_CODE = 0x06;
|
|
static constexpr uint32_t DOLPHIN_IOCTL_DISCORD_SET_CLIENT = 0x07;
|
|
static constexpr uint32_t DOLPHIN_IOCTL_DISCORD_SET_PRESENCE = 0x08;
|
|
static constexpr uint32_t DOLPHIN_IOCTL_DISCORD_RESET = 0x09;
|
|
static constexpr uint32_t DOLPHIN_IOCTL_GET_SYSTEM_TIME = 0x0A;
|
|
static constexpr uint32_t SHA_IOCTL_INIT = 0;
|
|
static constexpr uint32_t SHA_IOCTL_UPDATE = 1;
|
|
static constexpr uint32_t SHA_IOCTL_FINAL = 2;
|
|
|
|
static std::string ReadGuestCString(uint32_t address, size_t maxLength = 1024) {
|
|
std::string text;
|
|
if (address == 0) {
|
|
return text;
|
|
}
|
|
|
|
for (size_t i = 0; i < maxLength; ++i) {
|
|
const uint32_t current = address + static_cast<uint32_t>(i);
|
|
if (!Memory::Contains(current, 1)) {
|
|
break;
|
|
}
|
|
const char ch = static_cast<char>(Memory::Read8(current));
|
|
if (ch == '\0') {
|
|
break;
|
|
}
|
|
text.push_back(ch);
|
|
}
|
|
return text;
|
|
}
|
|
static constexpr uint32_t SHA_CONTEXT_SIZE = 0x1c;
|
|
static constexpr uint32_t SHA_DIGEST_SIZE = 0x14;
|
|
|
|
// Same guest layout as the /dev/net ioctlv descriptors; see runtime_parse_helpers.h.
|
|
using IosVector = RuntimeHle::IoVector;
|
|
using RuntimeHle::ReadIoVector;
|
|
|
|
static IosVector ReadIosVector(uint32_t vectorPtr, uint32_t index) {
|
|
return ReadIoVector(vectorPtr, index);
|
|
}
|
|
|
|
static uint64_t CurrentMkwTitleId() {
|
|
uint32_t low = CurrentMkwTitleIdLo();
|
|
return (static_cast<uint64_t>(kNandTitleIdHi) << 32) | low;
|
|
}
|
|
|
|
static bool WriteGuestBytes(uint32_t address, uint32_t size, const uint8_t* data, size_t dataSize) {
|
|
if (address == 0 || size < dataSize || !Memory::Contains(address, dataSize)) {
|
|
return false;
|
|
}
|
|
uint8_t* out = Memory::GetPointer(address, dataSize);
|
|
std::memcpy(out, data, dataSize);
|
|
return true;
|
|
}
|
|
|
|
static bool IsValidGuestRange(uint32_t address, uint32_t size) {
|
|
return size == 0 || (address != 0 && Memory::Contains(address, size));
|
|
}
|
|
|
|
using DolphinClock = std::chrono::steady_clock;
|
|
// Dolphin starts this clock when the emulation device is constructed, not on its first ioctl.
|
|
static const DolphinClock::time_point g_dolphinElapsedStart = DolphinClock::now();
|
|
|
|
static uint32_t DolphinElapsedMilliseconds() {
|
|
return static_cast<uint32_t>(
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(DolphinClock::now() - g_dolphinElapsedStart).count());
|
|
}
|
|
|
|
static int32_t HandleDolphinIoctlv(uint32_t cmd, uint32_t numIn, uint32_t numOut, uint32_t vectorPtr) {
|
|
if (vectorPtr != 0 && !Memory::Contains(vectorPtr, static_cast<size_t>(numIn + numOut) * 8u)) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
|
|
// Every /dev/dolphin command answers through exactly one output vector.
|
|
// `minimumSize` 0 means the case validates the buffer itself (WriteGuestBytes).
|
|
const auto singleOut = [&](uint32_t minimumSize, IosVector& out) -> bool {
|
|
if (numOut != 1 || vectorPtr == 0) {
|
|
return false;
|
|
}
|
|
out = ReadIosVector(vectorPtr, numIn);
|
|
return minimumSize == 0 ||
|
|
(out.size >= minimumSize && Memory::Contains(out.address, minimumSize));
|
|
};
|
|
|
|
IosVector out;
|
|
switch (cmd) {
|
|
case DOLPHIN_IOCTL_GET_ELAPSED_TIME: {
|
|
if (!singleOut(4u, out)) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
Memory::Write32(out.address, DolphinElapsedMilliseconds());
|
|
return ISFS_OK;
|
|
}
|
|
|
|
case DOLPHIN_IOCTL_GET_VERSION: {
|
|
if (!singleOut(0u, out)) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
static constexpr char kVersion[] = "WiiCompiled-DolphinDevice";
|
|
if (!WriteGuestBytes(out.address, out.size,
|
|
reinterpret_cast<const uint8_t*>(kVersion), sizeof(kVersion))) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
return ISFS_OK;
|
|
}
|
|
|
|
case DOLPHIN_IOCTL_GET_SPEED_LIMIT:
|
|
case DOLPHIN_IOCTL_GET_CPU_SPEED: {
|
|
if (!singleOut(4u, out)) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
Memory::Write32(out.address, cmd == DOLPHIN_IOCTL_GET_SPEED_LIMIT ? 100u : 729000000u);
|
|
return ISFS_OK;
|
|
}
|
|
|
|
case DOLPHIN_IOCTL_GET_REAL_PRODUCT_CODE: {
|
|
if (!singleOut(0u, out)) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
char productCode[8] = {};
|
|
uint32_t discId = Memory::Contains(0x80000000u, 4u) ? Memory::Read32(0x80000000u) : kNandTitleIdLo;
|
|
productCode[0] = static_cast<char>((discId >> 24) & 0xffu);
|
|
productCode[1] = static_cast<char>((discId >> 16) & 0xffu);
|
|
productCode[2] = static_cast<char>((discId >> 8) & 0xffu);
|
|
productCode[3] = static_cast<char>(discId & 0xffu);
|
|
productCode[4] = '0';
|
|
productCode[5] = '1';
|
|
if (!WriteGuestBytes(out.address, out.size,
|
|
reinterpret_cast<const uint8_t*>(productCode), sizeof(productCode))) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
return ISFS_OK;
|
|
}
|
|
|
|
case DOLPHIN_IOCTL_SET_SPEED_LIMIT:
|
|
return ISFS_OK;
|
|
|
|
case DOLPHIN_IOCTL_DISCORD_SET_CLIENT: {
|
|
if (numIn != 1 || numOut != 0 || vectorPtr == 0) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
const IosVector client = ReadIosVector(vectorPtr, 0);
|
|
if (!IsValidGuestRange(client.address, client.size)) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
if (RuntimeConfigFile::DiscordPresenceEnabled()) {
|
|
DiscordPresence::SetClient(ReadGuestCString(client.address, client.size));
|
|
}
|
|
return ISFS_OK;
|
|
}
|
|
|
|
case DOLPHIN_IOCTL_DISCORD_SET_PRESENCE: {
|
|
if (numIn != 10 || numOut != 0 || vectorPtr == 0) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
std::array<IosVector, 10> values{};
|
|
for (uint32_t index = 0; index < values.size(); ++index) {
|
|
values[index] = ReadIosVector(vectorPtr, index);
|
|
if (!IsValidGuestRange(values[index].address, values[index].size)) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
}
|
|
if (RuntimeConfigFile::DiscordPresenceEnabled()) {
|
|
DiscordPresence::Activity activity;
|
|
activity.details = ReadGuestCString(values[0].address, values[0].size);
|
|
activity.state = ReadGuestCString(values[1].address, values[1].size);
|
|
activity.largeImageKey = ReadGuestCString(values[2].address, values[2].size);
|
|
activity.largeImageText = ReadGuestCString(values[3].address, values[3].size);
|
|
activity.smallImageKey = ReadGuestCString(values[4].address, values[4].size);
|
|
activity.smallImageText = ReadGuestCString(values[5].address, values[5].size);
|
|
if (values[6].size >= 8 && Memory::Contains(values[6].address, 8)) {
|
|
activity.startTimestamp = static_cast<int64_t>(
|
|
(static_cast<uint64_t>(Memory::Read32(values[6].address)) << 32) |
|
|
Memory::Read32(values[6].address + 4));
|
|
}
|
|
if (values[7].size >= 8 && Memory::Contains(values[7].address, 8)) {
|
|
activity.endTimestamp = static_cast<int64_t>(
|
|
(static_cast<uint64_t>(Memory::Read32(values[7].address)) << 32) |
|
|
Memory::Read32(values[7].address + 4));
|
|
}
|
|
if (values[8].size >= 4) {
|
|
activity.partySize = Memory::Read32(values[8].address);
|
|
}
|
|
if (values[9].size >= 4) {
|
|
activity.partyMax = Memory::Read32(values[9].address);
|
|
}
|
|
DiscordPresence::SetActivity(std::move(activity));
|
|
}
|
|
return ISFS_OK;
|
|
}
|
|
|
|
case DOLPHIN_IOCTL_DISCORD_RESET:
|
|
if (numIn != 0 || numOut != 0) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
if (RuntimeConfigFile::DiscordPresenceEnabled()) {
|
|
DiscordPresence::Reset();
|
|
}
|
|
return ISFS_OK;
|
|
|
|
case DOLPHIN_IOCTL_GET_SYSTEM_TIME: {
|
|
if (!singleOut(8u, out)) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
const auto nowMs = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
std::chrono::system_clock::now().time_since_epoch()).count();
|
|
const uint64_t value = static_cast<uint64_t>(nowMs);
|
|
Memory::Write32(out.address, static_cast<uint32_t>(value >> 32));
|
|
Memory::Write32(out.address + 4u, static_cast<uint32_t>(value));
|
|
return ISFS_OK;
|
|
}
|
|
|
|
default:
|
|
LogNandWarning("IOS_Ioctlv", "/dev/dolphin unsupported cmd=%u", cmd);
|
|
return ISFS_EINVAL;
|
|
}
|
|
}
|
|
|
|
static bool WriteShaOutputs(const ShaHandle& handle, const IosVector& context, const IosVector& hash) {
|
|
CryptoPP::SHA1 snapshot = handle.hash;
|
|
std::array<uint8_t, CryptoPP::SHA1::DIGESTSIZE> digest{};
|
|
snapshot.Final(digest.data());
|
|
if (!WriteGuestBytes(hash.address, hash.size, digest.data(), digest.size())) {
|
|
return false;
|
|
}
|
|
|
|
if (context.address != 0 && context.size >= SHA_CONTEXT_SIZE && Memory::Contains(context.address, SHA_CONTEXT_SIZE)) {
|
|
Memory::Write32(context.address + 0x00, 0x67452301u);
|
|
Memory::Write32(context.address + 0x04, 0xEFCDAB89u);
|
|
Memory::Write32(context.address + 0x08, 0x98BADCFEu);
|
|
Memory::Write32(context.address + 0x0c, 0x10325476u);
|
|
Memory::Write32(context.address + 0x10, 0xC3D2E1F0u);
|
|
const uint64_t bitCount = handle.byteCount * 8u;
|
|
Memory::Write32(context.address + 0x14, static_cast<uint32_t>(bitCount >> 32));
|
|
Memory::Write32(context.address + 0x18, static_cast<uint32_t>(bitCount));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int32_t HandleShaIoctlv(int32_t fd, uint32_t cmd, uint32_t numIn, uint32_t numOut, uint32_t vectorPtr) {
|
|
if (!vectorPtr || !Memory::Contains(vectorPtr, static_cast<size_t>(numIn + numOut) * 8u)) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
if (numIn != 1 || numOut != 2) {
|
|
LogNandWarning("IOS_Ioctlv", "/dev/sha unsupported vector shape cmd=%u numIn=%u numOut=%u",
|
|
cmd, numIn, numOut);
|
|
return ISFS_EINVAL;
|
|
}
|
|
|
|
ShaHandle* handle = GetShaHandle(fd);
|
|
if (!handle) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
|
|
const IosVector input = ReadIosVector(vectorPtr, 0);
|
|
const IosVector context = ReadIosVector(vectorPtr, 1);
|
|
const IosVector hash = ReadIosVector(vectorPtr, 2);
|
|
if (!IsValidGuestRange(input.address, input.size) ||
|
|
context.size < SHA_CONTEXT_SIZE || hash.size < SHA_DIGEST_SIZE ||
|
|
!IsValidGuestRange(context.address, SHA_CONTEXT_SIZE) ||
|
|
!IsValidGuestRange(hash.address, SHA_DIGEST_SIZE)) {
|
|
LogNandWarning("IOS_Ioctlv",
|
|
"/dev/sha invalid buffers cmd=%u in=0x%08X/%u ctx=0x%08X/%u hash=0x%08X/%u",
|
|
cmd, input.address, input.size, context.address, context.size, hash.address, hash.size);
|
|
return ISFS_EINVAL;
|
|
}
|
|
|
|
if (cmd == SHA_IOCTL_INIT) {
|
|
handle->Restart();
|
|
} else if (cmd != SHA_IOCTL_UPDATE && cmd != SHA_IOCTL_FINAL) {
|
|
LogNandWarning("IOS_Ioctlv", "/dev/sha unsupported cmd=%u", cmd);
|
|
return ISFS_EINVAL;
|
|
}
|
|
|
|
if (input.size != 0) {
|
|
const uint8_t* bytes = Memory::GetPointer(input.address, input.size);
|
|
handle->hash.Update(bytes, input.size);
|
|
handle->byteCount += input.size;
|
|
}
|
|
|
|
if (!WriteShaOutputs(*handle, context, hash)) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
|
|
if (cmd == SHA_IOCTL_FINAL) {
|
|
handle->Restart();
|
|
}
|
|
return ISFS_OK;
|
|
}
|
|
|
|
extern "C" int32_t NAND_IOS_Open_HLE(uint32_t pathPtr, uint32_t mode) {
|
|
const std::string pathStorage = ReadGuestCString(pathPtr);
|
|
const char* path = pathPtr == 0 ? nullptr : pathStorage.c_str();
|
|
|
|
if (!path) {
|
|
LogNandError("IOS_Open", "null path");
|
|
return ISFS_EINVAL;
|
|
}
|
|
|
|
// Handle special device paths
|
|
if (std::strncmp(path, "/dev/", 5) == 0) {
|
|
if (std::strcmp(path, "/dev/fs") == 0) {
|
|
return ISFS_DEV_FD;
|
|
}
|
|
if (std::strcmp(path, "/dev/es") == 0) {
|
|
return ES_DEV_FD;
|
|
}
|
|
if (std::strcmp(path, "/dev/sha") == 0) {
|
|
const int32_t fd = AllocateShaFd();
|
|
return fd;
|
|
}
|
|
if (const int32_t netFd = Network_HLE_OpenDevice(path, mode)) {
|
|
return netFd;
|
|
}
|
|
if (std::strcmp(path, "/dev/dolphin") == 0) {
|
|
return DOLPHIN_DEV_FD;
|
|
}
|
|
LogNandWarning("IOS_Open", "unknown device '%s' mode=%u", path, mode);
|
|
return ISFS_ENOENT;
|
|
}
|
|
|
|
// It's a NAND file path
|
|
const std::filesystem::path hostPath = TranslateNandPath(path);
|
|
|
|
if (const auto result = NandCheckSystemSaveRead("IOS_Open", hostPath, mode, true))
|
|
return *result;
|
|
|
|
// Seed FaceLib resources before the existence check so every open mode can
|
|
// still find them on a fresh managed NAND.
|
|
if (!PathExists(hostPath) && IsFaceLibResourcePath(path)) {
|
|
SeedFaceLibResource(hostPath);
|
|
}
|
|
|
|
// Determine file mode. IOS never creates files on open - creation happens
|
|
// exclusively through ISFS CreateFile (which we implement). The previous
|
|
// create-on-open fallback ("w+b") silently materialized 0-byte files (for
|
|
// example /shared2/sys/net/02/config.dat) that later reads treated as
|
|
// valid, poisoning persistent state across sessions.
|
|
const char* fopenMode = "rb";
|
|
if (mode == 2 || mode == 3) {
|
|
if (!PathExists(hostPath)) {
|
|
LogNandWarning("IOS_Open", "'%s' does not exist; open mode %u never creates it",
|
|
HostPathText(hostPath).c_str(), mode);
|
|
return ISFS_ENOENT;
|
|
}
|
|
fopenMode = "r+b"; // Write-only opens still need read for seeks
|
|
}
|
|
|
|
FILE* file = NandFopen(hostPath, fopenMode);
|
|
|
|
if (!file) {
|
|
LogNandError("IOS_Open", "FAILED to open '%s'", HostPathText(hostPath).c_str());
|
|
return ISFS_ENOENT;
|
|
}
|
|
|
|
int32_t fd = AllocateFd(hostPath, file, mode);
|
|
return fd;
|
|
}
|
|
PPC_NATIVE_OVERRIDE(801938F8, NAND_IOS_Open_HLE, int32_t, (uint32_t pathPtr, uint32_t mode), (pathPtr, mode));
|
|
|
|
extern "C" void NAND_IOS_OpenBody_HLE_801938FC(CpuContext* ctx) {
|
|
const int32_t result = NAND_IOS_Open_HLE(ctx->gpr[3], ctx->gpr[4]);
|
|
ctx->gpr[3] = static_cast<uint32_t>(result);
|
|
ctx->gpr[1] = ctx->gpr[1] + 32u;
|
|
}
|
|
REGISTER_NATIVE_FUNCTION_AS(0x801938FC, NAND_IOS_OpenBody_HLE_801938FC, "NAND_IOS_OpenBody_HLE_801938FC");
|
|
|
|
extern "C" int32_t NAND_IOS_Close_HLE(uint32_t fd) {
|
|
if (fd == ISFS_DEV_FD) {
|
|
return ISFS_OK;
|
|
}
|
|
if (fd == ES_DEV_FD) {
|
|
return ISFS_OK;
|
|
}
|
|
if (fd == DOLPHIN_DEV_FD) {
|
|
return ISFS_OK;
|
|
}
|
|
if (GetShaHandle(static_cast<int32_t>(fd))) {
|
|
CloseShaFd(static_cast<int32_t>(fd));
|
|
return ISFS_OK;
|
|
}
|
|
if (Network_HLE_IsFd(fd)) {
|
|
return Network_HLE_Close(fd);
|
|
}
|
|
|
|
auto* handle = GetHandle(fd);
|
|
if (!handle) {
|
|
LogNandError("IOS_Close", "invalid fd=%d", fd);
|
|
return ISFS_EINVAL;
|
|
}
|
|
|
|
CloseFd(fd);
|
|
return ISFS_OK;
|
|
}
|
|
PPC_NATIVE_OVERRIDE(80193AD8, NAND_IOS_Close_HLE, int32_t, (uint32_t fd), (fd));
|
|
|
|
extern "C" int32_t NAND_IOS_Read_HLE(uint32_t fd, uint32_t bufferPtr, uint32_t length) {
|
|
auto* handle = GetHandle(fd);
|
|
if (!handle || !handle->file) {
|
|
LogNandError("IOS_Read", "invalid fd=%d", fd);
|
|
return ISFS_EINVAL;
|
|
}
|
|
|
|
if (!bufferPtr || length == 0) {
|
|
return 0;
|
|
}
|
|
|
|
uint8_t* buffer = (uint8_t*)Memory::GetPointer(bufferPtr);
|
|
if (!buffer) {
|
|
LogNandError("IOS_Read", "invalid buffer ptr 0x%08X", bufferPtr);
|
|
return ISFS_EINVAL;
|
|
}
|
|
|
|
size_t bytesRead = std::fread(buffer, 1, length, handle->file);
|
|
handle->position += static_cast<uint32_t>(bytesRead);
|
|
|
|
return static_cast<int32_t>(bytesRead);
|
|
}
|
|
PPC_NATIVE_OVERRIDE(80193C80, NAND_IOS_Read_HLE, int32_t, (uint32_t fd, uint32_t bufferPtr, uint32_t length), (fd, bufferPtr, length));
|
|
|
|
extern "C" int32_t NAND_IOS_Write_HLE(uint32_t fd, uint32_t bufferPtr, uint32_t length) {
|
|
auto* handle = GetHandle(fd);
|
|
if (!handle || !handle->file) {
|
|
LogNandError("IOS_Write", "invalid fd=%d", fd);
|
|
return ISFS_EINVAL;
|
|
}
|
|
|
|
if (!bufferPtr || length == 0) {
|
|
return 0;
|
|
}
|
|
|
|
const uint8_t* buffer = (const uint8_t*)Memory::GetPointer(bufferPtr);
|
|
if (!buffer) {
|
|
LogNandError("IOS_Write", "invalid buffer ptr 0x%08X", bufferPtr);
|
|
return ISFS_EINVAL;
|
|
}
|
|
|
|
size_t bytesWritten = std::fwrite(buffer, 1, length, handle->file);
|
|
std::fflush(handle->file);
|
|
handle->position += static_cast<uint32_t>(bytesWritten);
|
|
|
|
return static_cast<int32_t>(bytesWritten);
|
|
}
|
|
PPC_NATIVE_OVERRIDE(80193E88, NAND_IOS_Write_HLE, int32_t, (uint32_t fd, uint32_t bufferPtr, uint32_t length), (fd, bufferPtr, length));
|
|
|
|
extern "C" int32_t NAND_IOS_Seek_HLE(uint32_t fd, int32_t offset, int32_t whence) {
|
|
auto* handle = GetHandle(fd);
|
|
if (!handle || !handle->file) {
|
|
LogNandError("IOS_Seek", "invalid fd=%d", fd);
|
|
return ISFS_EINVAL;
|
|
}
|
|
|
|
if (std::fseek(handle->file, offset, NandSeekOrigin(whence)) != 0) {
|
|
LogNandError("IOS_Seek", "fd=%d offset=%d whence=%d FAILED", fd, offset, whence);
|
|
return ISFS_EIO;
|
|
}
|
|
|
|
handle->position = static_cast<uint32_t>(std::ftell(handle->file));
|
|
return static_cast<int32_t>(handle->position);
|
|
}
|
|
PPC_NATIVE_OVERRIDE(80194070, NAND_IOS_Seek_HLE, int32_t, (uint32_t fd, int32_t offset, int32_t whence), (fd, offset, whence));
|
|
|
|
// ============================================================================
|
|
// IOS_Ioctl HLE - Handles filesystem commands
|
|
// ============================================================================
|
|
|
|
// ISFS Ioctl commands
|
|
enum ISFSCommand {
|
|
ISFS_IOCTL_FORMAT = 1,
|
|
ISFS_IOCTL_GETSTATS = 2,
|
|
ISFS_IOCTL_CREATEDIR = 3,
|
|
ISFS_IOCTL_READDIR = 4,
|
|
ISFS_IOCTL_SETATTR = 5,
|
|
ISFS_IOCTL_GETATTR = 6,
|
|
ISFS_IOCTL_DELETE = 7,
|
|
ISFS_IOCTL_RENAME = 8,
|
|
ISFS_IOCTL_CREATEFILE = 9,
|
|
ISFS_IOCTL_SETFILEVERCTRL = 10,
|
|
ISFS_IOCTL_GETFILESTATS = 11,
|
|
ISFS_IOCTL_GETUSAGE = 12,
|
|
ISFS_IOCTL_SHUTDOWN = 13,
|
|
};
|
|
|
|
extern "C" int32_t NAND_IOS_Ioctl_HLE(
|
|
uint32_t fd,
|
|
uint32_t cmd,
|
|
uint32_t inBufPtr, uint32_t inLen,
|
|
uint32_t outBufPtr, uint32_t outLen)
|
|
{
|
|
|
|
if (Network_HLE_IsFd(fd)) {
|
|
return Network_HLE_Ioctl(fd, cmd, inBufPtr, inLen, outBufPtr, outLen);
|
|
}
|
|
|
|
if (GetShaHandle(static_cast<int32_t>(fd))) {
|
|
LogNandWarning("IOS_Ioctl", "/dev/sha does not support scalar ioctl cmd=%u", cmd);
|
|
return ISFS_EINVAL;
|
|
}
|
|
if (fd == DOLPHIN_DEV_FD) {
|
|
LogNandWarning("IOS_Ioctl", "/dev/dolphin does not support scalar ioctl cmd=%u", cmd);
|
|
return ISFS_EINVAL;
|
|
}
|
|
|
|
// Handle /dev/fs ISFS commands
|
|
if (fd == ISFS_DEV_FD) {
|
|
switch (cmd) {
|
|
case ISFS_IOCTL_CREATEDIR: {
|
|
// Input buffer: path + attributes
|
|
if (!inBufPtr || inLen < 0x4c) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
const char* path = (const char*)Memory::GetPointer(inBufPtr + 6);
|
|
const std::filesystem::path hostPath = TranslateNandPath(path);
|
|
|
|
if (CreateDirectoryPath(hostPath)) {
|
|
return ISFS_OK;
|
|
}
|
|
return ISFS_EIO;
|
|
}
|
|
|
|
case ISFS_IOCTL_DELETE: {
|
|
if (!inBufPtr || inLen < 0x40) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
const char* path = (const char*)Memory::GetPointer(inBufPtr);
|
|
const std::filesystem::path hostPath = TranslateNandPath(path);
|
|
|
|
// fs::remove refuses a non-empty directory, matching rmdir.
|
|
if (NandRemove(hostPath)) {
|
|
return ISFS_OK;
|
|
}
|
|
return ISFS_ENOENT;
|
|
}
|
|
|
|
case ISFS_IOCTL_GETATTR: {
|
|
if (!inBufPtr || !outBufPtr) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
const char* path = (const char*)Memory::GetPointer(inBufPtr);
|
|
const std::filesystem::path hostPath = TranslateNandPath(path);
|
|
|
|
if (!PathExists(hostPath)) {
|
|
return ISFS_ENOENT;
|
|
}
|
|
|
|
// Return fake attributes (owner UID, group ID, permissions)
|
|
// Format: u32 ownerID, u16 groupID, u8 ownerPerm, u8 groupPerm, u8 otherPerm, u8 attrs
|
|
uint8_t* outBuf = (uint8_t*)Memory::GetPointer(outBufPtr);
|
|
if (outBuf && outLen >= 0x4c) {
|
|
std::memset(outBuf, 0, outLen);
|
|
// Owner UID = 0
|
|
Memory::Write32(outBufPtr, 0);
|
|
// Group ID = 0
|
|
Memory::Write16(outBufPtr + 4, 0);
|
|
// Permissions: 3 = read/write for all
|
|
Memory::Write8(outBufPtr + 0x49, 3); // owner perm
|
|
Memory::Write8(outBufPtr + 0x46, 3); // group perm
|
|
Memory::Write8(outBufPtr + 0x47, 3); // other perm
|
|
Memory::Write8(outBufPtr + 0x48, IsDirectory(hostPath) ? 2 : 1); // attrs (2=dir, 1=file)
|
|
}
|
|
return ISFS_OK;
|
|
}
|
|
|
|
case ISFS_IOCTL_CREATEFILE: {
|
|
if (!inBufPtr || inLen < 0x4c) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
const char* path = (const char*)Memory::GetPointer(inBufPtr + 6);
|
|
const std::filesystem::path hostPath = TranslateNandPath(path);
|
|
CreateParentDirectories(hostPath);
|
|
|
|
// Create empty file
|
|
FILE* f = NandFopen(hostPath, "wb");
|
|
if (f) {
|
|
std::fclose(f);
|
|
return ISFS_OK;
|
|
}
|
|
return ISFS_EIO;
|
|
}
|
|
|
|
case ISFS_IOCTL_GETFILESTATS: {
|
|
// GETFILESTATS is addressed to a file fd, never to /dev/fs.
|
|
LogNandWarning("IOS_Ioctl", "GETFILESTATS on ISFS device - unexpected");
|
|
return ISFS_EINVAL;
|
|
}
|
|
|
|
case ISFS_IOCTL_RENAME: {
|
|
if (!inBufPtr || inLen < 0x80) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
const char* srcPath = (const char*)Memory::GetPointer(inBufPtr);
|
|
const char* dstPath = (const char*)Memory::GetPointer(inBufPtr + 0x40);
|
|
const std::filesystem::path srcHost = TranslateNandPath(srcPath);
|
|
const std::filesystem::path dstHost = TranslateNandPath(dstPath);
|
|
|
|
if (NandRename(srcHost, dstHost)) {
|
|
return ISFS_OK;
|
|
}
|
|
return ISFS_EIO;
|
|
}
|
|
|
|
case ISFS_IOCTL_GETSTATS: {
|
|
// Return filesystem stats (fake values)
|
|
if (outBufPtr && outLen >= 0x1c) {
|
|
Memory::Write32(outBufPtr + 0x00, 0x200000); // Total blocks
|
|
Memory::Write32(outBufPtr + 0x04, 0x100000); // Free blocks
|
|
Memory::Write32(outBufPtr + 0x08, 0); // Used blocks
|
|
Memory::Write32(outBufPtr + 0x0C, 0); // Bad blocks
|
|
Memory::Write32(outBufPtr + 0x10, 0); // Reserved blocks
|
|
Memory::Write32(outBufPtr + 0x14, 0x20); // Block size
|
|
Memory::Write32(outBufPtr + 0x18, 0); // Free inodes
|
|
}
|
|
return ISFS_OK;
|
|
}
|
|
|
|
case ISFS_IOCTL_SETATTR: {
|
|
// Ignore attribute changes - we don't implement file permissions
|
|
return ISFS_OK;
|
|
}
|
|
|
|
case ISFS_IOCTL_GETUSAGE: {
|
|
// Return usage info (fake values)
|
|
if (outBufPtr && outLen >= 8) {
|
|
Memory::Write32(outBufPtr + 0, 100); // Files
|
|
Memory::Write32(outBufPtr + 4, 10000); // Blocks used
|
|
}
|
|
return ISFS_OK;
|
|
}
|
|
|
|
case ISFS_IOCTL_READDIR: {
|
|
// Read directory listing
|
|
// This is complex - return empty for now
|
|
if (outBufPtr && outLen >= 4) {
|
|
Memory::Write32(outBufPtr, 0); // 0 entries
|
|
}
|
|
return ISFS_OK;
|
|
}
|
|
|
|
default:
|
|
LogNandWarning("IOS_Ioctl", "unknown ISFS cmd=%u", cmd);
|
|
return ISFS_OK;
|
|
}
|
|
}
|
|
|
|
// Handle file-specific commands
|
|
auto* handle = GetHandle(fd);
|
|
if (handle && handle->file) {
|
|
if (cmd == ISFS_IOCTL_GETFILESTATS) {
|
|
// Get file stats
|
|
if (!outBufPtr || outLen < 8) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
|
|
const NandFileExtent extent = NandProbeFileExtent(handle->file);
|
|
Memory::Write32(outBufPtr, static_cast<uint32_t>(extent.size));
|
|
Memory::Write32(outBufPtr + 4, static_cast<uint32_t>(extent.position));
|
|
|
|
return ISFS_OK;
|
|
}
|
|
}
|
|
|
|
// Unknown command - return success to not block game
|
|
return ISFS_OK;
|
|
}
|
|
|
|
// The stack frame a guest thread parks on while a deferred network ioctl runs.
|
|
// `newStack` is always oldStack - kFrameSize, even when the frame could not be
|
|
// built, because the sleep path installs it unconditionally.
|
|
struct IosWaitFrame {
|
|
bool valid = false;
|
|
uint32_t oldStack = 0;
|
|
uint32_t newStack = 0;
|
|
uint32_t waitQueue = 0;
|
|
};
|
|
|
|
static IosWaitFrame InitializeIosWaitQueueFrame(CpuContext* ctx) {
|
|
constexpr uint32_t kFrameSize = 0x40u;
|
|
constexpr uint32_t kWaitQueueOffset = 0x30u;
|
|
|
|
IosWaitFrame frame;
|
|
frame.oldStack = ctx->gpr[1];
|
|
frame.newStack = frame.oldStack - kFrameSize;
|
|
if (frame.oldStack < kFrameSize || !Memory::Contains(frame.newStack, kFrameSize)) {
|
|
return frame;
|
|
}
|
|
// Preserve the PPC linkage area and the required r3-r10 outgoing-argument
|
|
// save area. The queue lives in local storage beyond sp+0x28 so a guest
|
|
// switch callback cannot legally spill over it while this thread sleeps.
|
|
Memory::Write32(frame.newStack, frame.oldStack);
|
|
Memory::Write32(frame.newStack + 4u, 0);
|
|
Memory::Write32(frame.newStack + kWaitQueueOffset, 0);
|
|
Memory::Write32(frame.newStack + kWaitQueueOffset + 4u, 0);
|
|
frame.waitQueue = frame.newStack + kWaitQueueOffset;
|
|
frame.valid = true;
|
|
return frame;
|
|
}
|
|
|
|
static void FinishDeferredIosWait(CpuContext* ctx, uint32_t oldStack, uint64_t token) {
|
|
int32_t result = -101;
|
|
if (!Network_HLE_TakeSyncResult(token, &result)) {
|
|
RT_LOGF(RT_TAG_NAND,
|
|
"deferred network waiter resumed without result token=%llu\n",
|
|
static_cast<unsigned long long>(token));
|
|
}
|
|
ctx->gpr[1] = oldStack;
|
|
ctx->gpr[3] = static_cast<uint32_t>(result);
|
|
}
|
|
|
|
// IOS_Ioctl and IOS_Ioctlv park a network request the same way: build the wait
|
|
// frame, hand its queue to the network layer, and either sleep on it or take the
|
|
// immediate answer. True when the request was handled here.
|
|
template <typename StartSync>
|
|
static bool TryDeferredNetworkIosSync(CpuContext* ctx, StartSync&& startSync) {
|
|
const IosWaitFrame frame = InitializeIosWaitQueueFrame(ctx);
|
|
const auto deferred = startSync(frame.valid ? frame.waitQueue : 0u);
|
|
if (deferred.disposition == NetworkDeferredContract::StartDisposition::Started) {
|
|
ctx->gpr[1] = frame.newStack;
|
|
ctx->gpr[3] = frame.waitQueue;
|
|
OSSleepThread_HLE_801aa9b8(ctx);
|
|
FinishDeferredIosWait(ctx, frame.oldStack, deferred.token);
|
|
return true;
|
|
}
|
|
if (deferred.disposition == NetworkDeferredContract::StartDisposition::ImmediateResult) {
|
|
ctx->gpr[3] = static_cast<uint32_t>(deferred.result);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
extern "C" void NAND_IOS_Ioctl_Entry_HLE(CpuContext* ctx) {
|
|
const uint32_t fd = ctx->gpr[3];
|
|
const uint32_t cmd = ctx->gpr[4];
|
|
const uint32_t inBufPtr = ctx->gpr[5];
|
|
const uint32_t inLen = ctx->gpr[6];
|
|
const uint32_t outBufPtr = ctx->gpr[7];
|
|
const uint32_t outLen = ctx->gpr[8];
|
|
|
|
if (Network_HLE_IsFd(fd)) {
|
|
const bool handled = TryDeferredNetworkIosSync(ctx, [&](uint32_t waitQueue) {
|
|
return Network_HLE_StartIoctlSync(fd, cmd, inBufPtr, inLen, outBufPtr, outLen, waitQueue);
|
|
});
|
|
if (handled) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
ctx->gpr[3] = static_cast<uint32_t>(
|
|
NAND_IOS_Ioctl_HLE(fd, cmd, inBufPtr, inLen, outBufPtr, outLen));
|
|
}
|
|
PPC_NATIVE_OVERRIDE_VOID(80194290, NAND_IOS_Ioctl_Entry_HLE, (CpuContext* ctx), (ctx));
|
|
|
|
// ============================================================================
|
|
// ISFS_OpenLib - Initialize ISFS
|
|
// ============================================================================
|
|
|
|
// Global state for ISFS initialization
|
|
static bool g_isfsInitialized = false;
|
|
|
|
// The ISFS/IPC globals ISFS_OpenLib touches, as negative r13 (SDA1) offsets.
|
|
// These are address-exact: they name the SDK's own variables, so the numbers are
|
|
// load-bearing and must not be "tidied". Names come from the RVL IPC/ISFS
|
|
// sources; only the naming changed here, never a value.
|
|
namespace {
|
|
constexpr uint32_t kIsfsFdSda1Offset = 29408u; // __ISFS_fd
|
|
constexpr uint32_t kIsfsPathSda1Offset = 29400u; // __ISFS_path ("/dev/fs")
|
|
constexpr uint32_t kIpcBufferLoSda1Offset = 25620u; // IPC buffer window, low
|
|
constexpr uint32_t kIpcBufferHiSda1Offset = 25616u; // IPC buffer window, high
|
|
constexpr uint32_t kIpcArenaLoSda1Offset = 25732u; // __IPCArenaLo
|
|
constexpr uint32_t kIpcArenaHiSda1Offset = 25728u; // __IPCArenaHi
|
|
constexpr uint32_t kIsfsHeapHandleSda1Offset = 25724u; // ISFS heap handle
|
|
constexpr uint32_t kIsfsHeapBaseSda1Offset = 25740u; // ISFS heap base address
|
|
constexpr uint32_t kIsfsHeapInitializedSda1Offset = 25744u; // ISFS heap created flag
|
|
} // namespace
|
|
|
|
static void WriteGuestString(uint32_t address, const char* value) {
|
|
if (!value) {
|
|
return;
|
|
}
|
|
const size_t length = std::strlen(value) + 1;
|
|
if (!Memory::Contains(address, length)) {
|
|
return;
|
|
}
|
|
for (size_t i = 0; i < length; ++i) {
|
|
Memory::Write8(address + static_cast<uint32_t>(i), static_cast<uint8_t>(value[i]));
|
|
}
|
|
}
|
|
|
|
int32_t ISFS_OpenLib_Initialize(CpuContext* ctx) {
|
|
g_isfsInitialized = true;
|
|
|
|
// Create the title data directory if it doesn't exist
|
|
char titleId[32];
|
|
std::snprintf(titleId, sizeof(titleId), "%08x", kNandTitleIdHi);
|
|
char gameId[32];
|
|
std::snprintf(gameId, sizeof(gameId), "%08x", CurrentMkwTitleIdLo());
|
|
CreateDirectoryPath(GetNandBasePath() / "title" / titleId / gameId / "data");
|
|
|
|
if (!ctx) {
|
|
return ISFS_OK;
|
|
}
|
|
|
|
const uint32_t r13 = ctx->gpr[13];
|
|
if (r13 == 0) {
|
|
return ISFS_OK;
|
|
}
|
|
|
|
const uint32_t isfsFdGlobal = r13 - kIsfsFdSda1Offset;
|
|
const uint32_t isfsPathGlobal = r13 - kIsfsPathSda1Offset;
|
|
const uint32_t ipcBufferLoGlobal = r13 - kIpcBufferLoSda1Offset;
|
|
const uint32_t ipcBufferHiGlobal = r13 - kIpcBufferHiSda1Offset;
|
|
const uint32_t ipcArenaLoGlobal = r13 - kIpcArenaLoSda1Offset;
|
|
const uint32_t ipcArenaHiGlobal = r13 - kIpcArenaHiSda1Offset;
|
|
const uint32_t isfsHeapGlobal = r13 - kIsfsHeapHandleSda1Offset;
|
|
const uint32_t isfsHeapBaseGlobal = r13 - kIsfsHeapBaseSda1Offset;
|
|
const uint32_t isfsHeapInitializedGlobal = r13 - kIsfsHeapInitializedSda1Offset;
|
|
|
|
WriteGuestString(isfsPathGlobal, "/dev/fs");
|
|
|
|
if (Memory::Contains(isfsFdGlobal, 4)) {
|
|
Memory::Write32(isfsFdGlobal, static_cast<uint32_t>(ISFS_DEV_FD));
|
|
}
|
|
|
|
// The heap bring-up below reads and writes all seven IPC globals, so it only
|
|
// runs when every one of them is inside guest memory.
|
|
for (const uint32_t global : {ipcBufferLoGlobal, ipcBufferHiGlobal, ipcArenaLoGlobal,
|
|
ipcArenaHiGlobal, isfsHeapGlobal, isfsHeapBaseGlobal,
|
|
isfsHeapInitializedGlobal}) {
|
|
if (!Memory::Contains(global, 4)) {
|
|
return ISFS_OK;
|
|
}
|
|
}
|
|
|
|
uint32_t ipcLo = Memory::Read32(ipcBufferLoGlobal);
|
|
uint32_t ipcHi = Memory::Read32(ipcBufferHiGlobal);
|
|
if (ipcLo == 0 || ipcHi == 0 || ipcLo >= ipcHi) {
|
|
return ISFS_OK;
|
|
}
|
|
|
|
if (Memory::Read32(isfsHeapInitializedGlobal) == 0) {
|
|
Memory::Write32(ipcArenaLoGlobal, ipcLo);
|
|
Memory::Write32(ipcArenaHiGlobal, ipcHi);
|
|
|
|
const uint32_t heapBase = (ipcLo + 31u) & ~31u;
|
|
const uint32_t heapSize = 5440u;
|
|
if (heapBase + heapSize <= ipcHi) {
|
|
Memory::Write32(isfsHeapBaseGlobal, heapBase);
|
|
|
|
const uint32_t savedR3 = ctx->gpr[3];
|
|
const uint32_t savedR4 = ctx->gpr[4];
|
|
const uint32_t savedR5 = ctx->gpr[5];
|
|
const uint32_t savedLr = ctx->lr;
|
|
|
|
ctx->gpr[3] = heapBase;
|
|
ctx->gpr[4] = heapSize;
|
|
ctx->lr = 0x80169BCCu;
|
|
InvokeDirectCpu<0x801949B8u>(ctx);
|
|
const uint32_t heapHandle = ctx->gpr[3];
|
|
|
|
ctx->gpr[3] = heapBase + heapSize;
|
|
ctx->lr = 0x80169BCCu;
|
|
InvokeDirectCpu<0x80193040u>(ctx);
|
|
|
|
ctx->gpr[3] = savedR3;
|
|
ctx->gpr[4] = savedR4;
|
|
ctx->gpr[5] = savedR5;
|
|
ctx->lr = savedLr;
|
|
|
|
Memory::Write32(isfsHeapGlobal, heapHandle);
|
|
Memory::Write32(isfsHeapInitializedGlobal, 1u);
|
|
}
|
|
}
|
|
|
|
return ISFS_OK;
|
|
}
|
|
|
|
extern "C" void ISFS_OpenLib_HLE_80169BCC(CpuContext* ctx) {
|
|
ctx->gpr[3] = static_cast<uint32_t>(ISFS_OpenLib_Initialize(ctx));
|
|
}
|
|
|
|
REGISTER_NATIVE_FUNCTION_AS(0x80169BCC, ISFS_OpenLib_HLE_80169BCC, "ISFS_OpenLib_HLE_80169BCC");
|
|
|
|
// ============================================================================
|
|
// IOS_Ioctlv HLE - Vector Ioctl for complex ISFS operations
|
|
// ============================================================================
|
|
|
|
static int32_t HandleIsfsReadDir(uint32_t numIn, uint32_t numOut, uint32_t vectorPtr) {
|
|
const bool countOnly = (numIn == 1 && numOut == 1);
|
|
if (!countOnly && !(numIn == 2 && numOut == 2)) {
|
|
LogNandWarning("IOS_Ioctlv", "READDIR unsupported vector shape numIn=%u numOut=%u",
|
|
numIn, numOut);
|
|
return ISFS_EINVAL;
|
|
}
|
|
|
|
const IosVector pathVec = ReadIosVector(vectorPtr, 0);
|
|
const std::string wiiPath = ReadGuestCString(pathVec.address, 64);
|
|
if (wiiPath.empty()) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
const std::filesystem::path hostPath = TranslateNandPath(wiiPath.c_str());
|
|
if (!IsDirectory(hostPath)) {
|
|
return ISFS_ENOENT;
|
|
}
|
|
|
|
// NAND names are at most 12 characters; longer host names cannot exist on
|
|
// a real NAND (this also hides *.nandsafe.tmp write shadows).
|
|
constexpr size_t kMaxNandNameLength = 12;
|
|
std::vector<std::string> names;
|
|
std::error_code ec;
|
|
for (const auto& entry : std::filesystem::directory_iterator(hostPath, ec)) {
|
|
std::string name = HostPathText(entry.path().filename());
|
|
if (name.empty() || name.size() > kMaxNandNameLength) {
|
|
continue;
|
|
}
|
|
names.push_back(std::move(name));
|
|
}
|
|
std::sort(names.begin(), names.end());
|
|
|
|
if (countOnly) {
|
|
const IosVector countOut = ReadIosVector(vectorPtr, 1);
|
|
if (countOut.size < 4 || !Memory::Contains(countOut.address, 4)) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
Memory::Write32(countOut.address, static_cast<uint32_t>(names.size()));
|
|
return ISFS_OK;
|
|
}
|
|
|
|
const IosVector maxVec = ReadIosVector(vectorPtr, 1);
|
|
const IosVector namesOut = ReadIosVector(vectorPtr, 2);
|
|
const IosVector countOut = ReadIosVector(vectorPtr, 3);
|
|
if (maxVec.size < 4 || !Memory::Contains(maxVec.address, 4) ||
|
|
countOut.size < 4 || !Memory::Contains(countOut.address, 4) ||
|
|
!IsValidGuestRange(namesOut.address, namesOut.size)) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
const uint32_t maxCount = Memory::Read32(maxVec.address);
|
|
|
|
constexpr uint32_t kEntryWindow = 13; // 12 chars + terminator
|
|
uint32_t cursor = 0;
|
|
uint32_t written = 0;
|
|
for (const std::string& name : names) {
|
|
if (written >= maxCount || cursor + kEntryWindow > namesOut.size) {
|
|
break;
|
|
}
|
|
uint8_t* out = Memory::GetPointer(namesOut.address + cursor, kEntryWindow);
|
|
std::memset(out, 0, kEntryWindow);
|
|
std::memcpy(out, name.data(), name.size());
|
|
cursor += static_cast<uint32_t>(name.size()) + 1;
|
|
++written;
|
|
}
|
|
Memory::Write32(countOut.address, written);
|
|
return ISFS_OK;
|
|
}
|
|
|
|
extern "C" int32_t NAND_IOS_Ioctlv_HLE(
|
|
uint32_t fd,
|
|
uint32_t cmd,
|
|
uint32_t numIn,
|
|
uint32_t numOut,
|
|
uint32_t vectorPtr)
|
|
{
|
|
|
|
if (Network_HLE_IsFd(fd)) {
|
|
return Network_HLE_Ioctlv(fd, cmd, numIn, numOut, vectorPtr);
|
|
}
|
|
|
|
if (GetShaHandle(static_cast<int32_t>(fd))) {
|
|
return HandleShaIoctlv(static_cast<int32_t>(fd), cmd, numIn, numOut, vectorPtr);
|
|
}
|
|
|
|
if (fd == DOLPHIN_DEV_FD) {
|
|
return HandleDolphinIoctlv(cmd, numIn, numOut, vectorPtr);
|
|
}
|
|
|
|
if (fd == ISFS_DEV_FD) {
|
|
if (!vectorPtr || !Memory::Contains(vectorPtr, static_cast<size_t>(numIn + numOut) * 8u)) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
if (cmd == ISFS_IOCTL_READDIR) {
|
|
return HandleIsfsReadDir(numIn, numOut, vectorPtr);
|
|
}
|
|
return ISFS_OK;
|
|
}
|
|
|
|
if (fd == ES_DEV_FD) {
|
|
if (!vectorPtr || !Memory::Contains(vectorPtr, static_cast<size_t>(numIn + numOut) * 8u)) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
|
|
switch (cmd) {
|
|
case ES_IOCTL_GETDEVICEID: {
|
|
if (numIn != 0 || numOut != 1) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
const IosVector out = ReadIosVector(vectorPtr, 0);
|
|
if (out.size < 4 || out.address == 0 || !Memory::Contains(out.address, 4)) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
const WiiEsCrypto::Identity& identity = WiiEsCrypto::CurrentIdentity();
|
|
Memory::Write32(out.address, identity.deviceId);
|
|
return ISFS_OK;
|
|
}
|
|
|
|
case ES_IOCTL_GETDEVICECERT: {
|
|
if (numIn != 0 || numOut != 1) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
const IosVector out = ReadIosVector(vectorPtr, 0);
|
|
const auto cert = WiiEsCrypto::GetDeviceCertificate();
|
|
if (!WriteGuestBytes(out.address, out.size, cert.data(), cert.size())) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
return ISFS_OK;
|
|
}
|
|
|
|
case ES_IOCTL_GETTITLEID: {
|
|
if (numIn != 0 || numOut != 1) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
const IosVector out = ReadIosVector(vectorPtr, 0);
|
|
if (out.size < 8 || out.address == 0 || !Memory::Contains(out.address, 8)) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
const uint64_t titleId = CurrentMkwTitleId();
|
|
Memory::Write32(out.address, static_cast<uint32_t>(titleId >> 32));
|
|
Memory::Write32(out.address + 4u, static_cast<uint32_t>(titleId));
|
|
return ISFS_OK;
|
|
}
|
|
|
|
case ES_IOCTL_SIGN: {
|
|
if (numIn != 1 || numOut != 2) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
const IosVector in = ReadIosVector(vectorPtr, 0);
|
|
const IosVector sigOut = ReadIosVector(vectorPtr, 1);
|
|
const IosVector certOut = ReadIosVector(vectorPtr, 2);
|
|
if (in.address == 0 || !Memory::Contains(in.address, in.size)) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
const uint8_t* input = Memory::GetPointer(in.address, in.size);
|
|
WiiEsCrypto::EcSignature signature{};
|
|
WiiEsCrypto::EccCert cert{};
|
|
WiiEsCrypto::Sign(CurrentMkwTitleId(), input, in.size, signature, cert);
|
|
if (!WriteGuestBytes(sigOut.address, sigOut.size, signature.data(), signature.size()) ||
|
|
!WriteGuestBytes(certOut.address, certOut.size, cert.data(), cert.size())) {
|
|
return ISFS_EINVAL;
|
|
}
|
|
return ISFS_OK;
|
|
}
|
|
|
|
default:
|
|
LogNandWarning("IOS_Ioctlv", "unsupported /dev/es cmd=%u", cmd);
|
|
return ISFS_EINVAL;
|
|
}
|
|
}
|
|
|
|
// Non-device ioctlv has no ISFS command we need to service.
|
|
return ISFS_OK;
|
|
}
|
|
|
|
extern "C" void NAND_IOS_Ioctlv_Entry_HLE(CpuContext* ctx) {
|
|
const uint32_t fd = ctx->gpr[3];
|
|
const uint32_t cmd = ctx->gpr[4];
|
|
const uint32_t numIn = ctx->gpr[5];
|
|
const uint32_t numOut = ctx->gpr[6];
|
|
const uint32_t vectorPtr = ctx->gpr[7];
|
|
|
|
if (Network_HLE_IsFd(fd)) {
|
|
const bool handled = TryDeferredNetworkIosSync(ctx, [&](uint32_t waitQueue) {
|
|
return Network_HLE_StartIoctlvSync(fd, cmd, numIn, numOut, vectorPtr, waitQueue);
|
|
});
|
|
if (handled) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
ctx->gpr[3] = static_cast<uint32_t>(
|
|
NAND_IOS_Ioctlv_HLE(fd, cmd, numIn, numOut, vectorPtr));
|
|
}
|
|
PPC_NATIVE_OVERRIDE_VOID(801945E0, NAND_IOS_Ioctlv_Entry_HLE, (CpuContext* ctx), (ctx));
|