mirror of
https://git.suyu.dev/suyu/suyu.git
synced 2024-12-23 17:00:57 +01:00
Merge pull request #4655 from lioncash/internal2
patch_manager: Minor cleanup
This commit is contained in:
commit
b5f4221c3d
2 changed files with 95 additions and 87 deletions
|
@ -27,6 +27,7 @@
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
|
|
||||||
namespace FileSys {
|
namespace FileSys {
|
||||||
|
namespace {
|
||||||
|
|
||||||
constexpr u64 SINGLE_BYTE_MODULUS = 0x100;
|
constexpr u64 SINGLE_BYTE_MODULUS = 0x100;
|
||||||
constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
|
constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
|
||||||
|
@ -36,19 +37,28 @@ constexpr std::array<const char*, 14> EXEFS_FILE_NAMES{
|
||||||
"subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9",
|
"subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9",
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
|
enum class TitleVersionFormat : u8 {
|
||||||
|
ThreeElements, ///< vX.Y.Z
|
||||||
|
FourElements, ///< vX.Y.Z.W
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string FormatTitleVersion(u32 version,
|
||||||
|
TitleVersionFormat format = TitleVersionFormat::ThreeElements) {
|
||||||
std::array<u8, sizeof(u32)> bytes{};
|
std::array<u8, sizeof(u32)> bytes{};
|
||||||
bytes[0] = version % SINGLE_BYTE_MODULUS;
|
bytes[0] = static_cast<u8>(version % SINGLE_BYTE_MODULUS);
|
||||||
for (std::size_t i = 1; i < bytes.size(); ++i) {
|
for (std::size_t i = 1; i < bytes.size(); ++i) {
|
||||||
version /= SINGLE_BYTE_MODULUS;
|
version /= SINGLE_BYTE_MODULUS;
|
||||||
bytes[i] = version % SINGLE_BYTE_MODULUS;
|
bytes[i] = static_cast<u8>(version % SINGLE_BYTE_MODULUS);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (format == TitleVersionFormat::FourElements)
|
if (format == TitleVersionFormat::FourElements) {
|
||||||
return fmt::format("v{}.{}.{}.{}", bytes[3], bytes[2], bytes[1], bytes[0]);
|
return fmt::format("v{}.{}.{}.{}", bytes[3], bytes[2], bytes[1], bytes[0]);
|
||||||
|
}
|
||||||
return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]);
|
return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a directory with name matching name case-insensitive. Returns nullptr if directory
|
||||||
|
// doesn't have a directory with name.
|
||||||
VirtualDir FindSubdirectoryCaseless(const VirtualDir dir, std::string_view name) {
|
VirtualDir FindSubdirectoryCaseless(const VirtualDir dir, std::string_view name) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
return dir->GetSubdirectory(name);
|
return dir->GetSubdirectory(name);
|
||||||
|
@ -65,6 +75,45 @@ VirtualDir FindSubdirectoryCaseless(const VirtualDir dir, std::string_view name)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<std::vector<Core::Memory::CheatEntry>> ReadCheatFileFromFolder(
|
||||||
|
const Core::System& system, u64 title_id, const PatchManager::BuildID& build_id_,
|
||||||
|
const VirtualDir& base_path, bool upper) {
|
||||||
|
const auto build_id_raw = Common::HexToString(build_id_, upper);
|
||||||
|
const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
|
||||||
|
const auto file = base_path->GetFile(fmt::format("{}.txt", build_id));
|
||||||
|
|
||||||
|
if (file == nullptr) {
|
||||||
|
LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}",
|
||||||
|
title_id, build_id);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> data(file->GetSize());
|
||||||
|
if (file->Read(data.data(), data.size()) != data.size()) {
|
||||||
|
LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}",
|
||||||
|
title_id, build_id);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
Core::Memory::TextCheatParser parser;
|
||||||
|
return parser.Parse(system,
|
||||||
|
std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppendCommaIfNotEmpty(std::string& to, std::string_view with) {
|
||||||
|
if (to.empty()) {
|
||||||
|
to += with;
|
||||||
|
} else {
|
||||||
|
to += ", ";
|
||||||
|
to += with;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
|
||||||
|
return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
|
||||||
|
}
|
||||||
|
} // Anonymous namespace
|
||||||
|
|
||||||
PatchManager::PatchManager(u64 title_id) : title_id(title_id) {}
|
PatchManager::PatchManager(u64 title_id) : title_id(title_id) {}
|
||||||
|
|
||||||
PatchManager::~PatchManager() = default;
|
PatchManager::~PatchManager() = default;
|
||||||
|
@ -245,7 +294,7 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
|
bool PatchManager::HasNSOPatch(const BuildID& build_id_) const {
|
||||||
const auto build_id_raw = Common::HexToString(build_id_);
|
const auto build_id_raw = Common::HexToString(build_id_);
|
||||||
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
|
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
|
||||||
|
|
||||||
|
@ -265,36 +314,8 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
|
||||||
return !CollectPatches(patch_dirs, build_id).empty();
|
return !CollectPatches(patch_dirs, build_id).empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
|
||||||
std::optional<std::vector<Core::Memory::CheatEntry>> ReadCheatFileFromFolder(
|
|
||||||
const Core::System& system, u64 title_id, const std::array<u8, 0x20>& build_id_,
|
|
||||||
const VirtualDir& base_path, bool upper) {
|
|
||||||
const auto build_id_raw = Common::HexToString(build_id_, upper);
|
|
||||||
const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
|
|
||||||
const auto file = base_path->GetFile(fmt::format("{}.txt", build_id));
|
|
||||||
|
|
||||||
if (file == nullptr) {
|
|
||||||
LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}",
|
|
||||||
title_id, build_id);
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<u8> data(file->GetSize());
|
|
||||||
if (file->Read(data.data(), data.size()) != data.size()) {
|
|
||||||
LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}",
|
|
||||||
title_id, build_id);
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
Core::Memory::TextCheatParser parser;
|
|
||||||
return parser.Parse(system,
|
|
||||||
std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // Anonymous namespace
|
|
||||||
|
|
||||||
std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(
|
std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(
|
||||||
const Core::System& system, const std::array<u8, 32>& build_id_) const {
|
const Core::System& system, const BuildID& build_id_) const {
|
||||||
const auto load_dir = system.GetFileSystemController().GetModificationLoadRoot(title_id);
|
const auto load_dir = system.GetFileSystemController().GetModificationLoadRoot(title_id);
|
||||||
if (load_dir == nullptr) {
|
if (load_dir == nullptr) {
|
||||||
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
|
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
|
||||||
|
@ -435,21 +456,11 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
|
||||||
return romfs;
|
return romfs;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void AppendCommaIfNotEmpty(std::string& to, const std::string& with) {
|
PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile update_raw) const {
|
||||||
if (to.empty())
|
if (title_id == 0) {
|
||||||
to += with;
|
|
||||||
else
|
|
||||||
to += ", " + with;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
|
|
||||||
return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames(
|
|
||||||
VirtualFile update_raw) const {
|
|
||||||
if (title_id == 0)
|
|
||||||
return {};
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
std::map<std::string, std::string, std::less<>> out;
|
std::map<std::string, std::string, std::less<>> out;
|
||||||
const auto& installed = Core::System::GetInstance().GetContentProvider();
|
const auto& installed = Core::System::GetInstance().GetContentProvider();
|
||||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||||
|
@ -472,8 +483,7 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
|
||||||
if (meta_ver.value_or(0) == 0) {
|
if (meta_ver.value_or(0) == 0) {
|
||||||
out.insert_or_assign(update_label, "");
|
out.insert_or_assign(update_label, "");
|
||||||
} else {
|
} else {
|
||||||
out.insert_or_assign(
|
out.insert_or_assign(update_label, FormatTitleVersion(*meta_ver));
|
||||||
update_label, FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements));
|
|
||||||
}
|
}
|
||||||
} else if (update_raw != nullptr) {
|
} else if (update_raw != nullptr) {
|
||||||
out.insert_or_assign(update_label, "PACKED");
|
out.insert_or_assign(update_label, "PACKED");
|
||||||
|
@ -562,41 +572,47 @@ std::optional<u32> PatchManager::GetGameVersion() const {
|
||||||
return installed.GetEntryVersion(title_id);
|
return installed.GetEntryVersion(title_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
|
PatchManager::Metadata PatchManager::GetControlMetadata() const {
|
||||||
const auto& installed = Core::System::GetInstance().GetContentProvider();
|
const auto& installed = Core::System::GetInstance().GetContentProvider();
|
||||||
|
|
||||||
const auto base_control_nca = installed.GetEntry(title_id, ContentRecordType::Control);
|
const auto base_control_nca = installed.GetEntry(title_id, ContentRecordType::Control);
|
||||||
if (base_control_nca == nullptr)
|
if (base_control_nca == nullptr) {
|
||||||
return {};
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
return ParseControlNCA(*base_control_nca);
|
return ParseControlNCA(*base_control_nca);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(const NCA& nca) const {
|
PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const {
|
||||||
const auto base_romfs = nca.GetRomFS();
|
const auto base_romfs = nca.GetRomFS();
|
||||||
if (base_romfs == nullptr)
|
if (base_romfs == nullptr) {
|
||||||
return {};
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control);
|
const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control);
|
||||||
if (romfs == nullptr)
|
if (romfs == nullptr) {
|
||||||
return {};
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
const auto extracted = ExtractRomFS(romfs);
|
const auto extracted = ExtractRomFS(romfs);
|
||||||
if (extracted == nullptr)
|
if (extracted == nullptr) {
|
||||||
return {};
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
auto nacp_file = extracted->GetFile("control.nacp");
|
auto nacp_file = extracted->GetFile("control.nacp");
|
||||||
if (nacp_file == nullptr)
|
if (nacp_file == nullptr) {
|
||||||
nacp_file = extracted->GetFile("Control.nacp");
|
nacp_file = extracted->GetFile("Control.nacp");
|
||||||
|
}
|
||||||
|
|
||||||
auto nacp = nacp_file == nullptr ? nullptr : std::make_unique<NACP>(nacp_file);
|
auto nacp = nacp_file == nullptr ? nullptr : std::make_unique<NACP>(nacp_file);
|
||||||
|
|
||||||
VirtualFile icon_file;
|
VirtualFile icon_file;
|
||||||
for (const auto& language : FileSys::LANGUAGE_NAMES) {
|
for (const auto& language : FileSys::LANGUAGE_NAMES) {
|
||||||
icon_file = extracted->GetFile("icon_" + std::string(language) + ".dat");
|
icon_file = extracted->GetFile(std::string("icon_").append(language).append(".dat"));
|
||||||
if (icon_file != nullptr)
|
if (icon_file != nullptr) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {std::move(nacp), icon_file};
|
return {std::move(nacp), icon_file};
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,69 +22,61 @@ namespace FileSys {
|
||||||
class NCA;
|
class NCA;
|
||||||
class NACP;
|
class NACP;
|
||||||
|
|
||||||
enum class TitleVersionFormat : u8 {
|
|
||||||
ThreeElements, ///< vX.Y.Z
|
|
||||||
FourElements, ///< vX.Y.Z.W
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string FormatTitleVersion(u32 version,
|
|
||||||
TitleVersionFormat format = TitleVersionFormat::ThreeElements);
|
|
||||||
|
|
||||||
// Returns a directory with name matching name case-insensitive. Returns nullptr if directory
|
|
||||||
// doesn't have a directory with name.
|
|
||||||
VirtualDir FindSubdirectoryCaseless(VirtualDir dir, std::string_view name);
|
|
||||||
|
|
||||||
// A centralized class to manage patches to games.
|
// A centralized class to manage patches to games.
|
||||||
class PatchManager {
|
class PatchManager {
|
||||||
public:
|
public:
|
||||||
|
using BuildID = std::array<u8, 0x20>;
|
||||||
|
using Metadata = std::pair<std::unique_ptr<NACP>, VirtualFile>;
|
||||||
|
using PatchVersionNames = std::map<std::string, std::string, std::less<>>;
|
||||||
|
|
||||||
explicit PatchManager(u64 title_id);
|
explicit PatchManager(u64 title_id);
|
||||||
~PatchManager();
|
~PatchManager();
|
||||||
|
|
||||||
u64 GetTitleID() const;
|
[[nodiscard]] u64 GetTitleID() const;
|
||||||
|
|
||||||
// Currently tracked ExeFS patches:
|
// Currently tracked ExeFS patches:
|
||||||
// - Game Updates
|
// - Game Updates
|
||||||
VirtualDir PatchExeFS(VirtualDir exefs) const;
|
[[nodiscard]] VirtualDir PatchExeFS(VirtualDir exefs) const;
|
||||||
|
|
||||||
// Currently tracked NSO patches:
|
// Currently tracked NSO patches:
|
||||||
// - IPS
|
// - IPS
|
||||||
// - IPSwitch
|
// - IPSwitch
|
||||||
std::vector<u8> PatchNSO(const std::vector<u8>& nso, const std::string& name) const;
|
[[nodiscard]] std::vector<u8> PatchNSO(const std::vector<u8>& nso,
|
||||||
|
const std::string& name) const;
|
||||||
|
|
||||||
// Checks to see if PatchNSO() will have any effect given the NSO's build ID.
|
// Checks to see if PatchNSO() will have any effect given the NSO's build ID.
|
||||||
// Used to prevent expensive copies in NSO loader.
|
// Used to prevent expensive copies in NSO loader.
|
||||||
bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const;
|
[[nodiscard]] bool HasNSOPatch(const BuildID& build_id) const;
|
||||||
|
|
||||||
// Creates a CheatList object with all
|
// Creates a CheatList object with all
|
||||||
std::vector<Core::Memory::CheatEntry> CreateCheatList(
|
[[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList(
|
||||||
const Core::System& system, const std::array<u8, 0x20>& build_id) const;
|
const Core::System& system, const BuildID& build_id) const;
|
||||||
|
|
||||||
// Currently tracked RomFS patches:
|
// Currently tracked RomFS patches:
|
||||||
// - Game Updates
|
// - Game Updates
|
||||||
// - LayeredFS
|
// - LayeredFS
|
||||||
VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
|
[[nodiscard]] VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
|
||||||
ContentRecordType type = ContentRecordType::Program,
|
ContentRecordType type = ContentRecordType::Program,
|
||||||
VirtualFile update_raw = nullptr) const;
|
VirtualFile update_raw = nullptr) const;
|
||||||
|
|
||||||
// Returns a vector of pairs between patch names and patch versions.
|
// Returns a vector of pairs between patch names and patch versions.
|
||||||
// i.e. Update 3.2.2 will return {"Update", "3.2.2"}
|
// i.e. Update 3.2.2 will return {"Update", "3.2.2"}
|
||||||
std::map<std::string, std::string, std::less<>> GetPatchVersionNames(
|
[[nodiscard]] PatchVersionNames GetPatchVersionNames(VirtualFile update_raw = nullptr) const;
|
||||||
VirtualFile update_raw = nullptr) const;
|
|
||||||
|
|
||||||
// If the game update exists, returns the u32 version field in its Meta-type NCA. If that fails,
|
// If the game update exists, returns the u32 version field in its Meta-type NCA. If that fails,
|
||||||
// it will fallback to the Meta-type NCA of the base game. If that fails, the result will be
|
// it will fallback to the Meta-type NCA of the base game. If that fails, the result will be
|
||||||
// std::nullopt
|
// std::nullopt
|
||||||
std::optional<u32> GetGameVersion() const;
|
[[nodiscard]] std::optional<u32> GetGameVersion() const;
|
||||||
|
|
||||||
// Given title_id of the program, attempts to get the control data of the update and parse
|
// Given title_id of the program, attempts to get the control data of the update and parse
|
||||||
// it, falling back to the base control data.
|
// it, falling back to the base control data.
|
||||||
std::pair<std::unique_ptr<NACP>, VirtualFile> GetControlMetadata() const;
|
[[nodiscard]] Metadata GetControlMetadata() const;
|
||||||
|
|
||||||
// Version of GetControlMetadata that takes an arbitrary NCA
|
// Version of GetControlMetadata that takes an arbitrary NCA
|
||||||
std::pair<std::unique_ptr<NACP>, VirtualFile> ParseControlNCA(const NCA& nca) const;
|
[[nodiscard]] Metadata ParseControlNCA(const NCA& nca) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
|
[[nodiscard]] std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
|
||||||
const std::string& build_id) const;
|
const std::string& build_id) const;
|
||||||
|
|
||||||
u64 title_id;
|
u64 title_id;
|
||||||
|
|
Loading…
Reference in a new issue