Merge pull request #10932 from JosJuice/nfs

DiscIO: Add support for the NFS format
This commit is contained in:
Admiral H. Curtiss 2022-08-04 22:20:08 +02:00 committed by GitHub
commit 5508c52a95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 637 additions and 143 deletions

View File

@ -30,7 +30,8 @@ import java.util.Set;
public final class FileBrowserHelper
{
public static final HashSet<String> GAME_EXTENSIONS = new HashSet<>(Arrays.asList(
"gcm", "tgc", "iso", "ciso", "gcz", "wbfs", "wia", "rvz", "wad", "dol", "elf", "json"));
"gcm", "tgc", "iso", "ciso", "gcz", "wbfs", "wia", "rvz", "nfs", "wad", "dol", "elf",
"json"));
public static final HashSet<String> GAME_LIKE_EXTENSIONS = new HashSet<>(GAME_EXTENSIONS);

View File

@ -231,7 +231,7 @@ std::unique_ptr<BootParameters> BootParameters::GenerateFromFile(std::vector<std
#endif
static const std::unordered_set<std::string> disc_image_extensions = {
{".gcm", ".iso", ".tgc", ".wbfs", ".ciso", ".gcz", ".wia", ".rvz", ".dol", ".elf"}};
{".gcm", ".iso", ".tgc", ".wbfs", ".ciso", ".gcz", ".wia", ".rvz", ".nfs", ".dol", ".elf"}};
if (disc_image_extensions.find(extension) != disc_image_extensions.end() || is_drive)
{
std::unique_ptr<DiscIO::VolumeDisc> disc = DiscIO::CreateDisc(path);

View File

@ -430,9 +430,9 @@ void Shutdown()
static u64 GetDiscEndOffset(const DiscIO::VolumeDisc& disc)
{
u64 size = disc.GetSize();
u64 size = disc.GetDataSize();
if (disc.IsSizeAccurate())
if (disc.GetDataSizeType() == DiscIO::DataSizeType::Accurate)
{
if (size == DiscIO::MINI_DVD_SIZE)
return DiscIO::MINI_DVD_SIZE;
@ -464,7 +464,7 @@ void SetDisc(std::unique_ptr<DiscIO::VolumeDisc> disc,
if (has_disc)
{
s_disc_end_offset = GetDiscEndOffset(*disc);
if (!disc->IsSizeAccurate())
if (disc->GetDataSizeType() != DiscIO::DataSizeType::Accurate)
WARN_LOG_FMT(DVDINTERFACE, "Unknown disc size, guessing {0} bytes", s_disc_end_offset);
const DiscIO::BlobReader& blob = disc->GetBlobReader();
@ -1482,10 +1482,9 @@ static void ScheduleReads(u64 offset, u32 length, const DiscIO::Partition& parti
u32 buffered_blocks = 0;
u32 unbuffered_blocks = 0;
const u32 bytes_per_chunk =
partition != DiscIO::PARTITION_NONE && DVDThread::IsEncryptedAndHashed() ?
DiscIO::VolumeWii::BLOCK_DATA_SIZE :
DVD_ECC_BLOCK_SIZE;
const u32 bytes_per_chunk = partition != DiscIO::PARTITION_NONE && DVDThread::HasWiiHashes() ?
DiscIO::VolumeWii::BLOCK_DATA_SIZE :
DVD_ECC_BLOCK_SIZE;
do
{

View File

@ -184,10 +184,10 @@ bool HasDisc()
return s_disc != nullptr;
}
bool IsEncryptedAndHashed()
bool HasWiiHashes()
{
// IsEncryptedAndHashed is thread-safe, so calling WaitUntilIdle isn't necessary.
return s_disc->IsEncryptedAndHashed();
// HasWiiHashes is thread-safe, so calling WaitUntilIdle isn't necessary.
return s_disc->HasWiiHashes();
}
DiscIO::Platform GetDiscType()

View File

@ -41,7 +41,7 @@ void DoState(PointerWrap& p);
void SetDisc(std::unique_ptr<DiscIO::Volume> disc);
bool HasDisc();
bool IsEncryptedAndHashed();
bool HasWiiHashes();
DiscIO::Platform GetDiscType();
u64 PartitionOffsetToRawOffset(u64 offset, const DiscIO::Partition& partition);
IOS::ES::TMDReader GetTMD(const DiscIO::Partition& partition);

View File

@ -20,6 +20,7 @@
#include "DiscIO/DirectoryBlob.h"
#include "DiscIO/DriveBlob.h"
#include "DiscIO/FileBlob.h"
#include "DiscIO/NFSBlob.h"
#include "DiscIO/TGCBlob.h"
#include "DiscIO/WIABlob.h"
#include "DiscIO/WbfsBlob.h"
@ -52,6 +53,8 @@ std::string GetName(BlobType blob_type, bool translate)
return "RVZ";
case BlobType::MOD_DESCRIPTOR:
return translate_str("Mod");
case BlobType::NFS:
return "NFS";
default:
return "";
}
@ -242,6 +245,8 @@ std::unique_ptr<BlobReader> CreateBlobReader(const std::string& filename)
return WIAFileReader::Create(std::move(file), filename);
case RVZ_MAGIC:
return RVZFileReader::Create(std::move(file), filename);
case NFS_MAGIC:
return NFSFileReader::Create(std::move(file), filename);
default:
if (auto directory_blob = DirectoryBlobReader::Create(filename))
return std::move(directory_blob);

View File

@ -40,6 +40,19 @@ enum class BlobType
WIA,
RVZ,
MOD_DESCRIPTOR,
NFS,
};
// If you convert an ISO file to another format and then call GetDataSize on it, what is the result?
enum class DataSizeType
{
// The result is the same as for the ISO.
Accurate,
// The result is not larger than for the ISO. (It's usually a little smaller than for the ISO.)
// Reads to offsets that are larger than the result will return some kind of "blank" data.
LowerBound,
// The result is not smaller than for the ISO. (It's usually much larger than for the ISO.)
UpperBound,
};
std::string GetName(BlobType blob_type, bool translate);
@ -53,7 +66,7 @@ public:
virtual u64 GetRawSize() const = 0;
virtual u64 GetDataSize() const = 0;
virtual bool IsDataSizeAccurate() const = 0;
virtual DataSizeType GetDataSizeType() const = 0;
// Returns 0 if the format does not use blocks
virtual u64 GetBlockSize() const = 0;

View File

@ -38,10 +38,8 @@ public:
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; }
DataSizeType GetDataSizeType() const override { return DataSizeType::UpperBound; }
u64 GetBlockSize() const override { return m_block_size; }
bool HasFastRandomAccessInBlock() const override { return true; }

View File

@ -30,6 +30,8 @@ add_library(discio
MultithreadedCompressor.h
NANDImporter.cpp
NANDImporter.h
NFSBlob.cpp
NFSBlob.h
RiivolutionParser.cpp
RiivolutionParser.h
RiivolutionPatcher.cpp

View File

@ -273,7 +273,7 @@ bool ConvertToGCZ(BlobReader* infile, const std::string& infile_path,
const std::string& outfile_path, u32 sub_type, int block_size,
CompressCB callback)
{
ASSERT(infile->IsDataSizeAccurate());
ASSERT(infile->GetDataSizeType() == DataSizeType::Accurate);
File::IOFile outfile(outfile_path, "wb");
if (!outfile)

View File

@ -53,7 +53,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; }
DataSizeType GetDataSizeType() const override { return DataSizeType::Accurate; }
u64 GetBlockSize() const override { return m_header.block_size; }
bool HasFastRandomAccessInBlock() const override { return false; }

View File

@ -668,7 +668,7 @@ void DirectoryBlobReader::SetPartitions(std::vector<PartitionWithType>&& partiti
m_partitions.emplace(partition_data_offset, std::move(partitions[i].partition));
m_nonpartition_contents.Add(partition_data_offset, data_size,
ContentPartition{this, 0, partition_data_offset});
const u64 unaligned_next_partition_address = VolumeWii::EncryptedPartitionOffsetToRawOffset(
const u64 unaligned_next_partition_address = VolumeWii::OffsetInHashedPartitionToRawOffset(
data_size, Partition(partition_address), PARTITION_DATA_OFFSET);
partition_address = Common::AlignUp(unaligned_next_partition_address, 0x10000ull);
}
@ -743,7 +743,7 @@ void DirectoryBlobReader::SetPartitionHeader(DirectoryBlobPartition* partition,
if (wrapped_partition)
{
if (m_wrapped_volume->IsEncryptedAndHashed())
if (m_wrapped_volume->HasWiiHashes())
{
const std::optional<u64> offset = m_wrapped_volume->ReadSwappedAndShifted(
wrapped_partition->offset + WII_PARTITION_H3_OFFSET_ADDRESS, PARTITION_NONE);

View File

@ -284,7 +284,7 @@ public:
u64 GetRawSize() const override;
u64 GetDataSize() const override;
bool IsDataSizeAccurate() const override { return true; }
DataSizeType GetDataSizeType() const override { return DataSizeType::Accurate; }
u64 GetBlockSize() const override { return 0; }
bool HasFastRandomAccessInBlock() const override { return true; }

View File

@ -286,7 +286,7 @@ bool ExportSystemData(const Volume& volume, const Partition& partition,
success &= ExportTicket(volume, partition, export_folder + "/ticket.bin");
success &= ExportTMD(volume, partition, export_folder + "/tmd.bin");
success &= ExportCertificateChain(volume, partition, export_folder + "/cert.bin");
if (volume.IsEncryptedAndHashed())
if (volume.HasWiiHashes())
success &= ExportH3Hashes(volume, partition, export_folder + "/h3.bin");
}

View File

@ -30,7 +30,7 @@ bool DiscScrubber::SetupScrub(const Volume* disc)
return false;
m_disc = disc;
m_file_size = m_disc->GetSize();
m_file_size = m_disc->GetDataSize();
// Round up when diving by CLUSTER_SIZE, otherwise MarkAsUsed might write out of bounds
const size_t num_clusters = static_cast<size_t>((m_file_size + CLUSTER_SIZE - 1) / CLUSTER_SIZE);
@ -47,7 +47,11 @@ bool DiscScrubber::SetupScrub(const Volume* disc)
bool DiscScrubber::CanBlockBeScrubbed(u64 offset) const
{
return m_is_scrubbing && m_free_table[offset / CLUSTER_SIZE];
if (!m_is_scrubbing)
return false;
const u64 cluster_index = offset / CLUSTER_SIZE;
return cluster_index >= m_free_table.size() || m_free_table[cluster_index];
}
void DiscScrubber::MarkAsUsed(u64 offset, u64 size)
@ -92,7 +96,7 @@ void DiscScrubber::MarkAsUsedE(u64 partition_data_offset, u64 offset, u64 size)
// Compensate for 0x400 (SHA-1) per 0x8000 (cluster), and round to whole clusters
u64 DiscScrubber::ToClusterOffset(u64 offset) const
{
if (m_disc->IsEncryptedAndHashed())
if (m_disc->HasWiiHashes())
return offset / 0x7c00 * CLUSTER_SIZE;
else
return Common::AlignDown(offset, CLUSTER_SIZE);

View File

@ -27,7 +27,7 @@ public:
u64 GetRawSize() const override { return m_size; }
u64 GetDataSize() const override { return m_size; }
bool IsDataSizeAccurate() const override { return true; }
DataSizeType GetDataSizeType() const override { return DataSizeType::Accurate; }
u64 GetBlockSize() const override { return ECC_BLOCK_SIZE; }
bool HasFastRandomAccessInBlock() const override { return false; }

View File

@ -44,7 +44,7 @@ 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)
{
ASSERT(infile->IsDataSizeAccurate());
ASSERT(infile->GetDataSizeType() == DataSizeType::Accurate);
File::IOFile outfile(outfile_path, "wb");
if (!outfile)

View File

@ -22,7 +22,7 @@ public:
u64 GetRawSize() const override { return m_size; }
u64 GetDataSize() const override { return m_size; }
bool IsDataSizeAccurate() const override { return true; }
DataSizeType GetDataSizeType() const override { return DataSizeType::Accurate; }
u64 GetBlockSize() const override { return 0; }
bool HasFastRandomAccessInBlock() const override { return true; }

View File

@ -0,0 +1,306 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DiscIO/NFSBlob.h"
#include <algorithm>
#include <array>
#include <cstring>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <fmt/format.h>
#include "Common/Align.h"
#include "Common/CommonTypes.h"
#include "Common/Crypto/AES.h"
#include "Common/IOFile.h"
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "Common/Swap.h"
namespace DiscIO
{
bool NFSFileReader::ReadKey(const std::string& path, const std::string& directory, Key* key_out)
{
const std::string_view directory_without_trailing_slash =
std::string_view(directory).substr(0, directory.size() - 1);
std::string parent, parent_name, parent_extension;
SplitPath(directory_without_trailing_slash, &parent, &parent_name, &parent_extension);
if (parent_name + parent_extension != "content")
{
ERROR_LOG_FMT(DISCIO, "hif_000000.nfs is not in a directory named 'content': {}", path);
return false;
}
const std::string key_path = parent + "code/htk.bin";
File::IOFile key_file(key_path, "rb");
if (!key_file.ReadBytes(key_out->data(), key_out->size()))
{
ERROR_LOG_FMT(DISCIO, "Failed to read from {}", key_path);
return false;
}
return true;
}
std::vector<NFSLBARange> NFSFileReader::GetLBARanges(const NFSHeader& header)
{
const size_t lba_range_count =
std::min<size_t>(Common::swap32(header.lba_range_count), header.lba_ranges.size());
std::vector<NFSLBARange> lba_ranges;
lba_ranges.reserve(lba_range_count);
for (size_t i = 0; i < lba_range_count; ++i)
{
const NFSLBARange& unswapped_lba_range = header.lba_ranges[i];
lba_ranges.push_back(NFSLBARange{Common::swap32(unswapped_lba_range.start_block),
Common::swap32(unswapped_lba_range.num_blocks)});
}
return lba_ranges;
}
std::vector<File::IOFile> NFSFileReader::OpenFiles(const std::string& directory,
File::IOFile first_file, u64 expected_raw_size,
u64* raw_size_out)
{
const u64 file_count = Common::AlignUp(expected_raw_size, MAX_FILE_SIZE) / MAX_FILE_SIZE;
std::vector<File::IOFile> files;
files.reserve(file_count);
u64 raw_size = first_file.GetSize();
files.emplace_back(std::move(first_file));
for (u64 i = 1; i < file_count; ++i)
{
const std::string child_path = fmt::format("{}hif_{:06}.nfs", directory, i);
File::IOFile child(child_path, "rb");
if (!child)
{
ERROR_LOG_FMT(DISCIO, "Failed to open {}", child_path);
return {};
}
raw_size += child.GetSize();
files.emplace_back(std::move(child));
}
if (raw_size < expected_raw_size)
{
ERROR_LOG_FMT(
DISCIO,
"Expected sum of NFS file sizes for {} to be at least {} bytes, but it was {} bytes",
directory, expected_raw_size, raw_size);
return {};
}
return files;
}
u64 NFSFileReader::CalculateExpectedRawSize(const std::vector<NFSLBARange>& lba_ranges)
{
u64 total_blocks = 0;
for (const NFSLBARange& range : lba_ranges)
total_blocks += range.num_blocks;
return sizeof(NFSHeader) + total_blocks * BLOCK_SIZE;
}
u64 NFSFileReader::CalculateExpectedDataSize(const std::vector<NFSLBARange>& lba_ranges)
{
u32 greatest_block_index = 0;
for (const NFSLBARange& range : lba_ranges)
greatest_block_index = std::max(greatest_block_index, range.start_block + range.num_blocks);
return u64(greatest_block_index) * BLOCK_SIZE;
}
std::unique_ptr<NFSFileReader> NFSFileReader::Create(File::IOFile first_file,
const std::string& path)
{
std::string directory, filename, extension;
SplitPath(path, &directory, &filename, &extension);
if (filename + extension != "hif_000000.nfs")
return nullptr;
std::array<u8, 16> key;
if (!ReadKey(path, directory, &key))
return nullptr;
NFSHeader header;
if (!first_file.Seek(0, File::SeekOrigin::Begin) ||
!first_file.ReadArray(&header, 1) && header.magic != NFS_MAGIC)
{
return nullptr;
}
std::vector<NFSLBARange> lba_ranges = GetLBARanges(header);
const u64 expected_raw_size = CalculateExpectedRawSize(lba_ranges);
u64 raw_size;
std::vector<File::IOFile> files =
OpenFiles(directory, std::move(first_file), expected_raw_size, &raw_size);
if (files.empty())
return nullptr;
return std::unique_ptr<NFSFileReader>(
new NFSFileReader(std::move(lba_ranges), std::move(files), key, raw_size));
}
NFSFileReader::NFSFileReader(std::vector<NFSLBARange> lba_ranges, std::vector<File::IOFile> files,
Key key, u64 raw_size)
: m_lba_ranges(std::move(lba_ranges)), m_files(std::move(files)),
m_aes_context(Common::AES::CreateContextDecrypt(key.data())), m_raw_size(raw_size)
{
m_data_size = CalculateExpectedDataSize(m_lba_ranges);
}
u64 NFSFileReader::GetDataSize() const
{
return m_data_size;
}
u64 NFSFileReader::GetRawSize() const
{
return m_raw_size;
}
u64 NFSFileReader::ToPhysicalBlockIndex(u64 logical_block_index)
{
u64 physical_blocks_so_far = 0;
for (const NFSLBARange& range : m_lba_ranges)
{
if (logical_block_index >= range.start_block &&
logical_block_index < range.start_block + range.num_blocks)
{
return physical_blocks_so_far + (logical_block_index - range.start_block);
}
physical_blocks_so_far += range.num_blocks;
}
return std::numeric_limits<u64>::max();
}
bool NFSFileReader::ReadEncryptedBlock(u64 physical_block_index)
{
constexpr u64 BLOCKS_PER_FILE = MAX_FILE_SIZE / BLOCK_SIZE;
const u64 file_index = physical_block_index / BLOCKS_PER_FILE;
const u64 block_in_file = physical_block_index % BLOCKS_PER_FILE;
if (block_in_file == BLOCKS_PER_FILE - 1)
{
// Special case. Because of the 0x200 byte header at the very beginning,
// the last block of each file has its last 0x200 bytes stored in the next file.
constexpr size_t PART_1_SIZE = BLOCK_SIZE - sizeof(NFSHeader);
constexpr size_t PART_2_SIZE = sizeof(NFSHeader);
File::IOFile& file_1 = m_files[file_index];
File::IOFile& file_2 = m_files[file_index + 1];
if (!file_1.Seek(sizeof(NFSHeader) + block_in_file * BLOCK_SIZE, File::SeekOrigin::Begin) ||
!file_1.ReadBytes(m_current_block_encrypted.data(), PART_1_SIZE))
{
file_1.ClearError();
return false;
}
if (!file_2.Seek(0, File::SeekOrigin::Begin) ||
!file_2.ReadBytes(m_current_block_encrypted.data() + PART_1_SIZE, PART_2_SIZE))
{
file_2.ClearError();
return false;
}
}
else
{
// Normal case. The read is offset by 0x200 bytes, but it's all within one file.
File::IOFile& file = m_files[file_index];
if (!file.Seek(sizeof(NFSHeader) + block_in_file * BLOCK_SIZE, File::SeekOrigin::Begin) ||
!file.ReadBytes(m_current_block_encrypted.data(), BLOCK_SIZE))
{
file.ClearError();
return false;
}
}
return true;
}
void NFSFileReader::DecryptBlock(u64 logical_block_index)
{
std::array<u8, 16> iv{};
const u64 swapped_block_index = Common::swap64(logical_block_index);
std::memcpy(iv.data() + iv.size() - sizeof(swapped_block_index), &swapped_block_index,
sizeof(swapped_block_index));
m_aes_context->Crypt(iv.data(), m_current_block_encrypted.data(),
m_current_block_decrypted.data(), BLOCK_SIZE);
}
bool NFSFileReader::ReadAndDecryptBlock(u64 logical_block_index)
{
const u64 physical_block_index = ToPhysicalBlockIndex(logical_block_index);
if (physical_block_index == std::numeric_limits<u64>::max())
{
// The block isn't physically present. Treat its contents as all zeroes.
m_current_block_decrypted.fill(0);
}
else
{
if (!ReadEncryptedBlock(physical_block_index))
return false;
DecryptBlock(logical_block_index);
}
// Small hack: Set 0x61 of the header to 1 so that VolumeWii realizes that the disc is unencrypted
if (logical_block_index == 0)
m_current_block_decrypted[0x61] = 1;
return true;
}
bool NFSFileReader::Read(u64 offset, u64 nbytes, u8* out_ptr)
{
while (nbytes != 0)
{
const u64 logical_block_index = offset / BLOCK_SIZE;
const u64 offset_in_block = offset % BLOCK_SIZE;
if (logical_block_index != m_current_logical_block_index)
{
if (!ReadAndDecryptBlock(logical_block_index))
return false;
m_current_logical_block_index = logical_block_index;
}
const u64 bytes_to_copy = std::min(nbytes, BLOCK_SIZE - offset_in_block);
std::memcpy(out_ptr, m_current_block_decrypted.data() + offset_in_block, bytes_to_copy);
offset += bytes_to_copy;
nbytes -= bytes_to_copy;
out_ptr += bytes_to_copy;
}
return true;
}
} // namespace DiscIO

View File

@ -0,0 +1,91 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <limits>
#include <memory>
#include <string>
#include <vector>
#include "Common/CommonTypes.h"
#include "Common/Crypto/AES.h"
#include "Common/IOFile.h"
#include "DiscIO/Blob.h"
// This is the file format used for Wii games released on the Wii U eShop.
namespace DiscIO
{
static constexpr u32 NFS_MAGIC = 0x53474745; // "EGGS" (byteswapped to little endian)
struct NFSLBARange
{
u32 start_block;
u32 num_blocks;
};
struct NFSHeader
{
u32 magic; // EGGS
u32 version;
u32 unknown_1;
u32 unknown_2;
u32 lba_range_count;
std::array<NFSLBARange, 61> lba_ranges;
u32 end_magic; // SGGE
};
static_assert(sizeof(NFSHeader) == 0x200);
class NFSFileReader : public BlobReader
{
public:
static std::unique_ptr<NFSFileReader> Create(File::IOFile first_file,
const std::string& directory_path);
BlobType GetBlobType() const override { return BlobType::NFS; }
u64 GetRawSize() const override;
u64 GetDataSize() const override;
DataSizeType GetDataSizeType() const override { return DataSizeType::LowerBound; }
u64 GetBlockSize() const override { return BLOCK_SIZE; }
bool HasFastRandomAccessInBlock() const override { return false; }
std::string GetCompressionMethod() const override { return {}; }
std::optional<int> GetCompressionLevel() const override { return std::nullopt; }
bool Read(u64 offset, u64 nbytes, u8* out_ptr) override;
private:
using Key = std::array<u8, Common::AES::Context::KEY_SIZE>;
static constexpr u32 BLOCK_SIZE = 0x8000;
static constexpr u32 MAX_FILE_SIZE = 0xFA00000;
static bool ReadKey(const std::string& path, const std::string& directory, Key* key_out);
static std::vector<NFSLBARange> GetLBARanges(const NFSHeader& header);
static std::vector<File::IOFile> OpenFiles(const std::string& directory, File::IOFile first_file,
u64 expected_raw_size, u64* raw_size_out);
static u64 CalculateExpectedRawSize(const std::vector<NFSLBARange>& lba_ranges);
static u64 CalculateExpectedDataSize(const std::vector<NFSLBARange>& lba_ranges);
NFSFileReader(std::vector<NFSLBARange> lba_ranges, std::vector<File::IOFile> files, Key key,
u64 raw_size);
u64 ToPhysicalBlockIndex(u64 logical_block_index);
bool ReadEncryptedBlock(u64 physical_block_index);
void DecryptBlock(u64 logical_block_index);
bool ReadAndDecryptBlock(u64 logical_block_index);
std::array<u8, BLOCK_SIZE> m_current_block_encrypted;
std::array<u8, BLOCK_SIZE> m_current_block_decrypted;
u64 m_current_logical_block_index = std::numeric_limits<u64>::max();
std::vector<NFSLBARange> m_lba_ranges;
std::vector<File::IOFile> m_files;
std::unique_ptr<Common::AES::Context> m_aes_context;
u64 m_raw_size;
u64 m_data_size;
};
} // namespace DiscIO

View File

@ -22,7 +22,7 @@ public:
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(); }
DataSizeType GetDataSizeType() const override { return m_blob_reader->GetDataSizeType(); }
u64 GetBlockSize() const override { return m_blob_reader->GetBlockSize(); }
bool HasFastRandomAccessInBlock() const override

View File

@ -45,7 +45,7 @@ public:
u64 GetRawSize() const override { return m_size; }
u64 GetDataSize() const override;
bool IsDataSizeAccurate() const override { return true; }
DataSizeType GetDataSizeType() const override { return DataSizeType::Accurate; }
u64 GetBlockSize() const override { return 0; }
bool HasFastRandomAccessInBlock() const override { return true; }

View File

@ -22,6 +22,7 @@ namespace DiscIO
{
class BlobReader;
enum class BlobType;
enum class DataSizeType;
class FileSystem;
class VolumeDisc;
class VolumeWAD;
@ -63,7 +64,8 @@ public:
return static_cast<u64>(*temp) << GetOffsetShift();
}
virtual bool IsEncryptedAndHashed() const { return false; }
virtual bool HasWiiHashes() const { return false; }
virtual bool HasWiiEncryption() const { return false; }
virtual std::vector<Partition> GetPartitions() const { return {}; }
virtual Partition GetGamePartition() const { return PARTITION_NONE; }
virtual std::optional<u32> GetPartitionType(const Partition& partition) const
@ -122,7 +124,6 @@ public:
virtual Platform GetVolumeType() const = 0;
virtual bool IsDatelDisc() const = 0;
virtual bool IsNKit() const = 0;
virtual bool SupportsIntegrityCheck() const { return false; }
virtual bool CheckH3TableIntegrity(const Partition& partition) const { return false; }
virtual bool CheckBlockIntegrity(u64 block_index, const u8* encrypted_data,
const Partition& partition) const
@ -137,8 +138,8 @@ public:
virtual Country GetCountry(const Partition& partition = PARTITION_NONE) const = 0;
virtual BlobType GetBlobType() const = 0;
// Size of virtual disc (may be inaccurate depending on the blob type)
virtual u64 GetSize() const = 0;
virtual bool IsSizeAccurate() const = 0;
virtual u64 GetDataSize() const = 0;
virtual DataSizeType GetDataSizeType() const = 0;
// Size on disc (compressed size)
virtual u64 GetRawSize() const = 0;
virtual const BlobReader& GetBlobReader() const = 0;

View File

@ -25,7 +25,7 @@ public:
u64 GetRawSize() const override;
u64 GetDataSize() const override;
bool IsDataSizeAccurate() const override { return true; }
DataSizeType GetDataSizeType() const override { return DataSizeType::Accurate; }
u64 GetBlockSize() const override;
bool HasFastRandomAccessInBlock() const override;

View File

@ -119,14 +119,14 @@ BlobType VolumeGC::GetBlobType() const
return m_reader->GetBlobType();
}
u64 VolumeGC::GetSize() const
u64 VolumeGC::GetDataSize() const
{
return m_reader->GetDataSize();
}
bool VolumeGC::IsSizeAccurate() const
DataSizeType VolumeGC::GetDataSizeType() const
{
return m_reader->IsDataSizeAccurate();
return m_reader->GetDataSizeType();
}
u64 VolumeGC::GetRawSize() const

View File

@ -45,8 +45,8 @@ public:
bool IsDatelDisc() const override;
Region GetRegion() const override;
BlobType GetBlobType() const override;
u64 GetSize() const override;
bool IsSizeAccurate() const override;
u64 GetDataSize() const override;
DataSizeType GetDataSizeType() const override;
u64 GetRawSize() const override;
const BlobReader& GetBlobReader() const override;

View File

@ -62,7 +62,7 @@ void RedumpVerifier::Start(const Volume& volume)
m_revision = volume.GetRevision().value_or(0);
m_disc_number = volume.GetDiscNumber().value_or(0);
m_size = volume.GetSize();
m_size = volume.GetDataSize();
const DiscIO::Platform platform = volume.GetVolumeType();
@ -364,7 +364,7 @@ VolumeVerifier::VolumeVerifier(const Volume& volume, bool redump_verification,
m_hashes_to_calculate(hashes_to_calculate),
m_calculating_any_hash(hashes_to_calculate.crc32 || hashes_to_calculate.md5 ||
hashes_to_calculate.sha1),
m_max_progress(volume.GetSize())
m_max_progress(volume.GetDataSize()), m_data_size_type(volume.GetDataSizeType())
{
if (!m_calculating_any_hash)
m_redump_verification = false;
@ -403,9 +403,8 @@ void VolumeVerifier::Start()
m_is_tgc = m_volume.GetBlobType() == BlobType::TGC;
m_is_datel = m_volume.IsDatelDisc();
m_is_not_retail =
(m_volume.GetVolumeType() == Platform::WiiDisc && !m_volume.IsEncryptedAndHashed()) ||
IsDebugSigned();
m_is_not_retail = (m_volume.GetVolumeType() == Platform::WiiDisc && !m_volume.HasWiiHashes()) ||
IsDebugSigned();
const std::vector<Partition> partitions = CheckPartitions();
@ -492,7 +491,7 @@ std::vector<Partition> VolumeVerifier::CheckPartitions()
Common::GetStringT("The update partition is not at its normal position."));
}
const u64 normal_data_offset = m_volume.IsEncryptedAndHashed() ? 0xF800000 : 0x838000;
const u64 normal_data_offset = m_volume.HasWiiHashes() ? 0xF800000 : 0x838000;
if (m_volume.GetPartitionType(partition) == PARTITION_DATA &&
partition.offset != normal_data_offset && !has_channel_partition && !has_install_partition)
{
@ -593,14 +592,14 @@ bool VolumeVerifier::CheckPartition(const Partition& partition)
}
}
if (m_volume.SupportsIntegrityCheck() && !m_volume.CheckH3TableIntegrity(partition))
if (m_volume.HasWiiHashes() && !m_volume.CheckH3TableIntegrity(partition))
{
AddProblem(Severity::Low,
Common::FmtFormatT("The H3 hash table for the {0} partition is not correct.", name));
}
// Prepare for hash verification in the Process step
if (m_volume.SupportsIntegrityCheck())
if (m_volume.HasWiiHashes())
{
const u64 data_size =
m_volume.ReadSwappedAndShifted(partition.offset + 0x2bc, PARTITION_NONE).value_or(0);
@ -759,11 +758,10 @@ bool VolumeVerifier::ShouldBeDualLayer() const
void VolumeVerifier::CheckVolumeSize()
{
u64 volume_size = m_volume.GetSize();
u64 volume_size = m_volume.GetDataSize();
const bool is_disc = IsDisc(m_volume.GetVolumeType());
const bool should_be_dual_layer = is_disc && ShouldBeDualLayer();
const bool is_size_accurate = m_volume.IsSizeAccurate();
bool volume_size_roughly_known = is_size_accurate;
bool volume_size_roughly_known = m_data_size_type != DiscIO::DataSizeType::UpperBound;
if (should_be_dual_layer && m_biggest_referenced_offset <= SL_DVD_R_SIZE)
{
@ -774,13 +772,13 @@ void VolumeVerifier::CheckVolumeSize()
"This problem generally only exists in illegal copies of games."));
}
if (!is_size_accurate)
if (m_data_size_type != DiscIO::DataSizeType::Accurate)
{
AddProblem(Severity::Low,
Common::GetStringT("The format that the disc image is saved in does not "
"store the size of the disc image."));
if (m_volume.SupportsIntegrityCheck())
if (!volume_size_roughly_known && m_volume.HasWiiHashes())
{
volume_size = m_biggest_verified_offset;
volume_size_roughly_known = true;
@ -804,7 +802,10 @@ void VolumeVerifier::CheckVolumeSize()
return;
}
if (is_disc && is_size_accurate && !m_is_tgc)
// The reason why this condition is checking for m_data_size_type != UpperBound instead of
// m_data_size_type == Accurate is because we want to show the warning about input recordings and
// NetPlay for NFS disc images (which are the only disc images that have it set to LowerBound).
if (is_disc && m_data_size_type != DiscIO::DataSizeType::UpperBound && !m_is_tgc)
{
const Platform platform = m_volume.GetVolumeType();
const bool should_be_gc_size = platform == Platform::GameCubeDisc || m_is_datel;
@ -1118,7 +1119,7 @@ void VolumeVerifier::Process()
ASSERT(m_started);
ASSERT(!m_done);
if (m_progress == m_max_progress)
if (m_progress >= m_max_progress)
return;
IOS::ES::Content content{};
@ -1166,13 +1167,21 @@ void VolumeVerifier::Process()
if (m_progress + bytes_to_read > m_max_progress)
{
const u64 bytes_over_max = m_progress + bytes_to_read - m_max_progress;
bytes_to_read -= bytes_over_max;
if (excess_bytes < bytes_over_max)
excess_bytes = 0;
if (m_data_size_type == DataSizeType::LowerBound)
{
// Disc images in NFS format can have the last referenced block be past m_max_progress.
// For NFS, reading beyond m_max_progress doesn't return an error, so let's read beyond it.
excess_bytes = std::max(excess_bytes, bytes_over_max);
}
else
excess_bytes -= bytes_over_max;
content_read = false;
group_read = false;
{
// Don't read beyond the end of the disc.
bytes_to_read -= bytes_over_max;
excess_bytes -= std::min(excess_bytes, bytes_over_max);
content_read = false;
group_read = false;
}
}
const bool is_data_needed = m_calculating_any_hash || content_read || group_read;
@ -1376,8 +1385,18 @@ void VolumeVerifier::Finish()
if (m_result.redump.status == RedumpVerifier::Status::BadDump &&
highest_severity <= Severity::Low)
{
m_result.summary_text = Common::GetStringT(
"This is a bad dump. This doesn't necessarily mean that the game won't run correctly.");
if (m_volume.GetBlobType() == BlobType::NFS)
{
m_result.summary_text =
Common::GetStringT("Compared to the Wii disc release of the game, this is a bad dump. "
"Despite this, it's possible that this is a good dump compared to the "
"Wii U eShop release of the game. Dolphin can't verify this.");
}
else
{
m_result.summary_text = Common::GetStringT(
"This is a bad dump. This doesn't necessarily mean that the game won't run correctly.");
}
}
else
{
@ -1402,9 +1421,18 @@ void VolumeVerifier::Finish()
}
break;
case Severity::Low:
m_result.summary_text =
Common::GetStringT("Problems with low severity were found. They will most "
"likely not prevent the game from running.");
if (m_volume.GetBlobType() == BlobType::NFS)
{
m_result.summary_text = Common::GetStringT(
"Compared to the Wii disc release of the game, problems of low severity were found. "
"Despite this, it's possible that this is a good dump compared to the Wii U eShop "
"release of the game. Dolphin can't verify this.");
}
else
{
Common::GetStringT("Problems with low severity were found. They will most "
"likely not prevent the game from running.");
}
break;
case Severity::Medium:
m_result.summary_text +=

View File

@ -202,6 +202,7 @@ private:
bool m_done = false;
u64 m_progress = 0;
u64 m_max_progress = 0;
DataSizeType m_data_size_type;
};
} // namespace DiscIO

View File

@ -318,14 +318,14 @@ BlobType VolumeWAD::GetBlobType() const
return m_reader->GetBlobType();
}
u64 VolumeWAD::GetSize() const
u64 VolumeWAD::GetDataSize() const
{
return m_reader->GetDataSize();
}
bool VolumeWAD::IsSizeAccurate() const
DataSizeType VolumeWAD::GetDataSizeType() const
{
return m_reader->IsDataSizeAccurate();
return m_reader->GetDataSizeType();
}
u64 VolumeWAD::GetRawSize() const

View File

@ -64,8 +64,8 @@ public:
Country GetCountry(const Partition& partition = PARTITION_NONE) const override;
BlobType GetBlobType() const override;
u64 GetSize() const override;
bool IsSizeAccurate() const override;
u64 GetDataSize() const override;
DataSizeType GetDataSizeType() const override;
u64 GetRawSize() const override;
const BlobReader& GetBlobReader() const override;

View File

@ -41,7 +41,11 @@ VolumeWii::VolumeWii(std::unique_ptr<BlobReader> reader)
{
ASSERT(m_reader);
m_encrypted = m_reader->ReadSwapped<u32>(0x60) == u32(0);
m_has_hashes = m_reader->ReadSwapped<u8>(0x60) == u8(0);
m_has_encryption = m_reader->ReadSwapped<u8>(0x61) == u8(0);
if (m_has_encryption && !m_has_hashes)
ERROR_LOG_FMT(DISCIO, "Wii disc has encryption but no hashes! This probably won't work well");
for (u32 partition_group = 0; partition_group < 4; ++partition_group)
{
@ -114,7 +118,7 @@ VolumeWii::VolumeWii(std::unique_ptr<BlobReader> reader)
};
auto get_h3_table = [this, partition]() -> std::vector<u8> {
if (!m_encrypted)
if (!m_has_hashes)
return {};
const std::optional<u64> h3_table_offset = ReadSwappedAndShifted(
partition.offset + WII_PARTITION_H3_OFFSET_ADDRESS, PARTITION_NONE);
@ -170,35 +174,55 @@ bool VolumeWii::Read(u64 offset, u64 length, u8* buffer, const Partition& partit
const PartitionDetails& partition_details = it->second;
const u64 partition_data_offset = partition.offset + *partition_details.data_offset;
if (m_reader->SupportsReadWiiDecrypted(offset, length, partition_data_offset))
return m_reader->ReadWiiDecrypted(offset, length, buffer, partition_data_offset);
if (!m_encrypted)
if (m_has_hashes && m_has_encryption &&
m_reader->SupportsReadWiiDecrypted(offset, length, partition_data_offset))
{
return m_reader->Read(partition.offset + *partition_details.data_offset + offset, length,
buffer);
return m_reader->ReadWiiDecrypted(offset, length, buffer, partition_data_offset);
}
auto aes_context = partition_details.key->get();
if (!aes_context)
return false;
if (!m_has_hashes)
{
return m_reader->Read(partition_data_offset + offset, length, buffer);
}
Common::AES::Context* aes_context = nullptr;
std::unique_ptr<u8[]> read_buffer = nullptr;
if (m_has_encryption)
{
aes_context = partition_details.key->get();
if (!aes_context)
return false;
read_buffer = std::make_unique<u8[]>(BLOCK_TOTAL_SIZE);
}
auto read_buffer = std::make_unique<u8[]>(BLOCK_TOTAL_SIZE);
while (length > 0)
{
// Calculate offsets
u64 block_offset_on_disc = partition.offset + *partition_details.data_offset +
offset / BLOCK_DATA_SIZE * BLOCK_TOTAL_SIZE;
u64 block_offset_on_disc = partition_data_offset + offset / BLOCK_DATA_SIZE * BLOCK_TOTAL_SIZE;
u64 data_offset_in_block = offset % BLOCK_DATA_SIZE;
if (m_last_decrypted_block != block_offset_on_disc)
{
// Read the current block
if (!m_reader->Read(block_offset_on_disc, BLOCK_TOTAL_SIZE, read_buffer.get()))
return false;
if (m_has_encryption)
{
// Read the current block
if (!m_reader->Read(block_offset_on_disc, BLOCK_TOTAL_SIZE, read_buffer.get()))
return false;
// Decrypt the block's data
DecryptBlockData(read_buffer.get(), m_last_decrypted_block_data, aes_context);
}
else
{
// Read the current block
if (!m_reader->Read(block_offset_on_disc + BLOCK_HEADER_SIZE, BLOCK_DATA_SIZE,
m_last_decrypted_block_data))
{
return false;
}
}
// Decrypt the block's data
DecryptBlockData(read_buffer.get(), m_last_decrypted_block_data, aes_context);
m_last_decrypted_block = block_offset_on_disc;
}
@ -216,9 +240,14 @@ bool VolumeWii::Read(u64 offset, u64 length, u8* buffer, const Partition& partit
return true;
}
bool VolumeWii::IsEncryptedAndHashed() const
bool VolumeWii::HasWiiHashes() const
{
return m_encrypted;
return m_has_hashes;
}
bool VolumeWii::HasWiiEncryption() const
{
return m_has_encryption;
}
std::vector<Partition> VolumeWii::GetPartitions() const
@ -272,8 +301,8 @@ const FileSystem* VolumeWii::GetFileSystem(const Partition& partition) const
return it != m_partitions.end() ? it->second.file_system->get() : nullptr;
}
u64 VolumeWii::EncryptedPartitionOffsetToRawOffset(u64 offset, const Partition& partition,
u64 partition_data_offset)
u64 VolumeWii::OffsetInHashedPartitionToRawOffset(u64 offset, const Partition& partition,
u64 partition_data_offset)
{
if (partition == PARTITION_NONE)
return offset;
@ -289,10 +318,10 @@ u64 VolumeWii::PartitionOffsetToRawOffset(u64 offset, const Partition& partition
return offset;
const u64 data_offset = *it->second.data_offset;
if (!m_encrypted)
if (!m_has_hashes)
return partition.offset + data_offset + offset;
return EncryptedPartitionOffsetToRawOffset(offset, partition, data_offset);
return OffsetInHashedPartitionToRawOffset(offset, partition, data_offset);
}
std::string VolumeWii::GetGameTDBID(const Partition& partition) const
@ -340,14 +369,14 @@ BlobType VolumeWii::GetBlobType() const
return m_reader->GetBlobType();
}
u64 VolumeWii::GetSize() const
u64 VolumeWii::GetDataSize() const
{
return m_reader->GetDataSize();
}
bool VolumeWii::IsSizeAccurate() const
DataSizeType VolumeWii::GetDataSizeType() const
{
return m_reader->IsDataSizeAccurate();
return m_reader->GetDataSizeType();
}
u64 VolumeWii::GetRawSize() const
@ -415,23 +444,37 @@ bool VolumeWii::CheckBlockIntegrity(u64 block_index, const u8* encrypted_data,
if (block_index / BLOCKS_PER_GROUP * Common::SHA1::DIGEST_LEN >=
partition_details.h3_table->size())
{
return false;
auto aes_context = partition_details.key->get();
if (!aes_context)
return false;
}
HashBlock hashes;
DecryptBlockHashes(encrypted_data, &hashes, aes_context);
u8 cluster_data_buffer[BLOCK_DATA_SIZE];
const u8* cluster_data;
auto cluster_data = std::make_unique<u8[]>(BLOCK_DATA_SIZE);
DecryptBlockData(encrypted_data, cluster_data.get(), aes_context);
if (m_has_encryption)
{
Common::AES::Context* aes_context = partition_details.key->get();
if (!aes_context)
return false;
DecryptBlockHashes(encrypted_data, &hashes, aes_context);
DecryptBlockData(encrypted_data, cluster_data_buffer, aes_context);
cluster_data = cluster_data_buffer;
}
else
{
std::memcpy(&hashes, encrypted_data, BLOCK_HEADER_SIZE);
cluster_data = encrypted_data + BLOCK_HEADER_SIZE;
}
for (u32 hash_index = 0; hash_index < 31; ++hash_index)
{
if (Common::SHA1::CalculateDigest(&cluster_data[hash_index * 0x400], 0x400) !=
hashes.h0[hash_index])
{
return false;
}
}
if (Common::SHA1::CalculateDigest(hashes.h0) != hashes.h1[block_index % 8])

View File

@ -60,7 +60,8 @@ public:
VolumeWii(std::unique_ptr<BlobReader> reader);
~VolumeWii();
bool Read(u64 offset, u64 length, u8* buffer, const Partition& partition) const override;
bool IsEncryptedAndHashed() const override;
bool HasWiiHashes() const override;
bool HasWiiEncryption() const override;
std::vector<Partition> GetPartitions() const override;
Partition GetGamePartition() const override;
std::optional<u32> GetPartitionType(const Partition& partition) const override;
@ -69,8 +70,8 @@ public:
const IOS::ES::TMDReader& GetTMD(const Partition& partition) const override;
const std::vector<u8>& GetCertificateChain(const Partition& partition) const override;
const FileSystem* GetFileSystem(const Partition& partition) const override;
static u64 EncryptedPartitionOffsetToRawOffset(u64 offset, const Partition& partition,
u64 partition_data_offset);
static u64 OffsetInHashedPartitionToRawOffset(u64 offset, const Partition& partition,
u64 partition_data_offset);
u64 PartitionOffsetToRawOffset(u64 offset, const Partition& partition) const override;
std::string GetGameTDBID(const Partition& partition = PARTITION_NONE) const override;
std::map<Language, std::string> GetLongNames() const override;
@ -78,7 +79,6 @@ public:
Platform GetVolumeType() const override;
bool IsDatelDisc() const override;
bool SupportsIntegrityCheck() const override { return m_encrypted; }
bool CheckH3TableIntegrity(const Partition& partition) const override;
bool CheckBlockIntegrity(u64 block_index, const u8* encrypted_data,
const Partition& partition) const override;
@ -86,8 +86,8 @@ public:
Region GetRegion() const override;
BlobType GetBlobType() const override;
u64 GetSize() const override;
bool IsSizeAccurate() const override;
u64 GetDataSize() const override;
DataSizeType GetDataSizeType() const override;
u64 GetRawSize() const override;
const BlobReader& GetBlobReader() const override;
std::array<u8, 20> GetSyncHash() const override;
@ -128,7 +128,8 @@ private:
std::unique_ptr<BlobReader> m_reader;
std::map<Partition, PartitionDetails> m_partitions;
Partition m_game_partition;
bool m_encrypted;
bool m_has_hashes;
bool m_has_encryption;
mutable u64 m_last_decrypted_block;
mutable u8 m_last_decrypted_block_data[BLOCK_DATA_SIZE]{};

View File

@ -925,7 +925,7 @@ ConversionResultCode WIARVZFileReader<RVZ>::SetUpDataEntriesForWriting(
std::vector<DataEntry>* data_entries, std::vector<const FileSystem*>* partition_file_systems)
{
std::vector<Partition> partitions;
if (volume && volume->IsEncryptedAndHashed())
if (volume && volume->HasWiiHashes() && volume->HasWiiEncryption())
partitions = volume->GetPartitions();
std::sort(partitions.begin(), partitions.end(),
@ -1731,7 +1731,7 @@ WIARVZFileReader<RVZ>::Convert(BlobReader* infile, const VolumeDisc* infile_volu
File::IOFile* outfile, WIARVZCompressionType compression_type,
int compression_level, int chunk_size, CompressCB callback)
{
ASSERT(infile->IsDataSizeAccurate());
ASSERT(infile->GetDataSizeType() == DataSizeType::Accurate);
ASSERT(chunk_size > 0);
const u64 iso_size = infile->GetDataSize();

View File

@ -52,7 +52,7 @@ public:
u64 GetRawSize() const override { return Common::swap64(m_header_1.wia_file_size); }
u64 GetDataSize() const override { return Common::swap64(m_header_1.iso_file_size); }
bool IsDataSizeAccurate() const override { return true; }
DataSizeType GetDataSizeType() const override { return DataSizeType::Accurate; }
u64 GetBlockSize() const override { return Common::swap32(m_header_2.chunk_size); }
bool HasFastRandomAccessInBlock() const override { return false; }

View File

@ -25,11 +25,8 @@ public:
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
// (the size of a double-layer Wii disc).
u64 GetDataSize() const override;
bool IsDataSizeAccurate() const override { return false; }
DataSizeType GetDataSizeType() const override { return DataSizeType::UpperBound; }
u64 GetBlockSize() const override { return m_wbfs_sector_size; }
bool HasFastRandomAccessInBlock() const override { return true; }

View File

@ -442,6 +442,7 @@
<ClInclude Include="DiscIO\LaggedFibonacciGenerator.h" />
<ClInclude Include="DiscIO\MultithreadedCompressor.h" />
<ClInclude Include="DiscIO\NANDImporter.h" />
<ClInclude Include="DiscIO\NFSBlob.h" />
<ClInclude Include="DiscIO\RiivolutionParser.h" />
<ClInclude Include="DiscIO\RiivolutionPatcher.h" />
<ClInclude Include="DiscIO\ScrubbedBlob.h" />
@ -1056,6 +1057,7 @@
<ClCompile Include="DiscIO\GameModDescriptor.cpp" />
<ClCompile Include="DiscIO\LaggedFibonacciGenerator.cpp" />
<ClCompile Include="DiscIO\NANDImporter.cpp" />
<ClCompile Include="DiscIO\NFSBlob.cpp" />
<ClCompile Include="DiscIO\RiivolutionParser.cpp" />
<ClCompile Include="DiscIO\RiivolutionPatcher.cpp" />
<ClCompile Include="DiscIO\ScrubbedBlob.cpp" />

View File

@ -22,12 +22,13 @@
// NOTE: Qt likes to be case-sensitive here even though it shouldn't be thus this ugly regex hack
static const QStringList game_filters{
QStringLiteral("*.[gG][cC][mM]"), QStringLiteral("*.[iI][sS][oO]"),
QStringLiteral("*.[tT][gG][cC]"), QStringLiteral("*.[cC][iI][sS][oO]"),
QStringLiteral("*.[gG][cC][zZ]"), QStringLiteral("*.[wW][bB][fF][sS]"),
QStringLiteral("*.[wW][iI][aA]"), QStringLiteral("*.[rR][vV][zZ]"),
QStringLiteral("*.[wW][aA][dD]"), QStringLiteral("*.[eE][lL][fF]"),
QStringLiteral("*.[dD][oO][lL]"), QStringLiteral("*.[jJ][sS][oO][nN]")};
QStringLiteral("*.[gG][cC][mM]"), QStringLiteral("*.[iI][sS][oO]"),
QStringLiteral("*.[tT][gG][cC]"), QStringLiteral("*.[cC][iI][sS][oO]"),
QStringLiteral("*.[gG][cC][zZ]"), QStringLiteral("*.[wW][bB][fF][sS]"),
QStringLiteral("*.[wW][iI][aA]"), QStringLiteral("*.[rR][vV][zZ]"),
QStringLiteral("hif_000000.nfs"), QStringLiteral("*.[wW][aA][dD]"),
QStringLiteral("*.[eE][lL][fF]"), QStringLiteral("*.[dD][oO][lL]"),
QStringLiteral("*.[jJ][sS][oO][nN]")};
GameTracker::GameTracker(QObject* parent) : QFileSystemWatcher(parent)
{

View File

@ -14,6 +14,7 @@
<string>gcz</string>
<string>iso</string>
<string>m3u</string>
<string>nfs</string>
<string>rvz</string>
<string>tgc</string>
<string>wad</string>

View File

@ -725,8 +725,8 @@ QStringList MainWindow::PromptFileNames()
QStringList paths = DolphinFileDialog::getOpenFileNames(
this, tr("Select a File"),
settings.value(QStringLiteral("mainwindow/lastdir"), QString{}).toString(),
QStringLiteral("%1 (*.elf *.dol *.gcm *.iso *.tgc *.wbfs *.ciso *.gcz *.wia *.rvz *.wad "
"*.dff *.m3u *.json);;%2 (*)")
QStringLiteral("%1 (*.elf *.dol *.gcm *.iso *.tgc *.wbfs *.ciso *.gcz *.wia *.rvz "
"hif_000000.nfs *.wad *.dff *.m3u *.json);;%2 (*)")
.arg(tr("All GC/Wii files"))
.arg(tr("All Files")));

View File

@ -45,8 +45,8 @@ void PathPane::BrowseDefaultGame()
{
QString file = QDir::toNativeSeparators(DolphinFileDialog::getOpenFileName(
this, tr("Select a Game"), Settings::Instance().GetDefaultGame(),
QStringLiteral("%1 (*.elf *.dol *.gcm *.iso *.tgc *.wbfs *.ciso *.gcz *.wia *.rvz *.wad "
"*.m3u *.json);;%2 (*)")
QStringLiteral("%1 (*.elf *.dol *.gcm *.iso *.tgc *.wbfs *.ciso *.gcz *.wia *.rvz "
"hif_000000.nfs *.wad *.m3u *.json);;%2 (*)")
.arg(tr("All GC/Wii files"))
.arg(tr("All Files"))));

View File

@ -212,7 +212,7 @@ int ConvertCommand::Main(const std::vector<std::string>& args)
}
if (format == DiscIO::BlobType::GCZ && volume &&
!DiscIO::IsGCZBlockSizeLegacyCompatible(block_size_o.value(), volume->GetSize()))
!DiscIO::IsGCZBlockSizeLegacyCompatible(block_size_o.value(), volume->GetDataSize()))
{
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 "

View File

@ -133,8 +133,8 @@ GameFile::GameFile(std::string path) : m_file_path(std::move(path))
m_block_size = volume->GetBlobReader().GetBlockSize();
m_compression_method = volume->GetBlobReader().GetCompressionMethod();
m_file_size = volume->GetRawSize();
m_volume_size = volume->GetSize();
m_volume_size_is_accurate = volume->IsSizeAccurate();
m_volume_size = volume->GetDataSize();
m_volume_size_type = volume->GetDataSizeType();
m_is_datel_disc = volume->IsDatelDisc();
m_is_nkit = volume->IsNKit();
@ -158,7 +158,7 @@ GameFile::GameFile(std::string path) : m_file_path(std::move(path))
m_valid = true;
m_file_size = m_volume_size = File::GetSize(m_file_path);
m_game_id = SConfig::MakeGameID(m_file_name);
m_volume_size_is_accurate = true;
m_volume_size_type = DiscIO::DataSizeType::Accurate;
m_is_datel_disc = false;
m_is_nkit = false;
m_platform = DiscIO::Platform::ELFOrDOL;
@ -349,7 +349,7 @@ void GameFile::DoState(PointerWrap& p)
p.Do(m_file_size);
p.Do(m_volume_size);
p.Do(m_volume_size_is_accurate);
p.Do(m_volume_size_type);
p.Do(m_is_datel_disc);
p.Do(m_is_nkit);
@ -827,7 +827,7 @@ std::string GameFile::GetFileFormatName() const
bool GameFile::ShouldAllowConversion() const
{
return DiscIO::IsDisc(m_platform) && m_volume_size_is_accurate;
return DiscIO::IsDisc(m_platform) && m_volume_size_type == DiscIO::DataSizeType::Accurate;
}
bool GameFile::IsModDescriptor() const

View File

@ -104,7 +104,7 @@ public:
const std::string& GetApploaderDate() const { return m_apploader_date; }
u64 GetFileSize() const { return m_file_size; }
u64 GetVolumeSize() const { return m_volume_size; }
bool IsVolumeSizeAccurate() const { return m_volume_size_is_accurate; }
DiscIO::DataSizeType GetVolumeSizeType() const { return m_volume_size_type; }
bool IsDatelDisc() const { return m_is_datel_disc; }
bool IsNKit() const { return m_is_nkit; }
bool IsModDescriptor() const;
@ -145,7 +145,7 @@ private:
u64 m_file_size{};
u64 m_volume_size{};
bool m_volume_size_is_accurate{};
DiscIO::DataSizeType m_volume_size_type{};
bool m_is_datel_disc{};
bool m_is_nkit{};

View File

@ -27,14 +27,14 @@
namespace UICommon
{
static constexpr u32 CACHE_REVISION = 21; // Last changed in PR 10187
static constexpr u32 CACHE_REVISION = 23; // Last changed in PR 10932
std::vector<std::string> FindAllGamePaths(const std::vector<std::string>& directories_to_scan,
bool recursive_scan)
{
static const std::vector<std::string> search_extensions = {".gcm", ".tgc", ".iso", ".ciso",
".gcz", ".wbfs", ".wia", ".rvz",
".wad", ".dol", ".elf", ".json"};
static const std::vector<std::string> search_extensions = {
".gcm", ".tgc", ".iso", ".ciso", ".gcz", ".wbfs", ".wia",
".rvz", ".nfs", ".wad", ".dol", ".elf", ".json"};
// TODO: We could process paths iteratively as they are found
return Common::DoFileSearch(directories_to_scan, search_extensions, recursive_scan);