From bb27d4cc95ccdaacfb4dce9a2534534d263ce20b Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sun, 31 Jul 2022 13:28:01 +0200 Subject: [PATCH 1/6] DiscIO/VolumeWii: Decouple "is encrypted" from "is hashed" Needed for the next commit. NFS disc images are hashed but not encrypted. While we're at it, also get rid of SupportsIntegrityCheck. It does the same thing as old IsEncryptedAndHashed and new HasWiiHashes. --- Source/Core/Core/HW/DVD/DVDInterface.cpp | 7 +- Source/Core/Core/HW/DVD/DVDThread.cpp | 6 +- Source/Core/Core/HW/DVD/DVDThread.h | 2 +- Source/Core/DiscIO/DirectoryBlob.cpp | 4 +- Source/Core/DiscIO/DiscExtractor.cpp | 2 +- Source/Core/DiscIO/DiscScrubber.cpp | 2 +- Source/Core/DiscIO/Volume.h | 4 +- Source/Core/DiscIO/VolumeVerifier.cpp | 13 ++- Source/Core/DiscIO/VolumeWii.cpp | 107 ++++++++++++++++------- Source/Core/DiscIO/VolumeWii.h | 11 +-- Source/Core/DiscIO/WIABlob.cpp | 2 +- 11 files changed, 101 insertions(+), 59 deletions(-) diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index 857c6edb91..190f0b1213 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -1472,10 +1472,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 { diff --git a/Source/Core/Core/HW/DVD/DVDThread.cpp b/Source/Core/Core/HW/DVD/DVDThread.cpp index 4d37f823ec..5590f58b6e 100644 --- a/Source/Core/Core/HW/DVD/DVDThread.cpp +++ b/Source/Core/Core/HW/DVD/DVDThread.cpp @@ -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() diff --git a/Source/Core/Core/HW/DVD/DVDThread.h b/Source/Core/Core/HW/DVD/DVDThread.h index 420ab3a3d8..fe427e2e57 100644 --- a/Source/Core/Core/HW/DVD/DVDThread.h +++ b/Source/Core/Core/HW/DVD/DVDThread.h @@ -41,7 +41,7 @@ void DoState(PointerWrap& p); void SetDisc(std::unique_ptr 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); diff --git a/Source/Core/DiscIO/DirectoryBlob.cpp b/Source/Core/DiscIO/DirectoryBlob.cpp index 7decaac2e3..295235c605 100644 --- a/Source/Core/DiscIO/DirectoryBlob.cpp +++ b/Source/Core/DiscIO/DirectoryBlob.cpp @@ -668,7 +668,7 @@ void DirectoryBlobReader::SetPartitions(std::vector&& 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 offset = m_wrapped_volume->ReadSwappedAndShifted( wrapped_partition->offset + WII_PARTITION_H3_OFFSET_ADDRESS, PARTITION_NONE); diff --git a/Source/Core/DiscIO/DiscExtractor.cpp b/Source/Core/DiscIO/DiscExtractor.cpp index d0453d7669..229e2a59cc 100644 --- a/Source/Core/DiscIO/DiscExtractor.cpp +++ b/Source/Core/DiscIO/DiscExtractor.cpp @@ -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"); } diff --git a/Source/Core/DiscIO/DiscScrubber.cpp b/Source/Core/DiscIO/DiscScrubber.cpp index de0f17bb64..b79be16315 100644 --- a/Source/Core/DiscIO/DiscScrubber.cpp +++ b/Source/Core/DiscIO/DiscScrubber.cpp @@ -92,7 +92,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); diff --git a/Source/Core/DiscIO/Volume.h b/Source/Core/DiscIO/Volume.h index 2c974f447d..c49f2ee24e 100644 --- a/Source/Core/DiscIO/Volume.h +++ b/Source/Core/DiscIO/Volume.h @@ -63,7 +63,8 @@ public: return static_cast(*temp) << GetOffsetShift(); } - virtual bool IsEncryptedAndHashed() const { return false; } + virtual bool HasWiiHashes() const { return false; } + virtual bool HasWiiEncryption() const { return false; } virtual std::vector GetPartitions() const { return {}; } virtual Partition GetGamePartition() const { return PARTITION_NONE; } virtual std::optional GetPartitionType(const Partition& partition) const @@ -122,7 +123,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 diff --git a/Source/Core/DiscIO/VolumeVerifier.cpp b/Source/Core/DiscIO/VolumeVerifier.cpp index c4c5539fe2..460fd2a46b 100644 --- a/Source/Core/DiscIO/VolumeVerifier.cpp +++ b/Source/Core/DiscIO/VolumeVerifier.cpp @@ -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 partitions = CheckPartitions(); @@ -492,7 +491,7 @@ std::vector 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); @@ -780,7 +779,7 @@ void VolumeVerifier::CheckVolumeSize() 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 (m_volume.HasWiiHashes()) { volume_size = m_biggest_verified_offset; volume_size_roughly_known = true; diff --git a/Source/Core/DiscIO/VolumeWii.cpp b/Source/Core/DiscIO/VolumeWii.cpp index da7254e845..1b56e61ed6 100644 --- a/Source/Core/DiscIO/VolumeWii.cpp +++ b/Source/Core/DiscIO/VolumeWii.cpp @@ -41,7 +41,11 @@ VolumeWii::VolumeWii(std::unique_ptr reader) { ASSERT(m_reader); - m_encrypted = m_reader->ReadSwapped(0x60) == u32(0); + m_has_hashes = m_reader->ReadSwapped(0x60) == u8(0); + m_has_encryption = m_reader->ReadSwapped(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 reader) }; auto get_h3_table = [this, partition]() -> std::vector { - if (!m_encrypted) + if (!m_has_hashes) return {}; const std::optional 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 read_buffer = nullptr; + if (m_has_encryption) + { + aes_context = partition_details.key->get(); + if (!aes_context) + return false; + + read_buffer = std::make_unique(BLOCK_TOTAL_SIZE); + } - auto read_buffer = std::make_unique(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 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 @@ -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(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]) diff --git a/Source/Core/DiscIO/VolumeWii.h b/Source/Core/DiscIO/VolumeWii.h index d4837a26b6..05b43acaff 100644 --- a/Source/Core/DiscIO/VolumeWii.h +++ b/Source/Core/DiscIO/VolumeWii.h @@ -60,7 +60,8 @@ public: VolumeWii(std::unique_ptr 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 GetPartitions() const override; Partition GetGamePartition() const override; std::optional GetPartitionType(const Partition& partition) const override; @@ -69,8 +70,8 @@ public: const IOS::ES::TMDReader& GetTMD(const Partition& partition) const override; const std::vector& 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 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; @@ -128,7 +128,8 @@ private: std::unique_ptr m_reader; std::map 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]{}; diff --git a/Source/Core/DiscIO/WIABlob.cpp b/Source/Core/DiscIO/WIABlob.cpp index 35b6407d66..77accc590d 100644 --- a/Source/Core/DiscIO/WIABlob.cpp +++ b/Source/Core/DiscIO/WIABlob.cpp @@ -925,7 +925,7 @@ ConversionResultCode WIARVZFileReader::SetUpDataEntriesForWriting( std::vector* data_entries, std::vector* partition_file_systems) { std::vector partitions; - if (volume && volume->IsEncryptedAndHashed()) + if (volume && volume->HasWiiHashes() && volume->HasWiiEncryption()) partitions = volume->GetPartitions(); std::sort(partitions.begin(), partitions.end(), From 3a6df63e9bcfeedbcd9a479c9d0ba930d4939c1a Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sun, 31 Jul 2022 10:14:03 +0200 Subject: [PATCH 2/6] DiscIO: Add support for the NFS format For a few years now, I've been thinking it would be nice to make Dolphin support reading Wii games in the format they come in when you download them from the Wii U eShop. The Wii U eShop has some good deals on Wii games (Metroid Prime Trilogy especially is rather expensive if you try to buy it physically!), and it's the only place right now where you can buy Wii games digitally. Of course, Nintendo being Nintendo, next year they're going to shut down this only place where you can buy Wii games digitally. I kind of wish I had implemented this feature earlier so that people would've had ample time to buy the games they want, but... better late than never, right? I used MIT-licensed code from the NOD library as a reference when implementing this. None of the code has been directly copied, but you may notice that the names of the struct members are very similar. https://gitlab.axiodl.com/AxioDL/nod/blob/c1635245b881ed0004ff5e616896579ce1b19164/lib/DiscIONFS.cpp --- .../dolphinemu/utils/FileBrowserHelper.java | 3 +- Source/Core/Core/Boot/Boot.cpp | 2 +- Source/Core/DiscIO/Blob.cpp | 5 + Source/Core/DiscIO/Blob.h | 1 + Source/Core/DiscIO/CMakeLists.txt | 2 + Source/Core/DiscIO/NFSBlob.cpp | 306 ++++++++++++++++++ Source/Core/DiscIO/NFSBlob.h | 91 ++++++ Source/Core/DolphinLib.props | 2 + .../Core/DolphinQt/GameList/GameTracker.cpp | 13 +- Source/Core/DolphinQt/Info.plist.in | 1 + Source/Core/DolphinQt/MainWindow.cpp | 4 +- Source/Core/DolphinQt/Settings/PathPane.cpp | 4 +- Source/Core/UICommon/GameFileCache.cpp | 8 +- 13 files changed, 426 insertions(+), 16 deletions(-) create mode 100644 Source/Core/DiscIO/NFSBlob.cpp create mode 100644 Source/Core/DiscIO/NFSBlob.h diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/FileBrowserHelper.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/FileBrowserHelper.java index 388ed98b69..6924fd65ea 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/FileBrowserHelper.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/FileBrowserHelper.java @@ -30,7 +30,8 @@ import java.util.Set; public final class FileBrowserHelper { public static final HashSet 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 GAME_LIKE_EXTENSIONS = new HashSet<>(GAME_EXTENSIONS); diff --git a/Source/Core/Core/Boot/Boot.cpp b/Source/Core/Core/Boot/Boot.cpp index ba1e901d61..387d628e66 100644 --- a/Source/Core/Core/Boot/Boot.cpp +++ b/Source/Core/Core/Boot/Boot.cpp @@ -231,7 +231,7 @@ std::unique_ptr BootParameters::GenerateFromFile(std::vector 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 disc = DiscIO::CreateDisc(path); diff --git a/Source/Core/DiscIO/Blob.cpp b/Source/Core/DiscIO/Blob.cpp index 7062c363cd..7c6713e642 100644 --- a/Source/Core/DiscIO/Blob.cpp +++ b/Source/Core/DiscIO/Blob.cpp @@ -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 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); diff --git a/Source/Core/DiscIO/Blob.h b/Source/Core/DiscIO/Blob.h index 03a8644de5..d6a81d7c2d 100644 --- a/Source/Core/DiscIO/Blob.h +++ b/Source/Core/DiscIO/Blob.h @@ -40,6 +40,7 @@ enum class BlobType WIA, RVZ, MOD_DESCRIPTOR, + NFS, }; std::string GetName(BlobType blob_type, bool translate); diff --git a/Source/Core/DiscIO/CMakeLists.txt b/Source/Core/DiscIO/CMakeLists.txt index ec1562325a..61790d89d8 100644 --- a/Source/Core/DiscIO/CMakeLists.txt +++ b/Source/Core/DiscIO/CMakeLists.txt @@ -30,6 +30,8 @@ add_library(discio MultithreadedCompressor.h NANDImporter.cpp NANDImporter.h + NFSBlob.cpp + NFSBlob.h RiivolutionParser.cpp RiivolutionParser.h RiivolutionPatcher.cpp diff --git a/Source/Core/DiscIO/NFSBlob.cpp b/Source/Core/DiscIO/NFSBlob.cpp new file mode 100644 index 0000000000..da558361da --- /dev/null +++ b/Source/Core/DiscIO/NFSBlob.cpp @@ -0,0 +1,306 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "DiscIO/NFSBlob.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 NFSFileReader::GetLBARanges(const NFSHeader& header) +{ + const size_t lba_range_count = + std::min(Common::swap32(header.lba_range_count), header.lba_ranges.size()); + + std::vector 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 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 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& 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& 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::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 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 lba_ranges = GetLBARanges(header); + + const u64 expected_raw_size = CalculateExpectedRawSize(lba_ranges); + + u64 raw_size; + std::vector files = + OpenFiles(directory, std::move(first_file), expected_raw_size, &raw_size); + + if (files.empty()) + return nullptr; + + return std::unique_ptr( + new NFSFileReader(std::move(lba_ranges), std::move(files), key, raw_size)); +} + +NFSFileReader::NFSFileReader(std::vector lba_ranges, std::vector 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::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 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::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 diff --git a/Source/Core/DiscIO/NFSBlob.h b/Source/Core/DiscIO/NFSBlob.h new file mode 100644 index 0000000000..847c66324a --- /dev/null +++ b/Source/Core/DiscIO/NFSBlob.h @@ -0,0 +1,91 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#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 lba_ranges; + u32 end_magic; // SGGE +}; +static_assert(sizeof(NFSHeader) == 0x200); + +class NFSFileReader : public BlobReader +{ +public: + static std::unique_ptr 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; + bool IsDataSizeAccurate() const override { return false; } + + u64 GetBlockSize() const override { return BLOCK_SIZE; } + bool HasFastRandomAccessInBlock() const override { return false; } + std::string GetCompressionMethod() const override { return {}; } + std::optional GetCompressionLevel() const override { return std::nullopt; } + + bool Read(u64 offset, u64 nbytes, u8* out_ptr) override; + +private: + using Key = std::array; + 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 GetLBARanges(const NFSHeader& header); + static std::vector OpenFiles(const std::string& directory, File::IOFile first_file, + u64 expected_raw_size, u64* raw_size_out); + static u64 CalculateExpectedRawSize(const std::vector& lba_ranges); + static u64 CalculateExpectedDataSize(const std::vector& lba_ranges); + + NFSFileReader(std::vector lba_ranges, std::vector 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 m_current_block_encrypted; + std::array m_current_block_decrypted; + u64 m_current_logical_block_index = std::numeric_limits::max(); + + std::vector m_lba_ranges; + std::vector m_files; + std::unique_ptr m_aes_context; + u64 m_raw_size; + u64 m_data_size; +}; + +} // namespace DiscIO diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 0c73e3d980..592b423164 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -442,6 +442,7 @@ + @@ -1056,6 +1057,7 @@ + diff --git a/Source/Core/DolphinQt/GameList/GameTracker.cpp b/Source/Core/DolphinQt/GameList/GameTracker.cpp index b783cc0ab3..59421db9e8 100644 --- a/Source/Core/DolphinQt/GameList/GameTracker.cpp +++ b/Source/Core/DolphinQt/GameList/GameTracker.cpp @@ -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) { diff --git a/Source/Core/DolphinQt/Info.plist.in b/Source/Core/DolphinQt/Info.plist.in index b5f3a3f44b..2f5a108cb3 100644 --- a/Source/Core/DolphinQt/Info.plist.in +++ b/Source/Core/DolphinQt/Info.plist.in @@ -14,6 +14,7 @@ gcz iso m3u + nfs rvz tgc wad diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 281bbc78a8..7c4e0a0dd7 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -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"))); diff --git a/Source/Core/DolphinQt/Settings/PathPane.cpp b/Source/Core/DolphinQt/Settings/PathPane.cpp index 6ccc1c2a3b..3382f8f7f9 100644 --- a/Source/Core/DolphinQt/Settings/PathPane.cpp +++ b/Source/Core/DolphinQt/Settings/PathPane.cpp @@ -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")))); diff --git a/Source/Core/UICommon/GameFileCache.cpp b/Source/Core/UICommon/GameFileCache.cpp index dbb349dad9..675ec0dc43 100644 --- a/Source/Core/UICommon/GameFileCache.cpp +++ b/Source/Core/UICommon/GameFileCache.cpp @@ -27,14 +27,14 @@ namespace UICommon { -static constexpr u32 CACHE_REVISION = 21; // Last changed in PR 10187 +static constexpr u32 CACHE_REVISION = 22; // Last changed in PR 10932 std::vector FindAllGamePaths(const std::vector& directories_to_scan, bool recursive_scan) { - static const std::vector search_extensions = {".gcm", ".tgc", ".iso", ".ciso", - ".gcz", ".wbfs", ".wia", ".rvz", - ".wad", ".dol", ".elf", ".json"}; + static const std::vector 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); From a87dffe52d8bfaa05d07770a9113cf9406ea6db0 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Mon, 1 Aug 2022 11:53:30 +0200 Subject: [PATCH 3/6] DiscIO: Replace IsDataSizeAccurate with GetDataSizeType Previously, we had WBFS and CISO which both returned an upper bound of the size, and other formats which returned an accurate size. But now we also have NFS, which returns a lower bound of the size. To allow VolumeVerifier to make better informed decisions for NFS, let's use an enum instead of a bool for the type of data size a blob has. --- Source/Core/Core/HW/DVD/DVDInterface.cpp | 6 +++--- Source/Core/DiscIO/Blob.h | 14 +++++++++++++- Source/Core/DiscIO/CISOBlob.h | 4 +--- Source/Core/DiscIO/CompressedBlob.cpp | 2 +- Source/Core/DiscIO/CompressedBlob.h | 2 +- Source/Core/DiscIO/DirectoryBlob.h | 2 +- Source/Core/DiscIO/DiscScrubber.cpp | 2 +- Source/Core/DiscIO/DriveBlob.h | 2 +- Source/Core/DiscIO/FileBlob.cpp | 2 +- Source/Core/DiscIO/FileBlob.h | 2 +- Source/Core/DiscIO/NFSBlob.h | 2 +- Source/Core/DiscIO/ScrubbedBlob.h | 2 +- Source/Core/DiscIO/TGCBlob.h | 2 +- Source/Core/DiscIO/Volume.h | 5 +++-- Source/Core/DiscIO/VolumeFileBlobReader.h | 2 +- Source/Core/DiscIO/VolumeGC.cpp | 6 +++--- Source/Core/DiscIO/VolumeGC.h | 4 ++-- Source/Core/DiscIO/VolumeVerifier.cpp | 8 ++++---- Source/Core/DiscIO/VolumeWad.cpp | 6 +++--- Source/Core/DiscIO/VolumeWad.h | 4 ++-- Source/Core/DiscIO/VolumeWii.cpp | 6 +++--- Source/Core/DiscIO/VolumeWii.h | 4 ++-- Source/Core/DiscIO/WIABlob.cpp | 2 +- Source/Core/DiscIO/WIABlob.h | 2 +- Source/Core/DiscIO/WbfsBlob.h | 5 +---- Source/Core/DolphinTool/ConvertCommand.cpp | 2 +- Source/Core/UICommon/GameFile.cpp | 10 +++++----- Source/Core/UICommon/GameFile.h | 4 ++-- Source/Core/UICommon/GameFileCache.cpp | 2 +- 29 files changed, 62 insertions(+), 54 deletions(-) diff --git a/Source/Core/Core/HW/DVD/DVDInterface.cpp b/Source/Core/Core/HW/DVD/DVDInterface.cpp index 190f0b1213..8b96b54e4e 100644 --- a/Source/Core/Core/HW/DVD/DVDInterface.cpp +++ b/Source/Core/Core/HW/DVD/DVDInterface.cpp @@ -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 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(); diff --git a/Source/Core/DiscIO/Blob.h b/Source/Core/DiscIO/Blob.h index d6a81d7c2d..102c151200 100644 --- a/Source/Core/DiscIO/Blob.h +++ b/Source/Core/DiscIO/Blob.h @@ -43,6 +43,18 @@ enum class BlobType 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); class BlobReader @@ -54,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; diff --git a/Source/Core/DiscIO/CISOBlob.h b/Source/Core/DiscIO/CISOBlob.h index e7dacb3647..ddbd5d0719 100644 --- a/Source/Core/DiscIO/CISOBlob.h +++ b/Source/Core/DiscIO/CISOBlob.h @@ -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; } diff --git a/Source/Core/DiscIO/CompressedBlob.cpp b/Source/Core/DiscIO/CompressedBlob.cpp index 056c2f4fd3..139a1e6d17 100644 --- a/Source/Core/DiscIO/CompressedBlob.cpp +++ b/Source/Core/DiscIO/CompressedBlob.cpp @@ -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) diff --git a/Source/Core/DiscIO/CompressedBlob.h b/Source/Core/DiscIO/CompressedBlob.h index e32cc2569d..c828b1ad2b 100644 --- a/Source/Core/DiscIO/CompressedBlob.h +++ b/Source/Core/DiscIO/CompressedBlob.h @@ -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; } diff --git a/Source/Core/DiscIO/DirectoryBlob.h b/Source/Core/DiscIO/DirectoryBlob.h index a06e60688f..f616adc876 100644 --- a/Source/Core/DiscIO/DirectoryBlob.h +++ b/Source/Core/DiscIO/DirectoryBlob.h @@ -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; } diff --git a/Source/Core/DiscIO/DiscScrubber.cpp b/Source/Core/DiscIO/DiscScrubber.cpp index b79be16315..690af80045 100644 --- a/Source/Core/DiscIO/DiscScrubber.cpp +++ b/Source/Core/DiscIO/DiscScrubber.cpp @@ -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((m_file_size + CLUSTER_SIZE - 1) / CLUSTER_SIZE); diff --git a/Source/Core/DiscIO/DriveBlob.h b/Source/Core/DiscIO/DriveBlob.h index afe3dc8953..bea2e827e5 100644 --- a/Source/Core/DiscIO/DriveBlob.h +++ b/Source/Core/DiscIO/DriveBlob.h @@ -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; } diff --git a/Source/Core/DiscIO/FileBlob.cpp b/Source/Core/DiscIO/FileBlob.cpp index e185fe9cc2..6011f45ca9 100644 --- a/Source/Core/DiscIO/FileBlob.cpp +++ b/Source/Core/DiscIO/FileBlob.cpp @@ -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) diff --git a/Source/Core/DiscIO/FileBlob.h b/Source/Core/DiscIO/FileBlob.h index ea0bd55b89..d478e1a223 100644 --- a/Source/Core/DiscIO/FileBlob.h +++ b/Source/Core/DiscIO/FileBlob.h @@ -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; } diff --git a/Source/Core/DiscIO/NFSBlob.h b/Source/Core/DiscIO/NFSBlob.h index 847c66324a..c17687471d 100644 --- a/Source/Core/DiscIO/NFSBlob.h +++ b/Source/Core/DiscIO/NFSBlob.h @@ -48,7 +48,7 @@ public: u64 GetRawSize() const override; u64 GetDataSize() const override; - bool IsDataSizeAccurate() const override { return false; } + DataSizeType GetDataSizeType() const override { return DataSizeType::LowerBound; } u64 GetBlockSize() const override { return BLOCK_SIZE; } bool HasFastRandomAccessInBlock() const override { return false; } diff --git a/Source/Core/DiscIO/ScrubbedBlob.h b/Source/Core/DiscIO/ScrubbedBlob.h index 6530860afe..fa5648630b 100644 --- a/Source/Core/DiscIO/ScrubbedBlob.h +++ b/Source/Core/DiscIO/ScrubbedBlob.h @@ -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 diff --git a/Source/Core/DiscIO/TGCBlob.h b/Source/Core/DiscIO/TGCBlob.h index b6d393ea7d..2db1c9e8b7 100644 --- a/Source/Core/DiscIO/TGCBlob.h +++ b/Source/Core/DiscIO/TGCBlob.h @@ -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; } diff --git a/Source/Core/DiscIO/Volume.h b/Source/Core/DiscIO/Volume.h index c49f2ee24e..3747c4c250 100644 --- a/Source/Core/DiscIO/Volume.h +++ b/Source/Core/DiscIO/Volume.h @@ -22,6 +22,7 @@ namespace DiscIO { class BlobReader; enum class BlobType; +enum class DataSizeType; class FileSystem; class VolumeDisc; class VolumeWAD; @@ -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; diff --git a/Source/Core/DiscIO/VolumeFileBlobReader.h b/Source/Core/DiscIO/VolumeFileBlobReader.h index 88b0929cbc..89b87a9470 100644 --- a/Source/Core/DiscIO/VolumeFileBlobReader.h +++ b/Source/Core/DiscIO/VolumeFileBlobReader.h @@ -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; diff --git a/Source/Core/DiscIO/VolumeGC.cpp b/Source/Core/DiscIO/VolumeGC.cpp index 8279b84820..1517207f95 100644 --- a/Source/Core/DiscIO/VolumeGC.cpp +++ b/Source/Core/DiscIO/VolumeGC.cpp @@ -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 diff --git a/Source/Core/DiscIO/VolumeGC.h b/Source/Core/DiscIO/VolumeGC.h index 4dfe64cac4..a87ccd44d8 100644 --- a/Source/Core/DiscIO/VolumeGC.h +++ b/Source/Core/DiscIO/VolumeGC.h @@ -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; diff --git a/Source/Core/DiscIO/VolumeVerifier.cpp b/Source/Core/DiscIO/VolumeVerifier.cpp index 460fd2a46b..bc86a40681 100644 --- a/Source/Core/DiscIO/VolumeVerifier.cpp +++ b/Source/Core/DiscIO/VolumeVerifier.cpp @@ -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()) { if (!m_calculating_any_hash) m_redump_verification = false; @@ -758,10 +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(); + const bool is_size_accurate = m_volume.GetDataSizeType() != DiscIO::DataSizeType::Accurate; bool volume_size_roughly_known = is_size_accurate; if (should_be_dual_layer && m_biggest_referenced_offset <= SL_DVD_R_SIZE) diff --git a/Source/Core/DiscIO/VolumeWad.cpp b/Source/Core/DiscIO/VolumeWad.cpp index edfd4c76ee..81e2912671 100644 --- a/Source/Core/DiscIO/VolumeWad.cpp +++ b/Source/Core/DiscIO/VolumeWad.cpp @@ -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 diff --git a/Source/Core/DiscIO/VolumeWad.h b/Source/Core/DiscIO/VolumeWad.h index ac18b65271..3b723b7ce9 100644 --- a/Source/Core/DiscIO/VolumeWad.h +++ b/Source/Core/DiscIO/VolumeWad.h @@ -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; diff --git a/Source/Core/DiscIO/VolumeWii.cpp b/Source/Core/DiscIO/VolumeWii.cpp index 1b56e61ed6..eca23fb4f9 100644 --- a/Source/Core/DiscIO/VolumeWii.cpp +++ b/Source/Core/DiscIO/VolumeWii.cpp @@ -369,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 diff --git a/Source/Core/DiscIO/VolumeWii.h b/Source/Core/DiscIO/VolumeWii.h index 05b43acaff..b1ed4b9789 100644 --- a/Source/Core/DiscIO/VolumeWii.h +++ b/Source/Core/DiscIO/VolumeWii.h @@ -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 GetSyncHash() const override; diff --git a/Source/Core/DiscIO/WIABlob.cpp b/Source/Core/DiscIO/WIABlob.cpp index 77accc590d..b80753120b 100644 --- a/Source/Core/DiscIO/WIABlob.cpp +++ b/Source/Core/DiscIO/WIABlob.cpp @@ -1731,7 +1731,7 @@ WIARVZFileReader::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(); diff --git a/Source/Core/DiscIO/WIABlob.h b/Source/Core/DiscIO/WIABlob.h index d998005d06..633c5801ce 100644 --- a/Source/Core/DiscIO/WIABlob.h +++ b/Source/Core/DiscIO/WIABlob.h @@ -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; } diff --git a/Source/Core/DiscIO/WbfsBlob.h b/Source/Core/DiscIO/WbfsBlob.h index 30db2a7d4c..a981790f1c 100644 --- a/Source/Core/DiscIO/WbfsBlob.h +++ b/Source/Core/DiscIO/WbfsBlob.h @@ -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; } diff --git a/Source/Core/DolphinTool/ConvertCommand.cpp b/Source/Core/DolphinTool/ConvertCommand.cpp index 4d5e2062fa..94b7f7fd11 100644 --- a/Source/Core/DolphinTool/ConvertCommand.cpp +++ b/Source/Core/DolphinTool/ConvertCommand.cpp @@ -212,7 +212,7 @@ int ConvertCommand::Main(const std::vector& 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 " diff --git a/Source/Core/UICommon/GameFile.cpp b/Source/Core/UICommon/GameFile.cpp index 6e33f480e0..368eab3c1e 100644 --- a/Source/Core/UICommon/GameFile.cpp +++ b/Source/Core/UICommon/GameFile.cpp @@ -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 diff --git a/Source/Core/UICommon/GameFile.h b/Source/Core/UICommon/GameFile.h index eb1151a28a..fca89073a1 100644 --- a/Source/Core/UICommon/GameFile.h +++ b/Source/Core/UICommon/GameFile.h @@ -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{}; diff --git a/Source/Core/UICommon/GameFileCache.cpp b/Source/Core/UICommon/GameFileCache.cpp index 675ec0dc43..f9b26d344d 100644 --- a/Source/Core/UICommon/GameFileCache.cpp +++ b/Source/Core/UICommon/GameFileCache.cpp @@ -27,7 +27,7 @@ namespace UICommon { -static constexpr u32 CACHE_REVISION = 22; // Last changed in PR 10932 +static constexpr u32 CACHE_REVISION = 23; // Last changed in PR 10932 std::vector FindAllGamePaths(const std::vector& directories_to_scan, bool recursive_scan) From 40a4eb3893ce3cae8a3edcc6356812fdcb98c1b9 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Mon, 1 Aug 2022 11:53:30 +0200 Subject: [PATCH 4/6] DiscIO: Adjust GetDataSizeType logic for NFS --- Source/Core/DiscIO/DiscScrubber.cpp | 6 ++++- Source/Core/DiscIO/VolumeVerifier.cpp | 39 ++++++++++++++++++--------- Source/Core/DiscIO/VolumeVerifier.h | 1 + 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/Source/Core/DiscIO/DiscScrubber.cpp b/Source/Core/DiscIO/DiscScrubber.cpp index 690af80045..22aa06571e 100644 --- a/Source/Core/DiscIO/DiscScrubber.cpp +++ b/Source/Core/DiscIO/DiscScrubber.cpp @@ -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) diff --git a/Source/Core/DiscIO/VolumeVerifier.cpp b/Source/Core/DiscIO/VolumeVerifier.cpp index bc86a40681..da4acc5af6 100644 --- a/Source/Core/DiscIO/VolumeVerifier.cpp +++ b/Source/Core/DiscIO/VolumeVerifier.cpp @@ -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.GetDataSize()) + m_max_progress(volume.GetDataSize()), m_data_size_type(volume.GetDataSizeType()) { if (!m_calculating_any_hash) m_redump_verification = false; @@ -761,8 +761,7 @@ void VolumeVerifier::CheckVolumeSize() 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.GetDataSizeType() != DiscIO::DataSizeType::Accurate; - 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) { @@ -773,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.HasWiiHashes()) + if (!volume_size_roughly_known && m_volume.HasWiiHashes()) { volume_size = m_biggest_verified_offset; volume_size_roughly_known = true; @@ -803,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; @@ -1117,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{}; @@ -1165,13 +1167,24 @@ 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; + if (excess_bytes < bytes_over_max) + excess_bytes = 0; + else + excess_bytes -= bytes_over_max; + content_read = false; + group_read = false; + } } const bool is_data_needed = m_calculating_any_hash || content_read || group_read; diff --git a/Source/Core/DiscIO/VolumeVerifier.h b/Source/Core/DiscIO/VolumeVerifier.h index abd8c7f8ee..934e60b7b1 100644 --- a/Source/Core/DiscIO/VolumeVerifier.h +++ b/Source/Core/DiscIO/VolumeVerifier.h @@ -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 From 02e3125f23c3844a28887289011af36808426196 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Mon, 1 Aug 2022 12:15:43 +0200 Subject: [PATCH 5/6] DiscIO/VolumeVerifier: Small logic cleanup Just for ease of reading. No behavioral difference. --- Source/Core/DiscIO/VolumeVerifier.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Source/Core/DiscIO/VolumeVerifier.cpp b/Source/Core/DiscIO/VolumeVerifier.cpp index da4acc5af6..f6c2bb20e0 100644 --- a/Source/Core/DiscIO/VolumeVerifier.cpp +++ b/Source/Core/DiscIO/VolumeVerifier.cpp @@ -1178,10 +1178,7 @@ void VolumeVerifier::Process() { // Don't read beyond the end of the disc. bytes_to_read -= bytes_over_max; - if (excess_bytes < bytes_over_max) - excess_bytes = 0; - else - excess_bytes -= bytes_over_max; + excess_bytes -= std::min(excess_bytes, bytes_over_max); content_read = false; group_read = false; } From 6fc3bbbdd9caa53157ea850f19fa0f8d5da7c3f7 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Mon, 1 Aug 2022 16:53:42 +0200 Subject: [PATCH 6/6] DiscIO/VolumeVerifier: Add a note about NFS bad dumps --- Source/Core/DiscIO/VolumeVerifier.cpp | 29 ++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/Source/Core/DiscIO/VolumeVerifier.cpp b/Source/Core/DiscIO/VolumeVerifier.cpp index f6c2bb20e0..50cfb2a17f 100644 --- a/Source/Core/DiscIO/VolumeVerifier.cpp +++ b/Source/Core/DiscIO/VolumeVerifier.cpp @@ -1385,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 { @@ -1411,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 +=