Remove recursion from RomFS parse, clang-format

This commit is contained in:
offtkp 2023-07-29 17:01:44 +03:00
parent 2de35bd39d
commit fb6ec3aa5f
4 changed files with 202 additions and 194 deletions

View file

@ -1,22 +1,22 @@
#pragma once #pragma once
#include <cstddef> #include <vector>
#include <cstring>
#include "helpers.hpp" #include "helpers.hpp"
namespace IVFC { namespace IVFC {
struct IVFCLevel { struct IVFCLevel {
u64 logicalOffset; u64 logicalOffset;
u64 size; u64 size;
u64 blockSize; u64 blockSize;
}; };
struct IVFC { struct IVFC {
u64 masterHashSize; u64 masterHashSize;
std::vector<IVFCLevel> levels; std::vector<IVFCLevel> levels;
}; };
size_t parseIVFC(uintptr_t ivfcStart, IVFC& ivfc); size_t parseIVFC(uintptr_t ivfcStart, IVFC& ivfc);
} // namespace IVFC } // namespace IVFC

View file

@ -1,22 +1,23 @@
#pragma once #pragma once
#include "helpers.hpp" #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include <memory>
#include "helpers.hpp"
namespace RomFS { namespace RomFS {
struct RomFSNode { struct RomFSNode {
std::u16string name {}; std::u16string name;
// The file/directory offset relative to the start of the RomFS // The file/directory offset relative to the start of the RomFS
u64 offset { 0 }; u64 offset = 0;
u64 size { 0 }; u64 size = 0;
bool isDirectory { false }; bool isDirectory = false;
std::vector<std::unique_ptr<RomFSNode>> directories {}; std::vector<std::unique_ptr<RomFSNode>> directories;
std::vector<std::unique_ptr<RomFSNode>> files {}; std::vector<std::unique_ptr<RomFSNode>> files;
}; };
std::unique_ptr<RomFSNode> parseRomFSTree(uintptr_t romFS, u64 romFSSize); std::unique_ptr<RomFSNode> parseRomFSTree(uintptr_t romFS, u64 romFSSize);
} // namespace RomFS } // namespace RomFS

View file

@ -2,77 +2,77 @@
namespace IVFC { namespace IVFC {
size_t parseIVFC(uintptr_t ivfcStart, IVFC& ivfc) { size_t parseIVFC(uintptr_t ivfcStart, IVFC& ivfc) {
uintptr_t ivfcPointer = ivfcStart; uintptr_t ivfcPointer = ivfcStart;
std::string magicIVFC((char*)ivfcPointer, 4);
ivfcPointer += 4;
if (magicIVFC != "IVFC") { char* ivfcCharPtr = (char*)ivfcPointer;
printf("Invalid IVFC magic: %s\n", magicIVFC.c_str()); if (ivfcCharPtr[0] != 'I' || ivfcCharPtr[1] != 'V' || ivfcCharPtr[2] != 'F' || ivfcCharPtr[3] != 'C') {
return 0; printf("Invalid header on IVFC\n");
} return 0;
}
ivfcPointer += 4;
uint32_t magicIdentifier = *(u32*)ivfcPointer; u32 magicIdentifier = *(u32*)ivfcPointer;
ivfcPointer += 4; ivfcPointer += 4;
// RomFS IVFC uses 0x10000, DISA/DIFF IVFC uses 0x20000 here // RomFS IVFC uses 0x10000, DISA/DIFF IVFC uses 0x20000 here
if (magicIdentifier != 0x10000 && magicIdentifier != 0x20000) { if (magicIdentifier != 0x10000 && magicIdentifier != 0x20000) {
printf("Invalid IVFC magic identifier: %08X\n", magicIdentifier); printf("Invalid IVFC magic identifier: %08X\n", magicIdentifier);
return 0; return 0;
} }
if (magicIdentifier == 0x10000) { if (magicIdentifier == 0x10000) {
ivfc.masterHashSize = *(u32*)ivfcPointer; ivfc.masterHashSize = *(u32*)ivfcPointer;
ivfcPointer += 4; ivfcPointer += 4;
// RomFS IVFC uses 3 levels // RomFS IVFC uses 3 levels
ivfc.levels.resize(3); ivfc.levels.resize(3);
} else { } else {
ivfc.masterHashSize = *(u64*)ivfcPointer; ivfc.masterHashSize = *(u64*)ivfcPointer;
ivfcPointer += 8; ivfcPointer += 8;
// DISA/DIFF IVFC uses 4 levels // DISA/DIFF IVFC uses 4 levels
ivfc.levels.resize(4); ivfc.levels.resize(4);
} }
for (size_t i = 0; i < ivfc.levels.size(); i++) { for (size_t i = 0; i < ivfc.levels.size(); i++) {
IVFCLevel level; IVFCLevel level;
level.logicalOffset = *(u64*)ivfcPointer;
ivfcPointer += 8;
level.size = *(u64*)ivfcPointer; level.logicalOffset = *(u64*)ivfcPointer;
ivfcPointer += 8; ivfcPointer += 8;
// This field is in log2 level.size = *(u64*)ivfcPointer;
level.blockSize = 1 << *(u32*)ivfcPointer; ivfcPointer += 8;
ivfcPointer += 4;
// Skip 4 reserved bytes // This field is in log2
ivfcPointer += 4; level.blockSize = 1 << *(u32*)ivfcPointer;
ivfcPointer += 4;
ivfc.levels[i] = level; // Skip 4 reserved bytes
} ivfcPointer += 4;
u64 ivfcDescriptorSize = *(u64*)ivfcPointer; ivfc.levels[i] = level;
ivfcPointer += 8; }
uintptr_t ivfcActualSize = ivfcPointer - ivfcStart; u64 ivfcDescriptorSize = *(u64*)ivfcPointer;
ivfcPointer += 8;
// According to 3DBrew, this is usually the case but not guaranteed uintptr_t ivfcActualSize = ivfcPointer - ivfcStart;
if (ivfcActualSize != ivfcDescriptorSize) {
printf("IVFC descriptor size mismatch: %lx != %lx\n", ivfcActualSize, ivfcDescriptorSize);
}
if (magicIdentifier == 0x10000 && ivfcActualSize != 0x5C) { // According to 3DBrew, this is usually the case but not guaranteed
// This is always 0x5C bytes long if (ivfcActualSize != ivfcDescriptorSize) {
printf("Invalid IVFC size: %08x\n", (u32)ivfcActualSize); printf("IVFC descriptor size mismatch: %lx != %lx\n", ivfcActualSize, ivfcDescriptorSize);
return 0; }
} else if (magicIdentifier == 0x20000 && ivfcActualSize != 0x78) {
// This is always 0x78 bytes long
printf("Invalid IVFC size: %08x\n", (u32)ivfcActualSize);
return 0;
}
return ivfcActualSize; if (magicIdentifier == 0x10000 && ivfcActualSize != 0x5C) {
} // This is always 0x5C bytes long
printf("Invalid IVFC size: %08x\n", (u32)ivfcActualSize);
return 0;
} else if (magicIdentifier == 0x20000 && ivfcActualSize != 0x78) {
// This is always 0x78 bytes long
printf("Invalid IVFC size: %08x\n", (u32)ivfcActualSize);
return 0;
}
} // namespace IVFC return ivfcActualSize;
}
} // namespace IVFC

View file

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