Merge pull request #10252 from ssdsnake/feature_dolphintool
DolphinTool: Add CLI tool subsystem + commands for verifying and converting RVZ/ISO/WIA/GCZ
This commit is contained in:
commit
d5d21c6533
|
@ -32,6 +32,10 @@ if(NOT WIN32 AND NOT APPLE AND NOT HAIKU)
|
||||||
option(ENABLE_EGL "Enables EGL OpenGL Interface" ON)
|
option(ENABLE_EGL "Enables EGL OpenGL Interface" ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(NOT ANDROID)
|
||||||
|
option(ENABLE_CLI_TOOL "Enable dolphin-tool, a CLI-based utility for functions such as managing disc images" ON)
|
||||||
|
endif()
|
||||||
|
|
||||||
option(USE_SHARED_ENET "Use shared libenet if found rather than Dolphin's soon-to-compatibly-diverge version" OFF)
|
option(USE_SHARED_ENET "Use shared libenet if found rather than Dolphin's soon-to-compatibly-diverge version" OFF)
|
||||||
option(USE_UPNP "Enables UPnP port mapping support" ON)
|
option(USE_UPNP "Enables UPnP port mapping support" ON)
|
||||||
option(ENABLE_NOGUI "Enable NoGUI frontend" ON)
|
option(ENABLE_NOGUI "Enable NoGUI frontend" ON)
|
||||||
|
|
|
@ -11,6 +11,10 @@ if(ENABLE_NOGUI)
|
||||||
add_subdirectory(DolphinNoGUI)
|
add_subdirectory(DolphinNoGUI)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(ENABLE_CLI_TOOL)
|
||||||
|
add_subdirectory(DolphinTool)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(ENABLE_QT)
|
if(ENABLE_QT)
|
||||||
add_subdirectory(DolphinQt)
|
add_subdirectory(DolphinQt)
|
||||||
endif()
|
endif()
|
||||||
|
@ -23,7 +27,6 @@ if (APPLE)
|
||||||
add_subdirectory(MacUpdater)
|
add_subdirectory(MacUpdater)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
add_subdirectory(WinUpdater)
|
add_subdirectory(WinUpdater)
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
|
#include "Common/MathUtil.h"
|
||||||
|
#include "DiscIO/Blob.h"
|
||||||
#include "DiscIO/Filesystem.h"
|
#include "DiscIO/Filesystem.h"
|
||||||
#include "DiscIO/Volume.h"
|
#include "DiscIO/Volume.h"
|
||||||
|
|
||||||
|
@ -198,4 +200,49 @@ u64 GetBiggestReferencedOffset(const Volume& volume, const std::vector<Partition
|
||||||
return biggest_offset;
|
return biggest_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsGCZBlockSizeLegacyCompatible(int block_size, u64 file_size)
|
||||||
|
{
|
||||||
|
// In order for versions of Dolphin prior to 5.0-11893 to be able to convert a GCZ file
|
||||||
|
// to ISO without messing up the final part of the file in some way, the file size
|
||||||
|
// must be an integer multiple of the block size (fixed in 3aa463c) and must not be
|
||||||
|
// an integer multiple of the block size multiplied by 32 (fixed in 26b21e3).
|
||||||
|
return file_size % block_size == 0 && file_size % (block_size * 32) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsDiscImageBlockSizeValid(int block_size, DiscIO::BlobType format)
|
||||||
|
{
|
||||||
|
switch (format)
|
||||||
|
{
|
||||||
|
case DiscIO::BlobType::GCZ:
|
||||||
|
// Block size "must" be a power of 2
|
||||||
|
if (!MathUtil::IsPow2(block_size))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case DiscIO::BlobType::WIA:
|
||||||
|
// Block size must not be less than the minimum, and must be a multiple of it
|
||||||
|
if (block_size < WIA_MIN_BLOCK_SIZE || block_size % WIA_MIN_BLOCK_SIZE != 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case DiscIO::BlobType::RVZ:
|
||||||
|
// Block size must not be smaller than the minimum
|
||||||
|
// Block sizes smaller than the large block size threshold must be a power of 2
|
||||||
|
// Block sizes larger than that threshold must be a multiple of the threshold
|
||||||
|
if (block_size < RVZ_MIN_BLOCK_SIZE ||
|
||||||
|
(block_size < RVZ_BIG_BLOCK_SIZE_LCM && !MathUtil::IsPow2(block_size)) ||
|
||||||
|
(block_size > RVZ_BIG_BLOCK_SIZE_LCM && block_size % RVZ_BIG_BLOCK_SIZE_LCM != 0))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ASSERT(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace DiscIO
|
} // namespace DiscIO
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
|
#include "DiscIO/Blob.h"
|
||||||
|
|
||||||
namespace DiscIO
|
namespace DiscIO
|
||||||
{
|
{
|
||||||
|
@ -49,6 +50,31 @@ constexpr u32 WII_NONPARTITION_DISCHEADER_SIZE = 0x100;
|
||||||
constexpr u32 WII_REGION_DATA_ADDRESS = 0x4E000;
|
constexpr u32 WII_REGION_DATA_ADDRESS = 0x4E000;
|
||||||
constexpr u32 WII_REGION_DATA_SIZE = 0x20;
|
constexpr u32 WII_REGION_DATA_SIZE = 0x20;
|
||||||
|
|
||||||
|
// 128 KiB (0x20000) is the default block size for GCZ/RVZ images
|
||||||
|
constexpr int GCZ_RVZ_PREFERRED_BLOCK_SIZE = 0x20000;
|
||||||
|
|
||||||
|
// 32 KiB (0x8000) was picked because DVD timings are emulated as if we can't read less than
|
||||||
|
// an entire ECC block at once. Therefore, little reason to choose a smaller block size.
|
||||||
|
constexpr int PREFERRED_MIN_BLOCK_SIZE = 0x8000;
|
||||||
|
|
||||||
|
// 2 MiB (0x200000) was picked because it is the smallest block size supported by WIA.
|
||||||
|
// For performance reasons, blocks shouldn't be too large.
|
||||||
|
constexpr int PREFERRED_MAX_BLOCK_SIZE = 0x200000;
|
||||||
|
|
||||||
|
// If we didn't find a good GCZ block size, pick the block size which was hardcoded
|
||||||
|
// in legacy versions. That way, at least we're not worse than older versions.
|
||||||
|
// 16 KiB (0x4000) for supporting GCZs in versions of Dolphin prior to 5.0-11893
|
||||||
|
constexpr int GCZ_FALLBACK_BLOCK_SIZE = 0x4000;
|
||||||
|
|
||||||
|
// 2 MiB (0x200000) is the smallest block size supported by WIA.
|
||||||
|
constexpr int WIA_MIN_BLOCK_SIZE = 0x200000;
|
||||||
|
|
||||||
|
// 32 KiB (0x8000) is the smallest block size supported by RVZ.
|
||||||
|
constexpr int RVZ_MIN_BLOCK_SIZE = 0x8000;
|
||||||
|
|
||||||
|
// 2 MiB (0x200000): for RVZ, block sizes larger than 2 MiB must be an integer multiple of 2 MiB.
|
||||||
|
constexpr int RVZ_BIG_BLOCK_SIZE_LCM = 0x200000;
|
||||||
|
|
||||||
std::string NameForPartitionType(u32 partition_type, bool include_prefix);
|
std::string NameForPartitionType(u32 partition_type, bool include_prefix);
|
||||||
|
|
||||||
std::optional<u64> GetApploaderSize(const Volume& volume, const Partition& partition);
|
std::optional<u64> GetApploaderSize(const Volume& volume, const Partition& partition);
|
||||||
|
@ -59,4 +85,7 @@ std::optional<u64> GetFSTSize(const Volume& volume, const Partition& partition);
|
||||||
|
|
||||||
u64 GetBiggestReferencedOffset(const Volume& volume);
|
u64 GetBiggestReferencedOffset(const Volume& volume);
|
||||||
u64 GetBiggestReferencedOffset(const Volume& volume, const std::vector<Partition>& partitions);
|
u64 GetBiggestReferencedOffset(const Volume& volume, const std::vector<Partition>& partitions);
|
||||||
|
|
||||||
|
bool IsGCZBlockSizeLegacyCompatible(int block_size, u64 file_size);
|
||||||
|
bool IsDiscImageBlockSizeValid(int block_size, DiscIO::BlobType format);
|
||||||
} // namespace DiscIO
|
} // namespace DiscIO
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include "Common/Assert.h"
|
#include "Common/Assert.h"
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
#include "DiscIO/Blob.h"
|
#include "DiscIO/Blob.h"
|
||||||
|
#include "DiscIO/DiscUtils.h"
|
||||||
#include "DiscIO/ScrubbedBlob.h"
|
#include "DiscIO/ScrubbedBlob.h"
|
||||||
#include "DiscIO/WIABlob.h"
|
#include "DiscIO/WIABlob.h"
|
||||||
#include "DolphinQt/QtUtils/DolphinFileDialog.h"
|
#include "DolphinQt/QtUtils/DolphinFileDialog.h"
|
||||||
|
@ -118,10 +119,9 @@ void ConvertDialog::AddToBlockSizeComboBox(int size)
|
||||||
{
|
{
|
||||||
m_block_size->addItem(QString::fromStdString(UICommon::FormatSize(size, 0)), size);
|
m_block_size->addItem(QString::fromStdString(UICommon::FormatSize(size, 0)), size);
|
||||||
|
|
||||||
// Select 128 KiB by default, or if it is not available, the size closest to it.
|
// Select the default, or if it is not available, the size closest to it.
|
||||||
// This code assumes that sizes get added to the combo box in increasing order.
|
// This code assumes that sizes get added to the combo box in increasing order.
|
||||||
constexpr int DEFAULT_SIZE = 0x20000;
|
if (size <= DiscIO::GCZ_RVZ_PREFERRED_BLOCK_SIZE)
|
||||||
if (size <= DEFAULT_SIZE)
|
|
||||||
m_block_size->setCurrentIndex(m_block_size->count() - 1);
|
m_block_size->setCurrentIndex(m_block_size->count() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,14 +138,6 @@ void ConvertDialog::AddToCompressionLevelComboBox(int level)
|
||||||
|
|
||||||
void ConvertDialog::OnFormatChanged()
|
void ConvertDialog::OnFormatChanged()
|
||||||
{
|
{
|
||||||
// Because DVD timings are emulated as if we can't read less than an entire ECC block at once
|
|
||||||
// (32 KiB - 0x8000), there is little reason to use a block size smaller than that.
|
|
||||||
constexpr int MIN_BLOCK_SIZE = 0x8000;
|
|
||||||
|
|
||||||
// For performance reasons, blocks shouldn't be too large.
|
|
||||||
// 2 MiB (0x200000) was picked because it is the smallest block size supported by WIA.
|
|
||||||
constexpr int MAX_BLOCK_SIZE = 0x200000;
|
|
||||||
|
|
||||||
const DiscIO::BlobType format = static_cast<DiscIO::BlobType>(m_format->currentData().toInt());
|
const DiscIO::BlobType format = static_cast<DiscIO::BlobType>(m_format->currentData().toInt());
|
||||||
|
|
||||||
m_block_size->clear();
|
m_block_size->clear();
|
||||||
|
@ -156,21 +148,17 @@ void ConvertDialog::OnFormatChanged()
|
||||||
{
|
{
|
||||||
case DiscIO::BlobType::GCZ:
|
case DiscIO::BlobType::GCZ:
|
||||||
{
|
{
|
||||||
// In order for versions of Dolphin prior to 5.0-11893 to be able to convert a GCZ file
|
// To support legacy versions of dolphin, we have to check the GCZ block size
|
||||||
// to ISO without messing up the final part of the file in some way, the file size
|
// See DiscIO::IsGCZBlockSizeLegacyCompatible() for details
|
||||||
// must be an integer multiple of the block size (fixed in 3aa463c) and must not be
|
|
||||||
// an integer multiple of the block size multiplied by 32 (fixed in 26b21e3).
|
|
||||||
|
|
||||||
const auto block_size_ok = [this](int block_size) {
|
const auto block_size_ok = [this](int block_size) {
|
||||||
return std::all_of(m_files.begin(), m_files.end(), [block_size](const auto& file) {
|
return std::all_of(m_files.begin(), m_files.end(), [block_size](const auto& file) {
|
||||||
constexpr u64 BLOCKS_PER_BUFFER = 32;
|
return DiscIO::IsGCZBlockSizeLegacyCompatible(block_size, file->GetVolumeSize());
|
||||||
const u64 file_size = file->GetVolumeSize();
|
|
||||||
return file_size % block_size == 0 && file_size % (block_size * BLOCKS_PER_BUFFER) != 0;
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add all block sizes in the normal range that do not cause problems
|
// Add all block sizes in the normal range that do not cause problems
|
||||||
for (int block_size = MIN_BLOCK_SIZE; block_size <= MAX_BLOCK_SIZE; block_size *= 2)
|
for (int block_size = DiscIO::PREFERRED_MIN_BLOCK_SIZE;
|
||||||
|
block_size <= DiscIO::PREFERRED_MAX_BLOCK_SIZE; block_size *= 2)
|
||||||
{
|
{
|
||||||
if (block_size_ok(block_size))
|
if (block_size_ok(block_size))
|
||||||
AddToBlockSizeComboBox(block_size);
|
AddToBlockSizeComboBox(block_size);
|
||||||
|
@ -180,13 +168,12 @@ void ConvertDialog::OnFormatChanged()
|
||||||
// in older versions of Dolphin. That way, at least we're not worse than older versions.
|
// in older versions of Dolphin. That way, at least we're not worse than older versions.
|
||||||
if (m_block_size->count() == 0)
|
if (m_block_size->count() == 0)
|
||||||
{
|
{
|
||||||
constexpr int FALLBACK_BLOCK_SIZE = 0x4000;
|
if (!block_size_ok(DiscIO::GCZ_FALLBACK_BLOCK_SIZE))
|
||||||
if (!block_size_ok(FALLBACK_BLOCK_SIZE))
|
|
||||||
{
|
{
|
||||||
ERROR_LOG_FMT(MASTER_LOG, "Failed to find a block size which does not cause problems "
|
ERROR_LOG_FMT(MASTER_LOG, "Failed to find a block size which does not cause problems "
|
||||||
"when decompressing using an old version of Dolphin");
|
"when decompressing using an old version of Dolphin");
|
||||||
}
|
}
|
||||||
AddToBlockSizeComboBox(FALLBACK_BLOCK_SIZE);
|
AddToBlockSizeComboBox(DiscIO::GCZ_FALLBACK_BLOCK_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -195,13 +182,14 @@ void ConvertDialog::OnFormatChanged()
|
||||||
m_block_size->setEnabled(true);
|
m_block_size->setEnabled(true);
|
||||||
|
|
||||||
// This is the smallest block size supported by WIA. For performance, larger sizes are avoided.
|
// This is the smallest block size supported by WIA. For performance, larger sizes are avoided.
|
||||||
AddToBlockSizeComboBox(0x200000);
|
AddToBlockSizeComboBox(DiscIO::WIA_MIN_BLOCK_SIZE);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case DiscIO::BlobType::RVZ:
|
case DiscIO::BlobType::RVZ:
|
||||||
m_block_size->setEnabled(true);
|
m_block_size->setEnabled(true);
|
||||||
|
|
||||||
for (int block_size = MIN_BLOCK_SIZE; block_size <= MAX_BLOCK_SIZE; block_size *= 2)
|
for (int block_size = DiscIO::PREFERRED_MIN_BLOCK_SIZE;
|
||||||
|
block_size <= DiscIO::PREFERRED_MAX_BLOCK_SIZE; block_size *= 2)
|
||||||
AddToBlockSizeComboBox(block_size);
|
AddToBlockSizeComboBox(block_size);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -257,6 +245,7 @@ void ConvertDialog::OnFormatChanged()
|
||||||
m_block_size->setEnabled(m_block_size->count() > 1);
|
m_block_size->setEnabled(m_block_size->count() > 1);
|
||||||
m_compression->setEnabled(m_compression->count() > 1);
|
m_compression->setEnabled(m_compression->count() > 1);
|
||||||
|
|
||||||
|
// Block scrubbing of RVZ containers and Datel discs
|
||||||
const bool scrubbing_allowed =
|
const bool scrubbing_allowed =
|
||||||
format != DiscIO::BlobType::RVZ &&
|
format != DiscIO::BlobType::RVZ &&
|
||||||
std::none_of(m_files.begin(), m_files.end(), std::mem_fn(&UICommon::GameFile::IsDatelDisc));
|
std::none_of(m_files.begin(), m_files.end(), std::mem_fn(&UICommon::GameFile::IsDatelDisc));
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
add_executable(dolphin-tool
|
||||||
|
ToolHeadlessPlatform.cpp
|
||||||
|
Command.h
|
||||||
|
ConvertCommand.cpp
|
||||||
|
ConvertCommand.h
|
||||||
|
VerifyCommand.cpp
|
||||||
|
VerifyCommand.h
|
||||||
|
ToolMain.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set_target_properties(dolphin-tool PROPERTIES OUTPUT_NAME dolphin-tool)
|
||||||
|
|
||||||
|
target_link_libraries(dolphin-tool
|
||||||
|
PRIVATE
|
||||||
|
core
|
||||||
|
discio
|
||||||
|
videocommon
|
||||||
|
cpp-optparse
|
||||||
|
)
|
||||||
|
|
||||||
|
set(CPACK_PACKAGE_EXECUTABLES ${CPACK_PACKAGE_EXECUTABLES} dolphin-tool)
|
||||||
|
install(TARGETS dolphin-tool RUNTIME DESTINATION ${bindir})
|
|
@ -0,0 +1,19 @@
|
||||||
|
// Copyright 2021 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace DolphinTool
|
||||||
|
{
|
||||||
|
class Command
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Command() {}
|
||||||
|
virtual ~Command() {}
|
||||||
|
virtual int Main(const std::vector<std::string>& args) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace DolphinTool
|
|
@ -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
|
|
@ -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
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,6 @@
|
||||||
|
// Microsoft Visual C++ generated resource script.
|
||||||
|
//
|
||||||
|
#include "resource.h"
|
||||||
|
IDI_ICON1 ICON "..\\..\\..\\Installer\\Dolphin.ico"
|
||||||
|
"dolphin" ICON "..\\..\\..\\Installer\\Dolphin.ico"
|
||||||
|
|
|
@ -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>
|
|
@ -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
|
|
@ -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);
|
||||||
|
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -7,6 +7,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Dolphin", "Core\DolphinQt\D
|
||||||
EndProject
|
EndProject
|
||||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DolphinNoGUI", "Core\DolphinNoGUI\DolphinNoGUI.vcxproj", "{974E563D-23F8-4E8F-9083-F62876B04E08}"
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DolphinNoGUI", "Core\DolphinNoGUI\DolphinNoGUI.vcxproj", "{974E563D-23F8-4E8F-9083-F62876B04E08}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DolphinTool", "Core\DolphinTool\DolphinTool.vcxproj", "{8F91523C-5C5E-4B22-A1F1-67560B6DC714}"
|
||||||
|
EndProject
|
||||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DSPTool", "DSPTool\DSPTool.vcxproj", "{1970D175-3DE8-4738-942A-4D98D1CDBF64}"
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DSPTool", "DSPTool\DSPTool.vcxproj", "{1970D175-3DE8-4738-942A-4D98D1CDBF64}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTests", "UnitTests\UnitTests.vcxproj", "{474661E7-C73A-43A6-AFEE-EE1EC433D49E}"
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTests", "UnitTests\UnitTests.vcxproj", "{474661E7-C73A-43A6-AFEE-EE1EC433D49E}"
|
||||||
|
@ -95,6 +97,14 @@ Global
|
||||||
{974E563D-23F8-4E8F-9083-F62876B04E08}.Debug|x64.ActiveCfg = Debug|x64
|
{974E563D-23F8-4E8F-9083-F62876B04E08}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
{974E563D-23F8-4E8F-9083-F62876B04E08}.Release|ARM64.ActiveCfg = Release|ARM64
|
{974E563D-23F8-4E8F-9083-F62876B04E08}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||||
{974E563D-23F8-4E8F-9083-F62876B04E08}.Release|x64.ActiveCfg = Release|x64
|
{974E563D-23F8-4E8F-9083-F62876B04E08}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{8F91523C-5C5E-4B22-A1F1-67560B6DC714}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||||
|
{8F91523C-5C5E-4B22-A1F1-67560B6DC714}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
|
{8F91523C-5C5E-4B22-A1F1-67560B6DC714}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{8F91523C-5C5E-4B22-A1F1-67560B6DC714}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{8F91523C-5C5E-4B22-A1F1-67560B6DC714}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||||
|
{8F91523C-5C5E-4B22-A1F1-67560B6DC714}.Release|ARM64.Build.0 = Release|ARM64
|
||||||
|
{8F91523C-5C5E-4B22-A1F1-67560B6DC714}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{8F91523C-5C5E-4B22-A1F1-67560B6DC714}.Release|x64.Build.0 = Release|x64
|
||||||
{1970D175-3DE8-4738-942A-4D98D1CDBF64}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
{1970D175-3DE8-4738-942A-4D98D1CDBF64}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||||
{1970D175-3DE8-4738-942A-4D98D1CDBF64}.Debug|ARM64.Build.0 = Debug|ARM64
|
{1970D175-3DE8-4738-942A-4D98D1CDBF64}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||||
{1970D175-3DE8-4738-942A-4D98D1CDBF64}.Debug|x64.ActiveCfg = Debug|x64
|
{1970D175-3DE8-4738-942A-4D98D1CDBF64}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
|
Loading…
Reference in New Issue