// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include "common/common_types.h" #include "common/string_util.h" #include "common/swap.h" #include "core/file_sys/fsmitm_romfsbuild.h" #include "core/file_sys/romfs.h" #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_cached.h" #include "core/file_sys/vfs_concat.h" #include "core/file_sys/vfs_offset.h" #include "core/file_sys/vfs_vector.h" namespace FileSys { namespace { constexpr u32 ROMFS_ENTRY_EMPTY = 0xFFFFFFFF; struct TableLocation { u64_le offset; u64_le size; }; static_assert(sizeof(TableLocation) == 0x10, "TableLocation has incorrect size."); struct RomFSHeader { u64_le header_size; TableLocation directory_hash; TableLocation directory_meta; TableLocation file_hash; TableLocation file_meta; u64_le data_offset; }; static_assert(sizeof(RomFSHeader) == 0x50, "RomFSHeader has incorrect size."); struct DirectoryEntry { u32_le sibling; u32_le child_dir; u32_le child_file; u32_le hash; u32_le name_length; }; static_assert(sizeof(DirectoryEntry) == 0x14, "DirectoryEntry has incorrect size."); struct FileEntry { u32_le parent; u32_le sibling; u64_le offset; u64_le size; u32_le hash; u32_le name_length; }; static_assert(sizeof(FileEntry) == 0x20, "FileEntry has incorrect size."); template std::pair GetEntry(const VirtualFile& file, std::size_t offset) { Entry entry{}; if (file->ReadObject(&entry, offset) != sizeof(Entry)) return {}; std::string string(entry.name_length, '\0'); if (file->ReadArray(&string[0], string.size(), offset + sizeof(Entry)) != string.size()) return {}; return {entry, string}; } void ProcessFile(VirtualFile file, std::size_t file_offset, std::size_t data_offset, u32 this_file_offset, std::shared_ptr parent) { while (true) { auto entry = GetEntry(file, file_offset + this_file_offset); parent->AddFile(std::make_shared( file, entry.first.size, entry.first.offset + data_offset, entry.second)); if (entry.first.sibling == ROMFS_ENTRY_EMPTY) break; this_file_offset = entry.first.sibling; } } void ProcessDirectory(VirtualFile file, std::size_t dir_offset, std::size_t file_offset, std::size_t data_offset, u32 this_dir_offset, std::shared_ptr parent) { while (true) { auto entry = GetEntry(file, dir_offset + this_dir_offset); auto current = std::make_shared( std::vector{}, std::vector{}, entry.second); if (entry.first.child_file != ROMFS_ENTRY_EMPTY) { ProcessFile(file, file_offset, data_offset, entry.first.child_file, current); } if (entry.first.child_dir != ROMFS_ENTRY_EMPTY) { ProcessDirectory(file, dir_offset, file_offset, data_offset, entry.first.child_dir, current); } parent->AddDirectory(current); if (entry.first.sibling == ROMFS_ENTRY_EMPTY) break; this_dir_offset = entry.first.sibling; } } } // Anonymous namespace VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) { RomFSHeader header{}; if (file->ReadObject(&header) != sizeof(RomFSHeader)) return nullptr; if (header.header_size != sizeof(RomFSHeader)) return nullptr; const u64 file_offset = header.file_meta.offset; const u64 dir_offset = header.directory_meta.offset + 4; auto root = std::make_shared(std::vector{}, std::vector{}, file->GetName(), file->GetContainingDirectory()); ProcessDirectory(file, dir_offset, file_offset, header.data_offset, 0, root); VirtualDir out = std::move(root); if (type == RomFSExtractionType::SingleDiscard) return out->GetSubdirectories().front(); while (out->GetSubdirectories().size() == 1 && out->GetFiles().empty()) { if (Common::ToLower(out->GetSubdirectories().front()->GetName()) == "data" && type == RomFSExtractionType::Truncated) break; out = out->GetSubdirectories().front(); } return std::make_shared(out); } VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext) { if (dir == nullptr) return nullptr; RomFSBuildContext ctx{dir, ext}; auto file_map = ctx.Build(); return ConcatenatedVfsFile::MakeConcatenatedFile(0, file_map, dir->GetName()); } } // namespace FileSys