Merge pull request #1515 from DarkLordZach/dlc-lfs
patch_manager: Add support for LayeredFS on DLC RomFS
This commit is contained in:
commit
5edb2403c2
|
@ -168,7 +168,8 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
|
||||||
|
|
||||||
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
|
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
|
||||||
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
|
||||||
if (type != ContentRecordType::Program || load_dir == nullptr || load_dir->GetSize() <= 0) {
|
if ((type != ContentRecordType::Program && type != ContentRecordType::Data) ||
|
||||||
|
load_dir == nullptr || load_dir->GetSize() <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,7 +219,7 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
|
||||||
title_id, static_cast<u8>(type))
|
title_id, static_cast<u8>(type))
|
||||||
.c_str();
|
.c_str();
|
||||||
|
|
||||||
if (type == ContentRecordType::Program)
|
if (type == ContentRecordType::Program || type == ContentRecordType::Data)
|
||||||
LOG_INFO(Loader, log_string);
|
LOG_INFO(Loader, log_string);
|
||||||
else
|
else
|
||||||
LOG_DEBUG(Loader, log_string);
|
LOG_DEBUG(Loader, log_string);
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
#include <mbedtls/sha256.h>
|
#include <mbedtls/sha256.h>
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
|
@ -30,6 +31,14 @@ bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs)
|
||||||
return (lhs.title_id < rhs.title_id) || (lhs.title_id == rhs.title_id && lhs.type < rhs.type);
|
return (lhs.title_id < rhs.title_id) || (lhs.title_id == rhs.title_id && lhs.type < rhs.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool operator==(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) {
|
||||||
|
return std::tie(lhs.title_id, lhs.type) == std::tie(rhs.title_id, rhs.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) {
|
||||||
|
return !operator==(lhs, rhs);
|
||||||
|
}
|
||||||
|
|
||||||
static bool FollowsTwoDigitDirFormat(std::string_view name) {
|
static bool FollowsTwoDigitDirFormat(std::string_view name) {
|
||||||
static const std::regex two_digit_regex("000000[0-9A-F]{2}", std::regex_constants::ECMAScript |
|
static const std::regex two_digit_regex("000000[0-9A-F]{2}", std::regex_constants::ECMAScript |
|
||||||
std::regex_constants::icase);
|
std::regex_constants::icase);
|
||||||
|
@ -593,6 +602,9 @@ std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntries() const {
|
||||||
},
|
},
|
||||||
[](const CNMT& c, const ContentRecord& r) { return true; });
|
[](const CNMT& c, const ContentRecord& r) { return true; });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::sort(out.begin(), out.end());
|
||||||
|
out.erase(std::unique(out.begin(), out.end()), out.end());
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -616,6 +628,9 @@ std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntriesFilter(
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::sort(out.begin(), out.end());
|
||||||
|
out.erase(std::unique(out.begin(), out.end()), out.end());
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
} // namespace FileSys
|
} // namespace FileSys
|
||||||
|
|
|
@ -50,6 +50,10 @@ constexpr u64 GetUpdateTitleID(u64 base_title_id) {
|
||||||
// boost flat_map requires operator< for O(log(n)) lookups.
|
// boost flat_map requires operator< for O(log(n)) lookups.
|
||||||
bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
|
bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
|
||||||
|
|
||||||
|
// std unique requires operator== to identify duplicates.
|
||||||
|
bool operator==(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
|
||||||
|
bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A class that catalogues NCAs in the registered directory structure.
|
* A class that catalogues NCAs in the registered directory structure.
|
||||||
* Nintendo's registered format follows this structure:
|
* Nintendo's registered format follows this structure:
|
||||||
|
@ -60,8 +64,8 @@ bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs)
|
||||||
* | 00
|
* | 00
|
||||||
* | 01 <- Actual content split along 4GB boundaries. (optional)
|
* | 01 <- Actual content split along 4GB boundaries. (optional)
|
||||||
*
|
*
|
||||||
* (This impl also supports substituting the nca dir for an nca file, as that's more convenient when
|
* (This impl also supports substituting the nca dir for an nca file, as that's more convenient
|
||||||
* 4GB splitting can be ignored.)
|
* when 4GB splitting can be ignored.)
|
||||||
*/
|
*/
|
||||||
class RegisteredCache {
|
class RegisteredCache {
|
||||||
friend class RegisteredCacheUnion;
|
friend class RegisteredCacheUnion;
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include "core/file_sys/errors.h"
|
#include "core/file_sys/errors.h"
|
||||||
#include "core/file_sys/mode.h"
|
#include "core/file_sys/mode.h"
|
||||||
#include "core/file_sys/nca_metadata.h"
|
#include "core/file_sys/nca_metadata.h"
|
||||||
|
#include "core/file_sys/patch_manager.h"
|
||||||
#include "core/file_sys/savedata_factory.h"
|
#include "core/file_sys/savedata_factory.h"
|
||||||
#include "core/file_sys/vfs.h"
|
#include "core/file_sys/vfs.h"
|
||||||
#include "core/hle/ipc_helpers.h"
|
#include "core/hle/ipc_helpers.h"
|
||||||
|
@ -630,6 +631,7 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) {
|
||||||
static_cast<u8>(storage_id), unknown, title_id);
|
static_cast<u8>(storage_id), unknown, title_id);
|
||||||
|
|
||||||
auto data = OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data);
|
auto data = OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data);
|
||||||
|
|
||||||
if (data.Failed()) {
|
if (data.Failed()) {
|
||||||
// TODO(DarkLordZach): Find the right error code to use here
|
// TODO(DarkLordZach): Find the right error code to use here
|
||||||
LOG_ERROR(Service_FS,
|
LOG_ERROR(Service_FS,
|
||||||
|
@ -640,7 +642,9 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
IStorage storage(std::move(data.Unwrap()));
|
FileSys::PatchManager pm{title_id};
|
||||||
|
|
||||||
|
IStorage storage(pm.PatchRomFS(std::move(data.Unwrap()), 0, FileSys::ContentRecordType::Data));
|
||||||
|
|
||||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
|
|
|
@ -100,6 +100,8 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* "Callouts" are one-time instructional messages shown to the user. In the config settings, there
|
* "Callouts" are one-time instructional messages shown to the user. In the config settings, there
|
||||||
* is a bitfield "callout_flags" options, used to track if a message has already been shown to the
|
* is a bitfield "callout_flags" options, used to track if a message has already been shown to the
|
||||||
|
@ -823,14 +825,10 @@ static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src
|
||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) {
|
void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) {
|
||||||
const auto path = fmt::format("{}{:016X}/romfs",
|
const auto failed = [this] {
|
||||||
FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), program_id);
|
|
||||||
|
|
||||||
const auto failed = [this, &path] {
|
|
||||||
QMessageBox::warning(this, tr("RomFS Extraction Failed!"),
|
QMessageBox::warning(this, tr("RomFS Extraction Failed!"),
|
||||||
tr("There was an error copying the RomFS files or the user "
|
tr("There was an error copying the RomFS files or the user "
|
||||||
"cancelled the operation."));
|
"cancelled the operation."));
|
||||||
vfs->DeleteDirectory(path);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto loader = Loader::GetLoader(vfs->OpenFile(game_path, FileSys::Mode::Read));
|
const auto loader = Loader::GetLoader(vfs->OpenFile(game_path, FileSys::Mode::Read));
|
||||||
|
@ -845,10 +843,24 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto romfs =
|
const auto installed = Service::FileSystem::GetUnionContents();
|
||||||
loader->IsRomFSUpdatable()
|
auto romfs_title_id = SelectRomFSDumpTarget(*installed, program_id);
|
||||||
? FileSys::PatchManager(program_id).PatchRomFS(file, loader->ReadRomFSIVFCOffset())
|
|
||||||
: file;
|
if (!romfs_title_id) {
|
||||||
|
failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto path = fmt::format(
|
||||||
|
"{}{:016X}/romfs", FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), *romfs_title_id);
|
||||||
|
|
||||||
|
FileSys::VirtualFile romfs;
|
||||||
|
|
||||||
|
if (*romfs_title_id == program_id) {
|
||||||
|
romfs = file;
|
||||||
|
} else {
|
||||||
|
romfs = installed->GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS();
|
||||||
|
}
|
||||||
|
|
||||||
const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full);
|
const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full);
|
||||||
if (extracted == nullptr) {
|
if (extracted == nullptr) {
|
||||||
|
@ -860,6 +872,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
|
||||||
|
|
||||||
if (out == nullptr) {
|
if (out == nullptr) {
|
||||||
failed();
|
failed();
|
||||||
|
vfs->DeleteDirectory(path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -870,8 +883,11 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
|
||||||
"files into the new directory while <br>skeleton will only create the directory "
|
"files into the new directory while <br>skeleton will only create the directory "
|
||||||
"structure."),
|
"structure."),
|
||||||
{"Full", "Skeleton"}, 0, false, &ok);
|
{"Full", "Skeleton"}, 0, false, &ok);
|
||||||
if (!ok)
|
if (!ok) {
|
||||||
failed();
|
failed();
|
||||||
|
vfs->DeleteDirectory(path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const auto full = res == "Full";
|
const auto full = res == "Full";
|
||||||
const auto entry_size = CalculateRomFSEntrySize(extracted, full);
|
const auto entry_size = CalculateRomFSEntrySize(extracted, full);
|
||||||
|
@ -888,6 +904,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
|
||||||
} else {
|
} else {
|
||||||
progress.close();
|
progress.close();
|
||||||
failed();
|
failed();
|
||||||
|
vfs->DeleteDirectory(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1459,6 +1476,42 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boost::optional<u64> GMainWindow::SelectRomFSDumpTarget(
|
||||||
|
const FileSys::RegisteredCacheUnion& installed, u64 program_id) {
|
||||||
|
const auto dlc_entries =
|
||||||
|
installed.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
|
||||||
|
std::vector<FileSys::RegisteredCacheEntry> dlc_match;
|
||||||
|
dlc_match.reserve(dlc_entries.size());
|
||||||
|
std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
|
||||||
|
[&program_id, &installed](const FileSys::RegisteredCacheEntry& entry) {
|
||||||
|
return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id &&
|
||||||
|
installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success;
|
||||||
|
});
|
||||||
|
|
||||||
|
std::vector<u64> romfs_tids;
|
||||||
|
romfs_tids.push_back(program_id);
|
||||||
|
for (const auto& entry : dlc_match)
|
||||||
|
romfs_tids.push_back(entry.title_id);
|
||||||
|
|
||||||
|
if (romfs_tids.size() > 1) {
|
||||||
|
QStringList list{"Base"};
|
||||||
|
for (std::size_t i = 1; i < romfs_tids.size(); ++i)
|
||||||
|
list.push_back(QStringLiteral("DLC %1").arg(romfs_tids[i] & 0x7FF));
|
||||||
|
|
||||||
|
bool ok;
|
||||||
|
const auto res = QInputDialog::getItem(
|
||||||
|
this, tr("Select RomFS Dump Target"),
|
||||||
|
tr("Please select which RomFS you would like to dump."), list, 0, false, &ok);
|
||||||
|
if (!ok) {
|
||||||
|
return boost::none;
|
||||||
|
}
|
||||||
|
|
||||||
|
return romfs_tids[list.indexOf(res)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return program_id;
|
||||||
|
}
|
||||||
|
|
||||||
bool GMainWindow::ConfirmClose() {
|
bool GMainWindow::ConfirmClose() {
|
||||||
if (emu_thread == nullptr || !UISettings::values.confirm_before_closing)
|
if (emu_thread == nullptr || !UISettings::values.confirm_before_closing)
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include <boost/optional.hpp>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "ui_main.h"
|
#include "ui_main.h"
|
||||||
|
@ -29,8 +30,9 @@ class WaitTreeWidget;
|
||||||
enum class GameListOpenTarget;
|
enum class GameListOpenTarget;
|
||||||
|
|
||||||
namespace FileSys {
|
namespace FileSys {
|
||||||
|
class RegisteredCacheUnion;
|
||||||
class VfsFilesystem;
|
class VfsFilesystem;
|
||||||
}
|
} // namespace FileSys
|
||||||
|
|
||||||
namespace Tegra {
|
namespace Tegra {
|
||||||
class DebugContext;
|
class DebugContext;
|
||||||
|
@ -175,6 +177,8 @@ private slots:
|
||||||
void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
|
void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
boost::optional<u64> SelectRomFSDumpTarget(const FileSys::RegisteredCacheUnion&,
|
||||||
|
u64 program_id);
|
||||||
void UpdateStatusBar();
|
void UpdateStatusBar();
|
||||||
|
|
||||||
Ui::MainWindow ui;
|
Ui::MainWindow ui;
|
||||||
|
|
Reference in New Issue