mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-23 22:29:39 -06:00
Merge pull request #8738 from JosJuice/convert-dialog
Replace the compress/uncompress actions with a convert dialog
This commit is contained in:
@ -41,11 +41,16 @@ class BlobReader
|
||||
{
|
||||
public:
|
||||
virtual ~BlobReader() {}
|
||||
|
||||
virtual BlobType GetBlobType() const = 0;
|
||||
|
||||
virtual u64 GetRawSize() const = 0;
|
||||
virtual u64 GetDataSize() const = 0;
|
||||
virtual bool IsDataSizeAccurate() const = 0;
|
||||
|
||||
// Returns 0 if the format does not use blocks
|
||||
virtual u64 GetBlockSize() const { return 0; }
|
||||
|
||||
// NOT thread-safe - can't call this from multiple threads.
|
||||
virtual bool Read(u64 offset, u64 size, u8* out_ptr) = 0;
|
||||
template <typename T>
|
||||
@ -160,10 +165,11 @@ std::unique_ptr<BlobReader> CreateBlobReader(const std::string& filename);
|
||||
|
||||
typedef bool (*CompressCB)(const std::string& text, float percent, void* arg);
|
||||
|
||||
bool CompressFileToBlob(const std::string& infile_path, const std::string& outfile_path,
|
||||
u32 sub_type = 0, int sector_size = 16384, CompressCB callback = nullptr,
|
||||
void* arg = nullptr);
|
||||
bool DecompressBlobToFile(const std::string& infile_path, const std::string& outfile_path,
|
||||
CompressCB callback = nullptr, void* arg = nullptr);
|
||||
bool ConvertToGCZ(BlobReader* infile, const std::string& infile_path,
|
||||
const std::string& outfile_path, u32 sub_type, int sector_size = 16384,
|
||||
CompressCB callback = nullptr, void* arg = nullptr);
|
||||
bool ConvertToPlain(BlobReader* infile, const std::string& infile_path,
|
||||
const std::string& outfile_path, CompressCB callback = nullptr,
|
||||
void* arg = nullptr);
|
||||
|
||||
} // namespace DiscIO
|
||||
|
@ -37,12 +37,15 @@ public:
|
||||
static std::unique_ptr<CISOFileReader> Create(File::IOFile file);
|
||||
|
||||
BlobType GetBlobType() const override { return BlobType::CISO; }
|
||||
|
||||
u64 GetRawSize() const override;
|
||||
// The CISO format does not save the original file size.
|
||||
// This function returns an upper bound.
|
||||
u64 GetDataSize() const override;
|
||||
bool IsDataSizeAccurate() const override { return false; }
|
||||
|
||||
u64 GetRawSize() const override;
|
||||
u64 GetBlockSize() const override { return m_block_size; }
|
||||
|
||||
bool Read(u64 offset, u64 nbytes, u8* out_ptr) override;
|
||||
|
||||
private:
|
||||
|
@ -23,6 +23,8 @@ add_library(discio
|
||||
Filesystem.h
|
||||
NANDImporter.cpp
|
||||
NANDImporter.h
|
||||
ScrubbedBlob.cpp
|
||||
ScrubbedBlob.h
|
||||
TGCBlob.cpp
|
||||
TGCBlob.h
|
||||
Volume.cpp
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include <vector>
|
||||
#include <zlib.h>
|
||||
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/File.h"
|
||||
#include "Common/FileUtil.h"
|
||||
@ -153,23 +154,11 @@ bool CompressedBlobReader::GetBlock(u64 block_num, u8* out_ptr)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompressFileToBlob(const std::string& infile_path, const std::string& outfile_path,
|
||||
u32 sub_type, int block_size, CompressCB callback, void* arg)
|
||||
bool ConvertToGCZ(BlobReader* infile, const std::string& infile_path,
|
||||
const std::string& outfile_path, u32 sub_type, int block_size,
|
||||
CompressCB callback, void* arg)
|
||||
{
|
||||
bool scrubbing = false;
|
||||
|
||||
File::IOFile infile(infile_path, "rb");
|
||||
if (IsGCZBlob(infile))
|
||||
{
|
||||
PanicAlertT("\"%s\" is already compressed! Cannot compress it further.", infile_path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!infile)
|
||||
{
|
||||
PanicAlertT("Failed to open the input file \"%s\".", infile_path.c_str());
|
||||
return false;
|
||||
}
|
||||
ASSERT(infile->IsDataSizeAccurate());
|
||||
|
||||
File::IOFile outfile(outfile_path, "wb");
|
||||
if (!outfile)
|
||||
@ -181,21 +170,6 @@ bool CompressFileToBlob(const std::string& infile_path, const std::string& outfi
|
||||
return false;
|
||||
}
|
||||
|
||||
DiscScrubber disc_scrubber;
|
||||
std::unique_ptr<VolumeDisc> volume;
|
||||
if (sub_type == 1)
|
||||
{
|
||||
volume = CreateDisc(infile_path);
|
||||
if (!volume || !disc_scrubber.SetupScrub(volume.get(), block_size))
|
||||
{
|
||||
PanicAlertT("\"%s\" failed to be scrubbed. Probably the image is corrupt.",
|
||||
infile_path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
scrubbing = true;
|
||||
}
|
||||
|
||||
z_stream z = {};
|
||||
if (deflateInit(&z, 9) != Z_OK)
|
||||
return false;
|
||||
@ -206,7 +180,7 @@ bool CompressFileToBlob(const std::string& infile_path, const std::string& outfi
|
||||
header.magic_cookie = GCZ_MAGIC;
|
||||
header.sub_type = sub_type;
|
||||
header.block_size = block_size;
|
||||
header.data_size = infile.GetSize();
|
||||
header.data_size = infile->GetDataSize();
|
||||
|
||||
// round upwards!
|
||||
header.num_blocks = (u32)((header.data_size + (block_size - 1)) / block_size);
|
||||
@ -220,10 +194,9 @@ bool CompressFileToBlob(const std::string& infile_path, const std::string& outfi
|
||||
outfile.Seek(sizeof(CompressedBlobHeader), SEEK_CUR);
|
||||
// seek past the offset and hash tables (we will write them at the end)
|
||||
outfile.Seek((sizeof(u64) + sizeof(u32)) * header.num_blocks, SEEK_CUR);
|
||||
// seek to the start of the input file to make sure we get everything
|
||||
infile.Seek(0, SEEK_SET);
|
||||
|
||||
// Now we are ready to write compressed data!
|
||||
u64 inpos = 0;
|
||||
u64 position = 0;
|
||||
int num_compressed = 0;
|
||||
int num_stored = 0;
|
||||
@ -234,7 +207,6 @@ bool CompressFileToBlob(const std::string& infile_path, const std::string& outfi
|
||||
{
|
||||
if (i % progress_monitor == 0)
|
||||
{
|
||||
const u64 inpos = infile.Tell();
|
||||
int ratio = 0;
|
||||
if (inpos != 0)
|
||||
ratio = (int)(100 * position / inpos);
|
||||
@ -252,13 +224,16 @@ bool CompressFileToBlob(const std::string& infile_path, const std::string& outfi
|
||||
|
||||
offsets[i] = position;
|
||||
|
||||
size_t read_bytes;
|
||||
if (scrubbing)
|
||||
read_bytes = disc_scrubber.GetNextBlock(infile, in_buf.data());
|
||||
else
|
||||
infile.ReadArray(in_buf.data(), header.block_size, &read_bytes);
|
||||
if (read_bytes < header.block_size)
|
||||
std::fill(in_buf.begin() + read_bytes, in_buf.begin() + header.block_size, 0);
|
||||
const u64 bytes_to_read = std::min<u64>(block_size, header.data_size - inpos);
|
||||
|
||||
success = infile->Read(inpos, bytes_to_read, in_buf.data());
|
||||
if (!success)
|
||||
{
|
||||
PanicAlertT("Failed to read from the input file \"%s\".", infile_path.c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
std::fill(in_buf.begin() + bytes_to_read, in_buf.begin() + header.block_size, 0);
|
||||
|
||||
int retval = deflateReset(&z);
|
||||
z.next_in = in_buf.data();
|
||||
@ -305,6 +280,7 @@ bool CompressFileToBlob(const std::string& infile_path, const std::string& outfi
|
||||
break;
|
||||
}
|
||||
|
||||
inpos += block_size;
|
||||
position += write_size;
|
||||
|
||||
hashes[i] = Common::HashAdler32(write_buf, write_size);
|
||||
@ -337,84 +313,6 @@ bool CompressFileToBlob(const std::string& infile_path, const std::string& outfi
|
||||
return success;
|
||||
}
|
||||
|
||||
bool DecompressBlobToFile(const std::string& infile_path, const std::string& outfile_path,
|
||||
CompressCB callback, void* arg)
|
||||
{
|
||||
std::unique_ptr<CompressedBlobReader> reader;
|
||||
{
|
||||
File::IOFile infile(infile_path, "rb");
|
||||
if (!IsGCZBlob(infile))
|
||||
{
|
||||
PanicAlertT("File not compressed");
|
||||
return false;
|
||||
}
|
||||
|
||||
reader = CompressedBlobReader::Create(std::move(infile), infile_path);
|
||||
}
|
||||
|
||||
if (!reader)
|
||||
{
|
||||
PanicAlertT("Failed to open the input file \"%s\".", infile_path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
File::IOFile outfile(outfile_path, "wb");
|
||||
if (!outfile)
|
||||
{
|
||||
PanicAlertT("Failed to open the output file \"%s\".\n"
|
||||
"Check that you have permissions to write the target folder and that the media can "
|
||||
"be written.",
|
||||
outfile_path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
const CompressedBlobHeader& header = reader->GetHeader();
|
||||
static const size_t BUFFER_BLOCKS = 32;
|
||||
size_t buffer_size = header.block_size * BUFFER_BLOCKS;
|
||||
std::vector<u8> buffer(buffer_size);
|
||||
u32 num_buffers = (header.num_blocks + BUFFER_BLOCKS - 1) / BUFFER_BLOCKS;
|
||||
int progress_monitor = std::max<int>(1, num_buffers / 100);
|
||||
bool success = true;
|
||||
|
||||
for (u64 i = 0; i < num_buffers; i++)
|
||||
{
|
||||
if (i % progress_monitor == 0)
|
||||
{
|
||||
const bool was_cancelled =
|
||||
!callback(Common::GetStringT("Unpacking"), (float)i / (float)num_buffers, arg);
|
||||
if (was_cancelled)
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const u64 inpos = i * buffer_size;
|
||||
const u64 sz = std::min<u64>(buffer_size, header.data_size - inpos);
|
||||
reader->Read(inpos, sz, buffer.data());
|
||||
if (!outfile.WriteBytes(buffer.data(), sz))
|
||||
{
|
||||
PanicAlertT("Failed to write the output file \"%s\".\n"
|
||||
"Check that you have enough space available on the target drive.",
|
||||
outfile_path.c_str());
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
// Remove the incomplete output file.
|
||||
outfile.Close();
|
||||
File::Delete(outfile_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
outfile.Resize(header.data_size);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool IsGCZBlob(File::IOFile& file)
|
||||
{
|
||||
const u64 position = file.Tell();
|
||||
|
@ -52,6 +52,7 @@ public:
|
||||
u64 GetRawSize() const override { return m_file_size; }
|
||||
u64 GetDataSize() const override { return m_header.data_size; }
|
||||
bool IsDataSizeAccurate() const override { return true; }
|
||||
u64 GetBlockSize() const override { return m_header.block_size; }
|
||||
u64 GetBlockCompressedSize(u64 block_num) const;
|
||||
bool GetBlock(u64 block_num, u8* out_ptr) override;
|
||||
|
||||
|
@ -56,6 +56,7 @@
|
||||
<ClCompile Include="Filesystem.cpp" />
|
||||
<ClCompile Include="FileSystemGCWii.cpp" />
|
||||
<ClCompile Include="NANDImporter.cpp" />
|
||||
<ClCompile Include="ScrubbedBlob.cpp" />
|
||||
<ClCompile Include="TGCBlob.cpp" />
|
||||
<ClCompile Include="Volume.cpp" />
|
||||
<ClCompile Include="VolumeFileBlobReader.cpp" />
|
||||
@ -80,6 +81,7 @@
|
||||
<ClInclude Include="Filesystem.h" />
|
||||
<ClInclude Include="FileSystemGCWii.h" />
|
||||
<ClInclude Include="NANDImporter.h" />
|
||||
<ClInclude Include="ScrubbedBlob.h" />
|
||||
<ClInclude Include="TGCBlob.h" />
|
||||
<ClInclude Include="Volume.h" />
|
||||
<ClInclude Include="VolumeFileBlobReader.h" />
|
||||
|
@ -87,6 +87,9 @@
|
||||
<ClCompile Include="WiiEncryptionCache.cpp">
|
||||
<Filter>Volume\Blob</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ScrubbedBlob.cpp">
|
||||
<Filter>Volume\Blob</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="DiscScrubber.h">
|
||||
@ -155,6 +158,9 @@
|
||||
<ClInclude Include="WiiEncryptionCache.h">
|
||||
<Filter>Volume\Blob</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ScrubbedBlob.h">
|
||||
<Filter>Volume\Blob</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Text Include="CMakeLists.txt" />
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "Common/Align.h"
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/File.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
@ -24,24 +25,14 @@
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
constexpr size_t CLUSTER_SIZE = 0x8000;
|
||||
|
||||
DiscScrubber::DiscScrubber() = default;
|
||||
DiscScrubber::~DiscScrubber() = default;
|
||||
|
||||
bool DiscScrubber::SetupScrub(const Volume* disc, int block_size)
|
||||
bool DiscScrubber::SetupScrub(const Volume* disc)
|
||||
{
|
||||
if (!disc)
|
||||
return false;
|
||||
m_disc = disc;
|
||||
m_block_size = block_size;
|
||||
|
||||
if (CLUSTER_SIZE % m_block_size != 0)
|
||||
{
|
||||
ERROR_LOG(DISCIO, "Block size %u is not a factor of 0x8000, scrubbing not possible",
|
||||
m_block_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_file_size = m_disc->GetSize();
|
||||
|
||||
@ -54,34 +45,10 @@ bool DiscScrubber::SetupScrub(const Volume* disc, int block_size)
|
||||
// Fill out table of free blocks
|
||||
const bool success = ParseDisc();
|
||||
|
||||
m_block_count = 0;
|
||||
|
||||
m_is_scrubbing = success;
|
||||
return success;
|
||||
}
|
||||
|
||||
size_t DiscScrubber::GetNextBlock(File::IOFile& in, u8* buffer)
|
||||
{
|
||||
const u64 current_offset = m_block_count * m_block_size;
|
||||
|
||||
size_t read_bytes = 0;
|
||||
if (CanBlockBeScrubbed(current_offset))
|
||||
{
|
||||
DEBUG_LOG(DISCIO, "Freeing 0x%016" PRIx64, current_offset);
|
||||
std::fill(buffer, buffer + m_block_size, 0x00);
|
||||
in.Seek(m_block_size, SEEK_CUR);
|
||||
read_bytes = m_block_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_LOG(DISCIO, "Used 0x%016" PRIx64, current_offset);
|
||||
in.ReadArray(buffer, m_block_size, &read_bytes);
|
||||
}
|
||||
|
||||
m_block_count++;
|
||||
return read_bytes;
|
||||
}
|
||||
|
||||
bool DiscScrubber::CanBlockBeScrubbed(u64 offset) const
|
||||
{
|
||||
return m_is_scrubbing && m_free_table[offset / CLUSTER_SIZE];
|
||||
@ -89,8 +56,8 @@ bool DiscScrubber::CanBlockBeScrubbed(u64 offset) const
|
||||
|
||||
void DiscScrubber::MarkAsUsed(u64 offset, u64 size)
|
||||
{
|
||||
u64 current_offset = offset;
|
||||
const u64 end_offset = current_offset + size;
|
||||
u64 current_offset = Common::AlignDown(offset, CLUSTER_SIZE);
|
||||
const u64 end_offset = offset + size;
|
||||
|
||||
DEBUG_LOG(DISCIO, "Marking 0x%016" PRIx64 " - 0x%016" PRIx64 " as used", offset, end_offset);
|
||||
|
||||
@ -103,20 +70,27 @@ void DiscScrubber::MarkAsUsed(u64 offset, u64 size)
|
||||
|
||||
void DiscScrubber::MarkAsUsedE(u64 partition_data_offset, u64 offset, u64 size)
|
||||
{
|
||||
u64 first_cluster_start = ToClusterOffset(offset) + partition_data_offset;
|
||||
|
||||
u64 last_cluster_end;
|
||||
if (size == 0)
|
||||
if (partition_data_offset == 0)
|
||||
{
|
||||
// Without this special case, a size of 0 can be rounded to 1 cluster instead of 0
|
||||
last_cluster_end = first_cluster_start;
|
||||
MarkAsUsed(offset, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
last_cluster_end = ToClusterOffset(offset + size - 1) + CLUSTER_SIZE + partition_data_offset;
|
||||
}
|
||||
u64 first_cluster_start = ToClusterOffset(offset) + partition_data_offset;
|
||||
|
||||
MarkAsUsed(first_cluster_start, last_cluster_end - first_cluster_start);
|
||||
u64 last_cluster_end;
|
||||
if (size == 0)
|
||||
{
|
||||
// Without this special case, a size of 0 can be rounded to 1 cluster instead of 0
|
||||
last_cluster_end = first_cluster_start;
|
||||
}
|
||||
else
|
||||
{
|
||||
last_cluster_end = ToClusterOffset(offset + size - 1) + CLUSTER_SIZE + partition_data_offset;
|
||||
}
|
||||
|
||||
MarkAsUsed(first_cluster_start, last_cluster_end - first_cluster_start);
|
||||
}
|
||||
}
|
||||
|
||||
// Compensate for 0x400 (SHA-1) per 0x8000 (cluster), and round to whole clusters
|
||||
@ -147,35 +121,38 @@ bool DiscScrubber::ReadFromVolume(u64 offset, u64& buffer, const Partition& part
|
||||
|
||||
bool DiscScrubber::ParseDisc()
|
||||
{
|
||||
if (m_disc->GetPartitions().empty())
|
||||
return ParsePartitionData(PARTITION_NONE);
|
||||
|
||||
// Mark the header as used - it's mostly 0s anyways
|
||||
MarkAsUsed(0, 0x50000);
|
||||
|
||||
for (const DiscIO::Partition& partition : m_disc->GetPartitions())
|
||||
{
|
||||
PartitionHeader header;
|
||||
u32 tmd_size;
|
||||
u64 tmd_offset;
|
||||
u32 cert_chain_size;
|
||||
u64 cert_chain_offset;
|
||||
u64 h3_offset;
|
||||
// The H3 size is always 0x18000
|
||||
|
||||
if (!ReadFromVolume(partition.offset + 0x2a4, header.tmd_size, PARTITION_NONE) ||
|
||||
!ReadFromVolume(partition.offset + 0x2a8, header.tmd_offset, PARTITION_NONE) ||
|
||||
!ReadFromVolume(partition.offset + 0x2ac, header.cert_chain_size, PARTITION_NONE) ||
|
||||
!ReadFromVolume(partition.offset + 0x2b0, header.cert_chain_offset, PARTITION_NONE) ||
|
||||
!ReadFromVolume(partition.offset + 0x2b4, header.h3_offset, PARTITION_NONE) ||
|
||||
!ReadFromVolume(partition.offset + 0x2b8, header.data_offset, PARTITION_NONE) ||
|
||||
!ReadFromVolume(partition.offset + 0x2bc, header.data_size, PARTITION_NONE))
|
||||
if (!ReadFromVolume(partition.offset + 0x2a4, tmd_size, PARTITION_NONE) ||
|
||||
!ReadFromVolume(partition.offset + 0x2a8, tmd_offset, PARTITION_NONE) ||
|
||||
!ReadFromVolume(partition.offset + 0x2ac, cert_chain_size, PARTITION_NONE) ||
|
||||
!ReadFromVolume(partition.offset + 0x2b0, cert_chain_offset, PARTITION_NONE) ||
|
||||
!ReadFromVolume(partition.offset + 0x2b4, h3_offset, PARTITION_NONE))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
MarkAsUsed(partition.offset, 0x2c0);
|
||||
|
||||
MarkAsUsed(partition.offset + header.tmd_offset, header.tmd_size);
|
||||
MarkAsUsed(partition.offset + header.cert_chain_offset, header.cert_chain_size);
|
||||
MarkAsUsed(partition.offset + header.h3_offset, 0x18000);
|
||||
// This would mark the whole (encrypted) data area
|
||||
// we need to parse FST and other crap to find what's free within it!
|
||||
// MarkAsUsed(partition.offset + header.data_offset, header.data_size);
|
||||
MarkAsUsed(partition.offset + tmd_offset, tmd_size);
|
||||
MarkAsUsed(partition.offset + cert_chain_offset, cert_chain_size);
|
||||
MarkAsUsed(partition.offset + h3_offset, 0x18000);
|
||||
|
||||
// Parse Data! This is where the big gain is
|
||||
if (!ParsePartitionData(partition, &header))
|
||||
if (!ParsePartitionData(partition))
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -183,7 +160,7 @@ bool DiscScrubber::ParseDisc()
|
||||
}
|
||||
|
||||
// Operations dealing with encrypted space are done here
|
||||
bool DiscScrubber::ParsePartitionData(const Partition& partition, PartitionHeader* header)
|
||||
bool DiscScrubber::ParsePartitionData(const Partition& partition)
|
||||
{
|
||||
const FileSystem* filesystem = m_disc->GetFileSystem(partition);
|
||||
if (!filesystem)
|
||||
@ -193,17 +170,30 @@ bool DiscScrubber::ParsePartitionData(const Partition& partition, PartitionHeade
|
||||
return false;
|
||||
}
|
||||
|
||||
const u64 partition_data_offset = partition.offset + header->data_offset;
|
||||
u64 partition_data_offset;
|
||||
if (partition == PARTITION_NONE)
|
||||
{
|
||||
partition_data_offset = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
u64 data_offset;
|
||||
if (!ReadFromVolume(partition.offset + 0x2b8, data_offset, PARTITION_NONE))
|
||||
return false;
|
||||
|
||||
partition_data_offset = partition.offset + data_offset;
|
||||
}
|
||||
|
||||
// Mark things as used which are not in the filesystem
|
||||
// Header, Header Information, Apploader
|
||||
if (!ReadFromVolume(0x2440 + 0x14, header->apploader_size, partition) ||
|
||||
!ReadFromVolume(0x2440 + 0x18, header->apploader_size, partition))
|
||||
u32 apploader_size;
|
||||
u32 apploader_trailer_size;
|
||||
if (!ReadFromVolume(0x2440 + 0x14, apploader_size, partition) ||
|
||||
!ReadFromVolume(0x2440 + 0x18, apploader_trailer_size, partition))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
MarkAsUsedE(partition_data_offset, 0,
|
||||
0x2440 + header->apploader_size + header->apploader_trailer_size);
|
||||
MarkAsUsedE(partition_data_offset, 0, 0x2440 + apploader_size + apploader_trailer_size);
|
||||
|
||||
// DOL
|
||||
const std::optional<u64> dol_offset = GetBootDOLOffset(*m_disc, partition);
|
||||
@ -212,17 +202,14 @@ bool DiscScrubber::ParsePartitionData(const Partition& partition, PartitionHeade
|
||||
const std::optional<u64> dol_size = GetBootDOLSize(*m_disc, partition, *dol_offset);
|
||||
if (!dol_size)
|
||||
return false;
|
||||
header->dol_offset = *dol_offset;
|
||||
header->dol_size = *dol_size;
|
||||
MarkAsUsedE(partition_data_offset, header->dol_offset, header->dol_size);
|
||||
MarkAsUsedE(partition_data_offset, *dol_offset, *dol_size);
|
||||
|
||||
// FST
|
||||
if (!ReadFromVolume(0x424, header->fst_offset, partition) ||
|
||||
!ReadFromVolume(0x428, header->fst_size, partition))
|
||||
{
|
||||
const std::optional<u64> fst_offset = GetFSTOffset(*m_disc, partition);
|
||||
const std::optional<u64> fst_size = GetFSTSize(*m_disc, partition);
|
||||
if (!fst_offset || !fst_size)
|
||||
return false;
|
||||
}
|
||||
MarkAsUsedE(partition_data_offset, header->fst_offset, header->fst_size);
|
||||
MarkAsUsedE(partition_data_offset, *fst_offset, *fst_size);
|
||||
|
||||
// Go through the filesystem and mark entries as used
|
||||
ParseFileSystemData(partition_data_offset, filesystem->GetRoot());
|
||||
|
@ -2,11 +2,7 @@
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// DiscScrubber removes the garbage data from discs (currently Wii only) which
|
||||
// is on the disc due to encryption
|
||||
|
||||
// It could be adapted to GameCube discs, but the gain is most likely negligible,
|
||||
// and having 1:1 backups of discs is always nice when they are reasonably sized
|
||||
// DiscScrubber removes the pseudorandom padding data from discs
|
||||
|
||||
// Note: the technique is inspired by Wiiscrubber, but much simpler - intentionally :)
|
||||
|
||||
@ -34,46 +30,27 @@ public:
|
||||
DiscScrubber();
|
||||
~DiscScrubber();
|
||||
|
||||
bool SetupScrub(const Volume* disc, int block_size);
|
||||
size_t GetNextBlock(File::IOFile& in, u8* buffer);
|
||||
bool SetupScrub(const Volume* disc);
|
||||
|
||||
// Returns true if the specified 32 KiB block only contains unused data
|
||||
bool CanBlockBeScrubbed(u64 offset) const;
|
||||
|
||||
private:
|
||||
struct PartitionHeader final
|
||||
{
|
||||
u8* ticket[0x2a4];
|
||||
u32 tmd_size;
|
||||
u64 tmd_offset;
|
||||
u32 cert_chain_size;
|
||||
u64 cert_chain_offset;
|
||||
// H3Size is always 0x18000
|
||||
u64 h3_offset;
|
||||
u64 data_offset;
|
||||
u64 data_size;
|
||||
// TMD would be here
|
||||
u64 dol_offset;
|
||||
u64 dol_size;
|
||||
u64 fst_offset;
|
||||
u64 fst_size;
|
||||
u32 apploader_size;
|
||||
u32 apploader_trailer_size;
|
||||
};
|
||||
static constexpr size_t CLUSTER_SIZE = 0x8000;
|
||||
|
||||
private:
|
||||
void MarkAsUsed(u64 offset, u64 size);
|
||||
void MarkAsUsedE(u64 partition_data_offset, u64 offset, u64 size);
|
||||
u64 ToClusterOffset(u64 offset) const;
|
||||
bool ReadFromVolume(u64 offset, u32& buffer, const Partition& partition);
|
||||
bool ReadFromVolume(u64 offset, u64& buffer, const Partition& partition);
|
||||
bool ParseDisc();
|
||||
bool ParsePartitionData(const Partition& partition, PartitionHeader* header);
|
||||
bool ParsePartitionData(const Partition& partition);
|
||||
void ParseFileSystemData(u64 partition_data_offset, const FileInfo& directory);
|
||||
|
||||
const Volume* m_disc;
|
||||
|
||||
std::vector<u8> m_free_table;
|
||||
u64 m_file_size = 0;
|
||||
u64 m_block_count = 0;
|
||||
u32 m_block_size = 0;
|
||||
bool m_is_scrubbing = false;
|
||||
};
|
||||
|
||||
|
@ -23,11 +23,15 @@ class DriveReader : public SectorReader
|
||||
public:
|
||||
static std::unique_ptr<DriveReader> Create(const std::string& drive);
|
||||
~DriveReader();
|
||||
|
||||
BlobType GetBlobType() const override { return BlobType::DRIVE; }
|
||||
|
||||
u64 GetRawSize() const override { return m_size; }
|
||||
u64 GetDataSize() const override { return m_size; }
|
||||
bool IsDataSizeAccurate() const override { return true; }
|
||||
|
||||
u64 GetBlockSize() const override { return ECC_BLOCK_SIZE; }
|
||||
|
||||
private:
|
||||
DriveReader(const std::string& drive);
|
||||
bool GetBlock(u64 block_num, u8* out_ptr) override;
|
||||
@ -41,6 +45,7 @@ private:
|
||||
File::IOFile m_file;
|
||||
bool IsOK() const { return m_file.IsOpen() && m_file.IsGood(); }
|
||||
#endif
|
||||
static constexpr u64 ECC_BLOCK_SIZE = 0x8000;
|
||||
u64 m_size = 0;
|
||||
};
|
||||
|
||||
|
@ -2,10 +2,15 @@
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "DiscIO/FileBlob.h"
|
||||
|
||||
namespace DiscIO
|
||||
@ -36,4 +41,76 @@ bool PlainFileReader::Read(u64 offset, u64 nbytes, u8* out_ptr)
|
||||
}
|
||||
}
|
||||
|
||||
bool ConvertToPlain(BlobReader* infile, const std::string& infile_path,
|
||||
const std::string& outfile_path, CompressCB callback, void* arg)
|
||||
{
|
||||
ASSERT(infile->IsDataSizeAccurate());
|
||||
|
||||
File::IOFile outfile(outfile_path, "wb");
|
||||
if (!outfile)
|
||||
{
|
||||
PanicAlertT("Failed to open the output file \"%s\".\n"
|
||||
"Check that you have permissions to write the target folder and that the media can "
|
||||
"be written.",
|
||||
outfile_path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr size_t DESIRED_BUFFER_SIZE = 0x80000;
|
||||
u64 buffer_size = infile->GetBlockSize();
|
||||
if (buffer_size == 0)
|
||||
{
|
||||
buffer_size = DESIRED_BUFFER_SIZE;
|
||||
}
|
||||
else
|
||||
{
|
||||
while (buffer_size < DESIRED_BUFFER_SIZE)
|
||||
buffer_size *= 2;
|
||||
}
|
||||
|
||||
std::vector<u8> buffer(buffer_size);
|
||||
const u64 num_buffers = (infile->GetDataSize() + buffer_size - 1) / buffer_size;
|
||||
int progress_monitor = std::max<int>(1, num_buffers / 100);
|
||||
bool success = true;
|
||||
|
||||
for (u64 i = 0; i < num_buffers; i++)
|
||||
{
|
||||
if (i % progress_monitor == 0)
|
||||
{
|
||||
const bool was_cancelled =
|
||||
!callback(Common::GetStringT("Unpacking"), (float)i / (float)num_buffers, arg);
|
||||
if (was_cancelled)
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const u64 inpos = i * buffer_size;
|
||||
const u64 sz = std::min(buffer_size, infile->GetDataSize() - inpos);
|
||||
if (!infile->Read(inpos, sz, buffer.data()))
|
||||
{
|
||||
PanicAlertT("Failed to read from the input file \"%s\".", infile_path.c_str());
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
if (!outfile.WriteBytes(buffer.data(), sz))
|
||||
{
|
||||
PanicAlertT("Failed to write the output file \"%s\".\n"
|
||||
"Check that you have enough space available on the target drive.",
|
||||
outfile_path.c_str());
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
// Remove the incomplete output file.
|
||||
outfile.Close();
|
||||
File::Delete(outfile_path);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
} // namespace DiscIO
|
||||
|
67
Source/Core/DiscIO/ScrubbedBlob.cpp
Normal file
67
Source/Core/DiscIO/ScrubbedBlob.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright 2020 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DiscIO/ScrubbedBlob.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "Common/Align.h"
|
||||
#include "DiscIO/Blob.h"
|
||||
#include "DiscIO/DiscScrubber.h"
|
||||
#include "DiscIO/Volume.h"
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
ScrubbedBlob::ScrubbedBlob(std::unique_ptr<BlobReader> blob_reader, DiscScrubber scrubber)
|
||||
: m_blob_reader(std::move(blob_reader)), m_scrubber(std::move(scrubber))
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<ScrubbedBlob> ScrubbedBlob::Create(const std::string& path)
|
||||
{
|
||||
std::unique_ptr<VolumeDisc> disc = CreateDisc(path);
|
||||
if (!disc)
|
||||
return nullptr;
|
||||
|
||||
DiscScrubber scrubber;
|
||||
if (!scrubber.SetupScrub(disc.get()))
|
||||
return nullptr;
|
||||
|
||||
std::unique_ptr<BlobReader> blob = CreateBlobReader(path);
|
||||
if (!blob)
|
||||
return nullptr;
|
||||
|
||||
return std::unique_ptr<ScrubbedBlob>(new ScrubbedBlob(std::move(blob), std::move(scrubber)));
|
||||
}
|
||||
|
||||
bool ScrubbedBlob::Read(u64 offset, u64 size, u8* out_ptr)
|
||||
{
|
||||
while (size > 0)
|
||||
{
|
||||
constexpr size_t CLUSTER_SIZE = DiscScrubber::CLUSTER_SIZE;
|
||||
const u64 bytes_to_read =
|
||||
std::min(Common::AlignDown(offset + CLUSTER_SIZE, CLUSTER_SIZE) - offset, size);
|
||||
|
||||
if (m_scrubber.CanBlockBeScrubbed(offset))
|
||||
{
|
||||
std::fill_n(out_ptr, bytes_to_read, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_blob_reader->Read(offset, bytes_to_read, out_ptr))
|
||||
return false;
|
||||
}
|
||||
|
||||
offset += bytes_to_read;
|
||||
size -= bytes_to_read;
|
||||
out_ptr += bytes_to_read;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace DiscIO
|
37
Source/Core/DiscIO/ScrubbedBlob.h
Normal file
37
Source/Core/DiscIO/ScrubbedBlob.h
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright 2020 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "DiscIO/Blob.h"
|
||||
#include "DiscIO/DiscScrubber.h"
|
||||
|
||||
namespace DiscIO
|
||||
{
|
||||
// This class wraps another BlobReader and zeroes out data that has been
|
||||
// identified by DiscScrubber as unused.
|
||||
class ScrubbedBlob : public BlobReader
|
||||
{
|
||||
public:
|
||||
static std::unique_ptr<ScrubbedBlob> Create(const std::string& path);
|
||||
|
||||
BlobType GetBlobType() const override { return m_blob_reader->GetBlobType(); }
|
||||
u64 GetRawSize() const override { return m_blob_reader->GetRawSize(); }
|
||||
u64 GetDataSize() const override { return m_blob_reader->GetDataSize(); }
|
||||
bool IsDataSizeAccurate() const override { return m_blob_reader->IsDataSizeAccurate(); }
|
||||
u64 GetBlockSize() const override { return m_blob_reader->GetBlockSize(); }
|
||||
|
||||
bool Read(u64 offset, u64 size, u8* out_ptr) override;
|
||||
|
||||
private:
|
||||
ScrubbedBlob(std::unique_ptr<BlobReader> blob_reader, DiscScrubber scrubber);
|
||||
|
||||
std::unique_ptr<BlobReader> m_blob_reader;
|
||||
DiscScrubber m_scrubber;
|
||||
};
|
||||
|
||||
} // namespace DiscIO
|
@ -1040,7 +1040,7 @@ void VolumeVerifier::SetUpHashing()
|
||||
else if (m_volume.GetVolumeType() == Platform::WiiDisc)
|
||||
{
|
||||
// Set up a DiscScrubber for checking whether blocks with errors are unused
|
||||
m_scrubber.SetupScrub(&m_volume, VolumeWii::BLOCK_TOTAL_SIZE);
|
||||
m_scrubber.SetupScrub(&m_volume);
|
||||
}
|
||||
|
||||
std::sort(m_blocks.begin(), m_blocks.end(),
|
||||
|
@ -24,6 +24,7 @@ public:
|
||||
static std::unique_ptr<WbfsFileReader> Create(File::IOFile file, const std::string& path);
|
||||
|
||||
BlobType GetBlobType() const override { return BlobType::WBFS; }
|
||||
|
||||
u64 GetRawSize() const override { return m_size; }
|
||||
// The WBFS format does not save the original file size.
|
||||
// This function returns a constant upper bound
|
||||
@ -31,6 +32,8 @@ public:
|
||||
u64 GetDataSize() const override;
|
||||
bool IsDataSizeAccurate() const override { return false; }
|
||||
|
||||
u64 GetBlockSize() const override { return m_wbfs_sector_size; }
|
||||
|
||||
bool Read(u64 offset, u64 nbytes, u8* out_ptr) override;
|
||||
|
||||
private:
|
||||
|
Reference in New Issue
Block a user