patch_manager.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. // Copyright 2018 yuzu emulator team
  2. // Licensed under GPLv2 or any later version
  3. // Refer to the license.txt file included.
  4. #include <algorithm>
  5. #include <array>
  6. #include <cstddef>
  7. #include <cstring>
  8. #include "common/hex_util.h"
  9. #include "common/logging/log.h"
  10. #include "core/file_sys/content_archive.h"
  11. #include "core/file_sys/control_metadata.h"
  12. #include "core/file_sys/ips_layer.h"
  13. #include "core/file_sys/patch_manager.h"
  14. #include "core/file_sys/registered_cache.h"
  15. #include "core/file_sys/romfs.h"
  16. #include "core/file_sys/vfs_layered.h"
  17. #include "core/file_sys/vfs_vector.h"
  18. #include "core/hle/service/filesystem/filesystem.h"
  19. #include "core/loader/loader.h"
  20. namespace FileSys {
  21. constexpr u64 SINGLE_BYTE_MODULUS = 0x100;
  22. constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
  23. struct NSOBuildHeader {
  24. u32_le magic;
  25. INSERT_PADDING_BYTES(0x3C);
  26. std::array<u8, 0x20> build_id;
  27. INSERT_PADDING_BYTES(0xA0);
  28. };
  29. static_assert(sizeof(NSOBuildHeader) == 0x100, "NSOBuildHeader has incorrect size.");
  30. std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
  31. std::array<u8, sizeof(u32)> bytes{};
  32. bytes[0] = version % SINGLE_BYTE_MODULUS;
  33. for (std::size_t i = 1; i < bytes.size(); ++i) {
  34. version /= SINGLE_BYTE_MODULUS;
  35. bytes[i] = version % SINGLE_BYTE_MODULUS;
  36. }
  37. if (format == TitleVersionFormat::FourElements)
  38. return fmt::format("v{}.{}.{}.{}", bytes[3], bytes[2], bytes[1], bytes[0]);
  39. return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]);
  40. }
  41. PatchManager::PatchManager(u64 title_id) : title_id(title_id) {}
  42. PatchManager::~PatchManager() = default;
  43. VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
  44. LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id);
  45. if (exefs == nullptr)
  46. return exefs;
  47. const auto installed = Service::FileSystem::GetUnionContents();
  48. // Game Updates
  49. const auto update_tid = GetUpdateTitleID(title_id);
  50. const auto update = installed->GetEntry(update_tid, ContentRecordType::Program);
  51. if (update != nullptr) {
  52. if (update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
  53. update->GetExeFS() != nullptr) {
  54. LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
  55. FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
  56. exefs = update->GetExeFS();
  57. }
  58. }
  59. return exefs;
  60. }
  61. static std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
  62. const std::string& build_id) {
  63. std::vector<VirtualFile> out;
  64. out.reserve(patch_dirs.size());
  65. for (const auto& subdir : patch_dirs) {
  66. auto exefs_dir = subdir->GetSubdirectory("exefs");
  67. if (exefs_dir != nullptr) {
  68. for (const auto& file : exefs_dir->GetFiles()) {
  69. if (file->GetExtension() == "ips") {
  70. auto name = file->GetName();
  71. const auto p1 = name.substr(0, name.find('.'));
  72. const auto this_build_id = p1.substr(0, p1.find_last_not_of('0') + 1);
  73. if (build_id == this_build_id)
  74. out.push_back(file);
  75. } else if (file->GetExtension() == "pchtxt") {
  76. IPSwitchCompiler compiler{file};
  77. if (!compiler.IsValid())
  78. continue;
  79. auto this_build_id = Common::HexArrayToString(compiler.GetBuildID());
  80. this_build_id =
  81. this_build_id.substr(0, this_build_id.find_last_not_of('0') + 1);
  82. if (build_id == this_build_id)
  83. out.push_back(file);
  84. }
  85. }
  86. }
  87. }
  88. return out;
  89. }
  90. std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
  91. if (nso.size() < 0x100)
  92. return nso;
  93. NSOBuildHeader header;
  94. std::memcpy(&header, nso.data(), sizeof(NSOBuildHeader));
  95. if (header.magic != Common::MakeMagic('N', 'S', 'O', '0'))
  96. return nso;
  97. const auto build_id_raw = Common::HexArrayToString(header.build_id);
  98. const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
  99. LOG_INFO(Loader, "Patching NSO for build_id={}", build_id);
  100. const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
  101. auto patch_dirs = load_dir->GetSubdirectories();
  102. std::sort(patch_dirs.begin(), patch_dirs.end(),
  103. [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
  104. const auto patches = CollectPatches(patch_dirs, build_id);
  105. auto out = nso;
  106. for (const auto& patch_file : patches) {
  107. if (patch_file->GetExtension() == "ips") {
  108. LOG_INFO(Loader, " - Applying IPS patch from mod \"{}\"",
  109. patch_file->GetContainingDirectory()->GetParentDirectory()->GetName());
  110. const auto patched = PatchIPS(std::make_shared<VectorVfsFile>(out), patch_file);
  111. if (patched != nullptr)
  112. out = patched->ReadAllBytes();
  113. } else if (patch_file->GetExtension() == "pchtxt") {
  114. LOG_INFO(Loader, " - Applying IPSwitch patch from mod \"{}\"",
  115. patch_file->GetContainingDirectory()->GetParentDirectory()->GetName());
  116. const IPSwitchCompiler compiler{patch_file};
  117. const auto patched = compiler.Apply(std::make_shared<VectorVfsFile>(out));
  118. if (patched != nullptr)
  119. out = patched->ReadAllBytes();
  120. }
  121. }
  122. if (out.size() < 0x100)
  123. return nso;
  124. std::memcpy(out.data(), &header, sizeof(NSOBuildHeader));
  125. return out;
  126. }
  127. bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
  128. const auto build_id_raw = Common::HexArrayToString(build_id_);
  129. const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
  130. LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id);
  131. const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
  132. auto patch_dirs = load_dir->GetSubdirectories();
  133. std::sort(patch_dirs.begin(), patch_dirs.end(),
  134. [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
  135. return !CollectPatches(patch_dirs, build_id).empty();
  136. }
  137. static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
  138. const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
  139. if ((type != ContentRecordType::Program && type != ContentRecordType::Data) ||
  140. load_dir == nullptr || load_dir->GetSize() <= 0) {
  141. return;
  142. }
  143. auto extracted = ExtractRomFS(romfs);
  144. if (extracted == nullptr) {
  145. return;
  146. }
  147. auto patch_dirs = load_dir->GetSubdirectories();
  148. std::sort(patch_dirs.begin(), patch_dirs.end(),
  149. [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
  150. std::vector<VirtualDir> layers;
  151. std::vector<VirtualDir> layers_ext;
  152. layers.reserve(patch_dirs.size() + 1);
  153. layers_ext.reserve(patch_dirs.size() + 1);
  154. for (const auto& subdir : patch_dirs) {
  155. auto romfs_dir = subdir->GetSubdirectory("romfs");
  156. if (romfs_dir != nullptr)
  157. layers.push_back(std::move(romfs_dir));
  158. auto ext_dir = subdir->GetSubdirectory("romfs_ext");
  159. if (ext_dir != nullptr)
  160. layers_ext.push_back(std::move(ext_dir));
  161. }
  162. layers.push_back(std::move(extracted));
  163. auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers));
  164. if (layered == nullptr) {
  165. return;
  166. }
  167. auto layered_ext = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers_ext));
  168. auto packed = CreateRomFS(std::move(layered), std::move(layered_ext));
  169. if (packed == nullptr) {
  170. return;
  171. }
  172. LOG_INFO(Loader, " RomFS: LayeredFS patches applied successfully");
  173. romfs = std::move(packed);
  174. }
  175. VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type,
  176. VirtualFile update_raw) const {
  177. const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}",
  178. title_id, static_cast<u8>(type))
  179. .c_str();
  180. if (type == ContentRecordType::Program || type == ContentRecordType::Data)
  181. LOG_INFO(Loader, log_string);
  182. else
  183. LOG_DEBUG(Loader, log_string);
  184. if (romfs == nullptr)
  185. return romfs;
  186. const auto installed = Service::FileSystem::GetUnionContents();
  187. // Game Updates
  188. const auto update_tid = GetUpdateTitleID(title_id);
  189. const auto update = installed->GetEntryRaw(update_tid, type);
  190. if (update != nullptr) {
  191. const auto new_nca = std::make_shared<NCA>(update, romfs, ivfc_offset);
  192. if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
  193. new_nca->GetRomFS() != nullptr) {
  194. LOG_INFO(Loader, " RomFS: Update ({}) applied successfully",
  195. FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
  196. romfs = new_nca->GetRomFS();
  197. }
  198. } else if (update_raw != nullptr) {
  199. const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset);
  200. if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
  201. new_nca->GetRomFS() != nullptr) {
  202. LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully");
  203. romfs = new_nca->GetRomFS();
  204. }
  205. }
  206. // LayeredFS
  207. ApplyLayeredFS(romfs, title_id, type);
  208. return romfs;
  209. }
  210. static void AppendCommaIfNotEmpty(std::string& to, const std::string& with) {
  211. if (to.empty())
  212. to += with;
  213. else
  214. to += ", " + with;
  215. }
  216. static bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
  217. return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
  218. }
  219. std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames(
  220. VirtualFile update_raw) const {
  221. std::map<std::string, std::string, std::less<>> out;
  222. const auto installed = Service::FileSystem::GetUnionContents();
  223. // Game Updates
  224. const auto update_tid = GetUpdateTitleID(title_id);
  225. PatchManager update{update_tid};
  226. auto [nacp, discard_icon_file] = update.GetControlMetadata();
  227. if (nacp != nullptr) {
  228. out.insert_or_assign("Update", nacp->GetVersionString());
  229. } else {
  230. if (installed->HasEntry(update_tid, ContentRecordType::Program)) {
  231. const auto meta_ver = installed->GetEntryVersion(update_tid);
  232. if (meta_ver == boost::none || meta_ver.get() == 0) {
  233. out.insert_or_assign("Update", "");
  234. } else {
  235. out.insert_or_assign(
  236. "Update",
  237. FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements));
  238. }
  239. } else if (update_raw != nullptr) {
  240. out.insert_or_assign("Update", "PACKED");
  241. }
  242. }
  243. // General Mods (LayeredFS and IPS)
  244. const auto mod_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
  245. if (mod_dir != nullptr && mod_dir->GetSize() > 0) {
  246. for (const auto& mod : mod_dir->GetSubdirectories()) {
  247. std::string types;
  248. const auto exefs_dir = mod->GetSubdirectory("exefs");
  249. if (IsDirValidAndNonEmpty(exefs_dir)) {
  250. bool ips = false;
  251. bool ipswitch = false;
  252. for (const auto& file : exefs_dir->GetFiles()) {
  253. if (file->GetExtension() == "ips")
  254. ips = true;
  255. else if (file->GetExtension() == "pchtxt")
  256. ipswitch = true;
  257. }
  258. if (ips)
  259. AppendCommaIfNotEmpty(types, "IPS");
  260. if (ipswitch)
  261. AppendCommaIfNotEmpty(types, "IPSwitch");
  262. }
  263. if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs")))
  264. AppendCommaIfNotEmpty(types, "LayeredFS");
  265. if (types.empty())
  266. continue;
  267. out.insert_or_assign(mod->GetName(), types);
  268. }
  269. }
  270. // DLC
  271. const auto dlc_entries = installed->ListEntriesFilter(TitleType::AOC, ContentRecordType::Data);
  272. std::vector<RegisteredCacheEntry> dlc_match;
  273. dlc_match.reserve(dlc_entries.size());
  274. std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
  275. [this, &installed](const RegisteredCacheEntry& entry) {
  276. return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == title_id &&
  277. installed->GetEntry(entry)->GetStatus() ==
  278. Loader::ResultStatus::Success;
  279. });
  280. if (!dlc_match.empty()) {
  281. // Ensure sorted so DLC IDs show in order.
  282. std::sort(dlc_match.begin(), dlc_match.end());
  283. std::string list;
  284. for (size_t i = 0; i < dlc_match.size() - 1; ++i)
  285. list += fmt::format("{}, ", dlc_match[i].title_id & 0x7FF);
  286. list += fmt::format("{}", dlc_match.back().title_id & 0x7FF);
  287. out.insert_or_assign("DLC", std::move(list));
  288. }
  289. return out;
  290. }
  291. std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
  292. const auto installed{Service::FileSystem::GetUnionContents()};
  293. const auto base_control_nca = installed->GetEntry(title_id, ContentRecordType::Control);
  294. if (base_control_nca == nullptr)
  295. return {};
  296. return ParseControlNCA(*base_control_nca);
  297. }
  298. std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(const NCA& nca) const {
  299. const auto base_romfs = nca.GetRomFS();
  300. if (base_romfs == nullptr)
  301. return {};
  302. const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control);
  303. if (romfs == nullptr)
  304. return {};
  305. const auto extracted = ExtractRomFS(romfs);
  306. if (extracted == nullptr)
  307. return {};
  308. auto nacp_file = extracted->GetFile("control.nacp");
  309. if (nacp_file == nullptr)
  310. nacp_file = extracted->GetFile("Control.nacp");
  311. auto nacp = nacp_file == nullptr ? nullptr : std::make_unique<NACP>(nacp_file);
  312. VirtualFile icon_file;
  313. for (const auto& language : FileSys::LANGUAGE_NAMES) {
  314. icon_file = extracted->GetFile("icon_" + std::string(language) + ".dat");
  315. if (icon_file != nullptr)
  316. break;
  317. }
  318. return {std::move(nacp), icon_file};
  319. }
  320. } // namespace FileSys