#include "fs/romfs.hpp" #include #include #include #include "fs/ivfc.hpp" #include "helpers.hpp" namespace RomFS { constexpr u32 metadataInvalidEntry = 0xFFFFFFFF; struct Level3Header { u32 headerSize; u32 directoryHashTableOffset; u32 directoryHashTableSize; u32 directoryMetadataOffset; u32 directoryMetadataSize; u32 fileHashTableOffset; u32 fileHashTableSize; u32 fileMetadataOffset; u32 fileMetadataSize; u32 fileDataOffset; }; inline constexpr uintptr_t alignUp(uintptr_t value, uintptr_t alignment) { if (value % alignment == 0) return value; return value + (alignment - (value % alignment)); } void printNode(const RomFSNode& node, int indentation) { for (int i = 0; i < indentation; i++) { printf(" "); } printf("%s/\n", std::string(node.name.begin(), node.name.end()).c_str()); for (auto& file : node.files) { for (int i = 0; i <= indentation; i++) { printf(" "); } printf("%s\n", std::string(file->name.begin(), file->name.end()).c_str()); } indentation++; for (auto& directory : node.directories) { printNode(*directory, indentation); } indentation--; } std::vector> getFiles(uintptr_t fileMetadataBase, u32 currentFileOffset) { std::vector> files; while (currentFileOffset != metadataInvalidEntry) { u32* metadataPtr = (u32*)(fileMetadataBase + currentFileOffset); metadataPtr++; // Skip the containing directory u32 nextFileOffset = *metadataPtr++; u64 fileDataOffset = *(u64*)metadataPtr; metadataPtr += 2; u64 fileSize = *(u64*)metadataPtr; metadataPtr += 2; metadataPtr++; // Skip the offset of the next file in the same hash table bucket u32 nameLength = *metadataPtr++ / 2; // Arbitrary limit if (nameLength > 128) { printf("Invalid file name length: %08X\n", nameLength); return {}; } char16_t* namePtr = (char16_t*)metadataPtr; std::u16string name(namePtr, nameLength); std::unique_ptr file = std::make_unique(); file->isDirectory = false; file->name = name; file->metadataOffset = currentFileOffset; file->dataOffset = fileDataOffset; file->dataSize = fileSize; files.push_back(std::move(file)); currentFileOffset = nextFileOffset; } return files; } std::unique_ptr parseRootDirectory(uintptr_t directoryMetadataBase, uintptr_t fileMetadataBase) { std::unique_ptr rootDirectory = std::make_unique(); rootDirectory->isDirectory = true; rootDirectory->name = u"romfs:"; rootDirectory->metadataOffset = 0; u32 rootFilesOffset = *((u32*)(directoryMetadataBase) + 3); if (rootFilesOffset != metadataInvalidEntry) { rootDirectory->files = getFiles(fileMetadataBase, rootFilesOffset); } std::queue directoryOffsets; directoryOffsets.push(rootDirectory.get()); while (!directoryOffsets.empty()) { RomFSNode* currentNode = directoryOffsets.front(); directoryOffsets.pop(); u32* metadataPtr = (u32*)(directoryMetadataBase + currentNode->metadataOffset); metadataPtr += 2; // Offset of first child directory u32 currentDirectoryOffset = *metadataPtr; // Loop over all the sibling directories of the first child to get all the children directories // of the current directory while (currentDirectoryOffset != metadataInvalidEntry) { metadataPtr = (u32*)(directoryMetadataBase + currentDirectoryOffset); metadataPtr++; // Skip the parent offset u32 siblingDirectoryOffset = *metadataPtr++; metadataPtr++; // Skip offset of first child directory u32 currentFileOffset = *metadataPtr++; metadataPtr++; // Skip offset of next directory in the same hash table bucket u32 nameLength = *metadataPtr++ / 2; // Arbitrary limit if (nameLength > 128) { printf("Invalid directory name length: %08X\n", nameLength); return {}; } char16_t* namePtr = (char16_t*)metadataPtr; std::u16string name(namePtr, nameLength); std::unique_ptr directory = std::make_unique(); directory->isDirectory = true; directory->name = name; directory->metadataOffset = currentDirectoryOffset; directory->files = getFiles(fileMetadataBase, currentFileOffset); currentNode->directories.push_back(std::move(directory)); currentDirectoryOffset = siblingDirectoryOffset; } for (auto& directory : currentNode->directories) { directoryOffsets.push(directory.get()); } } return rootDirectory; } std::unique_ptr parseRomFSTree(uintptr_t romFS, u64 romFSSize) { IVFC::IVFC ivfc; size_t ivfcSize = IVFC::parseIVFC((uintptr_t)romFS, ivfc); if (ivfcSize == 0) { printf("Failed to parse IVFC\n"); return {}; } uintptr_t masterHashOffset = RomFS::alignUp(ivfcSize, 0x10); // For a weird reason, the level 3 offset is not the one in the IVFC, instead it's // the first block after the master hash // TODO: Find out why and explain in the comment uintptr_t level3Offset = RomFS::alignUp(masterHashOffset + ivfc.masterHashSize, ivfc.levels[2].blockSize); uintptr_t level3Base = (uintptr_t)romFS + level3Offset; u32* level3Ptr = (u32*)level3Base; Level3Header header; header.headerSize = *level3Ptr++; header.directoryHashTableOffset = *level3Ptr++; header.directoryHashTableSize = *level3Ptr++; header.directoryMetadataOffset = *level3Ptr++; header.directoryMetadataSize = *level3Ptr++; header.fileHashTableOffset = *level3Ptr++; header.fileHashTableSize = *level3Ptr++; header.fileMetadataOffset = *level3Ptr++; header.fileMetadataSize = *level3Ptr++; header.fileDataOffset = *level3Ptr; if (header.headerSize != 0x28) { printf("Invalid level 3 header size: %08X\n", header.headerSize); return {}; } std::unique_ptr root = parseRootDirectory(level3Base + header.directoryMetadataOffset, level3Base + header.fileMetadataOffset); // If you want to print the tree, uncomment this // printNode(*root, 0); return root; } } // namespace RomFS