diff --git a/runtime/src/hle/storage/nand_isfs.cpp b/runtime/src/hle/storage/nand_isfs.cpp index b92103d..accf2c3 100644 --- a/runtime/src/hle/storage/nand_isfs.cpp +++ b/runtime/src/hle/storage/nand_isfs.cpp @@ -899,6 +899,74 @@ REGISTER_NATIVE_FUNCTION_AS(0x80169BCC, ISFS_OpenLib_HLE_80169BCC, "ISFS_OpenLib // 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::string 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 names; + std::error_code ec; + for (const auto& entry : std::filesystem::directory_iterator(hostPath, ec)) { + std::string name = entry.path().filename().string(); + 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(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(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, @@ -919,6 +987,16 @@ extern "C" int32_t NAND_IOS_Ioctlv_HLE( return HandleDolphinIoctlv(cmd, numIn, numOut, vectorPtr); } + if (fd == ISFS_DEV_FD) { + if (!vectorPtr || !Memory::Contains(vectorPtr, static_cast(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(numIn + numOut) * 8u)) { return ISFS_EINVAL;