VolumeVerifier.cpp 52 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475
  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_compat.h>
  14. #include <pugixml.hpp>
  15. #include "Common/Align.h"
  16. #include "Common/Assert.h"
  17. #include "Common/CPUDetect.h"
  18. #include "Common/CommonPaths.h"
  19. #include "Common/CommonTypes.h"
  20. #include "Common/Crypto/SHA1.h"
  21. #include "Common/FileUtil.h"
  22. #include "Common/Hash.h"
  23. #include "Common/HttpRequest.h"
  24. #include "Common/IOFile.h"
  25. #include "Common/Logging/Log.h"
  26. #include "Common/MinizipUtil.h"
  27. #include "Common/MsgHandler.h"
  28. #include "Common/ScopeGuard.h"
  29. #include "Common/StringUtil.h"
  30. #include "Common/Swap.h"
  31. #include "Common/Version.h"
  32. #include "Core/IOS/Device.h"
  33. #include "Core/IOS/ES/ES.h"
  34. #include "Core/IOS/ES/Formats.h"
  35. #include "Core/IOS/IOS.h"
  36. #include "Core/IOS/IOSC.h"
  37. #include "DiscIO/Blob.h"
  38. #include "DiscIO/DiscScrubber.h"
  39. #include "DiscIO/DiscUtils.h"
  40. #include "DiscIO/Enums.h"
  41. #include "DiscIO/Filesystem.h"
  42. #include "DiscIO/Volume.h"
  43. #include "DiscIO/VolumeWii.h"
  44. namespace DiscIO
  45. {
  46. RedumpVerifier::DownloadState RedumpVerifier::m_gc_download_state;
  47. RedumpVerifier::DownloadState RedumpVerifier::m_wii_download_state;
  48. void RedumpVerifier::Start(const Volume& volume)
  49. {
  50. if (!volume.IsDatelDisc())
  51. {
  52. m_game_id = volume.GetGameID();
  53. if (m_game_id.size() > 4)
  54. m_game_id = m_game_id.substr(0, 4);
  55. }
  56. m_revision = volume.GetRevision().value_or(0);
  57. m_disc_number = volume.GetDiscNumber().value_or(0);
  58. m_size = volume.GetDataSize();
  59. const DiscIO::Platform platform = volume.GetVolumeType();
  60. m_future = std::async(std::launch::async, [this, platform]() -> std::vector<PotentialMatch> {
  61. std::string system;
  62. DownloadState* download_state;
  63. switch (platform)
  64. {
  65. case Platform::GameCubeDisc:
  66. system = "gc";
  67. download_state = &m_gc_download_state;
  68. break;
  69. case Platform::WiiDisc:
  70. system = "wii";
  71. download_state = &m_wii_download_state;
  72. break;
  73. default:
  74. m_result.status = Status::Error;
  75. return {};
  76. }
  77. {
  78. std::lock_guard lk(download_state->mutex);
  79. download_state->status = DownloadDatfile(system, download_state->status);
  80. }
  81. switch (download_state->status)
  82. {
  83. case DownloadStatus::FailButOldCacheAvailable:
  84. ERROR_LOG_FMT(DISCIO, "Failed to fetch data from Redump.org, using old cached data instead");
  85. [[fallthrough]];
  86. case DownloadStatus::Success:
  87. return ScanDatfile(ReadDatfile(system), system);
  88. case DownloadStatus::SystemNotAvailable:
  89. m_result = {Status::Error, Common::GetStringT("Wii data is not public yet")};
  90. return {};
  91. case DownloadStatus::Fail:
  92. default:
  93. m_result = {Status::Error, Common::GetStringT("Failed to connect to Redump.org")};
  94. return {};
  95. }
  96. });
  97. }
  98. static std::string GetPathForSystem(const std::string& system)
  99. {
  100. return File::GetUserPath(D_REDUMPCACHE_IDX) + DIR_SEP + system + ".zip";
  101. }
  102. RedumpVerifier::DownloadStatus RedumpVerifier::DownloadDatfile(const std::string& system,
  103. DownloadStatus old_status)
  104. {
  105. if (old_status == DownloadStatus::Success || old_status == DownloadStatus::SystemNotAvailable)
  106. return old_status;
  107. Common::HttpRequest request;
  108. const std::optional<std::vector<u8>> result =
  109. request.Get("http://redump.org/datfile/" + system + "/serial,version",
  110. {{"User-Agent", Common::GetScmRevStr()}});
  111. const std::string output_path = GetPathForSystem(system);
  112. if (!result)
  113. {
  114. return File::Exists(output_path) ? DownloadStatus::FailButOldCacheAvailable :
  115. DownloadStatus::Fail;
  116. }
  117. if (result->size() > 1 && (*result)[0] == '<' && (*result)[1] == '!')
  118. {
  119. // This is an HTML page, not a zip file like we want
  120. if (File::Exists(output_path))
  121. return DownloadStatus::FailButOldCacheAvailable;
  122. const std::string system_not_available_message = "System \"" + system + "\" doesn't exist.";
  123. const bool system_not_available_match =
  124. result->end() != std::search(result->begin(), result->end(),
  125. system_not_available_message.begin(),
  126. system_not_available_message.end());
  127. return system_not_available_match ? DownloadStatus::SystemNotAvailable : DownloadStatus::Fail;
  128. }
  129. File::CreateFullPath(output_path);
  130. if (!File::IOFile(output_path, "wb").WriteBytes(result->data(), result->size()))
  131. ERROR_LOG_FMT(DISCIO, "Failed to write downloaded datfile to {}", output_path);
  132. return DownloadStatus::Success;
  133. }
  134. std::vector<u8> RedumpVerifier::ReadDatfile(const std::string& system)
  135. {
  136. unzFile file = unzOpen(GetPathForSystem(system).c_str());
  137. if (!file)
  138. return {};
  139. Common::ScopeGuard file_guard{[&] { unzClose(file); }};
  140. // Check that the zip file contains exactly one file
  141. if (unzGoToFirstFile(file) != UNZ_OK)
  142. return {};
  143. if (unzGoToNextFile(file) != UNZ_END_OF_LIST_OF_FILE)
  144. return {};
  145. // Read the file
  146. if (unzGoToFirstFile(file) != UNZ_OK)
  147. return {};
  148. unz_file_info file_info;
  149. unzGetCurrentFileInfo(file, &file_info, nullptr, 0, nullptr, 0, nullptr, 0);
  150. std::vector<u8> data(file_info.uncompressed_size);
  151. if (!Common::ReadFileFromZip(file, &data))
  152. return {};
  153. return data;
  154. }
  155. static u8 ParseHexDigit(char c)
  156. {
  157. if (c < '0')
  158. return 0; // Error
  159. if (c >= 'a')
  160. c -= 'a' - 'A';
  161. if (c >= 'A')
  162. c -= 'A' - ('9' + 1);
  163. c -= '0';
  164. if (c >= 0x10)
  165. return 0; // Error
  166. return c;
  167. }
  168. static std::vector<u8> ParseHash(const char* str)
  169. {
  170. std::vector<u8> hash;
  171. while (str[0] && str[1])
  172. {
  173. hash.push_back(static_cast<u8>(ParseHexDigit(str[0]) * 0x10 + ParseHexDigit(str[1])));
  174. str += 2;
  175. }
  176. return hash;
  177. }
  178. std::vector<RedumpVerifier::PotentialMatch> RedumpVerifier::ScanDatfile(const std::vector<u8>& data,
  179. const std::string& system)
  180. {
  181. pugi::xml_document doc;
  182. if (!doc.load_buffer(data.data(), data.size()))
  183. {
  184. m_result = {Status::Error, Common::GetStringT("Failed to parse Redump.org data")};
  185. return {};
  186. }
  187. std::vector<PotentialMatch> potential_matches;
  188. bool serials_exist = false;
  189. bool versions_exist = false;
  190. const pugi::xml_node datafile = doc.child("datafile");
  191. for (const pugi::xml_node game : datafile.children("game"))
  192. {
  193. std::string version_string = game.child("version").text().as_string();
  194. if (!version_string.empty())
  195. versions_exist = true;
  196. // Strip out prefix (e.g. "v1.02" -> "02", "Rev 2" -> "2")
  197. const size_t last_non_numeric = version_string.find_last_not_of("0123456789");
  198. if (last_non_numeric != std::string::npos)
  199. version_string = version_string.substr(last_non_numeric + 1);
  200. const int version = version_string.empty() ? 0 : std::stoi(version_string);
  201. const std::string serials = game.child("serial").text().as_string();
  202. if (!serials.empty())
  203. serials_exist = true;
  204. // The revisions for Korean GameCube games whose four-char game IDs end in E are numbered from
  205. // 0x30 in ring codes and in disc headers, but Redump switched to numbering them from 0 in 2019.
  206. if (version % 0x30 != m_revision % 0x30)
  207. continue;
  208. if (serials.empty() || serials.starts_with("DS"))
  209. {
  210. // GC Datel discs have no serials in Redump, Wii Datel discs have serials like "DS000101"
  211. if (!m_game_id.empty())
  212. continue; // Non-empty m_game_id means we're verifying a non-Datel disc
  213. }
  214. else
  215. {
  216. bool serial_match_found = false;
  217. // If a disc has multiple possible serials, they are delimited with ", ". We want to loop
  218. // through all the serials until we find a match, because even though they normally only
  219. // differ in the region code at the end (which we don't care about), there is an edge case
  220. // disc with the game ID "G96P" and the serial "DL-DOL-D96P-EUR, DL-DOL-G96P-EUR".
  221. for (const std::string& serial_str : SplitString(serials, ','))
  222. {
  223. const std::string_view serial = StripWhitespace(serial_str);
  224. // Skip the prefix, normally either "DL-DOL-" or "RVL-" (depending on the console),
  225. // but there are some exceptions like the "RVLE-SBSE-USA-B0" serial.
  226. const size_t first_dash = serial.find_first_of('-', 3);
  227. const size_t game_id_start =
  228. first_dash == std::string::npos ? std::string::npos : first_dash + 1;
  229. if (game_id_start == std::string::npos || serial.size() < game_id_start + 4)
  230. {
  231. ERROR_LOG_FMT(DISCIO, "Invalid serial in redump datfile: {}", serial_str);
  232. continue;
  233. }
  234. const std::string_view game_id = serial.substr(game_id_start, 4);
  235. if (game_id != m_game_id)
  236. continue;
  237. u8 disc_number = 0;
  238. if (serial.size() > game_id_start + 5 && serial[game_id_start + 5] >= '0' &&
  239. serial[game_id_start + 5] <= '9')
  240. {
  241. disc_number = serial[game_id_start + 5] - '0';
  242. }
  243. if (disc_number != m_disc_number)
  244. continue;
  245. serial_match_found = true;
  246. break;
  247. }
  248. if (!serial_match_found)
  249. continue;
  250. }
  251. PotentialMatch& potential_match = potential_matches.emplace_back();
  252. const pugi::xml_node rom = game.child("rom");
  253. potential_match.size = rom.attribute("size").as_ullong();
  254. potential_match.hashes.crc32 = ParseHash(rom.attribute("crc").value());
  255. potential_match.hashes.md5 = ParseHash(rom.attribute("md5").value());
  256. potential_match.hashes.sha1 = ParseHash(rom.attribute("sha1").value());
  257. }
  258. if (!serials_exist || !versions_exist)
  259. {
  260. // If we reach this, the user has most likely downloaded a datfile manually,
  261. // so show a panic alert rather than just using ERROR_LOG
  262. // i18n: "Serial" refers to serial numbers, e.g. RVL-RSBE-USA
  263. PanicAlertFmtT(
  264. "Serial and/or version data is missing from {0}\n"
  265. "Please append \"{1}\" (without the quotes) to the datfile URL when downloading\n"
  266. "Example: {2}",
  267. GetPathForSystem(system), "serial,version", "http://redump.org/datfile/gc/serial,version");
  268. m_result = {Status::Error, Common::GetStringT("Failed to parse Redump.org data")};
  269. return {};
  270. }
  271. return potential_matches;
  272. }
  273. static bool HashesMatch(const std::vector<u8>& calculated, const std::vector<u8>& expected)
  274. {
  275. return calculated.empty() || calculated == expected;
  276. }
  277. RedumpVerifier::Result RedumpVerifier::Finish(const Hashes<std::vector<u8>>& hashes)
  278. {
  279. if (m_result.status == Status::Error)
  280. return m_result;
  281. if (hashes.crc32.empty() && hashes.md5.empty() && hashes.sha1.empty())
  282. return m_result;
  283. const std::vector<PotentialMatch> potential_matches = m_future.get();
  284. for (PotentialMatch p : potential_matches)
  285. {
  286. if (HashesMatch(hashes.crc32, p.hashes.crc32) && HashesMatch(hashes.md5, p.hashes.md5) &&
  287. HashesMatch(hashes.sha1, p.hashes.sha1) && m_size == p.size)
  288. {
  289. return {Status::GoodDump, Common::GetStringT("Good dump")};
  290. }
  291. }
  292. // We only return bad dump if there's a disc that we know this dump should match but that doesn't
  293. // match. For disc without IDs (i.e. Datel discs), we don't have a good way of knowing whether we
  294. // have a bad dump or just a dump that isn't in Redump, so we always pick unknown instead of bad
  295. // dump for those to be on the safe side. (Besides, it's possible to dump a Datel disc correctly
  296. // and have it not match Redump if you don't use the same replacement value for bad sectors.)
  297. if (!potential_matches.empty() && !m_game_id.empty())
  298. return {Status::BadDump, Common::GetStringT("Bad dump")};
  299. return {Status::Unknown, Common::GetStringT("Unknown disc")};
  300. }
  301. constexpr u64 DEFAULT_READ_SIZE = 0x20000; // Arbitrary value
  302. VolumeVerifier::VolumeVerifier(const Volume& volume, bool redump_verification,
  303. Hashes<bool> hashes_to_calculate)
  304. : m_volume(volume), m_redump_verification(redump_verification),
  305. m_hashes_to_calculate(hashes_to_calculate),
  306. m_calculating_any_hash(hashes_to_calculate.crc32 || hashes_to_calculate.md5 ||
  307. hashes_to_calculate.sha1),
  308. m_max_progress(volume.GetDataSize()), m_data_size_type(volume.GetDataSizeType())
  309. {
  310. if (!m_calculating_any_hash)
  311. m_redump_verification = false;
  312. }
  313. VolumeVerifier::~VolumeVerifier()
  314. {
  315. WaitForAsyncOperations();
  316. }
  317. Hashes<bool> VolumeVerifier::GetDefaultHashesToCalculate()
  318. {
  319. Hashes<bool> hashes_to_calculate{.crc32 = true, .md5 = true, .sha1 = true};
  320. // If the system can compute certain hashes faster than others, only default-enable the fast ones.
  321. const bool sha1_hw_accel = Common::SHA1::CreateContext()->HwAccelerated();
  322. // For crc32, we assume zlib-ng will be fast if cpu supports crc32
  323. const bool crc32_hw_accel = cpu_info.bCRC32;
  324. if (crc32_hw_accel || sha1_hw_accel)
  325. {
  326. hashes_to_calculate.crc32 = crc32_hw_accel;
  327. // md5 has no accelerated implementation at the moment, always default to off
  328. hashes_to_calculate.md5 = false;
  329. // Always enable SHA1, to avoid situation where only crc32 is computed
  330. hashes_to_calculate.sha1 = true;
  331. }
  332. return hashes_to_calculate;
  333. }
  334. void VolumeVerifier::Start()
  335. {
  336. ASSERT(!m_started);
  337. m_started = true;
  338. if (m_redump_verification)
  339. m_redump_verifier.Start(m_volume);
  340. m_is_tgc = m_volume.GetBlobType() == BlobType::TGC;
  341. m_is_datel = m_volume.IsDatelDisc();
  342. m_is_not_retail = (m_volume.GetVolumeType() == Platform::WiiDisc && !m_volume.HasWiiHashes()) ||
  343. IsDebugSigned();
  344. const std::vector<Partition> partitions = CheckPartitions();
  345. if (IsDisc(m_volume.GetVolumeType()))
  346. m_biggest_referenced_offset = GetBiggestReferencedOffset(m_volume, partitions);
  347. CheckMisc();
  348. SetUpHashing();
  349. }
  350. std::vector<Partition> VolumeVerifier::CheckPartitions()
  351. {
  352. if (m_volume.GetVolumeType() == Platform::WiiWAD)
  353. return {};
  354. const std::vector<Partition> partitions = m_volume.GetPartitions();
  355. if (partitions.empty())
  356. {
  357. if (!m_volume.GetFileSystem(m_volume.GetGamePartition()))
  358. {
  359. AddProblem(Severity::High,
  360. Common::GetStringT("The filesystem is invalid or could not be read."));
  361. return {};
  362. }
  363. return {m_volume.GetGamePartition()};
  364. }
  365. std::optional<u32> partitions_in_first_table = m_volume.ReadSwapped<u32>(0x40000, PARTITION_NONE);
  366. if (partitions_in_first_table && *partitions_in_first_table > 8)
  367. {
  368. // Not sure if 8 actually is the limit, but there certainly aren't any discs
  369. // released that have as many partitions as 8 in the first partition table.
  370. // The only game that has that many partitions in total is Super Smash Bros. Brawl,
  371. // and that game places all partitions other than UPDATE and DATA in the second table.
  372. AddProblem(Severity::Low,
  373. Common::GetStringT("There are too many partitions in the first partition table."));
  374. }
  375. std::vector<u32> types;
  376. for (const Partition& partition : partitions)
  377. {
  378. const std::optional<u32> type = m_volume.GetPartitionType(partition);
  379. if (type)
  380. types.emplace_back(*type);
  381. }
  382. if (std::find(types.cbegin(), types.cend(), PARTITION_UPDATE) == types.cend())
  383. AddProblem(Severity::Low, Common::GetStringT("The update partition is missing."));
  384. const bool has_data_partition =
  385. std::find(types.cbegin(), types.cend(), PARTITION_DATA) != types.cend();
  386. if (!m_is_datel && !has_data_partition)
  387. AddProblem(Severity::High, Common::GetStringT("The data partition is missing."));
  388. const bool has_channel_partition =
  389. std::find(types.cbegin(), types.cend(), PARTITION_CHANNEL) != types.cend();
  390. if (ShouldHaveChannelPartition() && !has_channel_partition)
  391. AddProblem(Severity::Medium, Common::GetStringT("The channel partition is missing."));
  392. const bool has_install_partition =
  393. std::find(types.cbegin(), types.cend(), PARTITION_INSTALL) != types.cend();
  394. if (ShouldHaveInstallPartition() && !has_install_partition)
  395. AddProblem(Severity::High, Common::GetStringT("The install partition is missing."));
  396. if (ShouldHaveMasterpiecePartitions() &&
  397. types.cend() == std::ranges::find_if(types, [](u32 type) { return type >= 0xFF; }))
  398. {
  399. // i18n: This string is referring to a game mode in Super Smash Bros. Brawl called Masterpieces
  400. // where you play demos of NES/SNES/N64 games. Official translations:
  401. // 名作トライアル (Japanese), Masterpieces (English), Meisterstücke (German), Chefs-d'œuvre
  402. // (French), Clásicos (Spanish), Capolavori (Italian), 클래식 게임 체험판 (Korean).
  403. // If your language is not one of the languages above, consider leaving the string untranslated
  404. // so that people will recognize it as the name of the game mode.
  405. AddProblem(Severity::Medium, Common::GetStringT("The Masterpiece partitions are missing."));
  406. }
  407. for (const Partition& partition : partitions)
  408. {
  409. if (m_volume.GetPartitionType(partition) == PARTITION_UPDATE && partition.offset != 0x50000)
  410. {
  411. AddProblem(Severity::Low,
  412. Common::GetStringT("The update partition is not at its normal position."));
  413. }
  414. const u64 normal_data_offset = m_volume.HasWiiHashes() ? 0xF800000 : 0x838000;
  415. if (m_volume.GetPartitionType(partition) == PARTITION_DATA &&
  416. partition.offset != normal_data_offset && !has_channel_partition && !has_install_partition)
  417. {
  418. AddProblem(Severity::Low,
  419. Common::GetStringT(
  420. "The data partition is not at its normal position. This will affect the "
  421. "emulated loading times. You will be unable to share input recordings and use "
  422. "NetPlay with anyone who is using a good dump."));
  423. }
  424. }
  425. std::vector<Partition> valid_partitions;
  426. for (const Partition& partition : partitions)
  427. {
  428. if (CheckPartition(partition))
  429. valid_partitions.push_back(partition);
  430. }
  431. return valid_partitions;
  432. }
  433. bool VolumeVerifier::CheckPartition(const Partition& partition)
  434. {
  435. std::optional<u32> type = m_volume.GetPartitionType(partition);
  436. if (!type)
  437. {
  438. // Not sure if this can happen in practice
  439. AddProblem(Severity::Medium, Common::GetStringT("The type of a partition could not be read."));
  440. return false;
  441. }
  442. Severity severity = Severity::Medium;
  443. if (*type == PARTITION_DATA || *type == PARTITION_INSTALL)
  444. severity = Severity::High;
  445. else if (*type == PARTITION_UPDATE)
  446. severity = Severity::Low;
  447. const std::string name = GetPartitionName(type);
  448. if (partition.offset % VolumeWii::BLOCK_TOTAL_SIZE != 0 ||
  449. m_volume.PartitionOffsetToRawOffset(0, partition) % VolumeWii::BLOCK_TOTAL_SIZE != 0)
  450. {
  451. AddProblem(Severity::Medium,
  452. Common::FmtFormatT("The {0} partition is not properly aligned.", name));
  453. }
  454. bool invalid_header = false;
  455. bool blank_contents = false;
  456. std::vector<u8> disc_header(0x80);
  457. if (!m_volume.Read(0, disc_header.size(), disc_header.data(), partition))
  458. {
  459. invalid_header = true;
  460. }
  461. else if (Common::swap32(disc_header.data() + 0x18) != WII_DISC_MAGIC)
  462. {
  463. for (size_t i = 0; i < disc_header.size(); i += 4)
  464. {
  465. if (Common::swap32(disc_header.data() + i) != i)
  466. {
  467. invalid_header = true;
  468. break;
  469. }
  470. }
  471. // The loop above ends without setting invalid_header for discs that legitimately lack
  472. // updates. No such discs have been released to end users. Most such discs are debug signed,
  473. // but there is apparently at least one that is retail signed, the Movie-Ch Install Disc.
  474. if (!invalid_header)
  475. blank_contents = true;
  476. }
  477. if (invalid_header)
  478. {
  479. // This can happen when certain programs that create WBFS files scrub the entirety of
  480. // the Masterpiece partitions in Super Smash Bros. Brawl without removing them from
  481. // the partition table. https://bugs.dolphin-emu.org/issues/8733
  482. AddProblem(severity,
  483. Common::FmtFormatT("The {0} partition does not seem to contain valid data.", name));
  484. return false;
  485. }
  486. if (!m_is_datel)
  487. {
  488. const auto console_type =
  489. IsDebugSigned() ? IOS::HLE::IOSC::ConsoleType::RVT : IOS::HLE::IOSC::ConsoleType::Retail;
  490. IOS::HLE::Kernel ios(console_type);
  491. auto& es = ios.GetESCore();
  492. const std::vector<u8>& cert_chain = m_volume.GetCertificateChain(partition);
  493. if (IOS::HLE::IPC_SUCCESS !=
  494. es.VerifyContainer(IOS::HLE::ESCore::VerifyContainerType::Ticket,
  495. IOS::HLE::ESCore::VerifyMode::DoNotUpdateCertStore,
  496. m_volume.GetTicket(partition), cert_chain) ||
  497. IOS::HLE::IPC_SUCCESS !=
  498. es.VerifyContainer(IOS::HLE::ESCore::VerifyContainerType::TMD,
  499. IOS::HLE::ESCore::VerifyMode::DoNotUpdateCertStore,
  500. m_volume.GetTMD(partition), cert_chain))
  501. {
  502. AddProblem(Severity::Low,
  503. Common::FmtFormatT("The {0} partition is not correctly signed.", name));
  504. }
  505. }
  506. if (m_volume.HasWiiHashes() && !m_volume.CheckH3TableIntegrity(partition))
  507. {
  508. AddProblem(Severity::Low,
  509. Common::FmtFormatT("The H3 hash table for the {0} partition is not correct.", name));
  510. }
  511. // Prepare for hash verification in the Process step
  512. if (m_volume.HasWiiHashes())
  513. {
  514. const u64 data_size =
  515. m_volume.ReadSwappedAndShifted(partition.offset + 0x2bc, PARTITION_NONE).value_or(0);
  516. const size_t blocks = static_cast<size_t>(data_size / VolumeWii::BLOCK_TOTAL_SIZE);
  517. if (data_size % VolumeWii::BLOCK_TOTAL_SIZE != 0)
  518. {
  519. std::string text = Common::FmtFormatT(
  520. "The data size for the {0} partition is not evenly divisible by the block size.", name);
  521. AddProblem(Severity::Low, std::move(text));
  522. }
  523. u64 offset = m_volume.PartitionOffsetToRawOffset(0, partition);
  524. for (size_t block_index = 0; block_index < blocks;
  525. block_index += VolumeWii::BLOCKS_PER_GROUP, offset += VolumeWii::GROUP_TOTAL_SIZE)
  526. {
  527. m_groups.emplace_back(
  528. GroupToVerify{partition, offset, block_index,
  529. std::min(block_index + VolumeWii::BLOCKS_PER_GROUP, blocks)});
  530. }
  531. m_block_errors.emplace(partition, 0);
  532. }
  533. if (blank_contents)
  534. return false;
  535. const DiscIO::FileSystem* filesystem = m_volume.GetFileSystem(partition);
  536. if (!filesystem)
  537. {
  538. if (m_is_datel)
  539. {
  540. // Datel's Wii Freeloader has an invalid FST in its only partition
  541. return true;
  542. }
  543. AddProblem(severity,
  544. Common::FmtFormatT("The {0} partition does not have a valid file system.", name));
  545. return false;
  546. }
  547. if (type == PARTITION_UPDATE)
  548. {
  549. const IOS::ES::TMDReader& tmd = m_volume.GetTMD(m_volume.GetGamePartition());
  550. // IOS9 is the only IOS which can be assumed to exist in a working state on any Wii
  551. // regardless of what updates have been installed. At least Mario Party 8
  552. // (RM8E01, revision 2) uses IOS9 without having it in its update partition.
  553. const u64 ios_ver = tmd.GetIOSId() & 0xFF;
  554. bool has_correct_ios = tmd.IsValid() && ios_ver == 9;
  555. if (!has_correct_ios && tmd.IsValid())
  556. {
  557. std::unique_ptr<FileInfo> file_info = filesystem->FindFileInfo("_sys");
  558. if (file_info)
  559. {
  560. const std::string ios_ver_str = std::to_string(ios_ver);
  561. const std::string correct_ios =
  562. IsDebugSigned() ? ("firmware.64." + ios_ver_str + ".") : ("ios" + ios_ver_str + "-");
  563. for (const FileInfo& f : *file_info)
  564. {
  565. std::string file_name = f.GetName();
  566. Common::ToLower(&file_name);
  567. if (file_name.starts_with(correct_ios))
  568. {
  569. has_correct_ios = true;
  570. break;
  571. }
  572. }
  573. }
  574. }
  575. if (!has_correct_ios)
  576. {
  577. // This is reached for hacked dumps where the update partition has been replaced with
  578. // a very old update partition so that no updates will be installed.
  579. AddProblem(
  580. Severity::Low,
  581. Common::GetStringT("The update partition does not contain the IOS used by this title."));
  582. }
  583. }
  584. return true;
  585. }
  586. std::string VolumeVerifier::GetPartitionName(std::optional<u32> type) const
  587. {
  588. if (!type)
  589. return "???";
  590. std::string name = NameForPartitionType(*type, false);
  591. if (ShouldHaveMasterpiecePartitions() && *type > 0xFF)
  592. {
  593. // i18n: This string is referring to a game mode in Super Smash Bros. Brawl called Masterpieces
  594. // where you play demos of NES/SNES/N64 games. This string is referring to a specific such demo
  595. // rather than the game mode as a whole, so please use the singular form. Official translations:
  596. // 名作トライアル (Japanese), Masterpieces (English), Meisterstücke (German), Chefs-d'œuvre
  597. // (French), Clásicos (Spanish), Capolavori (Italian), 클래식 게임 체험판 (Korean).
  598. // If your language is not one of the languages above, consider leaving the string untranslated
  599. // so that people will recognize it as the name of the game mode.
  600. name = Common::FmtFormatT("{0} (Masterpiece)", name);
  601. }
  602. return name;
  603. }
  604. bool VolumeVerifier::IsDebugSigned() const
  605. {
  606. const IOS::ES::TicketReader& ticket = m_volume.GetTicket(m_volume.GetGamePartition());
  607. return ticket.IsValid() ? ticket.GetConsoleType() == IOS::HLE::IOSC::ConsoleType::RVT : false;
  608. }
  609. bool VolumeVerifier::ShouldHaveChannelPartition() const
  610. {
  611. static constexpr std::array<std::string_view, 18> channel_discs = {
  612. "RFNE01", "RFNJ01", "RFNK01", "RFNP01", "RFNW01", "RFPE01", "RFPJ01", "RFPK01", "RFPP01",
  613. "RFPW01", "RGWE41", "RGWJ41", "RGWP41", "RGWX41", "RMCE01", "RMCJ01", "RMCK01", "RMCP01",
  614. };
  615. static_assert(std::ranges::is_sorted(channel_discs));
  616. return std::ranges::binary_search(channel_discs, m_volume.GetGameID());
  617. }
  618. bool VolumeVerifier::ShouldHaveInstallPartition() const
  619. {
  620. static constexpr std::array<std::string_view, 4> dragon_quest_x = {"S4MJGD", "S4SJGD", "S6TJGD",
  621. "SDQJGD"};
  622. const std::string& game_id = m_volume.GetGameID();
  623. return std::any_of(dragon_quest_x.cbegin(), dragon_quest_x.cend(),
  624. [&game_id](std::string_view x) { return x == game_id; });
  625. }
  626. bool VolumeVerifier::ShouldHaveMasterpiecePartitions() const
  627. {
  628. static constexpr std::array<std::string_view, 4> ssbb = {"RSBE01", "RSBJ01", "RSBK01", "RSBP01"};
  629. const std::string& game_id = m_volume.GetGameID();
  630. return std::any_of(ssbb.cbegin(), ssbb.cend(),
  631. [&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 != DiscIO::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 != DiscIO::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 != DiscIO::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 DiscIO::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::any_of(data.cbegin(), data.cend(), [](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::sort(m_groups.begin(), m_groups.end(),
  916. [](const GroupToVerify& a, const GroupToVerify& b) { return a.offset < b.offset; });
  917. if (m_hashes_to_calculate.crc32)
  918. m_crc32_context = Common::StartCRC32();
  919. if (m_hashes_to_calculate.md5)
  920. {
  921. mbedtls_md5_init(&m_md5_context);
  922. mbedtls_md5_starts_ret(&m_md5_context);
  923. }
  924. if (m_hashes_to_calculate.sha1)
  925. {
  926. m_sha1_context = Common::SHA1::CreateContext();
  927. }
  928. }
  929. void VolumeVerifier::WaitForAsyncOperations() const
  930. {
  931. if (m_crc32_future.valid())
  932. m_crc32_future.wait();
  933. if (m_md5_future.valid())
  934. m_md5_future.wait();
  935. if (m_sha1_future.valid())
  936. m_sha1_future.wait();
  937. if (m_content_future.valid())
  938. m_content_future.wait();
  939. if (m_group_future.valid())
  940. m_group_future.wait();
  941. }
  942. bool VolumeVerifier::ReadChunkAndWaitForAsyncOperations(u64 bytes_to_read)
  943. {
  944. std::vector<u8> data(bytes_to_read);
  945. const u64 bytes_to_copy = std::min(m_excess_bytes, bytes_to_read);
  946. if (bytes_to_copy > 0)
  947. std::memcpy(data.data(), m_data.data() + m_data.size() - m_excess_bytes, bytes_to_copy);
  948. bytes_to_read -= bytes_to_copy;
  949. if (bytes_to_read > 0)
  950. {
  951. if (!m_volume.Read(m_progress + bytes_to_copy, bytes_to_read, data.data() + bytes_to_copy,
  952. PARTITION_NONE))
  953. {
  954. return false;
  955. }
  956. }
  957. WaitForAsyncOperations();
  958. m_data = std::move(data);
  959. return true;
  960. }
  961. void VolumeVerifier::Process()
  962. {
  963. ASSERT(m_started);
  964. ASSERT(!m_done);
  965. if (m_progress >= m_max_progress)
  966. return;
  967. IOS::ES::Content content{};
  968. bool content_read = false;
  969. bool group_read = false;
  970. u64 bytes_to_read = DEFAULT_READ_SIZE;
  971. u64 excess_bytes = 0;
  972. if (m_content_index < m_content_offsets.size() &&
  973. m_content_offsets[m_content_index] == m_progress)
  974. {
  975. m_volume.GetTMD(PARTITION_NONE).GetContent(m_content_index, &content);
  976. bytes_to_read = Common::AlignUp(content.size, 0x40);
  977. content_read = true;
  978. const u16 next_content_index = m_content_index + 1;
  979. if (next_content_index < m_content_offsets.size() &&
  980. m_content_offsets[next_content_index] < m_progress + bytes_to_read)
  981. {
  982. excess_bytes = m_progress + bytes_to_read - m_content_offsets[next_content_index];
  983. }
  984. }
  985. else if (m_content_index < m_content_offsets.size() &&
  986. m_content_offsets[m_content_index] > m_progress)
  987. {
  988. bytes_to_read = std::min(bytes_to_read, m_content_offsets[m_content_index] - m_progress);
  989. }
  990. else if (m_group_index < m_groups.size() && m_groups[m_group_index].offset == m_progress)
  991. {
  992. const size_t blocks =
  993. m_groups[m_group_index].block_index_end - m_groups[m_group_index].block_index_start;
  994. bytes_to_read = VolumeWii::BLOCK_TOTAL_SIZE * blocks;
  995. group_read = true;
  996. if (m_group_index + 1 < m_groups.size() &&
  997. m_groups[m_group_index + 1].offset < m_progress + bytes_to_read)
  998. {
  999. excess_bytes = m_progress + bytes_to_read - m_groups[m_group_index + 1].offset;
  1000. }
  1001. }
  1002. else if (m_group_index < m_groups.size() && m_groups[m_group_index].offset > m_progress)
  1003. {
  1004. bytes_to_read = std::min(bytes_to_read, m_groups[m_group_index].offset - m_progress);
  1005. }
  1006. if (m_progress + bytes_to_read > m_max_progress)
  1007. {
  1008. const u64 bytes_over_max = m_progress + bytes_to_read - m_max_progress;
  1009. if (m_data_size_type == DataSizeType::LowerBound)
  1010. {
  1011. // Disc images in NFS format can have the last referenced block be past m_max_progress.
  1012. // For NFS, reading beyond m_max_progress doesn't return an error, so let's read beyond it.
  1013. excess_bytes = std::max(excess_bytes, bytes_over_max);
  1014. }
  1015. else
  1016. {
  1017. // Don't read beyond the end of the disc.
  1018. bytes_to_read -= bytes_over_max;
  1019. excess_bytes -= std::min(excess_bytes, bytes_over_max);
  1020. content_read = false;
  1021. group_read = false;
  1022. }
  1023. }
  1024. const bool is_data_needed = m_calculating_any_hash || content_read || group_read;
  1025. const bool read_failed = is_data_needed && !ReadChunkAndWaitForAsyncOperations(bytes_to_read);
  1026. if (read_failed)
  1027. {
  1028. ERROR_LOG_FMT(DISCIO, "Read failed at {:#x} to {:#x}", m_progress, m_progress + bytes_to_read);
  1029. m_read_errors_occurred = true;
  1030. m_calculating_any_hash = false;
  1031. }
  1032. m_excess_bytes = excess_bytes;
  1033. const u64 byte_increment = bytes_to_read - excess_bytes;
  1034. if (m_calculating_any_hash)
  1035. {
  1036. if (m_hashes_to_calculate.crc32)
  1037. {
  1038. m_crc32_future = std::async(std::launch::async, [this, byte_increment] {
  1039. m_crc32_context = Common::UpdateCRC32(m_crc32_context, m_data.data(),
  1040. static_cast<size_t>(byte_increment));
  1041. });
  1042. }
  1043. if (m_hashes_to_calculate.md5)
  1044. {
  1045. m_md5_future = std::async(std::launch::async, [this, byte_increment] {
  1046. mbedtls_md5_update_ret(&m_md5_context, m_data.data(), byte_increment);
  1047. });
  1048. }
  1049. if (m_hashes_to_calculate.sha1)
  1050. {
  1051. m_sha1_future = std::async(std::launch::async, [this, byte_increment] {
  1052. m_sha1_context->Update(m_data.data(), byte_increment);
  1053. });
  1054. }
  1055. }
  1056. if (content_read)
  1057. {
  1058. m_content_future = std::async(std::launch::async, [this, read_failed, content] {
  1059. if (read_failed || !m_volume.CheckContentIntegrity(content, m_data, m_ticket))
  1060. {
  1061. AddProblem(Severity::High, Common::FmtFormatT("Content {0:08x} is corrupt.", content.id));
  1062. }
  1063. });
  1064. m_content_index++;
  1065. }
  1066. if (group_read)
  1067. {
  1068. m_group_future = std::async(std::launch::async, [this, read_failed,
  1069. group_index = m_group_index] {
  1070. const GroupToVerify& group = m_groups[group_index];
  1071. u64 offset_in_group = 0;
  1072. for (u64 block_index = group.block_index_start; block_index < group.block_index_end;
  1073. ++block_index, offset_in_group += VolumeWii::BLOCK_TOTAL_SIZE)
  1074. {
  1075. const u64 block_offset = group.offset + offset_in_group;
  1076. if (!read_failed && m_volume.CheckBlockIntegrity(
  1077. block_index, m_data.data() + offset_in_group, group.partition))
  1078. {
  1079. m_biggest_verified_offset =
  1080. std::max(m_biggest_verified_offset, block_offset + VolumeWii::BLOCK_TOTAL_SIZE);
  1081. }
  1082. else
  1083. {
  1084. if (m_scrubber.CanBlockBeScrubbed(block_offset))
  1085. {
  1086. WARN_LOG_FMT(DISCIO, "Integrity check failed for unused block at {:#x}", block_offset);
  1087. m_unused_block_errors[group.partition]++;
  1088. }
  1089. else
  1090. {
  1091. WARN_LOG_FMT(DISCIO, "Integrity check failed for block at {:#x}", block_offset);
  1092. m_block_errors[group.partition]++;
  1093. }
  1094. }
  1095. }
  1096. });
  1097. m_group_index++;
  1098. }
  1099. m_progress += byte_increment;
  1100. }
  1101. u64 VolumeVerifier::GetBytesProcessed() const
  1102. {
  1103. return m_progress;
  1104. }
  1105. u64 VolumeVerifier::GetTotalBytes() const
  1106. {
  1107. return m_max_progress;
  1108. }
  1109. void VolumeVerifier::Finish()
  1110. {
  1111. if (m_done)
  1112. return;
  1113. m_done = true;
  1114. WaitForAsyncOperations();
  1115. if (m_calculating_any_hash)
  1116. {
  1117. if (m_hashes_to_calculate.crc32)
  1118. {
  1119. m_result.hashes.crc32 = std::vector<u8>(4);
  1120. const u32 crc32_be = Common::swap32(m_crc32_context);
  1121. std::memcpy(m_result.hashes.crc32.data(), &crc32_be, 4);
  1122. }
  1123. if (m_hashes_to_calculate.md5)
  1124. {
  1125. m_result.hashes.md5 = std::vector<u8>(16);
  1126. mbedtls_md5_finish_ret(&m_md5_context, m_result.hashes.md5.data());
  1127. }
  1128. if (m_hashes_to_calculate.sha1)
  1129. {
  1130. const auto digest = m_sha1_context->Finish();
  1131. m_result.hashes.sha1 = std::vector<u8>(digest.begin(), digest.end());
  1132. }
  1133. }
  1134. if (m_read_errors_occurred)
  1135. AddProblem(Severity::Medium, Common::GetStringT("Some of the data could not be read."));
  1136. CheckVolumeSize();
  1137. for (auto [partition, blocks] : m_block_errors)
  1138. {
  1139. if (blocks > 0)
  1140. {
  1141. const std::string name = GetPartitionName(m_volume.GetPartitionType(partition));
  1142. AddProblem(Severity::Medium,
  1143. Common::FmtFormatT("Errors were found in {0} blocks in the {1} partition.", blocks,
  1144. name));
  1145. }
  1146. }
  1147. for (auto [partition, blocks] : m_unused_block_errors)
  1148. {
  1149. if (blocks > 0)
  1150. {
  1151. const std::string name = GetPartitionName(m_volume.GetPartitionType(partition));
  1152. AddProblem(Severity::Low,
  1153. Common::FmtFormatT("Errors were found in {0} unused blocks in the {1} partition.",
  1154. blocks, name));
  1155. }
  1156. }
  1157. // Show the most serious problems at the top
  1158. std::stable_sort(m_result.problems.begin(), m_result.problems.end(),
  1159. [](const Problem& p1, const Problem& p2) { return p1.severity > p2.severity; });
  1160. const Severity highest_severity =
  1161. m_result.problems.empty() ? Severity::None : m_result.problems[0].severity;
  1162. if (m_redump_verification)
  1163. m_result.redump = m_redump_verifier.Finish(m_result.hashes);
  1164. if (m_result.redump.status == RedumpVerifier::Status::GoodDump ||
  1165. (m_volume.GetVolumeType() == Platform::WiiWAD && !m_is_not_retail &&
  1166. m_result.problems.empty()))
  1167. {
  1168. if (m_result.problems.empty())
  1169. {
  1170. m_result.summary_text = Common::GetStringT("This is a good dump.");
  1171. }
  1172. else
  1173. {
  1174. m_result.summary_text =
  1175. Common::GetStringT("This is a good dump according to Redump.org, but Dolphin has found "
  1176. "problems. This might be a bug in Dolphin.");
  1177. }
  1178. return;
  1179. }
  1180. if (m_is_datel)
  1181. {
  1182. m_result.summary_text = Common::GetStringT("Dolphin is unable to verify unlicensed discs.");
  1183. return;
  1184. }
  1185. if (m_is_tgc)
  1186. {
  1187. m_result.summary_text =
  1188. Common::GetStringT("Dolphin is unable to verify typical TGC files properly, "
  1189. "since they are not dumps of actual discs.");
  1190. return;
  1191. }
  1192. if (m_result.redump.status == RedumpVerifier::Status::BadDump &&
  1193. highest_severity <= Severity::Low)
  1194. {
  1195. if (m_volume.GetBlobType() == BlobType::NFS)
  1196. {
  1197. m_result.summary_text =
  1198. Common::GetStringT("Compared to the Wii disc release of the game, this is a bad dump. "
  1199. "Despite this, it's possible that this is a good dump compared to the "
  1200. "Wii U eShop release of the game. Dolphin can't verify this.");
  1201. }
  1202. else
  1203. {
  1204. m_result.summary_text = Common::GetStringT(
  1205. "This is a bad dump. This doesn't necessarily mean that the game won't run correctly.");
  1206. }
  1207. }
  1208. else
  1209. {
  1210. if (m_result.redump.status == RedumpVerifier::Status::BadDump)
  1211. {
  1212. m_result.summary_text = Common::GetStringT("This is a bad dump.") + "\n\n";
  1213. }
  1214. switch (highest_severity)
  1215. {
  1216. case Severity::None:
  1217. if (IsWii(m_volume.GetVolumeType()) && !m_is_not_retail)
  1218. {
  1219. m_result.summary_text = Common::GetStringT(
  1220. "No problems were found. This does not guarantee that this is a good dump, "
  1221. "but since Wii titles contain a lot of verification data, it does mean that "
  1222. "there most likely are no problems that will affect emulation.");
  1223. }
  1224. else
  1225. {
  1226. m_result.summary_text = Common::GetStringT("No problems were found.");
  1227. }
  1228. break;
  1229. case Severity::Low:
  1230. if (m_volume.GetBlobType() == BlobType::NFS)
  1231. {
  1232. m_result.summary_text = Common::GetStringT(
  1233. "Compared to the Wii disc release of the game, problems of low severity were found. "
  1234. "Despite this, it's possible that this is a good dump compared to the Wii U eShop "
  1235. "release of the game. Dolphin can't verify this.");
  1236. }
  1237. else
  1238. {
  1239. m_result.summary_text =
  1240. Common::GetStringT("Problems with low severity were found. They will most "
  1241. "likely not prevent the game from running.");
  1242. }
  1243. break;
  1244. case Severity::Medium:
  1245. m_result.summary_text +=
  1246. Common::GetStringT("Problems with medium severity were found. The whole game "
  1247. "or certain parts of the game might not work correctly.");
  1248. break;
  1249. case Severity::High:
  1250. m_result.summary_text += Common::GetStringT(
  1251. "Problems with high severity were found. The game will most likely not work at all.");
  1252. break;
  1253. }
  1254. }
  1255. if (m_volume.GetVolumeType() == Platform::GameCubeDisc)
  1256. {
  1257. m_result.summary_text +=
  1258. Common::GetStringT("\n\nBecause GameCube disc images contain little verification data, "
  1259. "there may be problems that Dolphin is unable to detect.");
  1260. }
  1261. else if (m_is_not_retail)
  1262. {
  1263. m_result.summary_text +=
  1264. Common::GetStringT("\n\nBecause this title is not for retail Wii consoles, "
  1265. "Dolphin cannot ensure that it hasn't been tampered with, even if "
  1266. "signatures appear valid.");
  1267. }
  1268. }
  1269. const VolumeVerifier::Result& VolumeVerifier::GetResult() const
  1270. {
  1271. return m_result;
  1272. }
  1273. void VolumeVerifier::AddProblem(Severity severity, std::string text)
  1274. {
  1275. m_result.problems.emplace_back(Problem{severity, std::move(text)});
  1276. }
  1277. } // namespace DiscIO