diff --git a/Source/Core/Core/Boot/Boot.cpp b/Source/Core/Core/Boot/Boot.cpp index 7d83cc7660..fd442a2a5b 100644 --- a/Source/Core/Core/Boot/Boot.cpp +++ b/Source/Core/Core/Boot/Boot.cpp @@ -63,35 +63,34 @@ std::unique_ptr BootParameters::GenerateFromFile(const std::stri std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); static const std::unordered_set disc_image_extensions = { - {".gcm", ".iso", ".tgc", ".wbfs", ".ciso", ".gcz"}}; + {".gcm", ".iso", ".tgc", ".wbfs", ".ciso", ".gcz", ".dol", ".elf"}}; if (disc_image_extensions.find(extension) != disc_image_extensions.end() || is_drive) { - auto volume = DiscIO::CreateVolumeFromFilename(path); - if (!volume) + std::unique_ptr volume = DiscIO::CreateVolumeFromFilename(path); + if (volume) + return std::make_unique(Disc{path, std::move(volume)}); + + if (extension == ".elf") + return std::make_unique(Executable{path, std::make_unique(path)}); + + if (extension == ".dol") + return std::make_unique(Executable{path, std::make_unique(path)}); + + if (is_drive) { - if (is_drive) - { - PanicAlertT("Could not read \"%s\". " - "There is no disc in the drive or it is not a GameCube/Wii backup. " - "Please note that Dolphin cannot play games directly from the original " - "GameCube and Wii discs.", - path.c_str()); - } - else - { - PanicAlertT("\"%s\" is an invalid GCM/ISO file, or is not a GC/Wii ISO.", path.c_str()); - } - return {}; + PanicAlertT("Could not read \"%s\". " + "There is no disc in the drive or it is not a GameCube/Wii backup. " + "Please note that Dolphin cannot play games directly from the original " + "GameCube and Wii discs.", + path.c_str()); } - return std::make_unique(Disc{path, std::move(volume)}); + else + { + PanicAlertT("\"%s\" is an invalid GCM/ISO file, or is not a GC/Wii ISO.", path.c_str()); + } + return {}; } - if (extension == ".elf") - return std::make_unique(Executable{path, std::make_unique(path)}); - - if (extension == ".dol") - return std::make_unique(Executable{path, std::make_unique(path)}); - if (extension == ".dff") return std::make_unique(DFF{path}); @@ -133,44 +132,6 @@ bool CBoot::DVDRead(const DiscIO::Volume& volume, u64 dvd_offset, u32 output_add return true; } -void CBoot::Load_FST(bool is_wii, const DiscIO::Volume* volume) -{ - if (!volume) - return; - - const DiscIO::Partition partition = volume->GetGamePartition(); - - // copy first 32 bytes of disc to start of Mem 1 - DVDRead(*volume, /*offset*/ 0, /*address*/ 0, /*length*/ 0x20, DiscIO::PARTITION_NONE); - - // copy of game id - Memory::Write_U32(Memory::Read_U32(0x0000), 0x3180); - - u32 shift = 0; - if (is_wii) - shift = 2; - - const std::optional fst_offset = volume->ReadSwapped(0x0424, partition); - const std::optional fst_size = volume->ReadSwapped(0x0428, partition); - const std::optional max_fst_size = volume->ReadSwapped(0x042c, partition); - if (!fst_offset || !fst_size || !max_fst_size) - return; - - u32 arena_high = Common::AlignDown(0x817FFFFF - (*max_fst_size << shift), 0x20); - Memory::Write_U32(arena_high, 0x00000034); - - // load FST - DVDRead(*volume, *fst_offset << shift, arena_high, *fst_size << shift, partition); - Memory::Write_U32(arena_high, 0x00000038); - Memory::Write_U32(*max_fst_size << shift, 0x0000003c); - - if (is_wii) - { - // the apploader changes IOS MEM1_ARENA_END too - Memory::Write_U32(arena_high, 0x00003110); - } -} - void CBoot::UpdateDebugger_MapLoaded() { Host_NotifyMapLoaded(); @@ -309,15 +270,11 @@ bool CBoot::Load_BS2(const std::string& boot_rom_filename) return true; } -static const DiscIO::Volume* SetDefaultDisc() +static void SetDefaultDisc() { const SConfig& config = SConfig::GetInstance(); - // load default image or create virtual drive from directory - if (!config.m_strDVDRoot.empty()) - return SetDisc(DiscIO::CreateVolumeFromDirectory(config.m_strDVDRoot, config.bWii)); if (!config.m_strDefaultISO.empty()) - return SetDisc(DiscIO::CreateVolumeFromFilename(config.m_strDefaultISO)); - return nullptr; + SetDisc(DiscIO::CreateVolumeFromFilename(config.m_strDefaultISO)); } // Third boot step after BootManager and Core. See Call schedule in BootManager.cpp @@ -359,34 +316,14 @@ bool CBoot::BootUp(std::unique_ptr boot) if (!executable.reader->IsValid()) return false; - const DiscIO::Volume* volume = nullptr; - // VolumeDirectory only works with DOLs. - if (StringEndsWith(executable.path, ".dol")) - { - if (!config.m_strDVDRoot.empty()) - { - NOTICE_LOG(BOOT, "Setting DVDRoot %s", config.m_strDVDRoot.c_str()); - volume = SetDisc(DiscIO::CreateVolumeFromDirectory( - config.m_strDVDRoot, config.bWii, config.m_strApploader, executable.path)); - } - else if (!config.m_strDefaultISO.empty()) - { - NOTICE_LOG(BOOT, "Loading default ISO %s", config.m_strDefaultISO.c_str()); - volume = SetDisc(DiscIO::CreateVolumeFromFilename(config.m_strDefaultISO)); - } - } - else - { - volume = SetDefaultDisc(); - } - if (!executable.reader->LoadIntoMemory()) { PanicAlertT("Failed to load the executable to memory."); return false; } - // Poor man's bootup + SetDefaultDisc(); + if (config.bWii) { HID4.SBE = 1; @@ -394,14 +331,13 @@ bool CBoot::BootUp(std::unique_ptr boot) SetupBAT(config.bWii); // Because there is no TMD to get the requested system (IOS) version from, // we default to IOS58, which is the version used by the Homebrew Channel. - SetupWiiMemory(volume, 0x000000010000003a); + SetupWiiMemory(nullptr, 0x000000010000003a); } else { - EmulatedBS2_GC(volume, true); + EmulatedBS2_GC(nullptr, true); } - Load_FST(config.bWii, volume); PC = executable.reader->GetEntryPoint(); if (executable.reader->LoadSymbols() || LoadMapFromFilename()) @@ -465,8 +401,13 @@ bool CBoot::BootUp(std::unique_ptr boot) } BootExecutableReader::BootExecutableReader(const std::string& file_name) + : BootExecutableReader(File::IOFile{file_name, "rb"}) { - File::IOFile file{file_name, "rb"}; +} + +BootExecutableReader::BootExecutableReader(File::IOFile file) +{ + file.Seek(0, SEEK_SET); m_bytes.resize(file.GetSize()); file.ReadBytes(m_bytes.data(), m_bytes.size()); } diff --git a/Source/Core/Core/Boot/Boot.h b/Source/Core/Core/Boot/Boot.h index 1f52daa1c5..69f784c827 100644 --- a/Source/Core/Core/Boot/Boot.h +++ b/Source/Core/Core/Boot/Boot.h @@ -15,6 +15,11 @@ #include "DiscIO/Enums.h" #include "DiscIO/Volume.h" +namespace File +{ +class IOFile; +} + struct RegionSetting { const std::string area; @@ -101,7 +106,6 @@ private: static bool EmulatedBS2_Wii(const DiscIO::Volume* volume); static bool EmulatedBS2(bool is_wii, const DiscIO::Volume* volume); static bool Load_BS2(const std::string& boot_rom_filename); - static void Load_FST(bool is_wii, const DiscIO::Volume* volume); static bool SetupWiiMemory(const DiscIO::Volume* volume, u64 ios_title_id); }; @@ -110,6 +114,7 @@ class BootExecutableReader { public: explicit BootExecutableReader(const std::string& file_name); + explicit BootExecutableReader(File::IOFile file); explicit BootExecutableReader(const std::vector& buffer); virtual ~BootExecutableReader(); diff --git a/Source/Core/Core/Boot/DolReader.cpp b/Source/Core/Core/Boot/DolReader.cpp index 391aace573..ef1ece315b 100644 --- a/Source/Core/Core/Boot/DolReader.cpp +++ b/Source/Core/Core/Boot/DolReader.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include "Common/File.h" @@ -18,6 +19,11 @@ DolReader::DolReader(const std::vector& buffer) : BootExecutableReader(buffe m_is_valid = Initialize(buffer); } +DolReader::DolReader(File::IOFile file) : BootExecutableReader(std::move(file)) +{ + m_is_valid = Initialize(m_bytes); +} + DolReader::DolReader(const std::string& filename) : BootExecutableReader(filename) { m_is_valid = Initialize(m_bytes); diff --git a/Source/Core/Core/Boot/DolReader.h b/Source/Core/Core/Boot/DolReader.h index e2daa04733..38f6616ccb 100644 --- a/Source/Core/Core/Boot/DolReader.h +++ b/Source/Core/Core/Boot/DolReader.h @@ -10,10 +10,16 @@ #include "Common/CommonTypes.h" #include "Core/Boot/Boot.h" +namespace File +{ +class IOFile; +} + class DolReader final : public BootExecutableReader { public: explicit DolReader(const std::string& filename); + explicit DolReader(File::IOFile file); explicit DolReader(const std::vector& buffer); ~DolReader(); diff --git a/Source/Core/Core/Boot/ElfReader.cpp b/Source/Core/Core/Boot/ElfReader.cpp index 78a77954ed..99faaa6fda 100644 --- a/Source/Core/Core/Boot/ElfReader.cpp +++ b/Source/Core/Core/Boot/ElfReader.cpp @@ -5,8 +5,10 @@ #include "Core/Boot/ElfReader.h" #include +#include #include "Common/CommonTypes.h" +#include "Common/FileUtil.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" #include "Common/Swap.h" @@ -71,6 +73,11 @@ ElfReader::ElfReader(const std::vector& buffer) : BootExecutableReader(buffe Initialize(m_bytes.data()); } +ElfReader::ElfReader(File::IOFile file) : BootExecutableReader(std::move(file)) +{ + Initialize(m_bytes.data()); +} + ElfReader::ElfReader(const std::string& filename) : BootExecutableReader(filename) { Initialize(m_bytes.data()); diff --git a/Source/Core/Core/Boot/ElfReader.h b/Source/Core/Core/Boot/ElfReader.h index 951f5a7b57..d9ffa78345 100644 --- a/Source/Core/Core/Boot/ElfReader.h +++ b/Source/Core/Core/Boot/ElfReader.h @@ -8,6 +8,11 @@ #include "Core/Boot/Boot.h" #include "Core/Boot/ElfTypes.h" +namespace File +{ +class IOFile; +} + enum KnownElfTypes { KNOWNELF_PSP = 0, @@ -22,6 +27,7 @@ class ElfReader final : public BootExecutableReader { public: explicit ElfReader(const std::string& filename); + explicit ElfReader(File::IOFile file); explicit ElfReader(const std::vector& buffer); ~ElfReader(); u32 Read32(int off) const { return base32[off >> 2]; } diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index 9a605fd563..cda249379c 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -252,8 +252,6 @@ void SConfig::SaveCoreSettings(IniFile& ini) core->Set("FPRF", bFPRF); core->Set("AccurateNaNs", bAccurateNaNs); core->Set("DefaultISO", m_strDefaultISO); - core->Set("DVDRoot", m_strDVDRoot); - core->Set("Apploader", m_strApploader); core->Set("EnableCheats", bEnableCheats); core->Set("SelectedLanguage", SelectedLanguage); core->Set("OverrideGCLang", bOverrideGCLanguage); @@ -568,8 +566,6 @@ void SConfig::LoadCoreSettings(IniFile& ini) core->Get("CPUThread", &bCPUThread, true); core->Get("SyncOnSkipIdle", &bSyncGPUOnSkipIdleHack, true); core->Get("DefaultISO", &m_strDefaultISO); - core->Get("DVDRoot", &m_strDVDRoot); - core->Get("Apploader", &m_strApploader); core->Get("EnableCheats", &bEnableCheats, false); core->Get("SelectedLanguage", &SelectedLanguage, 0); core->Get("OverrideGCLang", &bOverrideGCLanguage, false); diff --git a/Source/Core/Core/ConfigManager.h b/Source/Core/Core/ConfigManager.h index fa31751331..d7b3d1c77d 100644 --- a/Source/Core/Core/ConfigManager.h +++ b/Source/Core/Core/ConfigManager.h @@ -197,8 +197,6 @@ struct SConfig : NonCopyable std::string m_strBootROM; std::string m_strSRAM; std::string m_strDefaultISO; - std::string m_strDVDRoot; - std::string m_strApploader; std::string m_strWiiSDCardPath; std::string m_perfDir; diff --git a/Source/Core/DiscIO/Blob.cpp b/Source/Core/DiscIO/Blob.cpp index da8640c7bc..5e603610b2 100644 --- a/Source/Core/DiscIO/Blob.cpp +++ b/Source/Core/DiscIO/Blob.cpp @@ -16,6 +16,7 @@ #include "DiscIO/Blob.h" #include "DiscIO/CISOBlob.h" #include "DiscIO/CompressedBlob.h" +#include "DiscIO/DirectoryBlob.h" #include "DiscIO/DriveBlob.h" #include "DiscIO/FileBlob.h" #include "DiscIO/TGCBlob.h" @@ -183,12 +184,13 @@ std::unique_ptr CreateBlobReader(const std::string& filename) if (!file.ReadArray(&magic, 1)) return nullptr; - // Conveniently, every supported file format (except for plain disc images) starts - // with a 4-byte magic number that identifies the format, so we just need a simple - // switch statement to create the right blob type. If the magic number doesn't - // match any known magic number, we assume it's a plain disc image. If that - // assumption is wrong, the volume code that runs later will notice the error - // because the blob won't provide valid data when reading the GC/Wii disc header. + // Conveniently, every supported file format (except for plain disc images and + // extracted discs) starts with a 4-byte magic number that identifies the format, + // so we just need a simple switch statement to create the right blob type. If the + // magic number doesn't match any known magic number and the directory structure + // doesn't match the directory blob format, we assume it's a plain disc image. If + // that assumption is wrong, the volume code that runs later will notice the error + // because the blob won't provide the right data when reading the GC/Wii disc header. switch (magic) { @@ -201,6 +203,9 @@ std::unique_ptr CreateBlobReader(const std::string& filename) case WBFS_MAGIC: return WbfsFileReader::Create(std::move(file), filename); default: + if (auto directory_blob = DirectoryBlobReader::Create(filename)) + return std::move(directory_blob); + return PlainFileReader::Create(std::move(file)); } } diff --git a/Source/Core/DiscIO/Blob.h b/Source/Core/DiscIO/Blob.h index 686605ed80..20107bef0f 100644 --- a/Source/Core/DiscIO/Blob.h +++ b/Source/Core/DiscIO/Blob.h @@ -56,6 +56,12 @@ public: return Common::FromBigEndian(temp); } + virtual bool SupportsReadWiiDecrypted() const { return false; } + virtual bool ReadWiiDecrypted(u64 offset, u64 size, u8* out_ptr, u64 partition_offset) + { + return false; + } + protected: BlobReader() {} }; diff --git a/Source/Core/DiscIO/CMakeLists.txt b/Source/Core/DiscIO/CMakeLists.txt index b59435efa0..ef1598ecd7 100644 --- a/Source/Core/DiscIO/CMakeLists.txt +++ b/Source/Core/DiscIO/CMakeLists.txt @@ -3,6 +3,7 @@ set(SRCS CISOBlob.cpp WbfsBlob.cpp CompressedBlob.cpp + DirectoryBlob.cpp DiscExtractor.cpp DiscScrubber.cpp DriveBlob.cpp @@ -14,7 +15,6 @@ set(SRCS NANDImporter.cpp TGCBlob.cpp Volume.cpp - VolumeDirectory.cpp VolumeGC.cpp VolumeWad.cpp VolumeWii.cpp diff --git a/Source/Core/DiscIO/DirectoryBlob.cpp b/Source/Core/DiscIO/DirectoryBlob.cpp new file mode 100644 index 0000000000..364405aa01 --- /dev/null +++ b/Source/Core/DiscIO/DirectoryBlob.cpp @@ -0,0 +1,824 @@ +// Copyright 2008 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DiscIO/DirectoryBlob.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Common/Align.h" +#include "Common/Assert.h" +#include "Common/CommonPaths.h" +#include "Common/CommonTypes.h" +#include "Common/File.h" +#include "Common/FileUtil.h" +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" +#include "Common/Swap.h" +#include "Core/Boot/DolReader.h" +#include "DiscIO/Blob.h" +#include "DiscIO/VolumeWii.h" + +namespace DiscIO +{ +static const DiscContent& AddFileToContents(std::set* contents, + const std::string& path, u64 offset, + u64 max_size = UINT64_MAX); + +// Reads as many bytes as the vector fits (or less, if the file is smaller). +// Returns the number of bytes read. +static size_t ReadFileToVector(const std::string& path, std::vector* vector); + +static void PadToAddress(u64 start_address, u64* address, u64* length, u8** buffer); +static void Write32(u32 data, u32 offset, std::vector* buffer); + +static u32 ComputeNameSize(const File::FSTEntry& parent_entry); +static std::string ASCIIToUppercase(std::string str); +static void ConvertUTF8NamesToSHIFTJIS(File::FSTEntry* parent_entry); + +enum class PartitionType : u32 +{ + Game = 0, + Update = 1, + Channel = 2, + // There are more types used by Super Smash Bros. Brawl, but they don't have special names +}; + +// 0xFF is an arbitrarily picked value. Note that we can't use 0x00, because that means NTSC-J +constexpr u32 INVALID_REGION = 0xFF; + +constexpr u8 ENTRY_SIZE = 0x0c; +constexpr u8 FILE_ENTRY = 0; +constexpr u8 DIRECTORY_ENTRY = 1; + +DiscContent::DiscContent(u64 offset, u64 size, const std::string& path) + : m_offset(offset), m_size(size), m_content_source(path) +{ +} + +DiscContent::DiscContent(u64 offset, u64 size, const u8* data) + : m_offset(offset), m_size(size), m_content_source(data) +{ +} + +DiscContent::DiscContent(u64 offset) : m_offset(offset) +{ +} + +u64 DiscContent::GetOffset() const +{ + return m_offset; +} + +u64 DiscContent::GetSize() const +{ + return m_size; +} + +bool DiscContent::Read(u64* offset, u64* length, u8** buffer) const +{ + if (m_size == 0) + return true; + + _dbg_assert_(DISCIO, *offset >= m_offset); + const u64 offset_in_content = *offset - m_offset; + + if (offset_in_content < m_size) + { + const u64 bytes_to_read = std::min(m_size - offset_in_content, *length); + + if (std::holds_alternative(m_content_source)) + { + File::IOFile file(std::get(m_content_source), "rb"); + file.Seek(offset_in_content, SEEK_SET); + if (!file.ReadBytes(*buffer, bytes_to_read)) + return false; + } + else + { + const u8* const content_pointer = std::get(m_content_source) + offset_in_content; + std::copy(content_pointer, content_pointer + bytes_to_read, *buffer); + } + + *length -= bytes_to_read; + *buffer += bytes_to_read; + *offset += bytes_to_read; + } + + return true; +} + +static std::optional ParsePartitionDirectoryName(const std::string& name) +{ + if (name.size() < 2) + return {}; + + if (!strcasecmp(name.c_str(), "DATA")) + return PartitionType::Game; + if (!strcasecmp(name.c_str(), "UPDATE")) + return PartitionType::Update; + if (!strcasecmp(name.c_str(), "CHANNEL")) + return PartitionType::Channel; + + if (name[0] == 'P' || name[0] == 'p') + { + // e.g. "P-HA8E" (normally only used for Super Smash Bros. Brawl's VC partitions) + if (name[1] == '-' && name.size() == 6) + { + const u32 result = Common::swap32(reinterpret_cast(name.data() + 2)); + return static_cast(result); + } + + // e.g. "P0" + if (std::all_of(name.cbegin() + 1, name.cend(), [](char c) { return c >= '0' && c <= '9'; })) + { + u32 result; + if (TryParse(name.substr(1), &result)) + return static_cast(result); + } + } + + return {}; +} + +static bool IsDirectorySeparator(char c) +{ + return c == '/' +#ifdef _WIN32 + || c == '\\' +#endif + ; +} + +static bool PathCharactersEqual(char a, char b) +{ + return a == b || (IsDirectorySeparator(a) && IsDirectorySeparator(b)); +} + +static bool PathEndsWith(const std::string& path, const std::string& suffix) +{ + if (suffix.size() > path.size()) + return false; + + std::string::const_iterator path_iterator = path.cend() - suffix.size(); + std::string::const_iterator suffix_iterator = suffix.cbegin(); + while (path_iterator != path.cend()) + { + if (!PathCharactersEqual(*path_iterator, *suffix_iterator)) + return false; + path_iterator++; + suffix_iterator++; + } + + return true; +} + +static bool IsValidDirectoryBlob(const std::string& dol_path, std::string* partition_root, + std::string* true_root = nullptr) +{ + if (!PathEndsWith(dol_path, "/sys/main.dol")) + return false; + + const size_t chars_to_remove = std::string("sys/main.dol").size(); + *partition_root = dol_path.substr(0, dol_path.size() - chars_to_remove); + + if (File::GetSize(*partition_root + "sys/boot.bin") < 0x20) + return false; + +#ifdef _WIN32 + constexpr const char* dir_separator = "/\\"; +#else + constexpr char dir_separator = '/'; +#endif + if (true_root) + { + *true_root = + dol_path.substr(0, dol_path.find_last_of(dir_separator, partition_root->size() - 2) + 1); + } + + return true; +} + +static bool ExistsAndIsValidDirectoryBlob(const std::string& dol_path) +{ + std::string partition_root; + return File::Exists(dol_path) && IsValidDirectoryBlob(dol_path, &partition_root); +} + +static bool IsInFilesDirectory(const std::string& path) +{ + size_t files_pos = std::string::npos; + while (true) + { + files_pos = path.rfind("files", files_pos); + if (files_pos == std::string::npos) + return false; + + const size_t slash_before_pos = files_pos - 1; + const size_t slash_after_pos = files_pos + 5; + if ((files_pos == 0 || IsDirectorySeparator(path[slash_before_pos])) && + (slash_after_pos == path.size() || (IsDirectorySeparator(path[slash_after_pos]))) && + ExistsAndIsValidDirectoryBlob(path.substr(0, files_pos) + "sys/main.dol")) + { + return true; + } + + --files_pos; + } +} + +static bool IsMainDolForNonGamePartition(const std::string& path) +{ + std::string partition_root, true_root; + if (!IsValidDirectoryBlob(path, &partition_root, &true_root)) + return false; // This is not a /sys/main.dol + + std::string partition_directory_name = partition_root.substr(true_root.size()); + partition_directory_name.pop_back(); // Remove trailing slash + const std::optional partition_type = + ParsePartitionDirectoryName(partition_directory_name); + if (!partition_type || *partition_type == PartitionType::Game) + return false; // volume_path is the game partition's /sys/main.dol + + const File::FSTEntry true_root_entry = File::ScanDirectoryTree(true_root, false); + for (const File::FSTEntry& entry : true_root_entry.children) + { + if (entry.isDirectory && + ParsePartitionDirectoryName(entry.virtualName) == PartitionType::Game && + ExistsAndIsValidDirectoryBlob(entry.physicalName + "/sys/main.dol")) + { + return true; // volume_path is the /sys/main.dol for a non-game partition + } + } + + return false; // volume_path is the game partition's /sys/main.dol +} + +bool ShouldHideFromGameList(const std::string& volume_path) +{ + return IsInFilesDirectory(volume_path) || IsMainDolForNonGamePartition(volume_path); +} + +std::unique_ptr DirectoryBlobReader::Create(const std::string& dol_path) +{ + std::string partition_root, true_root; + if (!IsValidDirectoryBlob(dol_path, &partition_root, &true_root)) + return nullptr; + + return std::unique_ptr(new DirectoryBlobReader(partition_root, true_root)); +} + +DirectoryBlobReader::DirectoryBlobReader(const std::string& game_partition_root, + const std::string& true_root) +{ + DirectoryBlobPartition game_partition(game_partition_root, {}); + m_is_wii = game_partition.IsWii(); + + if (!m_is_wii) + { + m_gamecube_pseudopartition = std::move(game_partition); + m_data_size = m_gamecube_pseudopartition.GetDataSize(); + } + else + { + SetNonpartitionDiscHeader(game_partition.GetHeader(), game_partition_root); + SetWiiRegionData(game_partition_root); + + std::vector partitions; + partitions.emplace_back(std::move(game_partition), PartitionType::Game); + + std::string game_partition_directory_name = game_partition_root.substr(true_root.size()); + game_partition_directory_name.pop_back(); // Remove trailing slash + if (ParsePartitionDirectoryName(game_partition_directory_name) == PartitionType::Game) + { + const File::FSTEntry true_root_entry = File::ScanDirectoryTree(true_root, false); + for (const File::FSTEntry& entry : true_root_entry.children) + { + if (entry.isDirectory) + { + const std::optional type = ParsePartitionDirectoryName(entry.virtualName); + if (type && *type != PartitionType::Game) + { + partitions.emplace_back(DirectoryBlobPartition(entry.physicalName + "/", m_is_wii), + *type); + } + } + } + } + + SetPartitions(std::move(partitions)); + } +} + +bool DirectoryBlobReader::ReadInternal(u64 offset, u64 length, u8* buffer, + const std::set& contents) +{ + if (contents.empty()) + return true; + + // Determine which DiscContent the offset refers to + std::set::const_iterator it = contents.lower_bound(DiscContent(offset)); + if (it->GetOffset() > offset && it != contents.begin()) + --it; + + // zero fill to start of file data + PadToAddress(it->GetOffset(), &offset, &length, &buffer); + + while (it != contents.end() && length > 0) + { + _dbg_assert_(DISCIO, it->GetOffset() <= offset); + if (!it->Read(&offset, &length, &buffer)) + return false; + + ++it; + + if (it != contents.end()) + { + _dbg_assert_(DISCIO, it->GetOffset() >= offset); + PadToAddress(it->GetOffset(), &offset, &length, &buffer); + } + } + + return true; +} + +bool DirectoryBlobReader::Read(u64 offset, u64 length, u8* buffer) +{ + // TODO: We don't handle raw access to the encrypted area of Wii discs correctly. + + const std::set& contents = + m_is_wii ? m_nonpartition_contents : m_gamecube_pseudopartition.GetContents(); + return ReadInternal(offset, length, buffer, contents); +} + +bool DirectoryBlobReader::SupportsReadWiiDecrypted() const +{ + return m_is_wii; +} + +bool DirectoryBlobReader::ReadWiiDecrypted(u64 offset, u64 size, u8* buffer, u64 partition_offset) +{ + if (!m_is_wii) + return false; + + auto it = m_partitions.find(partition_offset); + if (it == m_partitions.end()) + return false; + + return ReadInternal(offset, size, buffer, it->second.GetContents()); +} + +BlobType DirectoryBlobReader::GetBlobType() const +{ + return BlobType::DIRECTORY; +} + +u64 DirectoryBlobReader::GetRawSize() const +{ + // Not implemented + return 0; +} + +u64 DirectoryBlobReader::GetDataSize() const +{ + return m_data_size; +} + +void DirectoryBlobReader::SetNonpartitionDiscHeader(const std::vector& partition_header, + const std::string& game_partition_root) +{ + constexpr u64 NONPARTITION_DISCHEADER_ADDRESS = 0; + constexpr u64 NONPARTITION_DISCHEADER_SIZE = 0x100; + + m_disc_header_nonpartition.resize(NONPARTITION_DISCHEADER_SIZE); + const size_t header_bin_bytes_read = + ReadFileToVector(game_partition_root + "disc/header.bin", &m_disc_header_nonpartition); + + // If header.bin is missing or smaller than expected, use the content of sys/boot.bin instead + std::copy(partition_header.data() + header_bin_bytes_read, + partition_header.data() + m_disc_header_nonpartition.size(), + m_disc_header_nonpartition.data() + header_bin_bytes_read); + + // 0x60 and 0x61 are the only differences between the partition and non-partition headers + if (header_bin_bytes_read < 0x60) + m_disc_header_nonpartition[0x60] = 0; + if (header_bin_bytes_read < 0x61) + m_disc_header_nonpartition[0x61] = 0; + + m_nonpartition_contents.emplace(NONPARTITION_DISCHEADER_ADDRESS, NONPARTITION_DISCHEADER_SIZE, + m_disc_header_nonpartition.data()); +} + +void DirectoryBlobReader::SetWiiRegionData(const std::string& game_partition_root) +{ + m_wii_region_data.resize(0x10, 0x00); + m_wii_region_data.resize(0x20, 0x80); + Write32(INVALID_REGION, 0, &m_wii_region_data); + + const std::string region_bin_path = game_partition_root + "disc/region.bin"; + const size_t bytes_read = ReadFileToVector(region_bin_path, &m_wii_region_data); + if (bytes_read < 0x4) + ERROR_LOG(DISCIO, "Couldn't read region from %s", region_bin_path.c_str()); + else if (bytes_read < 0x20) + ERROR_LOG(DISCIO, "Couldn't read age ratings from %s", region_bin_path.c_str()); + + constexpr u64 WII_REGION_DATA_ADDRESS = 0x4E000; + constexpr u64 WII_REGION_DATA_SIZE = 0x20; + m_nonpartition_contents.emplace(WII_REGION_DATA_ADDRESS, WII_REGION_DATA_SIZE, + m_wii_region_data.data()); +} + +void DirectoryBlobReader::SetPartitions(std::vector&& partitions) +{ + std::sort(partitions.begin(), partitions.end(), + [](const PartitionWithType& lhs, const PartitionWithType& rhs) { + if (lhs.type == rhs.type) + return lhs.partition.GetRootDirectory() < rhs.partition.GetRootDirectory(); + + // Ascending sort by partition type, except Update (1) comes before before Game (0) + return (lhs.type > PartitionType::Update || rhs.type > PartitionType::Update) ? + lhs.type < rhs.type : + lhs.type > rhs.type; + }); + + u32 subtable_1_size = 0; + while (subtable_1_size < partitions.size() && subtable_1_size < 3 && + partitions[subtable_1_size].type <= PartitionType::Channel) + { + ++subtable_1_size; + } + const u32 subtable_2_size = static_cast(partitions.size() - subtable_1_size); + + constexpr u32 PARTITION_TABLE_ADDRESS = 0x40000; + constexpr u32 PARTITION_SUBTABLE1_OFFSET = 0x20; + constexpr u32 PARTITION_SUBTABLE2_OFFSET = 0x40; + m_partition_table.resize(PARTITION_SUBTABLE2_OFFSET + subtable_2_size * 8); + + Write32(subtable_1_size, 0x0, &m_partition_table); + Write32((PARTITION_TABLE_ADDRESS + PARTITION_SUBTABLE1_OFFSET) >> 2, 0x4, &m_partition_table); + if (subtable_2_size != 0) + { + Write32(subtable_2_size, 0x8, &m_partition_table); + Write32((PARTITION_TABLE_ADDRESS + PARTITION_SUBTABLE2_OFFSET) >> 2, 0xC, &m_partition_table); + } + + constexpr u64 STANDARD_UPDATE_PARTITION_ADDRESS = 0x50000; + constexpr u64 STANDARD_GAME_PARTITION_ADDRESS = 0xF800000; + u64 partition_address = STANDARD_UPDATE_PARTITION_ADDRESS; + u64 offset_in_table = PARTITION_SUBTABLE1_OFFSET; + for (size_t i = 0; i < partitions.size(); ++i) + { + if (i == subtable_1_size) + offset_in_table = PARTITION_SUBTABLE2_OFFSET; + + if (partitions[i].type == PartitionType::Game) + partition_address = std::max(partition_address, STANDARD_GAME_PARTITION_ADDRESS); + + Write32(static_cast(partition_address >> 2), offset_in_table, &m_partition_table); + offset_in_table += 4; + Write32(static_cast(partitions[i].type), offset_in_table, &m_partition_table); + offset_in_table += 4; + + SetPartitionHeader(partitions[i].partition, partition_address); + + const u64 partition_data_size = partitions[i].partition.GetDataSize(); + m_partitions.emplace(partition_address, std::move(partitions[i].partition)); + const u64 unaligned_next_partition_address = + VolumeWii::PartitionOffsetToRawOffset(partition_data_size, Partition(partition_address)); + partition_address = Common::AlignUp(unaligned_next_partition_address, 0x10000ull); + } + m_data_size = partition_address; + + m_nonpartition_contents.emplace(PARTITION_TABLE_ADDRESS, m_partition_table.size(), + m_partition_table.data()); +} + +// This function sets the header that's shortly before the start of the encrypted +// area, not the header that's right at the beginning of the encrypted area +void DirectoryBlobReader::SetPartitionHeader(const DirectoryBlobPartition& partition, + u64 partition_address) +{ + constexpr u32 TICKET_OFFSET = 0x0; + constexpr u32 TICKET_SIZE = 0x2a4; + constexpr u32 TMD_OFFSET = 0x2c0; + constexpr u32 MAX_TMD_SIZE = 0x49e4; + constexpr u32 H3_OFFSET = 0x4000; + constexpr u32 H3_SIZE = 0x18000; + + const std::string& partition_root = partition.GetRootDirectory(); + + AddFileToContents(&m_nonpartition_contents, partition_root + "ticket.bin", + partition_address + TICKET_OFFSET, TICKET_SIZE); + + const DiscContent& tmd = AddFileToContents(&m_nonpartition_contents, partition_root + "tmd.bin", + partition_address + TMD_OFFSET, MAX_TMD_SIZE); + + const u64 cert_offset = Common::AlignUp(TMD_OFFSET + tmd.GetSize(), 0x20ull); + const u64 max_cert_size = H3_OFFSET - cert_offset; + const DiscContent& cert = AddFileToContents(&m_nonpartition_contents, partition_root + "cert.bin", + partition_address + cert_offset, max_cert_size); + + AddFileToContents(&m_nonpartition_contents, partition_root + "h3.bin", + partition_address + H3_OFFSET, H3_SIZE); + + constexpr u32 PARTITION_HEADER_SIZE = 0x1c; + constexpr u32 DATA_OFFSET = 0x20000; + const u64 data_size = Common::AlignUp(partition.GetDataSize(), 0x7c00) / 0x7c00 * 0x8000; + m_partition_headers.emplace_back(PARTITION_HEADER_SIZE); + std::vector& partition_header = m_partition_headers.back(); + Write32(static_cast(tmd.GetSize()), 0x0, &partition_header); + Write32(TMD_OFFSET >> 2, 0x4, &partition_header); + Write32(static_cast(cert.GetSize()), 0x8, &partition_header); + Write32(static_cast(cert_offset >> 2), 0x0C, &partition_header); + Write32(H3_OFFSET >> 2, 0x10, &partition_header); + Write32(DATA_OFFSET >> 2, 0x14, &partition_header); + Write32(static_cast(data_size >> 2), 0x18, &partition_header); + + m_nonpartition_contents.emplace(partition_address + TICKET_SIZE, PARTITION_HEADER_SIZE, + partition_header.data()); +} + +DirectoryBlobPartition::DirectoryBlobPartition(const std::string& root_directory, + std::optional is_wii) + : m_root_directory(root_directory) +{ + SetDiscHeaderAndDiscType(is_wii); + SetBI2(); + BuildFST(SetDOL(SetApploader())); +} + +void DirectoryBlobPartition::SetDiscHeaderAndDiscType(std::optional is_wii) +{ + constexpr u64 DISCHEADER_ADDRESS = 0; + constexpr u64 DISCHEADER_SIZE = 0x440; + + m_disc_header.resize(DISCHEADER_SIZE); + const std::string boot_bin_path = m_root_directory + "sys/boot.bin"; + if (ReadFileToVector(boot_bin_path, &m_disc_header) < 0x20) + ERROR_LOG(DISCIO, "%s doesn't exist or is too small", boot_bin_path.c_str()); + + m_contents.emplace(DISCHEADER_ADDRESS, DISCHEADER_SIZE, m_disc_header.data()); + + if (is_wii.has_value()) + { + m_is_wii = *is_wii; + } + else + { + m_is_wii = Common::swap32(&m_disc_header[0x18]) == 0x5d1c9ea3; + const bool is_gc = Common::swap32(&m_disc_header[0x1c]) == 0xc2339f3d; + if (m_is_wii == is_gc) + ERROR_LOG(DISCIO, "Couldn't detect disc type based on %s", boot_bin_path.c_str()); + } + + m_address_shift = m_is_wii ? 2 : 0; +} + +void DirectoryBlobPartition::SetBI2() +{ + constexpr u64 BI2_ADDRESS = 0x440; + constexpr u64 BI2_SIZE = 0x2000; + m_bi2.resize(BI2_SIZE); + + if (!m_is_wii) + Write32(INVALID_REGION, 0x18, &m_bi2); + + const std::string bi2_path = m_root_directory + "sys/bi2.bin"; + const size_t bytes_read = ReadFileToVector(bi2_path, &m_bi2); + if (!m_is_wii && bytes_read < 0x1C) + ERROR_LOG(DISCIO, "Couldn't read region from %s", bi2_path.c_str()); + + m_contents.emplace(BI2_ADDRESS, BI2_SIZE, m_bi2.data()); +} + +u64 DirectoryBlobPartition::SetApploader() +{ + bool success = false; + + const std::string path = m_root_directory + "sys/apploader.img"; + File::IOFile file(path, "rb"); + m_apploader.resize(file.GetSize()); + if (m_apploader.size() < 0x20 || !file.ReadBytes(m_apploader.data(), m_apploader.size())) + { + ERROR_LOG(DISCIO, "%s couldn't be accessed or is too small", path.c_str()); + } + else + { + const size_t apploader_size = 0x20 + Common::swap32(*(u32*)&m_apploader[0x14]) + + Common::swap32(*(u32*)&m_apploader[0x18]); + if (apploader_size != m_apploader.size()) + ERROR_LOG(DISCIO, "%s is the wrong size... Is it really an apploader?", path.c_str()); + else + success = true; + } + + if (!success) + { + m_apploader.resize(0x20); + // Make sure BS2 HLE doesn't try to run the apploader + Write32(static_cast(-1), 0x10, &m_apploader); + } + + constexpr u64 APPLOADER_ADDRESS = 0x2440; + + m_contents.emplace(APPLOADER_ADDRESS, m_apploader.size(), m_apploader.data()); + + // Return DOL address, 32 byte aligned (plus 32 byte padding) + return Common::AlignUp(APPLOADER_ADDRESS + m_apploader.size() + 0x20, 0x20ull); +} + +u64 DirectoryBlobPartition::SetDOL(u64 dol_address) +{ + const DiscContent& dol = + AddFileToContents(&m_contents, m_root_directory + "sys/main.dol", dol_address); + + Write32(static_cast(dol_address >> m_address_shift), 0x0420, &m_disc_header); + + // Return FST address, 32 byte aligned (plus 32 byte padding) + return Common::AlignUp(dol_address + dol.GetSize() + 0x20, 0x20ull); +} + +void DirectoryBlobPartition::BuildFST(u64 fst_address) +{ + m_fst_data.clear(); + + File::FSTEntry rootEntry = File::ScanDirectoryTree(m_root_directory + "files/", true); + + ConvertUTF8NamesToSHIFTJIS(&rootEntry); + + u32 name_table_size = Common::AlignUp(ComputeNameSize(rootEntry), 1ull << m_address_shift); + u64 total_entries = rootEntry.size + 1; // The root entry itself isn't counted in rootEntry.size + + const u64 name_table_offset = total_entries * ENTRY_SIZE; + m_fst_data.resize(name_table_offset + name_table_size); + + // 32 KiB aligned start of data on disc + u64 current_data_address = Common::AlignUp(fst_address + m_fst_data.size(), 0x8000ull); + + u32 fst_offset = 0; // Offset within FST data + u32 name_offset = 0; // Offset within name table + u32 root_offset = 0; // Offset of root of FST + + // write root entry + WriteEntryData(&fst_offset, DIRECTORY_ENTRY, 0, 0, total_entries, m_address_shift); + + WriteDirectory(rootEntry, &fst_offset, &name_offset, ¤t_data_address, root_offset, + name_table_offset); + + // overflow check, compare the aligned name offset with the aligned name table size + _assert_(Common::AlignUp(name_offset, 1ull << m_address_shift) == name_table_size); + + // write FST size and location + Write32((u32)(fst_address >> m_address_shift), 0x0424, &m_disc_header); + Write32((u32)(m_fst_data.size() >> m_address_shift), 0x0428, &m_disc_header); + Write32((u32)(m_fst_data.size() >> m_address_shift), 0x042c, &m_disc_header); + + m_contents.emplace(fst_address, m_fst_data.size(), m_fst_data.data()); + + m_data_size = current_data_address; +} + +void DirectoryBlobPartition::WriteEntryData(u32* entry_offset, u8 type, u32 name_offset, + u64 data_offset, u64 length, u32 address_shift) +{ + m_fst_data[(*entry_offset)++] = type; + + m_fst_data[(*entry_offset)++] = (name_offset >> 16) & 0xff; + m_fst_data[(*entry_offset)++] = (name_offset >> 8) & 0xff; + m_fst_data[(*entry_offset)++] = (name_offset)&0xff; + + Write32((u32)(data_offset >> address_shift), *entry_offset, &m_fst_data); + *entry_offset += 4; + + Write32((u32)length, *entry_offset, &m_fst_data); + *entry_offset += 4; +} + +void DirectoryBlobPartition::WriteEntryName(u32* name_offset, const std::string& name, + u64 name_table_offset) +{ + strncpy((char*)&m_fst_data[*name_offset + name_table_offset], name.c_str(), name.length() + 1); + + *name_offset += (u32)(name.length() + 1); +} + +void DirectoryBlobPartition::WriteDirectory(const File::FSTEntry& parent_entry, u32* fst_offset, + u32* name_offset, u64* data_offset, + u32 parent_entry_index, u64 name_table_offset) +{ + std::vector sorted_entries = parent_entry.children; + + // Sort for determinism + std::sort(sorted_entries.begin(), sorted_entries.end(), [](const File::FSTEntry& one, + const File::FSTEntry& two) { + const std::string one_upper = ASCIIToUppercase(one.virtualName); + const std::string two_upper = ASCIIToUppercase(two.virtualName); + return one_upper == two_upper ? one.virtualName < two.virtualName : one_upper < two_upper; + }); + + for (const File::FSTEntry& entry : sorted_entries) + { + if (entry.isDirectory) + { + u32 entry_index = *fst_offset / ENTRY_SIZE; + WriteEntryData(fst_offset, DIRECTORY_ENTRY, *name_offset, parent_entry_index, + entry_index + entry.size + 1, 0); + WriteEntryName(name_offset, entry.virtualName, name_table_offset); + + WriteDirectory(entry, fst_offset, name_offset, data_offset, entry_index, name_table_offset); + } + else + { + // put entry in FST + WriteEntryData(fst_offset, FILE_ENTRY, *name_offset, *data_offset, entry.size, + m_address_shift); + WriteEntryName(name_offset, entry.virtualName, name_table_offset); + + // write entry to virtual disc + auto result = m_contents.emplace(*data_offset, entry.size, entry.physicalName); + _dbg_assert_(DISCIO, result.second); // Check that this offset wasn't already occupied + + // 32 KiB aligned - many games are fine with less alignment, but not all + *data_offset = Common::AlignUp(*data_offset + std::max(entry.size, 1ull), 0x8000ull); + } + } +} + +static const DiscContent& AddFileToContents(std::set* contents, + const std::string& path, u64 offset, u64 max_size) +{ + return *(contents->emplace(offset, std::min(File::GetSize(path), max_size), path).first); +} + +static size_t ReadFileToVector(const std::string& path, std::vector* vector) +{ + File::IOFile file(path, "rb"); + size_t bytes_read; + file.ReadArray(vector->data(), std::min(file.GetSize(), vector->size()), &bytes_read); + return bytes_read; +} + +static void PadToAddress(u64 start_address, u64* address, u64* length, u8** buffer) +{ + if (start_address > *address && *length > 0) + { + u64 padBytes = std::min(start_address - *address, *length); + memset(*buffer, 0, (size_t)padBytes); + *length -= padBytes; + *buffer += padBytes; + *address += padBytes; + } +} + +static void Write32(u32 data, u32 offset, std::vector* buffer) +{ + (*buffer)[offset++] = (data >> 24); + (*buffer)[offset++] = (data >> 16) & 0xff; + (*buffer)[offset++] = (data >> 8) & 0xff; + (*buffer)[offset] = data & 0xff; +} + +static u32 ComputeNameSize(const File::FSTEntry& parent_entry) +{ + u32 name_size = 0; + for (const File::FSTEntry& entry : parent_entry.children) + { + if (entry.isDirectory) + name_size += ComputeNameSize(entry); + + name_size += (u32)entry.virtualName.length() + 1; + } + return name_size; +} + +static void ConvertUTF8NamesToSHIFTJIS(File::FSTEntry* parent_entry) +{ + for (File::FSTEntry& entry : parent_entry->children) + { + if (entry.isDirectory) + ConvertUTF8NamesToSHIFTJIS(&entry); + + entry.virtualName = UTF8ToSHIFTJIS(entry.virtualName); + } +} + +static std::string ASCIIToUppercase(std::string str) +{ + std::transform(str.begin(), str.end(), str.begin(), + [](char c) { return std::toupper(c, std::locale::classic()); }); + return str; +} + +} // namespace diff --git a/Source/Core/DiscIO/DirectoryBlob.h b/Source/Core/DiscIO/DirectoryBlob.h new file mode 100644 index 0000000000..666694e508 --- /dev/null +++ b/Source/Core/DiscIO/DirectoryBlob.h @@ -0,0 +1,169 @@ +// Copyright 2008 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "Common/FileUtil.h" +#include "DiscIO/Blob.h" + +namespace File +{ +struct FSTEntry; +class IOFile; +} + +namespace DiscIO +{ +enum class PartitionType : u32; + +// Returns true if the path is inside a DirectoryBlob and doesn't represent the DirectoryBlob itself +bool ShouldHideFromGameList(const std::string& volume_path); + +class DiscContent +{ +public: + using ContentSource = std::variant; + + DiscContent(u64 offset, u64 size, const std::string& path); + DiscContent(u64 offset, u64 size, const u8* data); + + // Provided because it's convenient when searching for DiscContent in an std::set + explicit DiscContent(u64 offset); + + u64 GetOffset() const; + u64 GetSize() const; + bool Read(u64* offset, u64* length, u8** buffer) const; + + bool operator==(const DiscContent& other) const { return m_offset == other.m_offset; } + bool operator!=(const DiscContent& other) const { return !(*this == other); } + bool operator<(const DiscContent& other) const { return m_offset < other.m_offset; } + bool operator>(const DiscContent& other) const { return other < *this; } + bool operator<=(const DiscContent& other) const { return !(*this < other); } + bool operator>=(const DiscContent& other) const { return !(*this > other); } +private: + u64 m_offset; + u64 m_size = 0; + std::string m_path; + ContentSource m_content_source; +}; + +class DirectoryBlobPartition +{ +public: + DirectoryBlobPartition() = default; + DirectoryBlobPartition(const std::string& root_directory, std::optional is_wii); + + // We do not allow copying, because it might mess up the pointers inside DiscContents + DirectoryBlobPartition(const DirectoryBlobPartition&) = delete; + DirectoryBlobPartition& operator=(const DirectoryBlobPartition&) = delete; + DirectoryBlobPartition(DirectoryBlobPartition&&) = default; + DirectoryBlobPartition& operator=(DirectoryBlobPartition&&) = default; + + bool IsWii() const { return m_is_wii; } + u64 GetDataSize() const { return m_data_size; } + const std::string& GetRootDirectory() const { return m_root_directory; } + const std::vector& GetHeader() const { return m_disc_header; } + const std::set& GetContents() const { return m_contents; } +private: + void SetDiscHeaderAndDiscType(std::optional is_wii); + void SetBI2(); + + // Returns DOL address + u64 SetApploader(); + // Returns FST address + u64 SetDOL(u64 dol_address); + + void BuildFST(u64 fst_address); + + // FST creation + void WriteEntryData(u32* entry_offset, u8 type, u32 name_offset, u64 data_offset, u64 length, + u32 address_shift); + void WriteEntryName(u32* name_offset, const std::string& name, u64 name_table_offset); + void WriteDirectory(const File::FSTEntry& parent_entry, u32* fst_offset, u32* name_offset, + u64* data_offset, u32 parent_entry_index, u64 name_table_offset); + + std::set m_contents; + std::vector m_disc_header; + std::vector m_bi2; + std::vector m_apploader; + std::vector m_fst_data; + + std::string m_root_directory; + bool m_is_wii = false; + // GameCube has no shift, Wii has 2 bit shift + u32 m_address_shift = 0; + + u64 m_data_size; +}; + +class DirectoryBlobReader : public BlobReader +{ +public: + static std::unique_ptr Create(const std::string& dol_path); + + // We do not allow copying, because it might mess up the pointers inside DiscContents + DirectoryBlobReader(const DirectoryBlobReader&) = delete; + DirectoryBlobReader& operator=(const DirectoryBlobReader&) = delete; + DirectoryBlobReader(DirectoryBlobReader&&) = default; + DirectoryBlobReader& operator=(DirectoryBlobReader&&) = default; + + bool Read(u64 offset, u64 length, u8* buffer) override; + bool SupportsReadWiiDecrypted() const override; + bool ReadWiiDecrypted(u64 offset, u64 size, u8* buffer, u64 partition_offset) override; + + BlobType GetBlobType() const override; + u64 GetRawSize() const override; + u64 GetDataSize() const override; + +private: + struct PartitionWithType + { + PartitionWithType(DirectoryBlobPartition&& partition_, PartitionType type_) + : partition(std::move(partition_)), type(type_) + { + } + + DirectoryBlobPartition partition; + PartitionType type; + }; + + explicit DirectoryBlobReader(const std::string& game_partition_root, + const std::string& true_root); + + bool ReadInternal(u64 offset, u64 length, u8* buffer, const std::set& contents); + + void SetNonpartitionDiscHeader(const std::vector& partition_header, + const std::string& game_partition_root); + void SetWiiRegionData(const std::string& game_partition_root); + void SetPartitions(std::vector&& partitions); + void SetPartitionHeader(const DirectoryBlobPartition& partition, u64 partition_address); + + // For GameCube: + DirectoryBlobPartition m_gamecube_pseudopartition; + + // For Wii: + std::set m_nonpartition_contents; + std::map m_partitions; + + bool m_is_wii; + + std::vector m_disc_header_nonpartition; + std::vector m_partition_table; + std::vector m_wii_region_data; + std::vector> m_partition_headers; + + u64 m_data_size; +}; + +} // namespace diff --git a/Source/Core/DiscIO/DiscIO.vcxproj b/Source/Core/DiscIO/DiscIO.vcxproj index 3a978ad262..b7ef5b9875 100644 --- a/Source/Core/DiscIO/DiscIO.vcxproj +++ b/Source/Core/DiscIO/DiscIO.vcxproj @@ -39,6 +39,7 @@ + @@ -50,7 +51,6 @@ - @@ -61,6 +61,7 @@ + @@ -72,7 +73,6 @@ - diff --git a/Source/Core/DiscIO/DiscIO.vcxproj.filters b/Source/Core/DiscIO/DiscIO.vcxproj.filters index 3c1f85ca10..3cebc18705 100644 --- a/Source/Core/DiscIO/DiscIO.vcxproj.filters +++ b/Source/Core/DiscIO/DiscIO.vcxproj.filters @@ -57,8 +57,8 @@ Volume\Blob - - Volume + + Volume\Blob Volume @@ -122,8 +122,8 @@ Volume - - Volume + + Volume\Blob Volume diff --git a/Source/Core/DiscIO/Volume.cpp b/Source/Core/DiscIO/Volume.cpp index 876136e6b9..014eda7d31 100644 --- a/Source/Core/DiscIO/Volume.cpp +++ b/Source/Core/DiscIO/Volume.cpp @@ -22,7 +22,6 @@ #include "DiscIO/Blob.h" #include "DiscIO/Enums.h" -#include "DiscIO/VolumeDirectory.h" #include "DiscIO/VolumeGC.h" #include "DiscIO/VolumeWad.h" #include "DiscIO/VolumeWii.h" @@ -112,14 +111,4 @@ std::unique_ptr CreateVolumeFromFilename(const std::string& filename) return nullptr; } -std::unique_ptr CreateVolumeFromDirectory(const std::string& directory, bool is_wii, - const std::string& apploader, - const std::string& dol) -{ - if (VolumeDirectory::IsValidDirectory(directory)) - return std::make_unique(directory, is_wii, apploader, dol); - - return nullptr; -} - } // namespace diff --git a/Source/Core/DiscIO/Volume.h b/Source/Core/DiscIO/Volume.h index 209a0d09a9..979a63a23f 100644 --- a/Source/Core/DiscIO/Volume.h +++ b/Source/Core/DiscIO/Volume.h @@ -120,8 +120,5 @@ protected: }; std::unique_ptr CreateVolumeFromFilename(const std::string& filename); -std::unique_ptr CreateVolumeFromDirectory(const std::string& directory, bool is_wii, - const std::string& apploader = "", - const std::string& dol = ""); } // namespace diff --git a/Source/Core/DiscIO/VolumeDirectory.cpp b/Source/Core/DiscIO/VolumeDirectory.cpp deleted file mode 100644 index 854aec3da6..0000000000 --- a/Source/Core/DiscIO/VolumeDirectory.cpp +++ /dev/null @@ -1,535 +0,0 @@ -// Copyright 2008 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Common/Align.h" -#include "Common/Assert.h" -#include "Common/CommonPaths.h" -#include "Common/CommonTypes.h" -#include "Common/File.h" -#include "Common/FileUtil.h" -#include "Common/Logging/Log.h" -#include "Common/StringUtil.h" -#include "DiscIO/Blob.h" -#include "DiscIO/Enums.h" -#include "DiscIO/Volume.h" -#include "DiscIO/VolumeDirectory.h" - -namespace DiscIO -{ -static u32 ComputeNameSize(const File::FSTEntry& parent_entry); -static std::string ASCIIToUppercase(std::string str); -static void ConvertUTF8NamesToSHIFTJIS(File::FSTEntry& parent_entry); - -const size_t VolumeDirectory::MAX_NAME_LENGTH; -const size_t VolumeDirectory::MAX_ID_LENGTH; - -VolumeDirectory::VolumeDirectory(const std::string& directory, bool is_wii, - const std::string& apploader, const std::string& dol) - : m_data_start_address(UINT64_MAX), m_disk_header(DISKHEADERINFO_ADDRESS), - m_disk_header_info(std::make_unique()), m_fst_address(0), m_dol_address(0) -{ - m_root_directory = ExtractDirectoryName(directory); - - // create the default disk header - SetGameID("AGBJ01"); - SetName("Default name"); - - if (is_wii) - SetDiskTypeWii(); - else - SetDiskTypeGC(); - - // Don't load the DOL if we don't have an apploader - if (SetApploader(apploader)) - SetDOL(dol); - - BuildFST(); -} - -VolumeDirectory::~VolumeDirectory() -{ -} - -bool VolumeDirectory::IsValidDirectory(const std::string& directory) -{ - return File::IsDirectory(ExtractDirectoryName(directory)); -} - -bool VolumeDirectory::Read(u64 offset, u64 length, u8* buffer, const Partition& partition) const -{ - bool decrypt = partition != PARTITION_NONE; - - if (!decrypt && (offset + length >= 0x400) && m_is_wii) - { - // Fully supporting this would require re-encrypting every file that's read. - // Only supporting the areas that IOS allows software to read could be more feasible. - // Currently, only the header (up to 0x400) is supported, though we're cheating a bit - // with it by reading the header inside the current partition instead. Supporting the - // header is enough for booting games, but not for running things like the Disc Channel. - return false; - } - - if (decrypt && !m_is_wii) - return false; - - // header - if (offset < DISKHEADERINFO_ADDRESS) - { - WriteToBuffer(DISKHEADER_ADDRESS, DISKHEADERINFO_ADDRESS, m_disk_header.data(), &offset, - &length, &buffer); - } - // header info - if (offset >= DISKHEADERINFO_ADDRESS && offset < APPLOADER_ADDRESS) - { - WriteToBuffer(DISKHEADERINFO_ADDRESS, sizeof(m_disk_header_info), (u8*)m_disk_header_info.get(), - &offset, &length, &buffer); - } - // apploader - if (offset >= APPLOADER_ADDRESS && offset < APPLOADER_ADDRESS + m_apploader.size()) - { - WriteToBuffer(APPLOADER_ADDRESS, m_apploader.size(), m_apploader.data(), &offset, &length, - &buffer); - } - // dol - if (offset >= m_dol_address && offset < m_dol_address + m_dol.size()) - { - WriteToBuffer(m_dol_address, m_dol.size(), m_dol.data(), &offset, &length, &buffer); - } - // fst - if (offset >= m_fst_address && offset < m_data_start_address) - { - WriteToBuffer(m_fst_address, m_fst_data.size(), m_fst_data.data(), &offset, &length, &buffer); - } - - if (m_virtual_disk.empty()) - return true; - - // Determine which file the offset refers to - std::map::const_iterator fileIter = m_virtual_disk.lower_bound(offset); - if (fileIter->first > offset && fileIter != m_virtual_disk.begin()) - --fileIter; - - // zero fill to start of file data - PadToAddress(fileIter->first, &offset, &length, &buffer); - - while (fileIter != m_virtual_disk.end() && length > 0) - { - _dbg_assert_(DVDINTERFACE, fileIter->first <= offset); - u64 fileOffset = offset - fileIter->first; - const std::string fileName = fileIter->second; - - File::IOFile file(fileName, "rb"); - if (!file) - return false; - - u64 fileSize = file.GetSize(); - - if (fileOffset < fileSize) - { - u64 fileBytes = std::min(fileSize - fileOffset, length); - - if (!file.Seek(fileOffset, SEEK_SET)) - return false; - if (!file.ReadBytes(buffer, fileBytes)) - return false; - - length -= fileBytes; - buffer += fileBytes; - offset += fileBytes; - } - - ++fileIter; - - if (fileIter != m_virtual_disk.end()) - { - _dbg_assert_(DVDINTERFACE, fileIter->first >= offset); - PadToAddress(fileIter->first, &offset, &length, &buffer); - } - } - - return true; -} - -std::vector VolumeDirectory::GetPartitions() const -{ - return m_is_wii ? std::vector{GetGamePartition()} : std::vector(); -} - -Partition VolumeDirectory::GetGamePartition() const -{ - return m_is_wii ? Partition(0x50000) : PARTITION_NONE; -} - -std::string VolumeDirectory::GetGameID(const Partition& partition) const -{ - return std::string(m_disk_header.begin(), m_disk_header.begin() + MAX_ID_LENGTH); -} - -void VolumeDirectory::SetGameID(const std::string& id) -{ - memcpy(m_disk_header.data(), id.c_str(), std::min(id.length(), MAX_ID_LENGTH)); -} - -Region VolumeDirectory::GetRegion() const -{ - if (m_is_wii) - return RegionSwitchWii(m_disk_header[3]); - - return RegionSwitchGC(m_disk_header[3]); -} - -Country VolumeDirectory::GetCountry(const Partition& partition) const -{ - return CountrySwitch(m_disk_header[3]); -} - -std::string VolumeDirectory::GetMakerID(const Partition& partition) const -{ - // Not implemented - return "00"; -} - -std::string VolumeDirectory::GetInternalName(const Partition& partition) const -{ - char name[0x60]; - if (Read(0x20, 0x60, (u8*)name, partition)) - return DecodeString(name); - else - return ""; -} - -std::map VolumeDirectory::GetLongNames() const -{ - std::string name = GetInternalName(); - if (name.empty()) - return {}; - return {{Language::LANGUAGE_UNKNOWN, name}}; -} - -std::vector VolumeDirectory::GetBanner(int* width, int* height) const -{ - // Not implemented - *width = 0; - *height = 0; - return std::vector(); -} - -void VolumeDirectory::SetName(const std::string& name) -{ - size_t length = std::min(name.length(), MAX_NAME_LENGTH); - memcpy(&m_disk_header[0x20], name.c_str(), length); - m_disk_header[length + 0x20] = 0; -} - -std::string VolumeDirectory::GetApploaderDate(const Partition& partition) const -{ - // Not implemented - return "VOID"; -} - -Platform VolumeDirectory::GetVolumeType() const -{ - return m_is_wii ? Platform::WII_DISC : Platform::GAMECUBE_DISC; -} - -BlobType VolumeDirectory::GetBlobType() const -{ - // VolumeDirectory isn't actually a blob, but it sort of acts - // like one, so it makes sense that it has its own blob type. - // It should be made into a proper blob in the future. - return BlobType::DIRECTORY; -} - -u64 VolumeDirectory::GetSize() const -{ - // Not implemented - return 0; -} - -u64 VolumeDirectory::GetRawSize() const -{ - // Not implemented - return 0; -} - -std::string VolumeDirectory::ExtractDirectoryName(const std::string& directory) -{ - std::string result = directory; - - size_t last_separator = result.find_last_of(DIR_SEP_CHR); - - if (last_separator != result.size() - 1) - { - // TODO: This assumes that file names will always have a dot in them - // and directory names never will; both assumptions are often - // right but in general wrong. - size_t extension_start = result.find_last_of('.'); - if (extension_start != std::string::npos && extension_start > last_separator) - { - result.resize(last_separator); - } - } - else - { - result.resize(last_separator); - } - - return result; -} - -void VolumeDirectory::SetDiskTypeWii() -{ - Write32(0x5d1c9ea3, 0x18, &m_disk_header); - memset(&m_disk_header[0x1c], 0, 4); - - m_is_wii = true; - m_address_shift = 2; -} - -void VolumeDirectory::SetDiskTypeGC() -{ - memset(&m_disk_header[0x18], 0, 4); - Write32(0xc2339f3d, 0x1c, &m_disk_header); - - m_is_wii = false; - m_address_shift = 0; -} - -bool VolumeDirectory::SetApploader(const std::string& apploader) -{ - if (!apploader.empty()) - { - std::string data; - if (!File::ReadFileToString(apploader, data)) - { - PanicAlertT("Apploader unable to load from file"); - return false; - } - size_t apploader_size = 0x20 + Common::swap32(*(u32*)&data.data()[0x14]) + - Common::swap32(*(u32*)&data.data()[0x18]); - if (apploader_size != data.size()) - { - PanicAlertT("Apploader is the wrong size...is it really an apploader?"); - return false; - } - m_apploader.resize(apploader_size); - std::copy(data.begin(), data.end(), m_apploader.begin()); - - // 32byte aligned (plus 0x20 padding) - m_dol_address = Common::AlignUp(APPLOADER_ADDRESS + m_apploader.size() + 0x20, 0x20ull); - return true; - } - else - { - m_apploader.resize(0x20); - // Make sure BS2 HLE doesn't try to run the apploader - Write32(static_cast(-1), 0x10, &m_apploader); - return false; - } -} - -void VolumeDirectory::SetDOL(const std::string& dol) -{ - if (!dol.empty()) - { - std::string data; - File::ReadFileToString(dol, data); - m_dol.resize(data.size()); - std::copy(data.begin(), data.end(), m_dol.begin()); - - Write32((u32)(m_dol_address >> m_address_shift), 0x0420, &m_disk_header); - - // 32byte aligned (plus 0x20 padding) - m_fst_address = Common::AlignUp(m_dol_address + m_dol.size() + 0x20, 0x20ull); - } -} - -void VolumeDirectory::BuildFST() -{ - m_fst_data.clear(); - - File::FSTEntry rootEntry = File::ScanDirectoryTree(m_root_directory, true); - - ConvertUTF8NamesToSHIFTJIS(rootEntry); - - u32 name_table_size = Common::AlignUp(ComputeNameSize(rootEntry), 1ull << m_address_shift); - u64 total_entries = rootEntry.size + 1; // The root entry itself isn't counted in rootEntry.size - - m_fst_name_offset = total_entries * ENTRY_SIZE; // offset of name table in FST - m_fst_data.resize(m_fst_name_offset + name_table_size); - - // if FST hasn't been assigned (ie no apploader/dol setup), set to default - if (m_fst_address == 0) - m_fst_address = APPLOADER_ADDRESS + 0x2000; - - // 32 KiB aligned start of data on disk - m_data_start_address = Common::AlignUp(m_fst_address + m_fst_data.size(), 0x8000ull); - u64 current_data_address = m_data_start_address; - - u32 fst_offset = 0; // Offset within FST data - u32 name_offset = 0; // Offset within name table - u32 root_offset = 0; // Offset of root of FST - - // write root entry - WriteEntryData(&fst_offset, DIRECTORY_ENTRY, 0, 0, total_entries, m_address_shift); - - WriteDirectory(rootEntry, &fst_offset, &name_offset, ¤t_data_address, root_offset); - - // overflow check, compare the aligned name offset with the aligned name table size - _assert_(Common::AlignUp(name_offset, 1ull << m_address_shift) == name_table_size); - - // write FST size and location - Write32((u32)(m_fst_address >> m_address_shift), 0x0424, &m_disk_header); - Write32((u32)(m_fst_data.size() >> m_address_shift), 0x0428, &m_disk_header); - Write32((u32)(m_fst_data.size() >> m_address_shift), 0x042c, &m_disk_header); -} - -void VolumeDirectory::WriteToBuffer(u64 source_start_address, u64 source_length, const u8* source, - u64* address, u64* length, u8** buffer) const -{ - if (*length == 0) - return; - - _dbg_assert_(DVDINTERFACE, *address >= source_start_address); - - u64 source_offset = *address - source_start_address; - - if (source_offset < source_length) - { - size_t bytes_to_read = std::min(source_length - source_offset, *length); - - memcpy(*buffer, source + source_offset, bytes_to_read); - - *length -= bytes_to_read; - *buffer += bytes_to_read; - *address += bytes_to_read; - } -} - -void VolumeDirectory::PadToAddress(u64 start_address, u64* address, u64* length, u8** buffer) const -{ - if (start_address > *address && *length > 0) - { - u64 padBytes = std::min(start_address - *address, *length); - memset(*buffer, 0, (size_t)padBytes); - *length -= padBytes; - *buffer += padBytes; - *address += padBytes; - } -} - -void VolumeDirectory::Write32(u32 data, u32 offset, std::vector* const buffer) -{ - (*buffer)[offset++] = (data >> 24); - (*buffer)[offset++] = (data >> 16) & 0xff; - (*buffer)[offset++] = (data >> 8) & 0xff; - (*buffer)[offset] = (data)&0xff; -} - -void VolumeDirectory::WriteEntryData(u32* entry_offset, u8 type, u32 name_offset, u64 data_offset, - u64 length, u32 address_shift) -{ - m_fst_data[(*entry_offset)++] = type; - - m_fst_data[(*entry_offset)++] = (name_offset >> 16) & 0xff; - m_fst_data[(*entry_offset)++] = (name_offset >> 8) & 0xff; - m_fst_data[(*entry_offset)++] = (name_offset)&0xff; - - Write32((u32)(data_offset >> address_shift), *entry_offset, &m_fst_data); - *entry_offset += 4; - - Write32((u32)length, *entry_offset, &m_fst_data); - *entry_offset += 4; -} - -void VolumeDirectory::WriteEntryName(u32* name_offset, const std::string& name) -{ - strncpy((char*)&m_fst_data[*name_offset + m_fst_name_offset], name.c_str(), name.length() + 1); - - *name_offset += (u32)(name.length() + 1); -} - -void VolumeDirectory::WriteDirectory(const File::FSTEntry& parent_entry, u32* fst_offset, - u32* name_offset, u64* data_offset, u32 parent_entry_index) -{ - std::vector sorted_entries = parent_entry.children; - - // Sort for determinism - std::sort(sorted_entries.begin(), sorted_entries.end(), [](const File::FSTEntry& one, - const File::FSTEntry& two) { - const std::string one_upper = ASCIIToUppercase(one.virtualName); - const std::string two_upper = ASCIIToUppercase(two.virtualName); - return one_upper == two_upper ? one.virtualName < two.virtualName : one_upper < two_upper; - }); - - for (const File::FSTEntry& entry : sorted_entries) - { - if (entry.isDirectory) - { - u32 entry_index = *fst_offset / ENTRY_SIZE; - WriteEntryData(fst_offset, DIRECTORY_ENTRY, *name_offset, parent_entry_index, - entry_index + entry.size + 1, 0); - WriteEntryName(name_offset, entry.virtualName); - - WriteDirectory(entry, fst_offset, name_offset, data_offset, entry_index); - } - else - { - // put entry in FST - WriteEntryData(fst_offset, FILE_ENTRY, *name_offset, *data_offset, entry.size, - m_address_shift); - WriteEntryName(name_offset, entry.virtualName); - - // write entry to virtual disk - _dbg_assert_(DVDINTERFACE, m_virtual_disk.find(*data_offset) == m_virtual_disk.end()); - m_virtual_disk.emplace(*data_offset, entry.physicalName); - - // 32 KiB aligned - many games are fine with less alignment, but not all - *data_offset = Common::AlignUp(*data_offset + std::max(entry.size, 1ull), 0x8000ull); - } - } -} - -static u32 ComputeNameSize(const File::FSTEntry& parent_entry) -{ - u32 name_size = 0; - for (const File::FSTEntry& entry : parent_entry.children) - { - if (entry.isDirectory) - name_size += ComputeNameSize(entry); - - name_size += (u32)entry.virtualName.length() + 1; - } - return name_size; -} - -static void ConvertUTF8NamesToSHIFTJIS(File::FSTEntry& parent_entry) -{ - for (File::FSTEntry& entry : parent_entry.children) - { - if (entry.isDirectory) - ConvertUTF8NamesToSHIFTJIS(entry); - - entry.virtualName = UTF8ToSHIFTJIS(entry.virtualName); - } -} - -static std::string ASCIIToUppercase(std::string str) -{ - std::transform(str.begin(), str.end(), str.begin(), - [](char c) { return std::toupper(c, std::locale::classic()); }); - return str; -} - -} // namespace diff --git a/Source/Core/DiscIO/VolumeDirectory.h b/Source/Core/DiscIO/VolumeDirectory.h deleted file mode 100644 index 360570b063..0000000000 --- a/Source/Core/DiscIO/VolumeDirectory.h +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2008 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include - -#include "Common/CommonTypes.h" -#include "DiscIO/Volume.h" - -namespace File -{ -struct FSTEntry; -} - -// -// --- this volume type is used for reading files directly from the hard drive --- -// - -namespace DiscIO -{ -enum class BlobType; -enum class Country; -enum class Language; -enum class Region; -enum class Platform; - -class VolumeDirectory : public Volume -{ -public: - VolumeDirectory(const std::string& directory, bool is_wii, const std::string& apploader = "", - const std::string& dol = ""); - - ~VolumeDirectory(); - - static bool IsValidDirectory(const std::string& directory); - - bool Read(u64 offset, u64 length, u8* buffer, const Partition& partition) const override; - std::vector GetPartitions() const override; - Partition GetGamePartition() const override; - - std::string GetGameID(const Partition& partition = PARTITION_NONE) const override; - void SetGameID(const std::string& id); - - std::string GetMakerID(const Partition& partition = PARTITION_NONE) const override; - - std::optional GetRevision(const Partition& partition = PARTITION_NONE) const override - { - return {}; - } - std::string GetInternalName(const Partition& partition = PARTITION_NONE) const override; - std::map GetLongNames() const override; - std::vector GetBanner(int* width, int* height) const override; - void SetName(const std::string&); - - std::string GetApploaderDate(const Partition& partition = PARTITION_NONE) const override; - Platform GetVolumeType() const override; - - Region GetRegion() const override; - Country GetCountry(const Partition& partition = PARTITION_NONE) const override; - - BlobType GetBlobType() const override; - u64 GetSize() const override; - u64 GetRawSize() const override; - - void BuildFST(); - -private: - static std::string ExtractDirectoryName(const std::string& directory); - - void SetDiskTypeWii(); - void SetDiskTypeGC(); - - bool SetApploader(const std::string& apploader); - - void SetDOL(const std::string& dol); - - // writing to read buffer - void WriteToBuffer(u64 source_start_address, u64 source_length, const u8* source, u64* address, - u64* length, u8** buffer) const; - - void PadToAddress(u64 start_address, u64* address, u64* length, u8** buffer) const; - - void Write32(u32 data, u32 offset, std::vector* const buffer); - - // FST creation - void WriteEntryData(u32* entry_offset, u8 type, u32 name_offset, u64 data_offset, u64 length, - u32 address_shift); - void WriteEntryName(u32* name_offset, const std::string& name); - void WriteDirectory(const File::FSTEntry& parent_entry, u32* fst_offset, u32* name_offset, - u64* data_offset, u32 parent_entry_index); - - std::string m_root_directory; - - std::map m_virtual_disk; - - bool m_is_wii; - - // GameCube has no shift, Wii has 2 bit shift - u32 m_address_shift; - - // first address on disk containing file data - u64 m_data_start_address; - - u64 m_fst_name_offset; - std::vector m_fst_data; - - std::vector m_disk_header; - -#pragma pack(push, 1) - struct SDiskHeaderInfo - { - u32 debug_monitor_size; - u32 simulated_mem_size; - u32 arg_offset; - u32 debug_flag; - u32 track_location; - u32 track_size; - u32 country_code; - u32 unknown; - u32 unknown2; - - // All the data is byteswapped - SDiskHeaderInfo() - { - debug_monitor_size = 0; - simulated_mem_size = 0; - arg_offset = 0; - debug_flag = 0; - track_location = 0; - track_size = 0; - country_code = 0; - unknown = 0; - unknown2 = 0; - } - }; -#pragma pack(pop) - std::unique_ptr m_disk_header_info; - - std::vector m_apploader; - std::vector m_dol; - - u64 m_fst_address; - u64 m_dol_address; - - static constexpr u8 ENTRY_SIZE = 0x0c; - static constexpr u8 FILE_ENTRY = 0; - static constexpr u8 DIRECTORY_ENTRY = 1; - static constexpr u64 DISKHEADER_ADDRESS = 0; - static constexpr u64 DISKHEADERINFO_ADDRESS = 0x440; - static constexpr u64 APPLOADER_ADDRESS = 0x2440; - static const size_t MAX_NAME_LENGTH = 0x3df; - static const size_t MAX_ID_LENGTH = 6; -}; - -} // namespace diff --git a/Source/Core/DiscIO/VolumeWii.cpp b/Source/Core/DiscIO/VolumeWii.cpp index 7f7670d6d7..4e8bba6b91 100644 --- a/Source/Core/DiscIO/VolumeWii.cpp +++ b/Source/Core/DiscIO/VolumeWii.cpp @@ -128,6 +128,9 @@ bool VolumeWii::Read(u64 _ReadOffset, u64 _Length, u8* _pBuffer, const Partition if (partition == PARTITION_NONE) return m_pReader->Read(_ReadOffset, _Length, _pBuffer); + if (m_pReader->SupportsReadWiiDecrypted()) + return m_pReader->ReadWiiDecrypted(_ReadOffset, _Length, _pBuffer, partition.offset); + // Get the decryption key for the partition auto it = m_partitions.find(partition); if (it == m_partitions.end()) diff --git a/Source/Core/DolphinQt2/GameList/GameTracker.cpp b/Source/Core/DolphinQt2/GameList/GameTracker.cpp index be66ab406e..fb832512b5 100644 --- a/Source/Core/DolphinQt2/GameList/GameTracker.cpp +++ b/Source/Core/DolphinQt2/GameList/GameTracker.cpp @@ -6,6 +6,7 @@ #include #include +#include "DiscIO/DirectoryBlob.h" #include "DolphinQt2/GameList/GameTracker.h" #include "DolphinQt2/Settings.h" @@ -137,3 +138,13 @@ void GameTracker::UpdateFile(const QString& file) emit GameRemoved(file); } } + +void GameLoader::LoadGame(const QString& path) +{ + if (!DiscIO::ShouldHideFromGameList(path.toStdString())) + { + auto game = QSharedPointer::create(path); + if (game->IsValid()) + emit GameLoaded(game); + } +} diff --git a/Source/Core/DolphinQt2/GameList/GameTracker.h b/Source/Core/DolphinQt2/GameList/GameTracker.h index 5867278b19..926e2a9ae2 100644 --- a/Source/Core/DolphinQt2/GameList/GameTracker.h +++ b/Source/Core/DolphinQt2/GameList/GameTracker.h @@ -54,12 +54,7 @@ class GameLoader final : public QObject Q_OBJECT public: - void LoadGame(const QString& path) - { - GameFile* game = new GameFile(path); - if (game->IsValid()) - emit GameLoaded(QSharedPointer(game)); - } + void LoadGame(const QString& path); signals: void GameLoaded(QSharedPointer game); diff --git a/Source/Core/DolphinQt2/Settings/PathPane.cpp b/Source/Core/DolphinQt2/Settings/PathPane.cpp index 2d21f20112..12faaa8af0 100644 --- a/Source/Core/DolphinQt2/Settings/PathPane.cpp +++ b/Source/Core/DolphinQt2/Settings/PathPane.cpp @@ -48,27 +48,6 @@ void PathPane::BrowseDefaultGame() } } -void PathPane::BrowseDVDRoot() -{ - QString dir = QFileDialog::getExistingDirectory(this, tr("Select DVD Root"), QDir::currentPath()); - if (!dir.isEmpty()) - { - m_dvd_edit->setText(dir); - SConfig::GetInstance().m_strDVDRoot = dir.toStdString(); - } -} - -void PathPane::BrowseApploader() -{ - QString file = QFileDialog::getOpenFileName(this, tr("Select an Apploader"), QDir::currentPath(), - tr("Apploaders (*.img)")); - if (!file.isEmpty()) - { - m_app_edit->setText(file); - SConfig::GetInstance().m_strApploader = file.toStdString(); - } -} - void PathPane::BrowseWiiNAND() { QString dir = @@ -127,32 +106,14 @@ QGridLayout* PathPane::MakePathsLayout() layout->addWidget(m_game_edit, 0, 1); layout->addWidget(game_open, 0, 2); - m_dvd_edit = new QLineEdit(QString::fromStdString(SConfig::GetInstance().m_strDVDRoot)); - connect(m_dvd_edit, &QLineEdit::editingFinished, - [=] { SConfig::GetInstance().m_strDVDRoot = m_dvd_edit->text().toStdString(); }); - QPushButton* dvd_open = new QPushButton; - connect(dvd_open, &QPushButton::clicked, this, &PathPane::BrowseDVDRoot); - layout->addWidget(new QLabel(tr("DVD Root:")), 1, 0); - layout->addWidget(m_dvd_edit, 1, 1); - layout->addWidget(dvd_open, 1, 2); - - m_app_edit = new QLineEdit(QString::fromStdString(SConfig::GetInstance().m_strApploader)); - connect(m_app_edit, &QLineEdit::editingFinished, - [=] { SConfig::GetInstance().m_strApploader = m_app_edit->text().toStdString(); }); - QPushButton* app_open = new QPushButton; - connect(app_open, &QPushButton::clicked, this, &PathPane::BrowseApploader); - layout->addWidget(new QLabel(tr("Apploader:")), 2, 0); - layout->addWidget(m_app_edit, 2, 1); - layout->addWidget(app_open, 2, 2); - m_nand_edit = new QLineEdit(QString::fromStdString(SConfig::GetInstance().m_NANDPath)); connect(m_nand_edit, &QLineEdit::editingFinished, [=] { SConfig::GetInstance().m_NANDPath = m_nand_edit->text().toStdString(); }); QPushButton* nand_open = new QPushButton; connect(nand_open, &QPushButton::clicked, this, &PathPane::BrowseWiiNAND); - layout->addWidget(new QLabel(tr("Wii NAND Root:")), 3, 0); - layout->addWidget(m_nand_edit, 3, 1); - layout->addWidget(nand_open, 3, 2); + layout->addWidget(new QLabel(tr("Wii NAND Root:")), 1, 0); + layout->addWidget(m_nand_edit, 1, 1); + layout->addWidget(nand_open, 1, 2); return layout; } diff --git a/Source/Core/DolphinQt2/Settings/PathPane.h b/Source/Core/DolphinQt2/Settings/PathPane.h index 753c5001c4..2d0f0b190c 100644 --- a/Source/Core/DolphinQt2/Settings/PathPane.h +++ b/Source/Core/DolphinQt2/Settings/PathPane.h @@ -19,8 +19,6 @@ public: private: void Browse(); void BrowseDefaultGame(); - void BrowseDVDRoot(); - void BrowseApploader(); void BrowseWiiNAND(); QGroupBox* MakeGameFolderBox(); QGridLayout* MakePathsLayout(); @@ -28,7 +26,5 @@ private: QListWidget* m_path_list; QLineEdit* m_game_edit; - QLineEdit* m_dvd_edit; - QLineEdit* m_app_edit; QLineEdit* m_nand_edit; }; diff --git a/Source/Core/DolphinWX/Config/PathConfigPane.cpp b/Source/Core/DolphinWX/Config/PathConfigPane.cpp index ec3376acc2..0d54738192 100644 --- a/Source/Core/DolphinWX/Config/PathConfigPane.cpp +++ b/Source/Core/DolphinWX/Config/PathConfigPane.cpp @@ -45,14 +45,6 @@ void PathConfigPane::InitializeGUI() wxString::Format("|*.elf;*.dol;*.gcm;*.iso;*.tgc;*.wbfs;*.ciso;*.gcz;*.wad|%s", wxGetTranslation(wxALL_FILES)), wxDefaultPosition, wxDefaultSize, wxFLP_USE_TEXTCTRL | wxFLP_OPEN | wxFLP_SMALL); - m_dvd_root_dirpicker = - new wxDirPickerCtrl(this, wxID_ANY, wxEmptyString, _("Choose a DVD root directory:"), - wxDefaultPosition, wxDefaultSize, wxDIRP_USE_TEXTCTRL | wxDIRP_SMALL); - m_apploader_path_filepicker = new wxFilePickerCtrl( - this, wxID_ANY, wxEmptyString, - _("Choose file to use as apploader: (applies to discs constructed from directories only)"), - _("apploader (.img)") + wxString::Format("|*.img|%s", wxGetTranslation(wxALL_FILES)), - wxDefaultPosition, wxDefaultSize, wxFLP_USE_TEXTCTRL | wxFLP_OPEN | wxFLP_SMALL); m_nand_root_dirpicker = new wxDirPickerCtrl(this, wxID_ANY, wxEmptyString, _("Choose a NAND root directory:"), wxDefaultPosition, wxDefaultSize, wxDIRP_USE_TEXTCTRL | wxDIRP_SMALL); @@ -82,21 +74,15 @@ void PathConfigPane::InitializeGUI() picker_sizer->Add(new wxStaticText(this, wxID_ANY, _("Default ISO:")), wxGBPosition(0, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); picker_sizer->Add(m_default_iso_filepicker, wxGBPosition(0, 1), wxDefaultSpan, wxEXPAND); - picker_sizer->Add(new wxStaticText(this, wxID_ANY, _("DVD Root:")), wxGBPosition(1, 0), + picker_sizer->Add(new wxStaticText(this, wxID_ANY, _("Wii NAND Root:")), wxGBPosition(1, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); - picker_sizer->Add(m_dvd_root_dirpicker, wxGBPosition(1, 1), wxDefaultSpan, wxEXPAND); - picker_sizer->Add(new wxStaticText(this, wxID_ANY, _("Apploader:")), wxGBPosition(2, 0), + picker_sizer->Add(m_nand_root_dirpicker, wxGBPosition(1, 1), wxDefaultSpan, wxEXPAND); + picker_sizer->Add(new wxStaticText(this, wxID_ANY, _("Dump Path:")), wxGBPosition(2, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); - picker_sizer->Add(m_apploader_path_filepicker, wxGBPosition(2, 1), wxDefaultSpan, wxEXPAND); - picker_sizer->Add(new wxStaticText(this, wxID_ANY, _("Wii NAND Root:")), wxGBPosition(3, 0), + picker_sizer->Add(m_dump_path_dirpicker, wxGBPosition(2, 1), wxDefaultSpan, wxEXPAND); + picker_sizer->Add(new wxStaticText(this, wxID_ANY, _("SD Card Path:")), wxGBPosition(3, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); - picker_sizer->Add(m_nand_root_dirpicker, wxGBPosition(3, 1), wxDefaultSpan, wxEXPAND); - picker_sizer->Add(new wxStaticText(this, wxID_ANY, _("Dump Path:")), wxGBPosition(4, 0), - wxDefaultSpan, wxALIGN_CENTER_VERTICAL); - picker_sizer->Add(m_dump_path_dirpicker, wxGBPosition(4, 1), wxDefaultSpan, wxEXPAND); - picker_sizer->Add(new wxStaticText(this, wxID_ANY, _("SD Card Path:")), wxGBPosition(5, 0), - wxDefaultSpan, wxALIGN_CENTER_VERTICAL); - picker_sizer->Add(m_wii_sdcard_filepicker, wxGBPosition(5, 1), wxDefaultSpan, wxEXPAND); + picker_sizer->Add(m_wii_sdcard_filepicker, wxGBPosition(3, 1), wxDefaultSpan, wxEXPAND); picker_sizer->AddGrowableCol(1); // Populate the Paths page @@ -116,8 +102,6 @@ void PathConfigPane::LoadGUIValues() m_recursive_iso_paths_checkbox->SetValue(SConfig::GetInstance().m_RecursiveISOFolder); m_default_iso_filepicker->SetPath(StrToWxStr(startup_params.m_strDefaultISO)); - m_dvd_root_dirpicker->SetPath(StrToWxStr(startup_params.m_strDVDRoot)); - m_apploader_path_filepicker->SetPath(StrToWxStr(startup_params.m_strApploader)); m_nand_root_dirpicker->SetPath(StrToWxStr(SConfig::GetInstance().m_NANDPath)); m_dump_path_dirpicker->SetPath(StrToWxStr(SConfig::GetInstance().m_DumpPath)); m_wii_sdcard_filepicker->SetPath(StrToWxStr(SConfig::GetInstance().m_strWiiSDCardPath)); @@ -136,9 +120,6 @@ void PathConfigPane::BindEvents() m_remove_iso_path_button->Bind(wxEVT_BUTTON, &PathConfigPane::OnRemoveISOPath, this); m_default_iso_filepicker->Bind(wxEVT_FILEPICKER_CHANGED, &PathConfigPane::OnDefaultISOChanged, this); - m_dvd_root_dirpicker->Bind(wxEVT_DIRPICKER_CHANGED, &PathConfigPane::OnDVDRootChanged, this); - m_apploader_path_filepicker->Bind(wxEVT_FILEPICKER_CHANGED, - &PathConfigPane::OnApploaderPathChanged, this); m_nand_root_dirpicker->Bind(wxEVT_DIRPICKER_CHANGED, &PathConfigPane::OnNANDRootChanged, this); m_dump_path_dirpicker->Bind(wxEVT_DIRPICKER_CHANGED, &PathConfigPane::OnDumpPathChanged, this); m_wii_sdcard_filepicker->Bind(wxEVT_FILEPICKER_CHANGED, &PathConfigPane::OnSdCardPathChanged, @@ -206,16 +187,6 @@ void PathConfigPane::OnDefaultISOChanged(wxCommandEvent& event) SConfig::GetInstance().m_strDefaultISO = WxStrToStr(m_default_iso_filepicker->GetPath()); } -void PathConfigPane::OnDVDRootChanged(wxCommandEvent& event) -{ - SConfig::GetInstance().m_strDVDRoot = WxStrToStr(m_dvd_root_dirpicker->GetPath()); -} - -void PathConfigPane::OnApploaderPathChanged(wxCommandEvent& event) -{ - SConfig::GetInstance().m_strApploader = WxStrToStr(m_apploader_path_filepicker->GetPath()); -} - void PathConfigPane::OnSdCardPathChanged(wxCommandEvent& event) { std::string sd_card_path = WxStrToStr(m_wii_sdcard_filepicker->GetPath()); diff --git a/Source/Core/DolphinWX/Config/PathConfigPane.h b/Source/Core/DolphinWX/Config/PathConfigPane.h index 484c6d937c..ae708c116c 100644 --- a/Source/Core/DolphinWX/Config/PathConfigPane.h +++ b/Source/Core/DolphinWX/Config/PathConfigPane.h @@ -29,8 +29,6 @@ private: void OnAddISOPath(wxCommandEvent&); void OnRemoveISOPath(wxCommandEvent&); void OnDefaultISOChanged(wxCommandEvent&); - void OnDVDRootChanged(wxCommandEvent&); - void OnApploaderPathChanged(wxCommandEvent&); void OnNANDRootChanged(wxCommandEvent&); void OnDumpPathChanged(wxCommandEvent&); void OnSdCardPathChanged(wxCommandEvent&); @@ -42,10 +40,8 @@ private: wxButton* m_add_iso_path_button; wxButton* m_remove_iso_path_button; - wxDirPickerCtrl* m_dvd_root_dirpicker; wxDirPickerCtrl* m_nand_root_dirpicker; wxFilePickerCtrl* m_default_iso_filepicker; - wxFilePickerCtrl* m_apploader_path_filepicker; wxDirPickerCtrl* m_dump_path_dirpicker; wxFilePickerCtrl* m_wii_sdcard_filepicker; }; diff --git a/Source/Core/DolphinWX/GameListCtrl.cpp b/Source/Core/DolphinWX/GameListCtrl.cpp index 2395028790..58005099be 100644 --- a/Source/Core/DolphinWX/GameListCtrl.cpp +++ b/Source/Core/DolphinWX/GameListCtrl.cpp @@ -53,6 +53,7 @@ #include "Core/Movie.h" #include "Core/TitleDatabase.h" #include "DiscIO/Blob.h" +#include "DiscIO/DirectoryBlob.h" #include "DiscIO/Enums.h" #include "DiscIO/Volume.h" #include "DolphinWX/Frame.h" @@ -80,7 +81,7 @@ public: wxProgressDialog* dialog; }; -static constexpr u32 CACHE_REVISION = 2; // Last changed in PR 5687 +static constexpr u32 CACHE_REVISION = 3; // Last changed in PR 5573 static bool sorted = false; @@ -761,6 +762,12 @@ void GameListCtrl::RescanList() auto search_results = Common::DoFileSearch(SConfig::GetInstance().m_ISOFolder, search_extensions, SConfig::GetInstance().m_RecursiveISOFolder); + // TODO Prevent DoFileSearch from looking inside /files/ directories of DirectoryBlobs at all? + // TODO Make DoFileSearch support filter predicates so we don't have remove things afterwards? + search_results.erase( + std::remove_if(search_results.begin(), search_results.end(), DiscIO::ShouldHideFromGameList), + search_results.end()); + std::vector cached_paths; for (const auto& file : m_cached_files) cached_paths.emplace_back(file->GetFileName());