| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,301 @@ | ||
| // Copyright 2021 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #include "DolphinTool/ConvertCommand.h" | ||
|
|
||
| #include <OptionParser.h> | ||
|
|
||
| namespace DolphinTool | ||
| { | ||
| int ConvertCommand::Main(const std::vector<std::string>& args) | ||
| { | ||
| auto parser = std::make_unique<optparse::OptionParser>(); | ||
|
|
||
| parser->usage("usage: convert [options]... [FILE]..."); | ||
|
|
||
| parser->add_option("-i", "--input") | ||
| .type("string") | ||
| .action("store") | ||
| .help("Path to disc image FILE.") | ||
| .metavar("FILE"); | ||
|
|
||
| parser->add_option("-o", "--output") | ||
| .type("string") | ||
| .action("store") | ||
| .help("Path to the destination FILE.") | ||
| .metavar("FILE"); | ||
|
|
||
| parser->add_option("-f", "--format") | ||
| .type("string") | ||
| .action("store") | ||
| .help("Container format to use. Default is RVZ. [%choices]") | ||
| .choices({"iso", "gcz", "wia", "rvz"}); | ||
|
|
||
| parser->add_option("-s", "--scrub") | ||
| .action("store_true") | ||
| .help("Scrub junk data as part of conversion."); | ||
|
|
||
| parser->add_option("-b", "--block_size") | ||
| .type("int") | ||
| .action("store") | ||
| .help("Block size for GCZ/WIA/RVZ formats, as an integer. Suggested value for RVZ: 131072 " | ||
| "(128 KiB)"); | ||
|
|
||
| parser->add_option("-c", "--compression") | ||
| .type("string") | ||
| .action("store") | ||
| .help("Compression method to use when converting to WIA/RVZ. Suggested value for RVZ: zstd " | ||
| "[%choices]") | ||
| .choices({"none", "zstd", "bzip", "lzma", "lzma2"}); | ||
|
|
||
| parser->add_option("-l", "--compression_level") | ||
| .type("int") | ||
| .action("store") | ||
| .help("Level of compression for the selected method. Ignored if 'none'. Suggested value for " | ||
| "zstd: 5"); | ||
|
|
||
| const optparse::Values& options = parser->parse_args(args); | ||
|
|
||
| // Validate options | ||
|
|
||
| // --input | ||
| const std::string input_file_path = static_cast<const char*>(options.get("input")); | ||
| if (input_file_path.empty()) | ||
| { | ||
| std::cerr << "Error: No input set" << std::endl; | ||
| return 1; | ||
| } | ||
|
|
||
| // --output | ||
| const std::string output_file_path = static_cast<const char*>(options.get("output")); | ||
| if (output_file_path.empty()) | ||
| { | ||
| std::cerr << "Error: No output set" << std::endl; | ||
| return 1; | ||
| } | ||
|
|
||
| // --format | ||
| const std::optional<DiscIO::BlobType> format_o = | ||
| ParseFormatString(static_cast<const char*>(options.get("format"))); | ||
| if (!format_o.has_value()) | ||
| { | ||
| std::cerr << "Error: No output format set" << std::endl; | ||
| return 1; | ||
| } | ||
| const DiscIO::BlobType format = format_o.value(); | ||
|
|
||
| // Open the volume now for inspection | ||
| std::unique_ptr<DiscIO::Volume> volume = DiscIO::CreateVolume(input_file_path); | ||
| if (!volume) | ||
| { | ||
| std::cerr << "Error: Unable to open disc image" << std::endl; | ||
| return 1; | ||
| } | ||
|
|
||
| // --scrub | ||
| const bool scrub = static_cast<bool>(options.get("scrub")); | ||
|
|
||
| if (scrub && volume->IsDatelDisc()) | ||
| { | ||
| std::cerr << "Error: Scrubbing a Datel disc is not supported"; | ||
| return 1; | ||
| } | ||
|
|
||
| if (scrub && format == DiscIO::BlobType::RVZ) | ||
| { | ||
| std::cerr << "Warning: Scrubbing an RVZ container does not offer significant space advantages. " | ||
| "Continuing anyway." | ||
| << std::endl; | ||
| } | ||
|
|
||
| if (scrub && format == DiscIO::BlobType::PLAIN) | ||
| { | ||
| std::cerr << "Warning: Scrubbing does not save space when converting to ISO unless using " | ||
| "external compression. Continuing anyway." | ||
| << std::endl; | ||
| } | ||
|
|
||
| if (!scrub && format == DiscIO::BlobType::GCZ && | ||
| volume->GetVolumeType() == DiscIO::Platform::WiiDisc && !volume->IsDatelDisc()) | ||
| { | ||
| std::cerr << "Warning: Converting Wii disc images to GCZ without scrubbing may not offer space " | ||
| "advantages over ISO. Continuing anyway." | ||
| << std::endl; | ||
| } | ||
|
|
||
| if (volume->IsNKit()) | ||
| { | ||
| std::cerr << "Warning: Converting an NKit file, output will still be NKit! Continuing anyway." | ||
| << std::endl; | ||
| } | ||
|
|
||
| // --block_size | ||
| std::optional<int> block_size_o; | ||
| if (options.is_set("block_size")) | ||
| block_size_o = static_cast<int>(options.get("block_size")); | ||
|
|
||
| if (format == DiscIO::BlobType::GCZ || format == DiscIO::BlobType::WIA || | ||
| format == DiscIO::BlobType::RVZ) | ||
| { | ||
| if (!block_size_o.has_value()) | ||
| { | ||
| std::cerr << "Error: Block size must be set for GCZ/RVZ/WIA" << std::endl; | ||
| return 1; | ||
| } | ||
|
|
||
| if (!DiscIO::IsDiscImageBlockSizeValid(block_size_o.value(), format)) | ||
| { | ||
| std::cerr << "Error: Block size is not valid for this format" << std::endl; | ||
| return 1; | ||
| } | ||
|
|
||
| if (block_size_o.value() < DiscIO::PREFERRED_MIN_BLOCK_SIZE || | ||
| block_size_o.value() > DiscIO::PREFERRED_MAX_BLOCK_SIZE) | ||
| { | ||
| std::cerr << "Warning: Block size is not ideal for performance. Continuing anyway." | ||
| << std::endl; | ||
| } | ||
|
|
||
| if (format == DiscIO::BlobType::GCZ && | ||
| !DiscIO::IsGCZBlockSizeLegacyCompatible(block_size_o.value(), volume->GetSize())) | ||
| { | ||
| std::cerr << "Warning: For GCZs to be compatible with Dolphin < 5.0-11893, " | ||
| "the file size must be an integer multiple of the block size " | ||
| "and must not be an integer multiple of the block size multiplied by 32. " | ||
| "Continuing anyway." | ||
| << std::endl; | ||
| } | ||
| } | ||
|
|
||
| // --compress, --compress_level | ||
| std::optional<DiscIO::WIARVZCompressionType> compression_o = | ||
| ParseCompressionTypeString(static_cast<const char*>(options.get("compression"))); | ||
|
|
||
| std::optional<int> compression_level_o; | ||
| if (options.is_set("compression_level")) | ||
| compression_level_o = static_cast<int>(options.get("compression_level")); | ||
|
|
||
| if (format == DiscIO::BlobType::WIA || format == DiscIO::BlobType::RVZ) | ||
| { | ||
| if (!compression_o.has_value()) | ||
| { | ||
| std::cerr << "Error: Compression format must be set for WIA or RVZ" << std::endl; | ||
| return 1; | ||
| } | ||
|
|
||
| if ((format == DiscIO::BlobType::WIA && | ||
| compression_o.value() == DiscIO::WIARVZCompressionType::Zstd) || | ||
| (format == DiscIO::BlobType::RVZ && | ||
| compression_o.value() == DiscIO::WIARVZCompressionType::Purge)) | ||
| { | ||
| std::cerr << "Error: Compression type is not supported for the container format" << std::endl; | ||
| return 1; | ||
| } | ||
|
|
||
| if (compression_o.value() == DiscIO::WIARVZCompressionType::None) | ||
| { | ||
| compression_level_o = 0; | ||
| } | ||
| else | ||
| { | ||
| if (!compression_level_o.has_value()) | ||
| { | ||
| std::cerr << "Error: Compression level must be set when compression type is not 'none'" | ||
| << std::endl; | ||
| return 1; | ||
| } | ||
|
|
||
| const std::pair<int, int> range = DiscIO::GetAllowedCompressionLevels(compression_o.value()); | ||
| if (compression_level_o.value() < range.first || compression_level_o.value() > range.second) | ||
| { | ||
| std::cerr << "Error: Compression level not in acceptable range" << std::endl; | ||
| return 1; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Open the blob reader | ||
| std::unique_ptr<DiscIO::BlobReader> blob_reader = | ||
| scrub ? DiscIO::ScrubbedBlob::Create(input_file_path) : | ||
| DiscIO::CreateBlobReader(input_file_path); | ||
| if (!blob_reader) | ||
| { | ||
| std::cerr << "Error: Unable to process disc image. If --scrub is enabled, try again without it." | ||
| << std::endl; | ||
| return 1; | ||
| } | ||
|
|
||
| // Perform the conversion | ||
| const auto NOOP_STATUS_CALLBACK = [](const std::string& text, float percent) { return true; }; | ||
|
|
||
| bool success = false; | ||
|
|
||
| switch (format) | ||
| { | ||
| case DiscIO::BlobType::PLAIN: | ||
| success = DiscIO::ConvertToPlain(blob_reader.get(), input_file_path, output_file_path, | ||
| NOOP_STATUS_CALLBACK); | ||
| break; | ||
|
|
||
| case DiscIO::BlobType::GCZ: | ||
| success = DiscIO::ConvertToGCZ(blob_reader.get(), input_file_path, output_file_path, | ||
| volume->GetVolumeType() == DiscIO::Platform::WiiDisc ? 1 : 0, | ||
| block_size_o.value(), NOOP_STATUS_CALLBACK); | ||
| break; | ||
|
|
||
| case DiscIO::BlobType::WIA: | ||
| case DiscIO::BlobType::RVZ: | ||
| success = DiscIO::ConvertToWIAOrRVZ(blob_reader.get(), input_file_path, output_file_path, | ||
| format == DiscIO::BlobType::RVZ, compression_o.value(), | ||
| compression_level_o.value(), block_size_o.value(), | ||
| NOOP_STATUS_CALLBACK); | ||
| break; | ||
|
|
||
| default: | ||
| ASSERT(false); | ||
| break; | ||
| } | ||
|
|
||
| if (!success) | ||
| { | ||
| std::cerr << "Error: Conversion failed" << std::endl; | ||
| return 1; | ||
| } | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| std::optional<DiscIO::WIARVZCompressionType> | ||
| ConvertCommand::ParseCompressionTypeString(const std::string compression_str) | ||
| { | ||
| if (compression_str == "none") | ||
| return DiscIO::WIARVZCompressionType::None; | ||
| else if (compression_str == "purge") | ||
| return DiscIO::WIARVZCompressionType::Purge; | ||
| else if (compression_str == "bzip2") | ||
| return DiscIO::WIARVZCompressionType::Bzip2; | ||
| else if (compression_str == "lzma") | ||
| return DiscIO::WIARVZCompressionType::LZMA; | ||
| else if (compression_str == "lzma2") | ||
| return DiscIO::WIARVZCompressionType::LZMA2; | ||
| else if (compression_str == "zstd") | ||
| return DiscIO::WIARVZCompressionType::Zstd; | ||
| else | ||
| return std::nullopt; | ||
| } | ||
|
|
||
| std::optional<DiscIO::BlobType> ConvertCommand::ParseFormatString(const std::string format_str) | ||
| { | ||
| if (format_str == "iso") | ||
| return DiscIO::BlobType::PLAIN; | ||
| else if (format_str == "gcz") | ||
| return DiscIO::BlobType::GCZ; | ||
| else if (format_str == "wia") | ||
| return DiscIO::BlobType::WIA; | ||
| else if (format_str == "rvz") | ||
| return DiscIO::BlobType::RVZ; | ||
| else | ||
| return std::nullopt; | ||
| } | ||
|
|
||
| } // namespace DolphinTool |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| // Copyright 2021 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <cstdint> | ||
| #include <optional> | ||
|
|
||
| #include "DiscIO/Blob.h" | ||
| #include "DiscIO/DiscUtils.h" | ||
| #include "DiscIO/ScrubbedBlob.h" | ||
| #include "DiscIO/Volume.h" | ||
| #include "DiscIO/VolumeDisc.h" | ||
| #include "DiscIO/WIABlob.h" | ||
| #include "DolphinTool/Command.h" | ||
|
|
||
| namespace DolphinTool | ||
| { | ||
| class ConvertCommand final : public Command | ||
| { | ||
| public: | ||
| int Main(const std::vector<std::string>& args) override; | ||
|
|
||
| private: | ||
| std::optional<DiscIO::WIARVZCompressionType> | ||
| ParseCompressionTypeString(const std::string compression_str); | ||
| std::optional<DiscIO::BlobType> ParseFormatString(const std::string format_str); | ||
| }; | ||
|
|
||
| } // namespace DolphinTool |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| <?xml version="1.0" encoding="UTF-8" standalone="yes"?> | ||
| <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> | ||
| <assemblyIdentity | ||
| version="1.0.0.0" | ||
| processorArchitecture="amd64" | ||
| name="DolphinTeam.DolphinTool" | ||
| type="win32" | ||
| /> | ||
| <description>Dolphin Tool</description> | ||
| <dependency> | ||
| <dependentAssembly> | ||
| <assemblyIdentity | ||
| type="win32" | ||
| name="Microsoft.Windows.Common-Controls" | ||
| version="6.0.0.0" | ||
| processorArchitecture="*" | ||
| publicKeyToken="6595b64144ccf1df" | ||
| language="*" | ||
| /> | ||
| </dependentAssembly> | ||
| </dependency> | ||
| <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> | ||
| <application> | ||
| <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/> | ||
| <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/> | ||
| <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> | ||
| <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> | ||
| </application> | ||
| </compatibility> | ||
| </assembly> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
| <ItemGroup> | ||
| <ClCompile Include="ConvertCommand.cpp" /> | ||
| <ClCompile Include="VerifyCommand.cpp" /> | ||
| <ClCompile Include="ToolHeadlessPlatform.cpp" /> | ||
| <ClCompile Include="ToolMain.cpp" /> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <ClInclude Include="Command.h" /> | ||
| <ClInclude Include="ConvertCommand.h" /> | ||
| <ClInclude Include="VerifyCommand.h" /> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <Manifest Include="DolphinTool.exe.manifest" /> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <Text Include="CMakeLists.txt" /> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <ResourceCompile Include="DolphinTool.rc" /> | ||
| </ItemGroup> | ||
| </Project> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| // Microsoft Visual C++ generated resource script. | ||
| // | ||
| #include "resource.h" | ||
| IDI_ICON1 ICON "..\\..\\..\\Installer\\Dolphin.ico" | ||
| "dolphin" ICON "..\\..\\..\\Installer\\Dolphin.ico" | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
| <Import Project="..\..\VSProps\Base.Macros.props" /> | ||
| <Import Project="$(VSPropsDir)Base.Targets.props" /> | ||
| <PropertyGroup Label="Globals"> | ||
| <ProjectGuid>{8F91523C-5C5E-4B22-A1F1-67560B6DC714}</ProjectGuid> | ||
| </PropertyGroup> | ||
| <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> | ||
| <Import Project="$(VSPropsDir)Configuration.Application.props" /> | ||
| <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> | ||
| <ImportGroup Label="ExtensionSettings" /> | ||
| <ImportGroup Label="PropertySheets"> | ||
| <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> | ||
| <Import Project="$(VSPropsDir)Base.props" /> | ||
| <Import Project="$(VSPropsDir)PCHUse.props" /> | ||
| </ImportGroup> | ||
| <PropertyGroup Label="UserMacros" /> | ||
| <ItemDefinitionGroup> | ||
| <Link> | ||
| <AdditionalDependencies>avrt.lib;iphlpapi.lib;winmm.lib;setupapi.lib;rpcrt4.lib;comctl32.lib;Shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> | ||
| <AdditionalDependencies Condition="'$(Platform)'=='x64'">opengl32.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;%(AdditionalDependencies)</AdditionalDependencies> | ||
| <AdditionalLibraryDirectories Condition="'$(Platform)'=='x64'">$(ExternalsDir)ffmpeg\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> | ||
| <SubSystem>Console</SubSystem> | ||
| </Link> | ||
| </ItemDefinitionGroup> | ||
| <ItemGroup> | ||
| <ProjectReference Include="$(CoreDir)DolphinLib.vcxproj"> | ||
| <Project>{D79392F7-06D6-4B4B-A39F-4D587C215D3A}</Project> | ||
| </ProjectReference> | ||
| <ProjectReference Include="$(CoreDir)Common\SCMRevGen.vcxproj"> | ||
| <Project>{41279555-f94f-4ebc-99de-af863c10c5c4}</Project> | ||
| </ProjectReference> | ||
| <ProjectReference Include="$(DolphinRootDir)Languages\Languages.vcxproj"> | ||
| <Project>{0e033be3-2e08-428e-9ae9-bc673efa12b5}</Project> | ||
| </ProjectReference> | ||
| </ItemGroup> | ||
| <Import Project="$(ExternalsDir)ExternalsReferenceAll.props" /> | ||
| <ItemGroup> | ||
| <ClCompile Include="ConvertCommand.cpp" /> | ||
| <ClCompile Include="VerifyCommand.cpp" /> | ||
| <ClCompile Include="ToolHeadlessPlatform.cpp" /> | ||
| <ClCompile Include="ToolMain.cpp" /> | ||
| </ItemGroup> | ||
| <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> | ||
| <ImportGroup Label="ExtensionTargets"> | ||
| </ImportGroup> | ||
| <!--Copy the .exe to binary output folder--> | ||
| <ItemGroup> | ||
| <SourceFiles Include="$(TargetPath)" /> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <ClInclude Include="Command.h" /> | ||
| <ClInclude Include="ConvertCommand.h" /> | ||
| <ClInclude Include="VerifyCommand.h" /> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <Manifest Include="DolphinTool.exe.manifest" /> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <Text Include="CMakeLists.txt" /> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <ResourceCompile Include="DolphinTool.rc" /> | ||
| </ItemGroup> | ||
| <Target Name="AfterBuild" Inputs="@(SourceFiles)" Outputs="@(SourceFiles -> '$(BinaryOutputDir)%(Filename)%(Extension)')"> | ||
| <Message Text="Copy: @(SourceFiles) -> $(BinaryOutputDir)" Importance="High" /> | ||
| <Copy SourceFiles="@(SourceFiles)" DestinationFolder="$(BinaryOutputDir)" /> | ||
| </Target> | ||
| </Project> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| // Copyright 2021 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #include <cstdio> | ||
| #include <thread> | ||
|
|
||
| #include <memory> | ||
| #include <string> | ||
|
|
||
| #include "Common/Flag.h" | ||
| #include "Common/WindowSystemInfo.h" | ||
|
|
||
| #include "Core/Core.h" | ||
| #include "Core/DolphinAnalytics.h" | ||
| #include "Core/Host.h" | ||
|
|
||
| // Begin stubs needed to satisfy Core dependencies | ||
| #include "VideoCommon/RenderBase.h" | ||
|
|
||
| std::vector<std::string> Host_GetPreferredLocales() | ||
| { | ||
| return {}; | ||
| } | ||
|
|
||
| void Host_NotifyMapLoaded() | ||
| { | ||
| } | ||
|
|
||
| void Host_RefreshDSPDebuggerWindow() | ||
| { | ||
| } | ||
|
|
||
| bool Host_UIBlocksControllerState() | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| void Host_Message(HostMessageID id) | ||
| { | ||
| } | ||
|
|
||
| void Host_UpdateTitle(const std::string& title) | ||
| { | ||
| } | ||
|
|
||
| void Host_UpdateDisasmDialog() | ||
| { | ||
| } | ||
|
|
||
| void Host_UpdateMainFrame() | ||
| { | ||
| } | ||
|
|
||
| void Host_RequestRenderWindowSize(int width, int height) | ||
| { | ||
| } | ||
|
|
||
| bool Host_RendererHasFocus() | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| bool Host_RendererHasFullFocus() | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| bool Host_RendererIsFullscreen() | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| void Host_YieldToUI() | ||
| { | ||
| } | ||
|
|
||
| void Host_TitleChanged() | ||
| { | ||
| } | ||
|
|
||
| std::unique_ptr<GBAHostInterface> Host_CreateGBAHost(std::weak_ptr<HW::GBA::Core> core) | ||
| { | ||
| return nullptr; | ||
| } | ||
| // End stubs to satisfy Core dependencies |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| // Copyright 2021 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #include <cstdio> | ||
| #include <cstring> | ||
| #include <iostream> | ||
| #include <string> | ||
| #include <vector> | ||
|
|
||
| #include "Common/Version.h" | ||
| #include "DolphinTool/Command.h" | ||
| #include "DolphinTool/ConvertCommand.h" | ||
| #include "DolphinTool/VerifyCommand.h" | ||
|
|
||
| static int PrintUsage(int code) | ||
| { | ||
| std::cerr << "usage: dolphin-tool COMMAND -h" << std::endl << std::endl; | ||
| std::cerr << "commands supported: [convert, verify]" << std::endl; | ||
|
|
||
| return code; | ||
| } | ||
|
|
||
| int main(int argc, char* argv[]) | ||
| { | ||
| if (argc < 2) | ||
| return PrintUsage(1); | ||
|
|
||
| std::vector<std::string> args(argv, argv + argc); | ||
|
|
||
| std::string command_str = args.at(1); | ||
|
|
||
| // Take off the command selector before passing arguments down | ||
| args.erase(args.begin(), args.begin() + 1); | ||
|
|
||
| std::unique_ptr<DolphinTool::Command> command; | ||
|
|
||
| if (command_str == "convert") | ||
| command = std::make_unique<DolphinTool::ConvertCommand>(); | ||
| else if (command_str == "verify") | ||
| command = std::make_unique<DolphinTool::VerifyCommand>(); | ||
| else | ||
| return PrintUsage(1); | ||
|
|
||
| return command->Main(args); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,175 @@ | ||
| // Copyright 2021 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #include "DolphinTool/VerifyCommand.h" | ||
|
|
||
| #include <OptionParser.h> | ||
|
|
||
| namespace DolphinTool | ||
| { | ||
| int VerifyCommand::Main(const std::vector<std::string>& args) | ||
| { | ||
| auto parser = std::make_unique<optparse::OptionParser>(); | ||
|
|
||
| parser->usage("usage: verify [options]..."); | ||
|
|
||
| parser->add_option("-i", "--input") | ||
| .type("string") | ||
| .action("store") | ||
| .help("Path to disc image 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"}); | ||
|
|
||
| const optparse::Values& options = parser->parse_args(args); | ||
|
|
||
| // Validate options | ||
| const std::string input_file_path = static_cast<const char*>(options.get("input")); | ||
| if (input_file_path.empty()) | ||
| { | ||
| std::cerr << "Error: No input set" << std::endl; | ||
| return 1; | ||
| } | ||
|
|
||
| std::optional<std::string> algorithm; | ||
| if (options.is_set("algorithm")) | ||
| { | ||
| algorithm = static_cast<const char*>(options.get("algorithm")); | ||
| } | ||
|
|
||
| bool enable_crc32 = algorithm == std::nullopt || algorithm == "crc32"; | ||
| bool enable_md5 = algorithm == std::nullopt || algorithm == "md5"; | ||
| bool enable_sha1 = algorithm == std::nullopt || algorithm == "sha1"; | ||
|
|
||
| if (!enable_crc32 && !enable_md5 && !enable_sha1) | ||
| { | ||
| // optparse should protect from this | ||
| std::cerr << "Error: No algorithms selected for the operation" << std::endl; | ||
| return 1; | ||
| } | ||
|
|
||
| // Open the volume | ||
| std::shared_ptr<DiscIO::VolumeDisc> volume = DiscIO::CreateDisc(input_file_path); | ||
| if (!volume) | ||
| { | ||
| std::cerr << "Error: Unable to open disc image" << std::endl; | ||
| return 1; | ||
| } | ||
|
|
||
| // Verify the volume | ||
| const std::optional<DiscIO::VolumeVerifier::Result> result = | ||
| VerifyVolume(volume, enable_crc32, enable_md5, enable_sha1); | ||
| if (!result) | ||
| { | ||
| std::cerr << "Error: Unable to verify volume" << std::endl; | ||
| return 1; | ||
| } | ||
|
|
||
| if (algorithm == std::nullopt) | ||
| { | ||
| PrintFullReport(result); | ||
| } | ||
| else | ||
| { | ||
| if (enable_crc32 && !result->hashes.crc32.empty()) | ||
| std::cout << HashToHexString(result->hashes.crc32) << std::endl; | ||
| else if (enable_md5 && !result->hashes.md5.empty()) | ||
| std::cout << HashToHexString(result->hashes.md5) << std::endl; | ||
| else if (enable_sha1 && !result->hashes.sha1.empty()) | ||
| std::cout << HashToHexString(result->hashes.sha1) << std::endl; | ||
| else | ||
| { | ||
| std::cerr << "Error: No hash available" << std::endl; | ||
| return 1; | ||
| } | ||
| } | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| void VerifyCommand::PrintFullReport(const std::optional<DiscIO::VolumeVerifier::Result> result) | ||
| { | ||
| if (!result->hashes.crc32.empty()) | ||
| std::cout << "CRC32: " << HashToHexString(result->hashes.crc32) << std::endl; | ||
| else | ||
| std::cout << "CRC32 not available" << std::endl; | ||
|
|
||
| if (!result->hashes.md5.empty()) | ||
| std::cout << "MD5: " << HashToHexString(result->hashes.md5) << std::endl; | ||
| else | ||
| std::cout << "MD5 not available" << std::endl; | ||
|
|
||
| if (!result->hashes.sha1.empty()) | ||
| std::cout << "SHA1: " << HashToHexString(result->hashes.sha1) << std::endl; | ||
| else | ||
| std::cout << "SHA1 not available" << std::endl; | ||
|
|
||
| std::cout << "Problems Found: " << (result->problems.size() > 0 ? "Yes" : "No") << std::endl; | ||
|
|
||
| for (int i = 0; i < static_cast<int>(result->problems.size()); ++i) | ||
| { | ||
| const DiscIO::VolumeVerifier::Problem problem = result->problems[i]; | ||
|
|
||
| std::cout << std::endl << "Severity: "; | ||
| switch (problem.severity) | ||
| { | ||
| case DiscIO::VolumeVerifier::Severity::Low: | ||
| std::cout << "Low"; | ||
| break; | ||
| case DiscIO::VolumeVerifier::Severity::Medium: | ||
| std::cout << "Medium"; | ||
| break; | ||
| case DiscIO::VolumeVerifier::Severity::High: | ||
| std::cout << "High"; | ||
| break; | ||
| case DiscIO::VolumeVerifier::Severity::None: | ||
| std::cout << "None"; | ||
| break; | ||
| default: | ||
| ASSERT(false); | ||
| break; | ||
| } | ||
| std::cout << std::endl; | ||
|
|
||
| std::cout << "Summary: " << problem.text << std::endl << std::endl; | ||
| } | ||
| } | ||
|
|
||
| std::optional<DiscIO::VolumeVerifier::Result> | ||
| VerifyCommand::VerifyVolume(std::shared_ptr<DiscIO::VolumeDisc> volume, bool enable_crc32, | ||
| bool enable_md5, bool enable_sha1) | ||
| { | ||
| if (!volume) | ||
| return std::nullopt; | ||
|
|
||
| DiscIO::VolumeVerifier verifier(*volume, false, {enable_crc32, enable_md5, enable_sha1}); | ||
|
|
||
| verifier.Start(); | ||
| while (verifier.GetBytesProcessed() != verifier.GetTotalBytes()) | ||
| { | ||
| verifier.Process(); | ||
| } | ||
| verifier.Finish(); | ||
|
|
||
| const DiscIO::VolumeVerifier::Result result = verifier.GetResult(); | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| std::string VerifyCommand::HashToHexString(const std::vector<u8>& hash) | ||
| { | ||
| std::stringstream ss; | ||
| ss << std::hex; | ||
| for (int i = 0; i < static_cast<int>(hash.size()); ++i) | ||
| { | ||
| ss << std::setw(2) << std::setfill('0') << static_cast<int>(hash[i]); | ||
| } | ||
| return ss.str(); | ||
| } | ||
|
|
||
| } // namespace DolphinTool |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| // Copyright 2021 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <iostream> | ||
| #include <string> | ||
| #include <vector> | ||
|
|
||
| #include "DiscIO/Volume.h" | ||
| #include "DiscIO/VolumeDisc.h" | ||
| #include "DiscIO/VolumeVerifier.h" | ||
| #include "DolphinTool/Command.h" | ||
|
|
||
| namespace DolphinTool | ||
| { | ||
| class VerifyCommand final : public Command | ||
| { | ||
| public: | ||
| int Main(const std::vector<std::string>& args) override; | ||
|
|
||
| private: | ||
| void PrintFullReport(const std::optional<DiscIO::VolumeVerifier::Result> result); | ||
|
|
||
| std::optional<DiscIO::VolumeVerifier::Result> | ||
| VerifyVolume(std::shared_ptr<DiscIO::VolumeDisc> volume, bool enable_crc32, bool enable_md5, | ||
| bool enable_sha1); | ||
|
|
||
| std::string HashToHexString(const std::vector<u8>& hash); | ||
| }; | ||
|
|
||
| } // namespace DolphinTool |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| // Copyright 2017 Dolphin Emulator Project | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| //{{NO_DEPENDENCIES}} | ||
| // Microsoft Visual C++ generated include file. | ||
| // Used by DolphinNoGui.rc | ||
| // | ||
| #ifdef RC_INVOKED | ||
| #define IDI_ICON1 101 | ||
| #else | ||
| #define IDI_ICON1 MAKEINTRESOURCE(101) | ||
| #endif | ||
|
|
||
| // Next default values for new objects | ||
| // | ||
| #ifdef APSTUDIO_INVOKED | ||
| #ifndef APSTUDIO_READONLY_SYMBOLS | ||
| #define _APS_NEXT_RESOURCE_VALUE 102 | ||
| #define _APS_NEXT_COMMAND_VALUE 40001 | ||
| #define _APS_NEXT_CONTROL_VALUE 1001 | ||
| #define _APS_NEXT_SYMED_VALUE 101 | ||
| #endif | ||
| #endif |