VolumeVerifier.cpp 52 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481
  1. // Copyright 2019 Dolphin Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include "DiscIO/VolumeVerifier.h"
  4. #include <algorithm>
  5. #include <future>
  6. #include <limits>
  7. #include <memory>
  8. #include <optional>
  9. #include <string>
  10. #include <string_view>
  11. #include <unordered_set>
  12. #include <mbedtls/md5.h>
  13. #include <mz.h>
  14. #include <mz_strm.h>
  15. #include <mz_zip.h>
  16. #include <mz_zip_rw.h>
  17. #include <pugixml.hpp>
  18. #include "Common/Align.h"
  19. #include "Common/Assert.h"
  20. #include "Common/CPUDetect.h"
  21. #include "Common/CommonPaths.h"
  22. #include "Common/CommonTypes.h"
  23. #include "Common/Contains.h"
  24. #include "Common/Crypto/SHA1.h"
  25. #include "Common/FileUtil.h"
  26. #include "Common/Hash.h"
  27. #include "Common/HttpRequest.h"
  28. #include "Common/IOFile.h"
  29. #include "Common/Logging/Log.h"
  30. #include "Common/MinizipUtil.h"
  31. #include "Common/MsgHandler.h"
  32. #include "Common/ScopeGuard.h"
  33. #include "Common/StringUtil.h"
  34. #include "Common/Swap.h"
  35. #include "Common/Version.h"
  36. #include "Core/IOS/Device.h"
  37. #include "Core/IOS/ES/ES.h"
  38. #include "Core/IOS/ES/Formats.h"
  39. #include "Core/IOS/IOS.h"
  40. #include "Core/IOS/IOSC.h"
  41. #include "DiscIO/Blob.h"
  42. #include "DiscIO/DiscScrubber.h"
  43. #include "DiscIO/DiscUtils.h"
  44. #include "DiscIO/Enums.h"
  45. #include "DiscIO/Filesystem.h"
  46. #include "DiscIO/Volume.h"
  47. #include "DiscIO/VolumeWii.h"
  48. namespace DiscIO
  49. {
  50. RedumpVerifier::DownloadState RedumpVerifier::m_gc_download_state;
  51. RedumpVerifier::DownloadState RedumpVerifier::m_wii_download_state;
  52. void RedumpVerifier::Start(const Volume& volume)
  53. {
  54. if (!volume.IsDatelDisc())
  55. {
  56. m_game_id = volume.GetGameID();
  57. if (m_game_id.size() > 4)
  58. m_game_id = m_game_id.substr(0, 4);
  59. }
  60. m_revision = volume.GetRevision().value_or(0);
  61. m_disc_number = volume.GetDiscNumber().value_or(0);
  62. m_size = volume.GetDataSize();
  63. const Platform platform = volume.GetVolumeType();
  64. m_future = std::async(std::launch::async, [this, platform]() -> std::vector<PotentialMatch> {
  65. std::string system;
  66. DownloadState* download_state;
  67. switch (platform)
  68. {
  69. case Platform::GameCubeDisc:
  70. system = "gc";
  71. download_state = &m_gc_download_state;
  72. break;
  73. case Platform::WiiDisc:
  74. system = "wii";
  75. download_state = &m_wii_download_state;
  76. break;
  77. default:
  78. m_result.status = Status::Error;
  79. return {};
  80. }
  81. {
  82. std::lock_guard lk(download_state->mutex);
  83. download_state->status = DownloadDatfile(system, download_state->status);
  84. }
  85. switch (download_state->status)
  86. {
  87. case DownloadStatus::FailButOldCacheAvailable:
  88. ERROR_LOG_FMT(DISCIO, "Failed to fetch data from Redump.org, using old cached data instead");
  89. [[fallthrough]];
  90. case DownloadStatus::Success:
  91. return ScanDatfile(ReadDatfile(system), system);
  92. case DownloadStatus::SystemNotAvailable:
  93. m_result = {Status::Error, Common::GetStringT("Wii data is not public yet")};
  94. return {};
  95. case DownloadStatus::Fail:
  96. default:
  97. m_result = {Status::Error, Common::GetStringT("Failed to connect to Redump.org")};
  98. return {};
  99. }
  100. });
  101. }
  102. static std::string GetPathForSystem(const std::string& system)
  103. {
  104. return File::GetUserPath(D_REDUMPCACHE_IDX) + DIR_SEP + system + ".zip";
  105. }
  106. RedumpVerifier::DownloadStatus RedumpVerifier::DownloadDatfile(const std::string& system,
  107. DownloadStatus old_status)
  108. {
  109. if (old_status == DownloadStatus::Success || old_status == DownloadStatus::SystemNotAvailable)
  110. return old_status;
  111. Common::HttpRequest request;
  112. const std::optional<std::vector<u8>> result =
  113. request.Get("http://redump.org/datfile/" + system + "/serial,version",
  114. {{"User-Agent", Common::GetScmRevStr()}});
  115. const std::string output_path = GetPathForSystem(system);
  116. if (!result)
  117. {
  118. return File::Exists(output_path) ? DownloadStatus::FailButOldCacheAvailable :
  119. DownloadStatus::Fail;
  120. }
  121. if (result->size() > 1 && (*result)[0] == '<' && (*result)[1] == '!')
  122. {
  123. // This is an HTML page, not a zip file like we want
  124. if (File::Exists(output_path))
  125. return DownloadStatus::FailButOldCacheAvailable;
  126. const bool system_not_available_match =
  127. Common::ContainsSubrange(*result, "System \"" + system + "\" doesn't exist.");
  128. return system_not_available_match ? DownloadStatus::SystemNotAvailable : DownloadStatus::Fail;
  129. }
  130. File::CreateFullPath(output_path);
  131. if (!File::IOFile(output_path, "wb").WriteBytes(result->data(), result->size()))
  132. ERROR_LOG_FMT(DISCIO, "Failed to write downloaded datfile to {}", output_path);
  133. return DownloadStatus::Success;
  134. }
  135. std::vector<u8> RedumpVerifier::ReadDatfile(const std::string& system)
  136. {
  137. void* zip_reader = mz_zip_reader_create();
  138. if (!zip_reader)
  139. return {};
  140. Common::ScopeGuard file_guard{[&] { mz_zip_reader_delete(&zip_reader); }};
  141. if (mz_zip_reader_open_file(zip_reader, GetPathForSystem(system).c_str()) != MZ_OK)
  142. return {};
  143. // Check that the zip file contains exactly one file
  144. if (mz_zip_reader_goto_first_entry(zip_reader) != MZ_OK)
  145. return {};
  146. if (mz_zip_reader_goto_next_entry(zip_reader) != MZ_END_OF_LIST)
  147. return {};
  148. // Read the file
  149. if (mz_zip_reader_goto_first_entry(zip_reader) != MZ_OK)
  150. return {};
  151. mz_zip_file* file_info;
  152. mz_zip_reader_entry_get_info(zip_reader, &file_info);
  153. std::vector<u8> data(file_info->uncompressed_size);
  154. if (!Common::ReadFileFromZip(zip_reader, data.data(), file_info->uncompressed_size))
  155. return {};
  156. return data;
  157. }
  158. static u8 ParseHexDigit(char c)
  159. {
  160. if (c < '0')
  161. return 0; // Error
  162. if (c >= 'a')
  163. c -= 'a' - 'A';
  164. if (c >= 'A')
  165. c -= 'A' - ('9' + 1);
  166. c -= '0';
  167. if (c >= 0x10)
  168. return 0; // Error
  169. return c;
  170. }
  171. static std::vector<u8> ParseHash(const char* str)
  172. {
  173. std::vector<u8> hash;
  174. while (str[0] && str[1])
  175. {
  176. hash.push_back(static_cast<u8>(ParseHexDigit(str[0]) * 0x10 + ParseHexDigit(str[1])));
  177. str += 2;
  178. }
  179. return hash;
  180. }
  181. std::vector<RedumpVerifier::PotentialMatch> RedumpVerifier::ScanDatfile(const std::vector<u8>& data,
  182. const std::string& system)
  183. {
  184. pugi::xml_document doc;
  185. if (!doc.load_buffer(data.data(), data.size()))
  186. {
  187. m_result = {Status::Error, Common::GetStringT("Failed to parse Redump.org data")};
  188. return {};
  189. }
  190. std::vector<PotentialMatch> potential_matches;
  191. bool serials_exist = false;
  192. bool versions_exist = false;
  193. const pugi::xml_node datafile = doc.child("datafile");
  194. for (const pugi::xml_node game : datafile.children("game"))
  195. {
  196. std::string version_string = game.child("version").text().as_string();
  197. if (!version_string.empty())
  198. versions_exist = true;
  199. // Strip out prefix (e.g. "v1.02" -> "02", "Rev 2" -> "2")
  200. const size_t last_non_numeric = version_string.find_last_not_of("0123456789");
  201. if (last_non_numeric != std::string::npos)
  202. version_string = version_string.substr(last_non_numeric + 1);
  203. const int version = version_string.empty() ? 0 : std::stoi(version_string);
  204. const std::string serials = game.child("serial").text().as_string();
  205. if (!serials.empty())
  206. serials_exist = true;
  207. // The revisions for Korean GameCube games whose four-char game IDs end in E are numbered from
  208. // 0x30 in ring codes and in disc headers, but Redump switched to numbering them from 0 in 2019.
  209. if (version % 0x30 != m_revision % 0x30)
  210. continue;
  211. if (serials.empty() || serials.starts_with("DS"))
  212. {
  213. // GC Datel discs have no serials in Redump, Wii Datel discs have serials like "DS000101"
  214. if (!m_game_id.empty())
  215. continue; // Non-empty m_game_id means we're verifying a non-Datel disc
  216. }
  217. else
  218. {
  219. bool serial_match_found = false;
  220. // If a disc has multiple possible serials, they are delimited with ", ". We want to loop
  221. // through all the serials until we find a match, because even though they normally only
  222. // differ in the region code at the end (which we don't care about), there is an edge case
  223. // disc with the game ID "G96P" and the serial "DL-DOL-D96P-EUR, DL-DOL-G96P-EUR".
  224. for (const std::string& serial_str : SplitString(serials, ','))
  225. {
  226. const std::string_view serial = StripWhitespace(serial_str);
  227. // Skip the prefix, normally either "DL-DOL-" or "RVL-" (depending on the console),
  228. // but there are some exceptions like the "RVLE-SBSE-USA-B0" serial.
  229. const size_t first_dash = serial.find_first_of('-', 3);
  230. const size_t game_id_start =
  231. first_dash == std::string::npos ? std::string::npos : first_dash + 1;
  232. if (game_id_start == std::string::npos || serial.size() < game_id_start + 4)
  233. {
  234. ERROR_LOG_FMT(DISCIO, "Invalid serial in redump datfile: {}", serial_str);
  235. continue;
  236. }
  237. const std::string_view game_id = serial.substr(game_id_start, 4);
  238. if (game_id != m_game_id)
  239. continue;
  240. u8 disc_number = 0;
  241. if (serial.size() > game_id_start + 5 && serial[game_id_start + 5] >= '0' &&
  242. serial[game_id_start + 5] <= '9')
  243. {
  244. disc_number = serial[game_id_start + 5] - '0';
  245. }
  246. if (disc_number != m_disc_number)
  247. continue;
  248. serial_match_found = true;
  249. break;
  250. }
  251. if (!serial_match_found)
  252. continue;
  253. }
  254. PotentialMatch& potential_match = potential_matches.emplace_back();
  255. const pugi::xml_node rom = game.child("rom");
  256. potential_match.size = rom.attribute("size").as_ullong();
  257. potential_match.hashes.crc32 = ParseHash(rom.attribute("crc").value());
  258. potential_match.hashes.md5 = ParseHash(rom.attribute("md5").value());
  259. potential_match.hashes.sha1 = ParseHash(rom.attribute("sha1").value());
  260. }
  261. if (!serials_exist || !versions_exist)
  262. {
  263. // If we reach this, the user has most likely downloaded a datfile manually,
  264. // so show a panic alert rather than just using ERROR_LOG
  265. // i18n: "Serial" refers to serial numbers, e.g. RVL-RSBE-USA
  266. PanicAlertFmtT(
  267. "Serial and/or version data is missing from {0}\n"
  268. "Please append \"{1}\" (without the quotes) to the datfile URL when downloading\n"
  269. "Example: {2}",
  270. GetPathForSystem(system), "serial,version", "http://redump.org/datfile/gc/serial,version");
  271. m_result = {Status::Error, Common::GetStringT("Failed to parse Redump.org data")};
  272. return {};
  273. }
  274. return potential_matches;
  275. }
  276. static bool HashesMatch(const std::vector<u8>& calculated, const std::vector<u8>& expected)
  277. {
  278. return calculated.empty() || calculated == expected;
  279. }
  280. RedumpVerifier::Result RedumpVerifier::Finish(const Hashes<std::vector<u8>>& hashes)
  281. {
  282. if (m_result.status == Status::Error)
  283. return m_result;
  284. if (hashes.crc32.empty() && hashes.md5.empty() && hashes.sha1.empty())
  285. return m_result;
  286. const std::vector<PotentialMatch> potential_matches = m_future.get();
  287. for (PotentialMatch p : potential_matches)
  288. {
  289. if (HashesMatch(hashes.crc32, p.hashes.crc32) && HashesMatch(hashes.md5, p.hashes.md5) &&
  290. HashesMatch(hashes.sha1, p.hashes.sha1) && m_size == p.size)
  291. {
  292. return {Status::GoodDump, Common::GetStringT("Good dump")};
  293. }
  294. }
  295. // We only return bad dump if there's a disc that we know this dump should match but that doesn't
  296. // match. For disc without IDs (i.e. Datel discs), we don't have a good way of knowing whether we
  297. // have a bad dump or just a dump that isn't in Redump, so we always pick unknown instead of bad
  298. // dump for those to be on the safe side. (Besides, it's possible to dump a Datel disc correctly
  299. // and have it not match Redump if you don't use the same replacement value for bad sectors.)
  300. if (!potential_matches.empty() && !m_game_id.empty())
  301. return {Status::BadDump, Common::GetStringT("Bad dump")};
  302. return {Status::Unknown, Common::GetStringT("Unknown disc")};
  303. }
  304. constexpr u64 DEFAULT_READ_SIZE = 0x20000; // Arbitrary value
  305. VolumeVerifier::VolumeVerifier(const Volume& volume, bool redump_verification,
  306. Hashes<bool> hashes_to_calculate)
  307. : m_volume(volume), m_redump_verification(redump_verification),
  308. m_hashes_to_calculate(hashes_to_calculate),
  309. m_calculating_any_hash(hashes_to_calculate.crc32 || hashes_to_calculate.md5 ||
  310. hashes_to_calculate.sha1),
  311. m_max_progress(volume.GetDataSize()), m_data_size_type(volume.GetDataSizeType())
  312. {
  313. if (!m_calculating_any_hash)
  314. m_redump_verification = false;
  315. }
  316. VolumeVerifier::~VolumeVerifier()
  317. {
  318. WaitForAsyncOperations();
  319. }
  320. Hashes<bool> VolumeVerifier::GetDefaultHashesToCalculate()
  321. {
  322. Hashes<bool> hashes_to_calculate{.crc32 = true, .md5 = true, .sha1 = true};
  323. // If the system can compute certain hashes faster than others, only default-enable the fast ones.
  324. const bool sha1_hw_accel = Common::SHA1::CreateContext()->HwAccelerated();
  325. // For crc32, we assume zlib-ng will be fast if cpu supports crc32
  326. const bool crc32_hw_accel = cpu_info.bCRC32;
  327. if (crc32_hw_accel || sha1_hw_accel)
  328. {
  329. hashes_to_calculate.crc32 = crc32_hw_accel;
  330. // md5 has no accelerated implementation at the moment, always default to off
  331. hashes_to_calculate.md5 = false;
  332. // Always enable SHA1, to avoid situation where only crc32 is computed
  333. hashes_to_calculate.sha1 = true;
  334. }
  335. return hashes_to_calculate;
  336. }
  337. void VolumeVerifier::Start()
  338. {
  339. ASSERT(!m_started);
  340. m_started = true;
  341. if (m_redump_verification)
  342. m_redump_verifier.Start(m_volume);
  343. m_is_tgc = m_volume.GetBlobType() == BlobType::TGC;
  344. m_is_datel = m_volume.IsDatelDisc();
  345. m_is_triforce = m_volume.GetVolumeType() == Platform::Triforce;
  346. m_is_not_retail = (m_volume.GetVolumeType() == Platform::WiiDisc && !m_volume.HasWiiHashes()) ||
  347. IsDebugSigned();
  348. const std::vector<Partition> partitions = CheckPartitions();
  349. if (IsDisc(m_volume.GetVolumeType()))
  350. m_biggest_referenced_offset = GetBiggestReferencedOffset(m_volume, partitions);
  351. CheckMisc();
  352. SetUpHashing();
  353. }
  354. std::vector<Partition> VolumeVerifier::CheckPartitions()
  355. {
  356. if (m_volume.GetVolumeType() == Platform::WiiWAD)
  357. return {};
  358. const std::vector<Partition> partitions = m_volume.GetPartitions();
  359. if (partitions.empty())
  360. {
  361. if (!m_volume.GetFileSystem(m_volume.GetGamePartition()))
  362. {
  363. AddProblem(Severity::High,
  364. Common::GetStringT("The filesystem is invalid or could not be read."));
  365. return {};
  366. }
  367. return {m_volume.GetGamePartition()};
  368. }
  369. std::optional<u32> partitions_in_first_table = m_volume.ReadSwapped<u32>(0x40000, PARTITION_NONE);
  370. if (partitions_in_first_table && *partitions_in_first_table > 8)
  371. {
  372. // Not sure if 8 actually is the limit, but there certainly aren't any discs
  373. // released that have as many partitions as 8 in the first partition table.
  374. // The only game that has that many partitions in total is Super Smash Bros. Brawl,
  375. // and that game places all partitions other than UPDATE and DATA in the second table.
  376. AddProblem(Severity::Low,
  377. Common::GetStringT("There are too many partitions in the first partition table."));
  378. }
  379. std::vector<u32> types;
  380. for (const Partition& partition : partitions)
  381. {
  382. const std::optional<u32> type = m_volume.GetPartitionType(partition);
  383. if (type)
  384. types.emplace_back(*type);
  385. }
  386. if (!Common::Contains(types, PARTITION_UPDATE))
  387. AddProblem(Severity::Low, Common::GetStringT("The update partition is missing."));
  388. const bool has_data_partition = Common::Contains(types, PARTITION_DATA);
  389. if (!m_is_datel && !has_data_partition)
  390. AddProblem(Severity::High, Common::GetStringT("The data partition is missing."));
  391. const bool has_channel_partition = Common::Contains(types, PARTITION_CHANNEL);
  392. if (ShouldHaveChannelPartition() && !has_channel_partition)
  393. AddProblem(Severity::Medium, Common::GetStringT("The channel partition is missing."));
  394. const bool has_install_partition = Common::Contains(types, PARTITION_INSTALL);
  395. if (ShouldHaveInstallPartition() && !has_install_partition)
  396. AddProblem(Severity::High, Common::GetStringT("The install partition is missing."));
  397. if (ShouldHaveMasterpiecePartitions() &&
  398. types.cend() == std::ranges::find_if(types, [](u32 type) { return type >= 0xFF; }))
  399. {
  400. // i18n: This string is referring to a game mode in Super Smash Bros. Brawl called Masterpieces
  401. // where you play demos of NES/SNES/N64 games. Official translations:
  402. // 名作トライアル (Japanese), Masterpieces (English), Meisterstücke (German), Chefs-d'œuvre
  403. // (French), Clásicos (Spanish), Capolavori (Italian), 클래식 게임 체험판 (Korean).
  404. // If your language is not one of the languages above, consider leaving the string untranslated
  405. // so that people will recognize it as the name of the game mode.
  406. AddProblem(Severity::Medium, Common::GetStringT("The Masterpiece partitions are missing."));
  407. }
  408. for (const Partition& partition : partitions)
  409. {
  410. if (m_volume.GetPartitionType(partition) == PARTITION_UPDATE && partition.offset != 0x50000)
  411. {
  412. AddProblem(Severity::Low,
  413. Common::GetStringT("The update partition is not at its normal position."));
  414. }
  415. const u64 normal_data_offset = m_volume.HasWiiHashes() ? 0xF800000 : 0x838000;
  416. if (m_volume.GetPartitionType(partition) == PARTITION_DATA &&
  417. partition.offset != normal_data_offset && !has_channel_partition && !has_install_partition)
  418. {
  419. AddProblem(Severity::Low,
  420. Common::GetStringT(
  421. "The data partition is not at its normal position. This will affect the "
  422. "emulated loading times. You will be unable to share input recordings and use "
  423. "NetPlay with anyone who is using a good dump."));
  424. }
  425. }
  426. std::vector<Partition> valid_partitions;
  427. for (const Partition& partition : partitions)
  428. {
  429. if (CheckPartition(partition))
  430. valid_partitions.push_back(partition);
  431. }
  432. return valid_partitions;
  433. }
  434. bool VolumeVerifier::CheckPartition(const Partition& partition)
  435. {
  436. std::optional<u32> type = m_volume.GetPartitionType(partition);
  437. if (!type)
  438. {
  439. // Not sure if this can happen in practice
  440. AddProblem(Severity::Medium, Common::GetStringT("The type of a partition could not be read."));
  441. return false;
  442. }
  443. Severity severity = Severity::Medium;
  444. if (*type == PARTITION_DATA || *type == PARTITION_INSTALL)
  445. severity = Severity::High;
  446. else if (*type == PARTITION_UPDATE)
  447. severity = Severity::Low;
  448. const std::string name = GetPartitionName(type);
  449. if (partition.offset % VolumeWii::BLOCK_TOTAL_SIZE != 0 ||
  450. m_volume.PartitionOffsetToRawOffset(0, partition) % VolumeWii::BLOCK_TOTAL_SIZE != 0)
  451. {
  452. AddProblem(Severity::Medium,
  453. Common::FmtFormatT("The {0} partition is not properly aligned.", name));
  454. }
  455. bool invalid_header = false;
  456. bool blank_contents = false;
  457. std::vector<u8> disc_header(0x80);
  458. if (!m_volume.Read(0, disc_header.size(), disc_header.data(), partition))
  459. {
  460. invalid_header = true;
  461. }
  462. else if (Common::swap32(disc_header.data() + 0x18) != WII_DISC_MAGIC)
  463. {
  464. for (size_t i = 0; i < disc_header.size(); i += 4)
  465. {
  466. if (Common::swap32(disc_header.data() + i) != i)
  467. {
  468. invalid_header = true;
  469. break;
  470. }
  471. }
  472. // The loop above ends without setting invalid_header for discs that legitimately lack
  473. // updates. No such discs have been released to end users. Most such discs are debug signed,
  474. // but there is apparently at least one that is retail signed, the Movie-Ch Install Disc.
  475. if (!invalid_header)
  476. blank_contents = true;
  477. }
  478. if (invalid_header)
  479. {
  480. // This can happen when certain programs that create WBFS files scrub the entirety of
  481. // the Masterpiece partitions in Super Smash Bros. Brawl without removing them from
  482. // the partition table. https://bugs.dolphin-emu.org/issues/8733
  483. AddProblem(severity,
  484. Common::FmtFormatT("The {0} partition does not seem to contain valid data.", name));
  485. return false;
  486. }
  487. if (!m_is_datel)
  488. {
  489. const auto console_type =
  490. IsDebugSigned() ? IOS::HLE::IOSC::ConsoleType::RVT : IOS::HLE::IOSC::ConsoleType::Retail;
  491. IOS::HLE::Kernel ios(console_type);
  492. auto& es = ios.GetESCore();
  493. const std::vector<u8>& cert_chain = m_volume.GetCertificateChain(partition);
  494. if (IOS::HLE::IPC_SUCCESS !=
  495. es.VerifyContainer(IOS::HLE::ESCore::VerifyContainerType::Ticket,
  496. IOS::HLE::ESCore::VerifyMode::DoNotUpdateCertStore,
  497. m_volume.GetTicket(partition), cert_chain) ||
  498. IOS::HLE::IPC_SUCCESS !=
  499. es.VerifyContainer(IOS::HLE::ESCore::VerifyContainerType::TMD,
  500. IOS::HLE::ESCore::VerifyMode::DoNotUpdateCertStore,
  501. m_volume.GetTMD(partition), cert_chain))
  502. {
  503. AddProblem(Severity::Low,
  504. Common::FmtFormatT("The {0} partition is not correctly signed.", name));
  505. }
  506. }
  507. if (m_volume.HasWiiHashes() && !m_volume.CheckH3TableIntegrity(partition))
  508. {
  509. AddProblem(Severity::Low,
  510. Common::FmtFormatT("The H3 hash table for the {0} partition is not correct.", name));
  511. }
  512. // Prepare for hash verification in the Process step
  513. if (m_volume.HasWiiHashes())
  514. {
  515. const u64 data_size =
  516. m_volume.ReadSwappedAndShifted(partition.offset + 0x2bc, PARTITION_NONE).value_or(0);
  517. const size_t blocks = static_cast<size_t>(data_size / VolumeWii::BLOCK_TOTAL_SIZE);
  518. if (data_size % VolumeWii::BLOCK_TOTAL_SIZE != 0)
  519. {
  520. std::string text = Common::FmtFormatT(
  521. "The data size for the {0} partition is not evenly divisible by the block size.", name);
  522. AddProblem(Severity::Low, std::move(text));
  523. }
  524. u64 offset = m_volume.PartitionOffsetToRawOffset(0, partition);
  525. for (size_t block_index = 0; block_index < blocks;
  526. block_index += VolumeWii::BLOCKS_PER_GROUP, offset += VolumeWii::GROUP_TOTAL_SIZE)
  527. {
  528. m_groups.emplace_back(
  529. GroupToVerify{partition, offset, block_index,
  530. std::min(block_index + VolumeWii::BLOCKS_PER_GROUP, blocks)});
  531. }
  532. m_block_errors.emplace(partition, 0);
  533. }
  534. if (blank_contents)
  535. return false;
  536. const FileSystem* filesystem = m_volume.GetFileSystem(partition);
  537. if (!filesystem)
  538. {
  539. if (m_is_datel)
  540. {
  541. // Datel's Wii Freeloader has an invalid FST in its only partition
  542. return true;
  543. }
  544. AddProblem(severity,
  545. Common::FmtFormatT("The {0} partition does not have a valid file system.", name));
  546. return false;
  547. }
  548. if (type == PARTITION_UPDATE)
  549. {
  550. const IOS::ES::TMDReader& tmd = m_volume.GetTMD(m_volume.GetGamePartition());
  551. // IOS9 is the only IOS which can be assumed to exist in a working state on any Wii
  552. // regardless of what updates have been installed. At least Mario Party 8
  553. // (RM8E01, revision 2) uses IOS9 without having it in its update partition.
  554. const u64 ios_ver = tmd.GetIOSId() & 0xFF;
  555. bool has_correct_ios = tmd.IsValid() && ios_ver == 9;
  556. if (!has_correct_ios && tmd.IsValid())
  557. {
  558. std::unique_ptr<FileInfo> file_info = filesystem->FindFileInfo("_sys");
  559. if (file_info)
  560. {
  561. const std::string ios_ver_str = std::to_string(ios_ver);
  562. const std::string correct_ios =
  563. IsDebugSigned() ? ("firmware.64." + ios_ver_str + ".") : ("ios" + ios_ver_str + "-");
  564. for (const FileInfo& f : *file_info)
  565. {
  566. std::string file_name = f.GetName();
  567. Common::ToLower(&file_name);
  568. if (file_name.starts_with(correct_ios))
  569. {
  570. has_correct_ios = true;
  571. break;
  572. }
  573. }
  574. }
  575. }
  576. if (!has_correct_ios)
  577. {
  578. // This is reached for hacked dumps where the update partition has been replaced with
  579. // a very old update partition so that no updates will be installed.
  580. AddProblem(
  581. Severity::Low,
  582. Common::GetStringT("The update partition does not contain the IOS used by this title."));
  583. }
  584. }
  585. return true;
  586. }
  587. std::string VolumeVerifier::GetPartitionName(std::optional<u32> type) const
  588. {
  589. if (!type)
  590. return "???";
  591. std::string name = NameForPartitionType(*type, false);
  592. if (ShouldHaveMasterpiecePartitions() && *type > 0xFF)
  593. {
  594. // i18n: This string is referring to a game mode in Super Smash Bros. Brawl called Masterpieces
  595. // where you play demos of NES/SNES/N64 games. This string is referring to a specific such demo
  596. // rather than the game mode as a whole, so please use the singular form. Official translations:
  597. // 名作トライアル (Japanese), Masterpieces (English), Meisterstücke (German), Chefs-d'œuvre
  598. // (French), Clásicos (Spanish), Capolavori (Italian), 클래식 게임 체험판 (Korean).
  599. // If your language is not one of the languages above, consider leaving the string untranslated
  600. // so that people will recognize it as the name of the game mode.
  601. return Common::FmtFormatT("{0} (Masterpiece)", name);
  602. }
  603. return name;
  604. }
  605. bool VolumeVerifier::IsDebugSigned() const
  606. {
  607. const IOS::ES::TicketReader& ticket = m_volume.GetTicket(m_volume.GetGamePartition());
  608. return ticket.IsValid() ? ticket.GetConsoleType() == IOS::HLE::IOSC::ConsoleType::RVT : false;
  609. }
  610. bool VolumeVerifier::ShouldHaveChannelPartition() const
  611. {
  612. static constexpr std::array<std::string_view, 18> channel_discs = {
  613. "RFNE01", "RFNJ01", "RFNK01", "RFNP01", "RFNW01", "RFPE01", "RFPJ01", "RFPK01", "RFPP01",
  614. "RFPW01", "RGWE41", "RGWJ41", "RGWP41", "RGWX41", "RMCE01", "RMCJ01", "RMCK01", "RMCP01",
  615. };
  616. static_assert(std::ranges::is_sorted(channel_discs));
  617. return std::ranges::binary_search(channel_discs, m_volume.GetGameID());
  618. }
  619. bool VolumeVerifier::ShouldHaveInstallPartition() const
  620. {
  621. static constexpr std::array<std::string_view, 4> dragon_quest_x = {"S4MJGD", "S4SJGD", "S6TJGD",
  622. "SDQJGD"};
  623. const std::string& game_id = m_volume.GetGameID();
  624. return std::ranges::any_of(dragon_quest_x,
  625. [&game_id](std::string_view x) { return x == game_id; });
  626. }
  627. bool VolumeVerifier::ShouldHaveMasterpiecePartitions() const
  628. {
  629. static constexpr std::array<std::string_view, 4> ssbb = {"RSBE01", "RSBJ01", "RSBK01", "RSBP01"};
  630. const std::string& game_id = m_volume.GetGameID();
  631. return std::ranges::any_of(ssbb, [&game_id](std::string_view x) { return x == game_id; });
  632. }
  633. bool VolumeVerifier::ShouldBeDualLayer() const
  634. {
  635. // The Japanese versions of Xenoblade and The Last Story are single-layer
  636. // (unlike the other versions) and must not be added to this list.
  637. static constexpr std::array<std::string_view, 33> dual_layer_discs = {
  638. "R3ME01", "R3MP01", "R3OE01", "R3OJ01", "R3OP01", "RSBE01", "RSBJ01", "RSBK01", "RSBP01",
  639. "RXMJ8P", "S59E01", "S59JC8", "S59P01", "S5QJC8", "SAKENS", "SAKPNS", "SK8V52", "SK8X52",
  640. "SLSEXJ", "SLSP01", "SQIE4Q", "SQIP4Q", "SQIY4Q", "SR5E41", "SR5P41", "SUOE41", "SUOP41",
  641. "SVXX52", "SVXY52", "SX4E01", "SX4P01", "SZ3EGT", "SZ3PGT",
  642. };
  643. static_assert(std::ranges::is_sorted(dual_layer_discs));
  644. return std::ranges::binary_search(dual_layer_discs, m_volume.GetGameID());
  645. }
  646. void VolumeVerifier::CheckVolumeSize()
  647. {
  648. u64 volume_size = m_volume.GetDataSize();
  649. const bool is_disc = IsDisc(m_volume.GetVolumeType());
  650. const bool should_be_dual_layer = is_disc && ShouldBeDualLayer();
  651. bool volume_size_roughly_known = m_data_size_type != DataSizeType::UpperBound;
  652. if (should_be_dual_layer && m_biggest_referenced_offset <= SL_DVD_R_SIZE)
  653. {
  654. AddProblem(Severity::Medium,
  655. Common::GetStringT(
  656. "This game has been hacked to fit on a single-layer DVD. Some content such as "
  657. "pre-rendered videos, extra languages or entire game modes will be broken. "
  658. "This problem generally only exists in illegal copies of games."));
  659. }
  660. if (m_data_size_type != DataSizeType::Accurate)
  661. {
  662. AddProblem(Severity::Low,
  663. Common::GetStringT("The format that the disc image is saved in does not "
  664. "store the size of the disc image."));
  665. if (!volume_size_roughly_known && m_volume.HasWiiHashes())
  666. {
  667. volume_size = m_biggest_verified_offset;
  668. volume_size_roughly_known = true;
  669. }
  670. }
  671. if (m_content_index != m_content_offsets.size() || m_group_index != m_groups.size() ||
  672. (!m_is_datel && volume_size_roughly_known && m_biggest_referenced_offset > volume_size))
  673. {
  674. const bool second_layer_missing = is_disc && volume_size_roughly_known &&
  675. volume_size >= SL_DVD_SIZE && volume_size <= SL_DVD_R_SIZE;
  676. std::string text =
  677. second_layer_missing ?
  678. Common::GetStringT("This disc image is too small and lacks some data. The problem is "
  679. "most likely that this is a dual-layer disc that has been dumped "
  680. "as a single-layer disc.") :
  681. Common::GetStringT("This disc image is too small and lacks some data. If your "
  682. "dumping program saved the disc image as several parts, you need "
  683. "to merge them into one file.");
  684. AddProblem(Severity::High, std::move(text));
  685. return;
  686. }
  687. // The reason why this condition is checking for m_data_size_type != UpperBound instead of
  688. // m_data_size_type == Accurate is because we want to show the warning about input recordings and
  689. // NetPlay for NFS disc images (which are the only disc images that have it set to LowerBound).
  690. if (is_disc && m_data_size_type != DataSizeType::UpperBound && !m_is_tgc)
  691. {
  692. const Platform platform = m_volume.GetVolumeType();
  693. const bool should_be_gc_size = platform == Platform::GameCubeDisc || m_is_datel;
  694. const bool valid_gamecube = volume_size == MINI_DVD_SIZE;
  695. const bool valid_retail_wii = volume_size == SL_DVD_SIZE || volume_size == DL_DVD_SIZE;
  696. const bool valid_debug_wii = volume_size == SL_DVD_R_SIZE || volume_size == DL_DVD_R_SIZE;
  697. const bool debug = IsDebugSigned();
  698. if ((should_be_gc_size && !valid_gamecube) ||
  699. (!should_be_gc_size && (debug ? !valid_debug_wii : !valid_retail_wii)))
  700. {
  701. if (debug && valid_retail_wii)
  702. {
  703. AddProblem(
  704. Severity::Low,
  705. Common::GetStringT("This debug disc image has the size of a retail disc image."));
  706. }
  707. else
  708. {
  709. u64 normal_size;
  710. if (should_be_gc_size)
  711. normal_size = MINI_DVD_SIZE;
  712. else if (!should_be_dual_layer)
  713. normal_size = SL_DVD_SIZE;
  714. else
  715. normal_size = DL_DVD_SIZE;
  716. if (volume_size < normal_size)
  717. {
  718. AddProblem(
  719. Severity::Low,
  720. Common::GetStringT(
  721. "This disc image has an unusual size. This will likely make the emulated "
  722. "loading times longer. You will likely be unable to share input recordings "
  723. "and use NetPlay with anyone who is using a good dump."));
  724. }
  725. else
  726. {
  727. AddProblem(Severity::Low, Common::GetStringT("This disc image has an unusual size."));
  728. }
  729. }
  730. }
  731. }
  732. }
  733. void VolumeVerifier::CheckMisc()
  734. {
  735. const std::string game_id_unencrypted = m_volume.GetGameID(PARTITION_NONE);
  736. const std::string game_id_encrypted = m_volume.GetGameID(m_volume.GetGamePartition());
  737. if (game_id_unencrypted != game_id_encrypted)
  738. {
  739. bool inconsistent_game_id = true;
  740. if (game_id_encrypted == "RELSAB")
  741. {
  742. if (game_id_unencrypted.starts_with("410"))
  743. {
  744. // This is the Wii Backup Disc (aka "pinkfish" disc),
  745. // which legitimately has an inconsistent game ID.
  746. inconsistent_game_id = false;
  747. }
  748. else if (game_id_unencrypted.starts_with("010"))
  749. {
  750. // Hacked version of the Wii Backup Disc (aka "pinkfish" disc).
  751. std::string proper_game_id = game_id_unencrypted;
  752. proper_game_id[0] = '4';
  753. AddProblem(Severity::Low, Common::FmtFormatT("The game ID is {0} but should be {1}.",
  754. game_id_unencrypted, proper_game_id));
  755. inconsistent_game_id = false;
  756. }
  757. }
  758. if (inconsistent_game_id)
  759. {
  760. AddProblem(Severity::Low, Common::GetStringT("The game ID is inconsistent."));
  761. }
  762. }
  763. const Region region = m_volume.GetRegion();
  764. constexpr std::string_view GAMECUBE_PLACEHOLDER_ID = "RELSAB";
  765. constexpr std::string_view WII_PLACEHOLDER_ID = "RABAZZ";
  766. if (game_id_encrypted.size() < 4)
  767. {
  768. AddProblem(Severity::Low, Common::GetStringT("The game ID is unusually short."));
  769. }
  770. else if (!m_is_datel && game_id_encrypted != GAMECUBE_PLACEHOLDER_ID &&
  771. game_id_encrypted != WII_PLACEHOLDER_ID)
  772. {
  773. char country_code;
  774. if (IsDisc(m_volume.GetVolumeType()))
  775. country_code = game_id_encrypted[3];
  776. else
  777. country_code = static_cast<char>(m_volume.GetTitleID().value_or(0) & 0xff);
  778. const Platform platform = m_volume.GetVolumeType();
  779. const std::optional<u16> revision = m_volume.GetRevision();
  780. if (CountryCodeToRegion(country_code, platform, region, revision) != region)
  781. {
  782. AddProblem(Severity::Medium,
  783. Common::GetStringT(
  784. "The region code does not match the game ID. If this is because the "
  785. "region code has been modified, the game might run at the wrong speed, "
  786. "graphical elements might be offset, or the game might not run at all."));
  787. }
  788. }
  789. const IOS::ES::TMDReader& tmd = m_volume.GetTMD(m_volume.GetGamePartition());
  790. if (tmd.IsValid())
  791. {
  792. const u64 ios_id = tmd.GetIOSId() & 0xFF;
  793. // List of launch day Korean IOSes obtained from https://hackmii.com/2008/09/korean-wii/.
  794. // More IOSes were released later that were used in Korean games, but they're all over 40.
  795. // Also, old IOSes like IOS36 did eventually get released for Korean Wiis as part of system
  796. // updates, but there are likely no Korean games using them since those IOSes were old by then.
  797. if (region == Region::NTSC_K && ios_id < 40 && ios_id != 4 && ios_id != 9 && ios_id != 21 &&
  798. ios_id != 37)
  799. {
  800. // This is intended to catch Korean games (usually but not always pirated) that have the IOS
  801. // slot set to 36 as a side effect of having to fakesign after changing the common key slot to
  802. // 0. (IOS36 was the last IOS with the Trucha bug.) https://bugs.dolphin-emu.org/issues/10319
  803. AddProblem(
  804. Severity::High,
  805. // i18n: You may want to leave the term "ERROR #002" untranslated,
  806. // since the emulated software always displays it in English.
  807. Common::GetStringT("This Korean title is set to use an IOS that typically isn't used on "
  808. "Korean consoles. This is likely to lead to ERROR #002."));
  809. }
  810. if (ios_id >= 0x80)
  811. {
  812. // This is intended to catch the same kind of fakesigned Korean games,
  813. // but this time with the IOS slot set to cIOS instead of IOS36.
  814. AddProblem(Severity::High, Common::GetStringT("This title is set to use an invalid IOS."));
  815. }
  816. }
  817. m_ticket = m_volume.GetTicket(m_volume.GetGamePartition());
  818. if (m_ticket.IsValid())
  819. {
  820. const u8 specified_common_key_index = m_ticket.GetCommonKeyIndex();
  821. // Wii discs only use common key 0 (regular) and common key 1 (Korean), not common key 2 (vWii).
  822. if (m_volume.GetVolumeType() == Platform::WiiDisc && specified_common_key_index > 1)
  823. {
  824. AddProblem(Severity::High,
  825. // i18n: This is "common" as in "shared", not the opposite of "uncommon"
  826. Common::GetStringT("This title is set to use an invalid common key."));
  827. }
  828. if (m_volume.GetVolumeType() == Platform::WiiWAD)
  829. {
  830. m_ticket = m_volume.GetTicketWithFixedCommonKey();
  831. const u8 fixed_common_key_index = m_ticket.GetCommonKeyIndex();
  832. if (specified_common_key_index != fixed_common_key_index)
  833. {
  834. // Many fakesigned WADs have the common key index set to a (random?) bogus value.
  835. // For WADs, Dolphin will detect this and use the correct key, making this low severity.
  836. AddProblem(Severity::Low,
  837. // i18n: This is "common" as in "shared", not the opposite of "uncommon"
  838. Common::FmtFormatT("The specified common key index is {0} but should be {1}.",
  839. specified_common_key_index, fixed_common_key_index));
  840. }
  841. }
  842. }
  843. if (m_volume.GetVolumeType() == Platform::WiiWAD)
  844. {
  845. IOS::HLE::Kernel ios(m_ticket.GetConsoleType());
  846. auto& es = ios.GetESCore();
  847. const std::vector<u8>& cert_chain = m_volume.GetCertificateChain(PARTITION_NONE);
  848. if (IOS::HLE::IPC_SUCCESS !=
  849. es.VerifyContainer(IOS::HLE::ESCore::VerifyContainerType::Ticket,
  850. IOS::HLE::ESCore::VerifyMode::DoNotUpdateCertStore, m_ticket,
  851. cert_chain))
  852. {
  853. // i18n: "Ticket" here is a kind of digital authorization to use a certain title (e.g. a game)
  854. AddProblem(Severity::Low, Common::GetStringT("The ticket is not correctly signed."));
  855. }
  856. if (IOS::HLE::IPC_SUCCESS !=
  857. es.VerifyContainer(IOS::HLE::ESCore::VerifyContainerType::TMD,
  858. IOS::HLE::ESCore::VerifyMode::DoNotUpdateCertStore, tmd, cert_chain))
  859. {
  860. AddProblem(
  861. Severity::Medium,
  862. Common::GetStringT("The TMD is not correctly signed. If you move or copy this title to "
  863. "the SD Card, the Wii System Menu will not launch it anymore and will "
  864. "also refuse to copy or move it back to the NAND."));
  865. }
  866. }
  867. if (m_volume.IsNKit())
  868. {
  869. AddProblem(
  870. Severity::Low,
  871. Common::GetStringT("This disc image is in the NKit format. It is not a good dump in its "
  872. "current form, but it might become a good dump if converted back. "
  873. "The CRC32 of this file might match the CRC32 of a good dump even "
  874. "though the files are not identical."));
  875. }
  876. if (IsDisc(m_volume.GetVolumeType()) && game_id_unencrypted.starts_with("R8P"))
  877. CheckSuperPaperMario();
  878. }
  879. void VolumeVerifier::CheckSuperPaperMario()
  880. {
  881. // When Super Paper Mario (any region/revision) reads setup/aa1_01.dat when starting a new game,
  882. // it also reads a few extra bytes so that the read length is divisible by 0x20. If these extra
  883. // bytes are zeroes like in good dumps, the game works correctly, but otherwise it can freeze
  884. // (depending on the exact values of the extra bytes). https://bugs.dolphin-emu.org/issues/11900
  885. const Partition partition = m_volume.GetGamePartition();
  886. const FileSystem* fs = m_volume.GetFileSystem(partition);
  887. if (!fs)
  888. return;
  889. std::unique_ptr<FileInfo> file_info = fs->FindFileInfo("setup/aa1_01.dat");
  890. if (!file_info)
  891. return;
  892. const u64 offset = file_info->GetOffset() + file_info->GetSize();
  893. const u64 length = Common::AlignUp(offset, 0x20) - offset;
  894. std::vector<u8> data(length);
  895. if (!m_volume.Read(offset, length, data.data(), partition))
  896. return;
  897. if (std::ranges::any_of(data, [](u8 x) { return x != 0; }))
  898. {
  899. AddProblem(Severity::High,
  900. Common::GetStringT("Some padding data that should be zero is not zero. "
  901. "This can make the game freeze at certain points."));
  902. }
  903. }
  904. void VolumeVerifier::SetUpHashing()
  905. {
  906. if (m_volume.GetVolumeType() == Platform::WiiWAD)
  907. {
  908. m_content_offsets = m_volume.GetContentOffsets();
  909. }
  910. else if (m_volume.GetVolumeType() == Platform::WiiDisc)
  911. {
  912. // Set up a DiscScrubber for checking whether blocks with errors are unused
  913. m_scrubber.SetupScrub(m_volume);
  914. }
  915. std::ranges::sort(m_groups, {}, &GroupToVerify::offset);
  916. if (m_hashes_to_calculate.crc32)
  917. m_crc32_context = Common::StartCRC32();
  918. if (m_hashes_to_calculate.md5)
  919. {
  920. mbedtls_md5_init(&m_md5_context);
  921. mbedtls_md5_starts_ret(&m_md5_context);
  922. }
  923. if (m_hashes_to_calculate.sha1)
  924. {
  925. m_sha1_context = Common::SHA1::CreateContext();
  926. }
  927. }
  928. void VolumeVerifier::WaitForAsyncOperations() const
  929. {
  930. if (m_crc32_future.valid())
  931. m_crc32_future.wait();
  932. if (m_md5_future.valid())
  933. m_md5_future.wait();
  934. if (m_sha1_future.valid())
  935. m_sha1_future.wait();
  936. if (m_content_future.valid())
  937. m_content_future.wait();
  938. if (m_group_future.valid())
  939. m_group_future.wait();
  940. }
  941. bool VolumeVerifier::ReadChunkAndWaitForAsyncOperations(u64 bytes_to_read)
  942. {
  943. std::vector<u8> data(bytes_to_read);
  944. const u64 bytes_to_copy = std::min(m_excess_bytes, bytes_to_read);
  945. if (bytes_to_copy > 0)
  946. std::memcpy(data.data(), m_data.data() + m_data.size() - m_excess_bytes, bytes_to_copy);
  947. bytes_to_read -= bytes_to_copy;
  948. if (bytes_to_read > 0)
  949. {
  950. if (!m_volume.Read(m_progress + bytes_to_copy, bytes_to_read, data.data() + bytes_to_copy,
  951. PARTITION_NONE))
  952. {
  953. return false;
  954. }
  955. }
  956. WaitForAsyncOperations();
  957. m_data = std::move(data);
  958. return true;
  959. }
  960. void VolumeVerifier::Process()
  961. {
  962. ASSERT(m_started);
  963. ASSERT(!m_done);
  964. if (m_progress >= m_max_progress)
  965. return;
  966. IOS::ES::Content content{};
  967. bool content_read = false;
  968. bool group_read = false;
  969. u64 bytes_to_read = DEFAULT_READ_SIZE;
  970. u64 excess_bytes = 0;
  971. if (m_content_index < m_content_offsets.size() &&
  972. m_content_offsets[m_content_index] == m_progress)
  973. {
  974. m_volume.GetTMD(PARTITION_NONE).GetContent(m_content_index, &content);
  975. bytes_to_read = Common::AlignUp(content.size, 0x40);
  976. content_read = true;
  977. const u16 next_content_index = m_content_index + 1;
  978. if (next_content_index < m_content_offsets.size() &&
  979. m_content_offsets[next_content_index] < m_progress + bytes_to_read)
  980. {
  981. excess_bytes = m_progress + bytes_to_read - m_content_offsets[next_content_index];
  982. }
  983. }
  984. else if (m_content_index < m_content_offsets.size() &&
  985. m_content_offsets[m_content_index] > m_progress)
  986. {
  987. bytes_to_read = std::min(bytes_to_read, m_content_offsets[m_content_index] - m_progress);
  988. }
  989. else if (m_group_index < m_groups.size() && m_groups[m_group_index].offset == m_progress)
  990. {
  991. const size_t blocks =
  992. m_groups[m_group_index].block_index_end - m_groups[m_group_index].block_index_start;
  993. bytes_to_read = VolumeWii::BLOCK_TOTAL_SIZE * blocks;
  994. group_read = true;
  995. if (m_group_index + 1 < m_groups.size() &&
  996. m_groups[m_group_index + 1].offset < m_progress + bytes_to_read)
  997. {
  998. excess_bytes = m_progress + bytes_to_read - m_groups[m_group_index + 1].offset;
  999. }
  1000. }
  1001. else if (m_group_index < m_groups.size() && m_groups[m_group_index].offset > m_progress)
  1002. {
  1003. bytes_to_read = std::min(bytes_to_read, m_groups[m_group_index].offset - m_progress);
  1004. }
  1005. if (m_progress + bytes_to_read > m_max_progress)
  1006. {
  1007. const u64 bytes_over_max = m_progress + bytes_to_read - m_max_progress;
  1008. if (m_data_size_type == DataSizeType::LowerBound)
  1009. {
  1010. // Disc images in NFS format can have the last referenced block be past m_max_progress.
  1011. // For NFS, reading beyond m_max_progress doesn't return an error, so let's read beyond it.
  1012. excess_bytes = std::max(excess_bytes, bytes_over_max);
  1013. }
  1014. else
  1015. {
  1016. // Don't read beyond the end of the disc.
  1017. bytes_to_read -= bytes_over_max;
  1018. excess_bytes -= std::min(excess_bytes, bytes_over_max);
  1019. content_read = false;
  1020. group_read = false;
  1021. }
  1022. }
  1023. const bool is_data_needed = m_calculating_any_hash || content_read || group_read;
  1024. const bool read_failed = is_data_needed && !ReadChunkAndWaitForAsyncOperations(bytes_to_read);
  1025. if (read_failed)
  1026. {
  1027. ERROR_LOG_FMT(DISCIO, "Read failed at {:#x} to {:#x}", m_progress, m_progress + bytes_to_read);
  1028. m_read_errors_occurred = true;
  1029. m_calculating_any_hash = false;
  1030. }
  1031. m_excess_bytes = excess_bytes;
  1032. const u64 byte_increment = bytes_to_read - excess_bytes;
  1033. if (m_calculating_any_hash)
  1034. {
  1035. if (m_hashes_to_calculate.crc32)
  1036. {
  1037. m_crc32_future = std::async(std::launch::async, [this, byte_increment] {
  1038. m_crc32_context = Common::UpdateCRC32(m_crc32_context, m_data.data(),
  1039. static_cast<size_t>(byte_increment));
  1040. });
  1041. }
  1042. if (m_hashes_to_calculate.md5)
  1043. {
  1044. m_md5_future = std::async(std::launch::async, [this, byte_increment] {
  1045. mbedtls_md5_update_ret(&m_md5_context, m_data.data(), byte_increment);
  1046. });
  1047. }
  1048. if (m_hashes_to_calculate.sha1)
  1049. {
  1050. m_sha1_future = std::async(std::launch::async, [this, byte_increment] {
  1051. m_sha1_context->Update(m_data.data(), byte_increment);
  1052. });
  1053. }
  1054. }
  1055. if (content_read)
  1056. {
  1057. m_content_future = std::async(std::launch::async, [this, read_failed, content] {
  1058. if (read_failed || !m_volume.CheckContentIntegrity(content, m_data, m_ticket))
  1059. {
  1060. AddProblem(Severity::High, Common::FmtFormatT("Content {0:08x} is corrupt.", content.id));
  1061. }
  1062. });
  1063. m_content_index++;
  1064. }
  1065. if (group_read)
  1066. {
  1067. m_group_future = std::async(std::launch::async, [this, read_failed,
  1068. group_index = m_group_index] {
  1069. const GroupToVerify& group = m_groups[group_index];
  1070. u64 offset_in_group = 0;
  1071. for (u64 block_index = group.block_index_start; block_index < group.block_index_end;
  1072. ++block_index, offset_in_group += VolumeWii::BLOCK_TOTAL_SIZE)
  1073. {
  1074. const u64 block_offset = group.offset + offset_in_group;
  1075. if (!read_failed && m_volume.CheckBlockIntegrity(
  1076. block_index, m_data.data() + offset_in_group, group.partition))
  1077. {
  1078. m_biggest_verified_offset =
  1079. std::max(m_biggest_verified_offset, block_offset + VolumeWii::BLOCK_TOTAL_SIZE);
  1080. }
  1081. else
  1082. {
  1083. if (m_scrubber.CanBlockBeScrubbed(block_offset))
  1084. {
  1085. WARN_LOG_FMT(DISCIO, "Integrity check failed for unused block at {:#x}", block_offset);
  1086. m_unused_block_errors[group.partition]++;
  1087. }
  1088. else
  1089. {
  1090. WARN_LOG_FMT(DISCIO, "Integrity check failed for block at {:#x}", block_offset);
  1091. m_block_errors[group.partition]++;
  1092. }
  1093. }
  1094. }
  1095. });
  1096. m_group_index++;
  1097. }
  1098. m_progress += byte_increment;
  1099. }
  1100. u64 VolumeVerifier::GetBytesProcessed() const
  1101. {
  1102. return m_progress;
  1103. }
  1104. u64 VolumeVerifier::GetTotalBytes() const
  1105. {
  1106. return m_max_progress;
  1107. }
  1108. void VolumeVerifier::Finish()
  1109. {
  1110. if (m_done)
  1111. return;
  1112. m_done = true;
  1113. WaitForAsyncOperations();
  1114. if (m_calculating_any_hash)
  1115. {
  1116. if (m_hashes_to_calculate.crc32)
  1117. {
  1118. m_result.hashes.crc32 = std::vector<u8>(4);
  1119. const u32 crc32_be = Common::swap32(m_crc32_context);
  1120. std::memcpy(m_result.hashes.crc32.data(), &crc32_be, 4);
  1121. }
  1122. if (m_hashes_to_calculate.md5)
  1123. {
  1124. m_result.hashes.md5 = std::vector<u8>(16);
  1125. mbedtls_md5_finish_ret(&m_md5_context, m_result.hashes.md5.data());
  1126. }
  1127. if (m_hashes_to_calculate.sha1)
  1128. {
  1129. const auto digest = m_sha1_context->Finish();
  1130. m_result.hashes.sha1 = std::vector<u8>(digest.begin(), digest.end());
  1131. }
  1132. }
  1133. if (m_read_errors_occurred)
  1134. AddProblem(Severity::Medium, Common::GetStringT("Some of the data could not be read."));
  1135. CheckVolumeSize();
  1136. for (auto [partition, blocks] : m_block_errors)
  1137. {
  1138. if (blocks > 0)
  1139. {
  1140. const std::string name = GetPartitionName(m_volume.GetPartitionType(partition));
  1141. AddProblem(Severity::Medium,
  1142. Common::FmtFormatT("Errors were found in {0} blocks in the {1} partition.", blocks,
  1143. name));
  1144. }
  1145. }
  1146. for (auto [partition, blocks] : m_unused_block_errors)
  1147. {
  1148. if (blocks > 0)
  1149. {
  1150. const std::string name = GetPartitionName(m_volume.GetPartitionType(partition));
  1151. AddProblem(Severity::Low,
  1152. Common::FmtFormatT("Errors were found in {0} unused blocks in the {1} partition.",
  1153. blocks, name));
  1154. }
  1155. }
  1156. // Show the most serious problems at the top
  1157. std::ranges::stable_sort(m_result.problems, std::ranges::greater{}, &Problem::severity);
  1158. const Severity highest_severity =
  1159. m_result.problems.empty() ? Severity::None : m_result.problems[0].severity;
  1160. if (m_redump_verification)
  1161. m_result.redump = m_redump_verifier.Finish(m_result.hashes);
  1162. if (m_result.redump.status == RedumpVerifier::Status::GoodDump ||
  1163. (m_volume.GetVolumeType() == Platform::WiiWAD && !m_is_not_retail &&
  1164. m_result.problems.empty()))
  1165. {
  1166. if (m_result.problems.empty())
  1167. {
  1168. m_result.summary_text = Common::GetStringT("This is a good dump.");
  1169. }
  1170. else
  1171. {
  1172. m_result.summary_text =
  1173. Common::GetStringT("This is a good dump according to Redump.org, but Dolphin has found "
  1174. "problems. This might be a bug in Dolphin.");
  1175. }
  1176. return;
  1177. }
  1178. if (m_is_datel)
  1179. {
  1180. m_result.summary_text = Common::GetStringT("Dolphin is unable to verify unlicensed discs.");
  1181. return;
  1182. }
  1183. if (m_is_tgc)
  1184. {
  1185. m_result.summary_text =
  1186. Common::GetStringT("Dolphin is unable to verify typical TGC files properly, "
  1187. "since they are not dumps of actual discs.");
  1188. return;
  1189. }
  1190. if (m_is_triforce)
  1191. {
  1192. m_result.summary_text =
  1193. Common::GetStringT("Dolphin is currently unable to verify Triforce games.");
  1194. return;
  1195. }
  1196. if (m_result.redump.status == RedumpVerifier::Status::BadDump &&
  1197. highest_severity <= Severity::Low)
  1198. {
  1199. if (m_volume.GetBlobType() == BlobType::NFS)
  1200. {
  1201. m_result.summary_text =
  1202. Common::GetStringT("Compared to the Wii disc release of the game, this is a bad dump. "
  1203. "Despite this, it's possible that this is a good dump compared to the "
  1204. "Wii U eShop release of the game. Dolphin can't verify this.");
  1205. }
  1206. else
  1207. {
  1208. m_result.summary_text = Common::GetStringT(
  1209. "This is a bad dump. This doesn't necessarily mean that the game won't run correctly.");
  1210. }
  1211. }
  1212. else
  1213. {
  1214. if (m_result.redump.status == RedumpVerifier::Status::BadDump)
  1215. {
  1216. m_result.summary_text = Common::GetStringT("This is a bad dump.") + "\n\n";
  1217. }
  1218. switch (highest_severity)
  1219. {
  1220. case Severity::None:
  1221. if (IsWii(m_volume.GetVolumeType()) && !m_is_not_retail)
  1222. {
  1223. m_result.summary_text = Common::GetStringT(
  1224. "No problems were found. This does not guarantee that this is a good dump, "
  1225. "but since Wii titles contain a lot of verification data, it does mean that "
  1226. "there most likely are no problems that will affect emulation.");
  1227. }
  1228. else
  1229. {
  1230. m_result.summary_text = Common::GetStringT("No problems were found.");
  1231. }
  1232. break;
  1233. case Severity::Low:
  1234. if (m_volume.GetBlobType() == BlobType::NFS)
  1235. {
  1236. m_result.summary_text = Common::GetStringT(
  1237. "Compared to the Wii disc release of the game, problems of low severity were found. "
  1238. "Despite this, it's possible that this is a good dump compared to the Wii U eShop "
  1239. "release of the game. Dolphin can't verify this.");
  1240. }
  1241. else
  1242. {
  1243. m_result.summary_text =
  1244. Common::GetStringT("Problems with low severity were found. They will most "
  1245. "likely not prevent the game from running.");
  1246. }
  1247. break;
  1248. case Severity::Medium:
  1249. m_result.summary_text +=
  1250. Common::GetStringT("Problems with medium severity were found. The whole game "
  1251. "or certain parts of the game might not work correctly.");
  1252. break;
  1253. case Severity::High:
  1254. m_result.summary_text += Common::GetStringT(
  1255. "Problems with high severity were found. The game will most likely not work at all.");
  1256. break;
  1257. }
  1258. }
  1259. if (m_volume.GetVolumeType() == Platform::GameCubeDisc)
  1260. {
  1261. m_result.summary_text +=
  1262. Common::GetStringT("\n\nBecause GameCube disc images contain little verification data, "
  1263. "there may be problems that Dolphin is unable to detect.");
  1264. }
  1265. else if (m_is_not_retail)
  1266. {
  1267. m_result.summary_text +=
  1268. Common::GetStringT("\n\nBecause this title is not for retail Wii consoles, "
  1269. "Dolphin cannot ensure that it hasn't been tampered with, even if "
  1270. "signatures appear valid.");
  1271. }
  1272. }
  1273. const VolumeVerifier::Result& VolumeVerifier::GetResult() const
  1274. {
  1275. return m_result;
  1276. }
  1277. void VolumeVerifier::AddProblem(Severity severity, std::string text)
  1278. {
  1279. m_result.problems.emplace_back(Problem{severity, std::move(text)});
  1280. }
  1281. } // namespace DiscIO