aboutsummaryrefslogtreecommitdiff
path: root/src/core/loader/deconstructed_rom_directory.cpp
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-11-01 16:21:45 -0500
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-11-01 16:21:45 -0500
commita41d11108b6478ad862fc7fcac36ab51c83b8602 (patch)
tree2c16c13764e6ef2b48c7327055fde64d7af2cd37 /src/core/loader/deconstructed_rom_directory.cpp
parent01effa81ab0e151f8cfd674bf4da952d24645943 (diff)
parentac22c4087d9a6380c1937337530a5d4e54808ad1 (diff)
Merge remote-tracking branch 'origin/nx2' into merge/nx2-experimental-1101
# Conflicts: # src/core/loader/deconstructed_rom_directory.cpp
Diffstat (limited to 'src/core/loader/deconstructed_rom_directory.cpp')
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp89
1 files changed, 66 insertions, 23 deletions
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index bff7e76efd..cda1645548 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -5,6 +5,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
+#include <cctype>
#include <cstring>
#include <string_view>
#include "common/logging.h"
@@ -130,6 +131,38 @@ std::string RomFSProductVersion(const FileSys::VirtualDir& romfs_dir) {
return fmt::format("{}.{}.{}", best[0], best[1], best[2]);
}
+// Loose ExeFS dumps carry no control data, but icon/nacp sidecars can be dropped next to the
+// ExeFS or in the game folder above it (<game>/exefs/main, <game>/base/exefs/main and
+// <game>/updates/<title id>/exefs/main layouts).
+std::vector<FileSys::VirtualDir> FindSidecarDirs(const FileSys::VirtualDir& exefs_dir) {
+ const auto equals_ignore_case = [](const std::string& a, std::string_view b) {
+ return a.size() == b.size() &&
+ std::equal(a.begin(), a.end(), b.begin(), [](char x, char y) {
+ return std::tolower(static_cast<unsigned char>(x)) ==
+ std::tolower(static_cast<unsigned char>(y));
+ });
+ };
+
+ std::vector<FileSys::VirtualDir> dirs{exefs_dir};
+ for (auto dir = exefs_dir; dirs.size() < 4;) {
+ const auto parent = dir->GetParentDirectory();
+ if (parent == nullptr) {
+ break;
+ }
+ const auto& name = parent->GetName();
+ dirs.push_back(parent);
+ const bool storage_level =
+ equals_ignore_case(name, "base") || equals_ignore_case(name, "update") ||
+ equals_ignore_case(name, "updates") ||
+ (!name.empty() && name.find_first_not_of("0123456789abcdefABCDEF") == std::string::npos);
+ if (!storage_level) {
+ break;
+ }
+ dir = parent;
+ }
+ return dirs;
+}
+
} // namespace
AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_, bool override_update_)
@@ -137,6 +170,7 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys
, override_update(override_update_)
{
const auto file_dir = file->GetContainingDirectory();
+ const auto sidecar_dirs = FindSidecarDirs(file_dir);
// Title ID
const auto npdm = file_dir->GetFile("main.npdm");
@@ -148,8 +182,13 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys
// Icon
FileSys::VirtualFile icon_file = nullptr;
- for (const auto& language : FileSys::LANGUAGE_NAMES) {
- icon_file = file_dir->GetFile("icon_" + std::string(language) + ".dat");
+ for (const auto& sidecar_dir : sidecar_dirs) {
+ for (const auto& language : FileSys::LANGUAGE_NAMES) {
+ icon_file = sidecar_dir->GetFile("icon_" + std::string(language) + ".dat");
+ if (icon_file != nullptr) {
+ break;
+ }
+ }
if (icon_file != nullptr) {
icon_data = icon_file->ReadAllBytes();
break;
@@ -158,31 +197,35 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys
if (icon_data.empty()) {
// Any png, jpeg, or bmp file
- const auto& files = file_dir->GetFiles();
- const auto icon_iter =
- std::find_if(files.begin(), files.end(), [](const FileSys::VirtualFile& f) {
- return f->GetExtension() == "png" || f->GetExtension() == "jpg" ||
- f->GetExtension() == "bmp" || f->GetExtension() == "jpeg";
- });
- if (icon_iter != files.end())
- icon_data = (*icon_iter)->ReadAllBytes();
- }
-
- // Metadata: control.nacp (or any .nacp) next to main, else a control.nacp in the folders above
- // it (dumps keep the title's or the update's control data at their root).
- FileSys::VirtualFile nacp_file = file_dir->GetFile("control.nacp");
- if (nacp_file == nullptr) {
- const auto& files = file_dir->GetFiles();
+ for (const auto& sidecar_dir : sidecar_dirs) {
+ const auto& files = sidecar_dir->GetFiles();
+ const auto icon_iter =
+ std::find_if(files.begin(), files.end(), [](const FileSys::VirtualFile& f) {
+ return f->GetExtension() == "png" || f->GetExtension() == "jpg" ||
+ f->GetExtension() == "bmp" || f->GetExtension() == "jpeg";
+ });
+ if (icon_iter != files.end()) {
+ icon_data = (*icon_iter)->ReadAllBytes();
+ break;
+ }
+ }
+ }
+
+ // Metadata
+ FileSys::VirtualFile nacp_file = nullptr;
+ for (const auto& sidecar_dir : sidecar_dirs) {
+ nacp_file = sidecar_dir->GetFile("control.nacp");
+ if (nacp_file != nullptr) {
+ break;
+ }
+ const auto& files = sidecar_dir->GetFiles();
const auto nacp_iter =
std::find_if(files.begin(), files.end(),
[](const FileSys::VirtualFile& f) { return f->GetExtension() == "nacp"; });
- if (nacp_iter != files.end())
+ if (nacp_iter != files.end()) {
nacp_file = *nacp_iter;
- }
- auto parent = file_dir->GetParentDirectory();
- for (int level = 0; level < 3 && nacp_file == nullptr && parent != nullptr; ++level) {
- nacp_file = parent->GetFile("control.nacp");
- parent = parent->GetParentDirectory();
+ break;
+ }
}
if (nacp_file != nullptr) {