#include "fs/archive_ncch.hpp" #include "fs/bad_word_list.hpp" #include "fs/country_list.hpp" #include "fs/mii_data.hpp" #include #include namespace PathType { enum : u32 { RomFS = 0, Code = 1, ExeFS = 2, }; }; namespace MediaType { enum : u8 { NAND = 0, SD = 1, Gamecard = 2 }; }; HorizonResult NCCHArchive::createFile(const FSPath& path, u64 size) { Helpers::panic("[NCCH] CreateFile not yet supported"); return Result::Success; } HorizonResult NCCHArchive::deleteFile(const FSPath& path) { Helpers::panic("[NCCH] Unimplemented DeleteFile"); return Result::Success; } FileDescriptor NCCHArchive::openFile(const FSPath& path, const FilePerms& perms) { if (path.type != PathType::Binary || path.binary.size() != 20) { Helpers::panic("NCCHArchive::OpenFile: Invalid path"); } const u32 media = *(u32*)&path.binary[0]; // 0 for NCCH, 1 for SaveData if (media != 0) Helpers::panic("NCCHArchive::OpenFile: Tried to read non-NCCH file"); // Third word of the binary path indicates what we're reading from. const u32 type = *(u32*)&path.binary[8]; if (media == 0 && type > 2) Helpers::panic("NCCHArchive::OpenFile: Invalid file path type"); return NoFile; } Rust::Result NCCHArchive::openArchive(const FSPath& path) { if (path.type != PathType::Binary || path.binary.size() != 16) { Helpers::panic("NCCHArchive::OpenArchive: Invalid path"); } const u32 mediaType = path.binary[8]; if (mediaType != 0) Helpers::panic("NCCH archive. Tried to access a mediatype other than the NAND. Type: %d", mediaType); return Ok((ArchiveBase*)this); } std::optional NCCHArchive::readFile(FileSession* file, u64 offset, u32 size, u32 dataPointer) { const auto& path = file->path.binary; // Path of the file const auto& archivePath = file->archivePath.binary; // Path of the archive const auto mediaType = archivePath[8]; const auto media = *(u32*)&path[0]; // 0 for NCCH, 1 for savedata const auto partition = *(u32*)&path[4]; const auto type = *(u32*)&path[8]; // Type of the path if (mediaType == MediaType::NAND) { const u32 lowProgramID = *(u32*)&archivePath[0]; const u32 highProgramID = *(u32*)&archivePath[4]; // High Title ID of the archive (from Citra). https://3dbrew.org/wiki/Title_list. constexpr u32 sharedDataArchive = 0x0004009B; constexpr u32 systemDataArchive = 0x000400DB; // Low ID (taken from Citra) constexpr u32 miiData = 0x00010202; constexpr u32 regionManifest = 0x00010402; constexpr u32 badWordList = 0x00010302; constexpr u32 sharedFont = 0x00014002; std::vector fileData; if (highProgramID == sharedDataArchive) { if (lowProgramID == miiData) fileData = std::vector(std::begin(MII_DATA), std::end(MII_DATA)); else if (lowProgramID == regionManifest) fileData = std::vector(std::begin(COUNTRY_LIST_DATA), std::end(COUNTRY_LIST_DATA)); else Helpers::panic("[NCCH archive] Read unimplemented NAND file. ID: %08X", lowProgramID); } else if (highProgramID == systemDataArchive && lowProgramID == badWordList) { fileData = std::vector(std::begin(BAD_WORD_LIST_DATA), std::end(BAD_WORD_LIST_DATA)); } else { Helpers::panic("[NCCH archive] Read from unimplemented NCCH archive file. High program ID: %08X, low ID: %08X", highProgramID, lowProgramID); } if (offset >= fileData.size()) { Helpers::panic("[NCCH archive] Out of bounds read from NAND file"); } u32 availableBytes = u32(fileData.size() - offset); // How many bytes we can read from the file u32 bytesRead = std::min(size, availableBytes); // Cap the amount of bytes to read if we're going to go out of bounds for (u32 i = 0; i < bytesRead; i++) { mem.write8(dataPointer + i, fileData[offset + i]); } return bytesRead; } else { Helpers::panic("NCCH archive tried to read non-NAND file"); } // Code below is for mediaType == 2 (gamecard). Currently unused if (partition != 0) Helpers::panic("[NCCH] Tried to read from non-zero partition"); if (type == PathType::RomFS && !hasRomFS()) { Helpers::panic("Tried to read file from non-existent RomFS"); return std::nullopt; } if (type == PathType::ExeFS && !hasExeFS()) { Helpers::panic("Tried to read file from non-existent RomFS"); return std::nullopt; } if (!file->isOpen) { printf("Tried to read from closed NCCH file session"); return std::nullopt; } auto cxi = mem.getCXI(); // Seek to file offset depending on if we're reading from RomFS, ExeFS, etc switch (type) { case PathType::RomFS: { const u64 romFSSize = cxi->romFS.size; const u64 romFSOffset = cxi->romFS.offset; if ((offset >> 32) || (offset >= romFSSize) || (offset + size >= romFSSize)) { Helpers::panic("Tried to read from NCCH with too big of an offset"); } offset += 0x1000; break; } default: Helpers::panic("Unimplemented file path type for NCCH archive"); } std::unique_ptr data(new u8[size]); auto [success, bytesRead] = cxi->readFromFile(mem.CXIFile, cxi->romFS, &data[0], offset, size); if (!success) { Helpers::panic("Failed to read from NCCH archive"); } for (u64 i = 0; i < bytesRead; i++) { mem.write8(u32(dataPointer + i), data[i]); } return u32(bytesRead); }