From b0c6bf497a1eabec14c116b710dcc757e77455bf Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Tue, 31 Oct 2023 20:11:14 -0400
Subject: [PATCH] romfs: fix extraction of single-directory root

---
 src/core/file_sys/romfs.cpp                   | 44 +++++++------------
 src/core/file_sys/romfs.h                     |  9 +---
 .../service/am/applets/applet_web_browser.cpp |  3 +-
 src/yuzu/main.cpp                             |  2 +-
 4 files changed, 18 insertions(+), 40 deletions(-)

diff --git a/src/core/file_sys/romfs.cpp b/src/core/file_sys/romfs.cpp
index 1c580de57..1eb1f439a 100644
--- a/src/core/file_sys/romfs.cpp
+++ b/src/core/file_sys/romfs.cpp
@@ -35,13 +35,14 @@ struct RomFSHeader {
 static_assert(sizeof(RomFSHeader) == 0x50, "RomFSHeader has incorrect size.");
 
 struct DirectoryEntry {
+    u32_le parent;
     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.");
+static_assert(sizeof(DirectoryEntry) == 0x18, "DirectoryEntry has incorrect size.");
 
 struct FileEntry {
     u32_le parent;
@@ -64,25 +65,22 @@ std::pair<Entry, std::string> GetEntry(const VirtualFile& file, std::size_t offs
     return {entry, string};
 }
 
-void ProcessFile(VirtualFile file, std::size_t file_offset, std::size_t data_offset,
-                 u32 this_file_offset, std::shared_ptr<VectorVfsDirectory> parent) {
-    while (true) {
+void ProcessFile(const VirtualFile& file, std::size_t file_offset, std::size_t data_offset,
+                 u32 this_file_offset, std::shared_ptr<VectorVfsDirectory>& parent) {
+    while (this_file_offset != ROMFS_ENTRY_EMPTY) {
         auto entry = GetEntry<FileEntry>(file, file_offset + this_file_offset);
 
         parent->AddFile(std::make_shared<OffsetVfsFile>(
             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,
+void ProcessDirectory(const VirtualFile& file, std::size_t dir_offset, std::size_t file_offset,
                       std::size_t data_offset, u32 this_dir_offset,
-                      std::shared_ptr<VectorVfsDirectory> parent) {
-    while (true) {
+                      std::shared_ptr<VectorVfsDirectory>& parent) {
+    while (this_dir_offset != ROMFS_ENTRY_EMPTY) {
         auto entry = GetEntry<DirectoryEntry>(file, dir_offset + this_dir_offset);
         auto current = std::make_shared<VectorVfsDirectory>(
             std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, entry.second);
@@ -97,14 +95,12 @@ void ProcessDirectory(VirtualFile file, std::size_t dir_offset, std::size_t file
         }
 
         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) {
+VirtualDir ExtractRomFS(VirtualFile file) {
     RomFSHeader header{};
     if (file->ReadObject(&header) != sizeof(RomFSHeader))
         return nullptr;
@@ -113,27 +109,17 @@ VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) {
         return nullptr;
 
     const u64 file_offset = header.file_meta.offset;
-    const u64 dir_offset = header.directory_meta.offset + 4;
+    const u64 dir_offset = header.directory_meta.offset;
 
-    auto root =
-        std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{}, std::vector<VirtualDir>{},
-                                             file->GetName(), file->GetContainingDirectory());
+    auto root_container = std::make_shared<VectorVfsDirectory>();
 
-    ProcessDirectory(file, dir_offset, file_offset, header.data_offset, 0, root);
+    ProcessDirectory(file, dir_offset, file_offset, header.data_offset, 0, root_container);
 
-    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();
+    if (auto root = root_container->GetSubdirectory(""); root) {
+        return std::make_shared<CachedVfsDirectory>(std::move(root));
     }
 
-    return std::make_shared<CachedVfsDirectory>(std::move(out));
+    return nullptr;
 }
 
 VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext) {
diff --git a/src/core/file_sys/romfs.h b/src/core/file_sys/romfs.h
index 5d7f0c2a8..b75ff1aad 100644
--- a/src/core/file_sys/romfs.h
+++ b/src/core/file_sys/romfs.h
@@ -7,16 +7,9 @@
 
 namespace FileSys {
 
-enum class RomFSExtractionType {
-    Full,          // Includes data directory
-    Truncated,     // Traverses into data directory
-    SingleDiscard, // Traverses into the first subdirectory of root
-};
-
 // Converts a RomFS binary blob to VFS Filesystem
 // Returns nullptr on failure
-VirtualDir ExtractRomFS(VirtualFile file,
-                        RomFSExtractionType type = RomFSExtractionType::Truncated);
+VirtualDir ExtractRomFS(VirtualFile file);
 
 // Converts a VFS filesystem into a RomFS binary
 // Returns nullptr on failure
diff --git a/src/core/hle/service/am/applets/applet_web_browser.cpp b/src/core/hle/service/am/applets/applet_web_browser.cpp
index 1c9a1dc29..b0ea2b381 100644
--- a/src/core/hle/service/am/applets/applet_web_browser.cpp
+++ b/src/core/hle/service/am/applets/applet_web_browser.cpp
@@ -330,8 +330,7 @@ void WebBrowser::ExtractOfflineRomFS() {
     LOG_DEBUG(Service_AM, "Extracting RomFS to {}",
               Common::FS::PathToUTF8String(offline_cache_dir));
 
-    const auto extracted_romfs_dir =
-        FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard);
+    const auto extracted_romfs_dir = FileSys::ExtractRomFS(offline_romfs);
 
     const auto temp_dir = system.GetFilesystem()->CreateDirectory(
         Common::FS::PathToUTF8String(offline_cache_dir), FileSys::Mode::ReadWrite);
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 0df163029..db9da6dc8 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -2737,7 +2737,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
         return;
     }
 
-    const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full);
+    const auto extracted = FileSys::ExtractRomFS(romfs);
     if (extracted == nullptr) {
         failed();
         return;