// Copyright 2021 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "DolphinTool/VerifyCommand.h" #include #include #include #include #include #include #include "Common/StringUtil.h" #include "Core/AchievementManager.h" #include "DiscIO/Volume.h" #include "DiscIO/VolumeVerifier.h" #include "UICommon/UICommon.h" namespace DolphinTool { static std::string HashToHexString(const std::vector& hash) { std::stringstream ss; ss << std::hex; for (int i = 0; i < static_cast(hash.size()); ++i) { ss << std::setw(2) << std::setfill('0') << static_cast(hash[i]); } return ss.str(); } static void PrintFullReport(const DiscIO::VolumeVerifier::Result& result) { if (!result.hashes.crc32.empty()) fmt::print(std::cout, "CRC32: {}\n", HashToHexString(result.hashes.crc32)); else fmt::print(std::cout, "CRC32 not computed\n"); if (!result.hashes.md5.empty()) fmt::print(std::cout, "MD5: {}\n", HashToHexString(result.hashes.md5)); else fmt::print(std::cout, "MD5 not computed\n"); if (!result.hashes.sha1.empty()) fmt::print(std::cout, "SHA1: {}\n", HashToHexString(result.hashes.sha1)); else fmt::print(std::cout, "SHA1 not computed\n"); fmt::print(std::cout, "Problems Found: {}\n", result.problems.empty() ? "No" : "Yes"); for (const auto& problem : result.problems) { fmt::print(std::cout, "\nSeverity: "); switch (problem.severity) { case DiscIO::VolumeVerifier::Severity::Low: fmt::print(std::cout, "Low"); break; case DiscIO::VolumeVerifier::Severity::Medium: fmt::print(std::cout, "Medium"); break; case DiscIO::VolumeVerifier::Severity::High: fmt::print(std::cout, "High"); break; case DiscIO::VolumeVerifier::Severity::None: fmt::print(std::cout, "None"); break; default: ASSERT(false); break; } fmt::print(std::cout, "\nSummary: {}\n\n", problem.text); } } int VerifyCommand(const std::vector& args) { optparse::OptionParser parser; parser.usage("usage: verify [options]..."); parser.add_option("-u", "--user") .type("string") .action("store") .help("User folder path, required for temporary processing files. " "Will be automatically created if this option is not set.") .set_default(""); parser.add_option("-i", "--input") .type("string") .action("store") .help("Path to input file.") .metavar("FILE"); parser.add_option("-a", "--algorithm") .type("string") .action("store") .help("Optional. Compute and print the digest using the selected algorithm, then exit. " "[%choices]") .choices({"crc32", "md5", "sha1", "rchash"}); const optparse::Values& options = parser.parse_args(args); // Initialize the dolphin user directory, required for temporary processing files // If this is not set, destructive file operations could occur due to path confusion UICommon::SetUserDirectory(options["user"]); UICommon::Init(); // Validate options if (!options.is_set("input")) { fmt::print(std::cerr, "Error: No input set\n"); return EXIT_FAILURE; } const std::string& input_file_path = options["input"]; bool rc_hash_calculate = false; std::string rc_hash_result = "0"; DiscIO::Hashes hashes_to_calculate{}; const bool algorithm_is_set = options.is_set("algorithm"); if (!algorithm_is_set) { hashes_to_calculate = DiscIO::VolumeVerifier::GetDefaultHashesToCalculate(); } else { const std::string& algorithm = options["algorithm"]; if (algorithm == "crc32") hashes_to_calculate.crc32 = true; else if (algorithm == "md5") hashes_to_calculate.md5 = true; else if (algorithm == "sha1") hashes_to_calculate.sha1 = true; else if (algorithm == "rchash") rc_hash_calculate = true; } if (!hashes_to_calculate.crc32 && !hashes_to_calculate.md5 && !hashes_to_calculate.sha1 && !rc_hash_calculate) { // optparse should protect from this fmt::print(std::cerr, "Error: No algorithms selected for the operation\n"); return EXIT_FAILURE; } // Open the volume const std::unique_ptr volume = DiscIO::CreateVolume(input_file_path); if (!volume) { fmt::print(std::cerr, "Error: Unable to open input file\n"); return EXIT_FAILURE; } // Verify the volume DiscIO::VolumeVerifier verifier(*volume, false, hashes_to_calculate); verifier.Start(); while (verifier.GetBytesProcessed() != verifier.GetTotalBytes()) { verifier.Process(); } verifier.Finish(); const DiscIO::VolumeVerifier::Result& result = verifier.GetResult(); // Calculate rcheevos hash if (rc_hash_calculate) { rc_hash_result = AchievementManager::CalculateHash(input_file_path); } // Print the report if (!algorithm_is_set) { PrintFullReport(result); } else { if (hashes_to_calculate.crc32 && !result.hashes.crc32.empty()) fmt::print(std::cout, "{}\n", HashToHexString(result.hashes.crc32)); else if (hashes_to_calculate.md5 && !result.hashes.md5.empty()) fmt::print(std::cout, "{}\n", HashToHexString(result.hashes.md5)); else if (hashes_to_calculate.sha1 && !result.hashes.sha1.empty()) fmt::print(std::cout, "{}\n", HashToHexString(result.hashes.sha1)); else if (rc_hash_calculate) fmt::print(std::cout, "{}\n", rc_hash_result); else { fmt::print(std::cerr, "Error: No hash computed\n"); return EXIT_FAILURE; } } return EXIT_SUCCESS; } } // namespace DolphinTool