RiivolutionPatcher.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  1. // Copyright 2021 Dolphin Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include "DiscIO/RiivolutionPatcher.h"
  4. #include <algorithm>
  5. #include <locale>
  6. #include <string>
  7. #include <string_view>
  8. #include <vector>
  9. #include <fmt/format.h>
  10. #include "Common/FileUtil.h"
  11. #include "Common/IOFile.h"
  12. #include "Common/StringUtil.h"
  13. #include "Core/AchievementManager.h"
  14. #include "Core/Core.h"
  15. #include "Core/HLE/HLE.h"
  16. #include "Core/HW/Memmap.h"
  17. #include "Core/IOS/FS/FileSystem.h"
  18. #include "Core/PowerPC/MMU.h"
  19. #include "Core/System.h"
  20. #include "DiscIO/DirectoryBlob.h"
  21. #include "DiscIO/RiivolutionParser.h"
  22. namespace DiscIO::Riivolution
  23. {
  24. FileDataLoader::~FileDataLoader() = default;
  25. FileDataLoaderHostFS::FileDataLoaderHostFS(std::string sd_root, const std::string& xml_path,
  26. std::string_view patch_root)
  27. : m_sd_root(std::move(sd_root))
  28. {
  29. // Riivolution treats 'external' file paths as follows:
  30. // - If it starts with a '/', it's an absolute path, ie. relative to the SD card root.
  31. // - Otherwise:
  32. // - If the 'root' parameter of the current patch is not set or is empty, the path is relative
  33. // to the folder the XML file is in.
  34. // - If the 'root' parameter of the current patch starts with a '/', the path is relative to
  35. // that folder on the SD card, starting at the SD card root.
  36. // - If the 'root' parameter of the current patch starts without a '/', the path is relative to
  37. // that folder on the SD card, starting at the folder the XML file is in.
  38. // The following initialization should properly replicate this behavior.
  39. // First set m_patch_root to the folder the parsed XML file is in.
  40. SplitPath(xml_path, &m_patch_root, nullptr, nullptr);
  41. // Then try to resolve the given patch_root as if it was a file path, and on success replace the
  42. // m_patch_root with it.
  43. if (!patch_root.empty())
  44. {
  45. auto r = MakeAbsoluteFromRelative(patch_root);
  46. if (r)
  47. m_patch_root = std::move(*r);
  48. }
  49. }
  50. std::optional<std::string>
  51. FileDataLoaderHostFS::MakeAbsoluteFromRelative(std::string_view external_relative_path)
  52. {
  53. #ifdef _WIN32
  54. // Riivolution treats a backslash as just a standard filename character, but we can't replicate
  55. // this properly on Windows. So if a file contains a backslash, immediately error out.
  56. if (external_relative_path.find("\\") != std::string_view::npos)
  57. return std::nullopt;
  58. #endif
  59. const std::string& root = external_relative_path.starts_with('/') ? m_sd_root : m_patch_root;
  60. std::string result = root;
  61. std::string_view work = external_relative_path;
  62. // Strip away all leading and trailing path separators.
  63. while (work.starts_with('/'))
  64. work.remove_prefix(1);
  65. while (work.ends_with('/'))
  66. work.remove_suffix(1);
  67. size_t depth = 0;
  68. while (true)
  69. {
  70. if (work.empty())
  71. break;
  72. // Extract a single path element.
  73. size_t separator_position = work.find('/');
  74. std::string_view element = work.substr(0, separator_position);
  75. if (element == ".")
  76. {
  77. // This is a harmless element, doesn't change any state.
  78. }
  79. else if (element == "..")
  80. {
  81. // We're going up a level.
  82. // If this isn't possible someone is trying to exit the root directory, prevent that.
  83. if (depth == 0)
  84. return std::nullopt;
  85. --depth;
  86. // Remove the last path element from the result string.
  87. // This must have been previously attached in the branch below (otherwise depth would have
  88. // been 0), so there's no need to check whether the string is empty or anything like that.
  89. while (result.back() != '/')
  90. result.pop_back();
  91. result.pop_back();
  92. }
  93. else if (std::ranges::all_of(element, [](char c) { return c == '.'; }))
  94. {
  95. // This is a triple, quadruple, etc. dot.
  96. // Some file systems treat this as several 'up' path traversals, but Riivolution does not.
  97. // If someone tries this just error out, it wouldn't work sensibly in Riivolution anyway.
  98. return std::nullopt;
  99. }
  100. else
  101. {
  102. // We're going down a level.
  103. ++depth;
  104. // Append path element to result string.
  105. result += '/';
  106. result += element;
  107. // Riivolution assumes a case-insensitive file system, which means it's possible that an XML
  108. // file references a 'file.bin' but the actual file is named 'File.bin' or 'FILE.BIN'. To
  109. // preserve this behavior, we modify the file path to match any existing file in the file
  110. // system, if one exists.
  111. if (!::File::Exists(result))
  112. {
  113. // Drop path element again so we can search in the directory.
  114. result.erase(result.size() - element.size(), element.size());
  115. // Re-attach an element that actually matches the capitalization in the host filesystem.
  116. auto possible_files = ::File::ScanDirectoryTree(result, false);
  117. bool found = false;
  118. for (auto& f : possible_files.children)
  119. {
  120. if (Common::CaseInsensitiveEquals(element, f.virtualName))
  121. {
  122. result += f.virtualName;
  123. found = true;
  124. break;
  125. }
  126. }
  127. // If there isn't any file that matches just use the given element.
  128. if (!found)
  129. result += element;
  130. }
  131. }
  132. // If this was the last path element, we're done.
  133. if (separator_position == std::string_view::npos)
  134. break;
  135. // Remove element from work string.
  136. work = work.substr(separator_position + 1);
  137. // Remove any potential extra path separators.
  138. while (work.starts_with('/'))
  139. work = work.substr(1);
  140. }
  141. return result;
  142. }
  143. std::optional<u64>
  144. FileDataLoaderHostFS::GetExternalFileSize(std::string_view external_relative_path)
  145. {
  146. auto path = MakeAbsoluteFromRelative(external_relative_path);
  147. if (!path)
  148. return std::nullopt;
  149. ::File::FileInfo f(*path);
  150. if (!f.IsFile())
  151. return std::nullopt;
  152. return f.GetSize();
  153. }
  154. std::vector<u8> FileDataLoaderHostFS::GetFileContents(std::string_view external_relative_path)
  155. {
  156. auto path = MakeAbsoluteFromRelative(external_relative_path);
  157. if (!path)
  158. return {};
  159. ::File::IOFile f(*path, "rb");
  160. if (!f)
  161. return {};
  162. const u64 length = f.GetSize();
  163. std::vector<u8> value;
  164. value.resize(length);
  165. if (!f.ReadBytes(value.data(), length))
  166. return {};
  167. return value;
  168. }
  169. std::vector<FileDataLoader::Node>
  170. FileDataLoaderHostFS::GetFolderContents(std::string_view external_relative_path)
  171. {
  172. auto path = MakeAbsoluteFromRelative(external_relative_path);
  173. if (!path)
  174. return {};
  175. ::File::FSTEntry external_files = ::File::ScanDirectoryTree(*path, false);
  176. std::vector<FileDataLoader::Node> nodes;
  177. nodes.reserve(external_files.children.size());
  178. for (auto& file : external_files.children)
  179. nodes.emplace_back(FileDataLoader::Node{std::move(file.virtualName), file.isDirectory});
  180. return nodes;
  181. }
  182. BuilderContentSource
  183. FileDataLoaderHostFS::MakeContentSource(std::string_view external_relative_path,
  184. u64 external_offset, u64 external_size, u64 disc_offset)
  185. {
  186. auto path = MakeAbsoluteFromRelative(external_relative_path);
  187. if (!path)
  188. return BuilderContentSource{disc_offset, external_size, ContentFixedByte{}};
  189. return BuilderContentSource{disc_offset, external_size,
  190. ContentFile{std::move(*path), external_offset}};
  191. }
  192. std::optional<std::string>
  193. FileDataLoaderHostFS::ResolveSavegameRedirectPath(std::string_view external_relative_path)
  194. {
  195. return MakeAbsoluteFromRelative(external_relative_path);
  196. }
  197. // 'before' and 'after' should be two copies of the same source
  198. // 'split_at' needs to be between the start and end of the source, may not match either boundary
  199. static void SplitAt(BuilderContentSource* before, BuilderContentSource* after, u64 split_at)
  200. {
  201. const u64 start = before->m_offset;
  202. const u64 size = before->m_size;
  203. const u64 end = start + size;
  204. // The source before the split point just needs its length reduced.
  205. before->m_size = split_at - start;
  206. // The source after the split needs its length reduced and its start point adjusted.
  207. after->m_offset += before->m_size;
  208. after->m_size = end - split_at;
  209. if (std::holds_alternative<ContentFile>(after->m_source))
  210. {
  211. std::get<ContentFile>(after->m_source).m_offset += before->m_size;
  212. }
  213. else if (std::holds_alternative<ContentMemory>(after->m_source))
  214. {
  215. after->m_source = std::make_shared<std::vector<u8>>(
  216. std::get<ContentMemory>(after->m_source)->begin() + before->m_size,
  217. std::get<ContentMemory>(after->m_source)->end());
  218. }
  219. else if (std::holds_alternative<ContentPartition>(after->m_source))
  220. {
  221. std::get<ContentPartition>(after->m_source).m_offset += before->m_size;
  222. }
  223. else if (std::holds_alternative<ContentVolume>(after->m_source))
  224. {
  225. std::get<ContentVolume>(after->m_source).m_offset += before->m_size;
  226. }
  227. }
  228. static void ApplyPatchToFile(const Patch& patch, FSTBuilderNode* file_node,
  229. std::string_view external_filename, u64 file_patch_offset,
  230. u64 raw_external_file_offset, u64 file_patch_length, bool resize)
  231. {
  232. const auto f = patch.m_file_data_loader->GetExternalFileSize(external_filename);
  233. if (!f)
  234. return;
  235. auto& content = std::get<std::vector<BuilderContentSource>>(file_node->m_content);
  236. const u64 raw_external_filesize = *f;
  237. const u64 external_file_offset = std::min(raw_external_file_offset, raw_external_filesize);
  238. const u64 external_filesize = raw_external_filesize - external_file_offset;
  239. const u64 patch_start = file_patch_offset;
  240. const u64 patch_size = file_patch_length == 0 ? external_filesize : file_patch_length;
  241. const u64 patch_end = patch_start + patch_size;
  242. const u64 target_filesize = resize ? patch_end : std::max(file_node->m_size, patch_end);
  243. size_t insert_where = 0;
  244. if (patch_start >= file_node->m_size)
  245. {
  246. // If the patch is at or past the end of the existing file no existing content needs to be
  247. // touched, just extend the file.
  248. if (patch_start > file_node->m_size)
  249. {
  250. // Insert an padding area between the old file and the patch data.
  251. content.emplace_back(BuilderContentSource{file_node->m_size, patch_start - file_node->m_size,
  252. ContentFixedByte{}});
  253. }
  254. insert_where = content.size();
  255. }
  256. else
  257. {
  258. // Patch is at the start or somewhere in the middle of the existing file. At least one source
  259. // needs to be modified or removed, and a new source with the patch data inserted instead.
  260. // To make this easier, we first split up existing sources at the patch start and patch end
  261. // offsets, then discard all overlapping sources and insert the patch sources there.
  262. for (size_t i = 0; i < content.size(); ++i)
  263. {
  264. const u64 source_start = content[i].m_offset;
  265. const u64 source_end = source_start + content[i].m_size;
  266. if (patch_start > source_start && patch_start < source_end)
  267. {
  268. content.insert(content.begin() + i + 1, content[i]);
  269. SplitAt(&content[i], &content[i + 1], patch_start);
  270. continue;
  271. }
  272. if (patch_end > source_start && patch_end < source_end)
  273. {
  274. content.insert(content.begin() + i + 1, content[i]);
  275. SplitAt(&content[i], &content[i + 1], patch_end);
  276. }
  277. }
  278. // Now discard the overlapping areas and remember where they were so we can insert there.
  279. for (size_t i = 0; i < content.size(); ++i)
  280. {
  281. if (patch_start == content[i].m_offset)
  282. {
  283. insert_where = i;
  284. while (i < content.size() && patch_end >= content[i].m_offset + content[i].m_size)
  285. ++i;
  286. content.erase(content.begin() + insert_where, content.begin() + i);
  287. break;
  288. }
  289. }
  290. }
  291. // Insert the actual patch data.
  292. if (patch_size > 0 && external_filesize > 0)
  293. {
  294. BuilderContentSource source = patch.m_file_data_loader->MakeContentSource(
  295. external_filename, external_file_offset, std::min(patch_size, external_filesize),
  296. patch_start);
  297. content.emplace(content.begin() + insert_where, std::move(source));
  298. ++insert_where;
  299. }
  300. // Pad with zeroes if the patch file is smaller than the patch size.
  301. if (external_filesize < patch_size)
  302. {
  303. BuilderContentSource padding{patch_start + external_filesize, patch_size - external_filesize,
  304. ContentFixedByte{}};
  305. content.emplace(content.begin() + insert_where, std::move(padding));
  306. }
  307. // Update the filesize of the file.
  308. file_node->m_size = target_filesize;
  309. // Drop any source past the new end of the file -- this can happen on file truncation.
  310. while (!content.empty() && content.back().m_offset >= target_filesize)
  311. content.pop_back();
  312. }
  313. static void ApplyPatchToFile(const Patch& patch, const File& file_patch, FSTBuilderNode* file_node)
  314. {
  315. // The last two bits of the offset seem to be ignored by actual Riivolution.
  316. ApplyPatchToFile(patch, file_node, file_patch.m_external, file_patch.m_offset & ~u64(3),
  317. file_patch.m_fileoffset, file_patch.m_length, file_patch.m_resize);
  318. }
  319. static FSTBuilderNode* FindFileNodeInFST(std::string_view path, std::vector<FSTBuilderNode>* fst,
  320. bool create_if_not_exists)
  321. {
  322. const size_t path_separator = path.find('/');
  323. const bool is_file = path_separator == std::string_view::npos;
  324. const std::string_view name = is_file ? path : path.substr(0, path_separator);
  325. const auto it = std::ranges::find_if(*fst, [&](const FSTBuilderNode& node) {
  326. return Common::CaseInsensitiveEquals(node.m_filename, name);
  327. });
  328. if (it == fst->end())
  329. {
  330. if (!create_if_not_exists)
  331. return nullptr;
  332. if (is_file)
  333. {
  334. return &fst->emplace_back(
  335. FSTBuilderNode{std::string(name), 0, std::vector<BuilderContentSource>()});
  336. }
  337. auto& new_folder =
  338. fst->emplace_back(FSTBuilderNode{std::string(name), 0, std::vector<FSTBuilderNode>()});
  339. return FindFileNodeInFST(path.substr(path_separator + 1),
  340. &std::get<std::vector<FSTBuilderNode>>(new_folder.m_content), true);
  341. }
  342. const bool is_existing_node_file = it->IsFile();
  343. if (is_file != is_existing_node_file)
  344. return nullptr;
  345. if (is_file)
  346. return &*it;
  347. return FindFileNodeInFST(path.substr(path_separator + 1),
  348. &std::get<std::vector<FSTBuilderNode>>(it->m_content),
  349. create_if_not_exists);
  350. }
  351. static FSTBuilderNode* FindFilenameNodeInFST(std::string_view filename,
  352. std::vector<FSTBuilderNode>& fst)
  353. {
  354. for (FSTBuilderNode& node : fst)
  355. {
  356. if (node.IsFolder())
  357. {
  358. FSTBuilderNode* result = FindFilenameNodeInFST(filename, node.GetFolderContent());
  359. if (result)
  360. return result;
  361. }
  362. else if (Common::CaseInsensitiveEquals(node.m_filename, filename))
  363. {
  364. return &node;
  365. }
  366. }
  367. return nullptr;
  368. }
  369. static void ApplyFilePatchToFST(const Patch& patch, const File& file,
  370. std::vector<FSTBuilderNode>* fst, FSTBuilderNode* dol_node)
  371. {
  372. if (!file.m_disc.empty() && file.m_disc[0] == '/')
  373. {
  374. // If the disc path starts with a / then we should patch that specific disc path.
  375. FSTBuilderNode* node =
  376. FindFileNodeInFST(std::string_view(file.m_disc).substr(1), fst, file.m_create);
  377. if (node)
  378. ApplyPatchToFile(patch, file, node);
  379. }
  380. else if (dol_node && Common::CaseInsensitiveEquals(file.m_disc, "main.dol"))
  381. {
  382. // Special case: If the filename is "main.dol", we want to patch the main executable.
  383. ApplyPatchToFile(patch, file, dol_node);
  384. }
  385. else
  386. {
  387. // Otherwise we want to patch the first file in the FST that matches that filename.
  388. FSTBuilderNode* node = FindFilenameNodeInFST(file.m_disc, *fst);
  389. if (node)
  390. ApplyPatchToFile(patch, file, node);
  391. }
  392. }
  393. static void ApplyFolderPatchToFST(const Patch& patch, const Folder& folder,
  394. std::vector<FSTBuilderNode>* fst, FSTBuilderNode* dol_node,
  395. std::string_view disc_path, std::string_view external_path)
  396. {
  397. const auto external_files = patch.m_file_data_loader->GetFolderContents(external_path);
  398. for (const auto& child : external_files)
  399. {
  400. const auto combine_paths = [](std::string_view a, std::string_view b) {
  401. if (a.empty())
  402. return std::string(b);
  403. if (b.empty())
  404. return std::string(a);
  405. if (a.ends_with('/'))
  406. a.remove_suffix(1);
  407. if (b.starts_with('/'))
  408. b.remove_prefix(1);
  409. return fmt::format("{}/{}", a, b);
  410. };
  411. std::string child_disc_path = combine_paths(disc_path, child.m_filename);
  412. std::string child_external_path = combine_paths(external_path, child.m_filename);
  413. if (child.m_is_directory)
  414. {
  415. if (folder.m_recursive)
  416. ApplyFolderPatchToFST(patch, folder, fst, dol_node, child_disc_path, child_external_path);
  417. }
  418. else
  419. {
  420. File file;
  421. file.m_disc = std::move(child_disc_path);
  422. file.m_external = std::move(child_external_path);
  423. file.m_resize = folder.m_resize;
  424. file.m_create = folder.m_create;
  425. file.m_length = folder.m_length;
  426. ApplyFilePatchToFST(patch, file, fst, dol_node);
  427. }
  428. }
  429. }
  430. static void ApplyFolderPatchToFST(const Patch& patch, const Folder& folder,
  431. std::vector<FSTBuilderNode>* fst, FSTBuilderNode* dol_node)
  432. {
  433. ApplyFolderPatchToFST(patch, folder, fst, dol_node, folder.m_disc, folder.m_external);
  434. }
  435. void ApplyPatchesToFiles(std::span<const Patch> patches, PatchIndex index,
  436. std::vector<FSTBuilderNode>* fst, FSTBuilderNode* dol_node)
  437. {
  438. for (const auto& patch : patches)
  439. {
  440. const auto& file_patches =
  441. index == PatchIndex::DolphinSysFiles ? patch.m_sys_file_patches : patch.m_file_patches;
  442. const auto& folder_patches =
  443. index == PatchIndex::DolphinSysFiles ? patch.m_sys_folder_patches : patch.m_folder_patches;
  444. for (const auto& file : file_patches)
  445. ApplyFilePatchToFST(patch, file, fst, dol_node);
  446. for (const auto& folder : folder_patches)
  447. ApplyFolderPatchToFST(patch, folder, fst, dol_node);
  448. }
  449. }
  450. static bool MemoryMatchesAt(const Core::CPUThreadGuard& guard, u32 offset,
  451. std::span<const u8> value)
  452. {
  453. for (u32 i = 0; i < value.size(); ++i)
  454. {
  455. auto result = PowerPC::MMU::HostTryReadU8(guard, offset + i);
  456. if (!result || result->value != value[i])
  457. return false;
  458. }
  459. return true;
  460. }
  461. static void ApplyMemoryPatch(const Core::CPUThreadGuard& guard, u32 offset,
  462. std::span<const u8> value, std::span<const u8> original)
  463. {
  464. if (AchievementManager::GetInstance().IsHardcoreModeActive())
  465. return;
  466. if (value.empty())
  467. return;
  468. if (!original.empty() && !MemoryMatchesAt(guard, offset, original))
  469. return;
  470. auto& system = guard.GetSystem();
  471. const u32 size = static_cast<u32>(value.size());
  472. for (u32 i = 0; i < size; ++i)
  473. PowerPC::MMU::HostTryWriteU8(guard, value[i], offset + i);
  474. const u32 overlapping_hook_count = HLE::UnpatchRange(system, offset, offset + size);
  475. if (overlapping_hook_count != 0)
  476. {
  477. WARN_LOG_FMT(OSHLE, "Riivolution memory patch overlaps {} HLE hook(s) at {:08x} (size: {})",
  478. overlapping_hook_count, offset, value.size());
  479. }
  480. }
  481. static std::vector<u8> GetMemoryPatchValue(const Patch& patch, const Memory& memory_patch)
  482. {
  483. if (!memory_patch.m_valuefile.empty())
  484. return patch.m_file_data_loader->GetFileContents(memory_patch.m_valuefile);
  485. return memory_patch.m_value;
  486. }
  487. static void ApplyMemoryPatch(const Core::CPUThreadGuard& guard, const Patch& patch,
  488. const Memory& memory_patch)
  489. {
  490. if (memory_patch.m_offset == 0)
  491. return;
  492. ApplyMemoryPatch(guard, memory_patch.m_offset | 0x80000000,
  493. GetMemoryPatchValue(patch, memory_patch), memory_patch.m_original);
  494. }
  495. static void ApplySearchMemoryPatch(const Core::CPUThreadGuard& guard, const Patch& patch,
  496. const Memory& memory_patch, u32 ram_start, u32 length)
  497. {
  498. if (memory_patch.m_original.empty() || memory_patch.m_align == 0)
  499. return;
  500. const u32 stride = memory_patch.m_align;
  501. for (u32 i = 0; i < length - (stride - 1); i += stride)
  502. {
  503. const u32 address = ram_start + i;
  504. if (MemoryMatchesAt(guard, address, memory_patch.m_original))
  505. {
  506. ApplyMemoryPatch(guard, address, GetMemoryPatchValue(patch, memory_patch), {});
  507. break;
  508. }
  509. }
  510. }
  511. static void ApplyOcarinaMemoryPatch(const Core::CPUThreadGuard& guard, const Patch& patch,
  512. const Memory& memory_patch, u32 ram_start, u32 length)
  513. {
  514. if (memory_patch.m_offset == 0)
  515. return;
  516. const std::vector<u8> value = GetMemoryPatchValue(patch, memory_patch);
  517. if (value.empty())
  518. return;
  519. auto& system = guard.GetSystem();
  520. for (u32 i = 0; i < length; i += 4)
  521. {
  522. // first find the pattern
  523. const u32 address = ram_start + i;
  524. if (MemoryMatchesAt(guard, address, value))
  525. {
  526. for (; i < length; i += 4)
  527. {
  528. // from the pattern find the next blr instruction
  529. const u32 blr_address = ram_start + i;
  530. auto blr = PowerPC::MMU::HostTryReadU32(guard, blr_address);
  531. if (blr && blr->value == 0x4e800020)
  532. {
  533. // and replace it with a jump to the given offset
  534. const u32 target = memory_patch.m_offset | 0x80000000;
  535. const u32 jmp = ((target - blr_address) & 0x03fffffc) | 0x48000000;
  536. PowerPC::MMU::HostTryWriteU32(guard, jmp, blr_address);
  537. const u32 overlapping_hook_count =
  538. HLE::UnpatchRange(system, blr_address, blr_address + 4);
  539. if (overlapping_hook_count != 0)
  540. {
  541. WARN_LOG_FMT(OSHLE, "Riivolution ocarina patch overlaps HLE hook at {}", blr_address);
  542. }
  543. return;
  544. }
  545. }
  546. return;
  547. }
  548. }
  549. }
  550. void ApplyGeneralMemoryPatches(const Core::CPUThreadGuard& guard, std::span<const Patch> patches)
  551. {
  552. const auto& system = guard.GetSystem();
  553. const auto& system_memory = system.GetMemory();
  554. for (const auto& patch : patches)
  555. {
  556. for (const auto& memory : patch.m_memory_patches)
  557. {
  558. if (memory.m_ocarina)
  559. continue;
  560. if (memory.m_search)
  561. ApplySearchMemoryPatch(guard, patch, memory, 0x80000000, system_memory.GetRamSize());
  562. else
  563. ApplyMemoryPatch(guard, patch, memory);
  564. }
  565. }
  566. }
  567. void ApplyApploaderMemoryPatches(const Core::CPUThreadGuard& guard, std::span<const Patch> patches,
  568. u32 ram_address, u32 ram_length)
  569. {
  570. for (const auto& patch : patches)
  571. {
  572. for (const auto& memory : patch.m_memory_patches)
  573. {
  574. if (!memory.m_ocarina && !memory.m_search)
  575. continue;
  576. if (memory.m_ocarina)
  577. ApplyOcarinaMemoryPatch(guard, patch, memory, ram_address, ram_length);
  578. else
  579. ApplySearchMemoryPatch(guard, patch, memory, ram_address, ram_length);
  580. }
  581. }
  582. }
  583. std::optional<SavegameRedirect> ExtractSavegameRedirect(std::span<const Patch> riivolution_patches)
  584. {
  585. for (const auto& patch : riivolution_patches)
  586. {
  587. if (!patch.m_savegame_patches.empty())
  588. {
  589. const auto& save_patch = patch.m_savegame_patches[0];
  590. auto resolved = patch.m_file_data_loader->ResolveSavegameRedirectPath(save_patch.m_external);
  591. if (resolved)
  592. return SavegameRedirect{std::move(*resolved), save_patch.m_clone};
  593. return std::nullopt;
  594. }
  595. }
  596. return std::nullopt;
  597. }
  598. } // namespace DiscIO::Riivolution