VolumeVerifier.cpp 52 KB

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