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 96658720c741..a27b2f2e0563 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 @@ -22,7 +22,7 @@ public final class FileBrowserHelper { public static final HashSet GAME_EXTENSIONS = new HashSet<>(Arrays.asList( - "gcm", "tgc", "iso", "ciso", "gcz", "wbfs", "wad", "dol", "elf", "dff")); + "gcm", "tgc", "iso", "ciso", "gcz", "wbfs", "wia", "wad", "dol", "elf", "dff")); public static final HashSet RAW_EXTENSION = new HashSet<>(Collections.singletonList( "raw")); diff --git a/Source/Core/Core/Boot/Boot.cpp b/Source/Core/Core/Boot/Boot.cpp index a4f85208e3d6..f06c1077f92f 100644 --- a/Source/Core/Core/Boot/Boot.cpp +++ b/Source/Core/Core/Boot/Boot.cpp @@ -159,7 +159,7 @@ BootParameters::GenerateFromFile(std::vector paths, paths.clear(); static const std::unordered_set disc_image_extensions = { - {".gcm", ".iso", ".tgc", ".wbfs", ".ciso", ".gcz", ".dol", ".elf"}}; + {".gcm", ".iso", ".tgc", ".wbfs", ".ciso", ".gcz", ".wia", ".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 20fe23e9ca45..97e48eb047c5 100644 --- a/Source/Core/DiscIO/Blob.cpp +++ b/Source/Core/DiscIO/Blob.cpp @@ -20,6 +20,7 @@ #include "DiscIO/DriveBlob.h" #include "DiscIO/FileBlob.h" #include "DiscIO/TGCBlob.h" +#include "DiscIO/WIABlob.h" #include "DiscIO/WbfsBlob.h" namespace DiscIO @@ -205,6 +206,8 @@ std::unique_ptr CreateBlobReader(const std::string& filename) return TGCFileReader::Create(std::move(file)); case WBFS_MAGIC: return WbfsFileReader::Create(std::move(file), filename); + case WIA_MAGIC: + return WIAFileReader::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 e8846753d168..5371478d6582 100644 --- a/Source/Core/DiscIO/Blob.h +++ b/Source/Core/DiscIO/Blob.h @@ -34,7 +34,8 @@ enum class BlobType GCZ, CISO, WBFS, - TGC + TGC, + WIA }; class BlobReader diff --git a/Source/Core/DiscIO/CMakeLists.txt b/Source/Core/DiscIO/CMakeLists.txt index 6437bf35d108..1e234bea4d1c 100644 --- a/Source/Core/DiscIO/CMakeLists.txt +++ b/Source/Core/DiscIO/CMakeLists.txt @@ -42,6 +42,8 @@ add_library(discio VolumeWii.h WbfsBlob.cpp WbfsBlob.h + WIABlob.cpp + WIABlob.h WiiEncryptionCache.cpp WiiEncryptionCache.h WiiSaveBanner.cpp diff --git a/Source/Core/DiscIO/DiscIO.vcxproj b/Source/Core/DiscIO/DiscIO.vcxproj index ee1d2ecd3663..9c2c9f7954a4 100644 --- a/Source/Core/DiscIO/DiscIO.vcxproj +++ b/Source/Core/DiscIO/DiscIO.vcxproj @@ -65,6 +65,7 @@ + @@ -91,6 +92,7 @@ + diff --git a/Source/Core/DiscIO/DiscIO.vcxproj.filters b/Source/Core/DiscIO/DiscIO.vcxproj.filters index 43cf82fa03b4..b7990b19d676 100644 --- a/Source/Core/DiscIO/DiscIO.vcxproj.filters +++ b/Source/Core/DiscIO/DiscIO.vcxproj.filters @@ -90,6 +90,9 @@ Volume\Blob + + Volume\Blob + @@ -164,6 +167,9 @@ Volume\Blob + + Volume\Blob + diff --git a/Source/Core/DiscIO/WIABlob.cpp b/Source/Core/DiscIO/WIABlob.cpp new file mode 100644 index 000000000000..85f7f8ce38a1 --- /dev/null +++ b/Source/Core/DiscIO/WIABlob.cpp @@ -0,0 +1,92 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DiscIO/WIABlob.h" + +#include + +#include "Common/CommonTypes.h" +#include "Common/File.h" +#include "Common/Logging/Log.h" +#include "Common/StringUtil.h" +#include "Common/Swap.h" + +#include "DiscIO/VolumeWii.h" + +namespace DiscIO +{ +WIAFileReader::WIAFileReader(File::IOFile file, const std::string& path) : m_file(std::move(file)) +{ + m_valid = Initialize(path); +} + +WIAFileReader::~WIAFileReader() = default; + +bool WIAFileReader::Initialize(const std::string& path) +{ + if (!m_file.Seek(0, SEEK_SET) || !m_file.ReadArray(&m_header_1, 1)) + return false; + + if (m_header_1.magic != WIA_MAGIC) + return false; + + const u32 version = Common::swap32(m_header_1.version); + const u32 version_compatible = Common::swap32(m_header_1.version_compatible); + if (WIA_VERSION < version_compatible || WIA_VERSION_READ_COMPATIBLE > version) + { + ERROR_LOG(DISCIO, "Unsupported WIA version %s in %s", VersionToString(version).c_str(), + path.c_str()); + return false; + } + + if (Common::swap64(m_header_1.wia_file_size) != m_file.GetSize()) + { + ERROR_LOG(DISCIO, "File size is incorrect for %s", path.c_str()); + return false; + } + + if (Common::swap32(m_header_1.header_2_size) < sizeof(WIAHeader2)) + return false; + + if (!m_file.ReadArray(&m_header_2, 1)) + return false; + + const u32 chunk_size = Common::swap32(m_header_2.chunk_size); + if (chunk_size % VolumeWii::GROUP_TOTAL_SIZE != 0) + return false; + + return true; +} + +std::unique_ptr WIAFileReader::Create(File::IOFile file, const std::string& path) +{ + std::unique_ptr blob(new WIAFileReader(std::move(file), path)); + return blob->m_valid ? std::move(blob) : nullptr; +} + +bool WIAFileReader::Read(u64 offset, u64 size, u8* out_ptr) +{ + if (offset + size <= sizeof(WIAHeader2::disc_header)) + { + std::memcpy(out_ptr, m_header_2.disc_header.data() + offset, size); + return true; + } + + // Not implemented + return false; +} + +std::string WIAFileReader::VersionToString(u32 version) +{ + const u8 a = version >> 24; + const u8 b = (version >> 16) & 0xff; + const u8 c = (version >> 8) & 0xff; + const u8 d = version & 0xff; + + if (d == 0 || d == 0xff) + return StringFromFormat("%u.%02x.%02x", a, b, c); + else + return StringFromFormat("%u.%02x.%02x.beta%u", a, b, c, d); +} +} // namespace DiscIO diff --git a/Source/Core/DiscIO/WIABlob.h b/Source/Core/DiscIO/WIABlob.h new file mode 100644 index 000000000000..edd7d0b77633 --- /dev/null +++ b/Source/Core/DiscIO/WIABlob.h @@ -0,0 +1,102 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include "Common/CommonTypes.h" +#include "Common/File.h" +#include "Common/Swap.h" +#include "DiscIO/Blob.h" + +namespace DiscIO +{ +constexpr u32 WIA_MAGIC = 0x01414957; // "WIA\x1" (byteswapped to little endian) + +class WIAFileReader : public BlobReader +{ +public: + ~WIAFileReader(); + + static std::unique_ptr Create(File::IOFile file, const std::string& path); + + BlobType GetBlobType() const override { return BlobType::WIA; } + + 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; } + + u64 GetBlockSize() const override { return Common::swap32(m_header_2.chunk_size); } + bool HasFastRandomAccessInBlock() const override { return false; } + + bool Read(u64 offset, u64 size, u8* out_ptr) override; + +private: + explicit WIAFileReader(File::IOFile file, const std::string& path); + bool Initialize(const std::string& path); + + static std::string VersionToString(u32 version); + + using SHA1 = std::array; + +#pragma pack(push, 1) + struct WIAHeader1 + { + u32 magic; + u32 version; + u32 version_compatible; + u32 header_2_size; + SHA1 header_2_hash; + u64 iso_file_size; + u64 wia_file_size; + SHA1 header_1_hash; + }; +#pragma pack(pop) + static_assert(sizeof(WIAHeader1) == 0x48, "Wrong size for WIA header 1"); + +#pragma pack(push, 1) + struct WIAHeader2 + { + u32 disc_type; + u32 compression_type; + u32 compression_level; // Informative only + u32 chunk_size; + + std::array disc_header; + + u32 number_of_partitions_entries; + u32 partition_entry_size; + u64 partition_entries_offset; + SHA1 partition_entries_hash; + + u32 number_of_raw_data_entries; + u64 raw_data_entries_offset; + u32 raw_data_entries_size; + + u32 number_of_group_entries; + u64 group_entries_offset; + u32 group_entries_size; + }; +#pragma pack(pop) + static_assert(sizeof(WIAHeader2) == 0xd4, "Wrong size for WIA header 2"); + + bool m_valid; + + File::IOFile m_file; + + WIAHeader1 m_header_1; + WIAHeader2 m_header_2; + + static constexpr u32 WIA_VERSION = 0x01000000; + static constexpr u32 WIA_VERSION_WRITE_COMPATIBLE = 0x01000000; + static constexpr u32 WIA_VERSION_READ_COMPATIBLE = 0x00080000; + + // Perhaps we could set WIA_VERSION_WRITE_COMPATIBLE to 0.9, but WIA version 0.9 was never in + // any official release of wit, and interim versions (either source or binaries) are hard to find. + // Since we've been unable to check if we're write compatible with 0.9, we set it 1.0 to be safe. +}; + +} // namespace DiscIO diff --git a/Source/Core/DolphinQt/GameList/GameTracker.cpp b/Source/Core/DolphinQt/GameList/GameTracker.cpp index 4caf643dd542..4aa930140c93 100644 --- a/Source/Core/DolphinQt/GameList/GameTracker.cpp +++ b/Source/Core/DolphinQt/GameList/GameTracker.cpp @@ -24,8 +24,8 @@ 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][aA][dD]"), QStringLiteral("*.[eE][lL][fF]"), - QStringLiteral("*.[dD][oO][lL]")}; + QStringLiteral("*.[wW][iI][aA]"), QStringLiteral("*.[wW][aA][dD]"), + QStringLiteral("*.[eE][lL][fF]"), QStringLiteral("*.[dD][oO][lL]")}; GameTracker::GameTracker(QObject* parent) : QFileSystemWatcher(parent) { diff --git a/Source/Core/DolphinQt/Info.plist.in b/Source/Core/DolphinQt/Info.plist.in index 7fd60fa8e0d3..979f5f734882 100644 --- a/Source/Core/DolphinQt/Info.plist.in +++ b/Source/Core/DolphinQt/Info.plist.in @@ -16,6 +16,7 @@ m3u tgc wad + wia wbfs CFBundleTypeIconFile diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 8b8eb37f4097..508b8224271d 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -686,8 +686,8 @@ QStringList MainWindow::PromptFileNames() QStringList paths = QFileDialog::getOpenFileNames( this, tr("Select a File"), settings.value(QStringLiteral("mainwindow/lastdir"), QString{}).toString(), - tr("All GC/Wii files (*.elf *.dol *.gcm *.iso *.tgc *.wbfs *.ciso *.gcz *.wad *.dff *.m3u);;" - "All Files (*)")); + tr("All GC/Wii files (*.elf *.dol *.gcm *.iso *.tgc *.wbfs *.ciso *.gcz *.wia *.wad *.dff " + "*.m3u);;All Files (*)")); if (!paths.isEmpty()) { diff --git a/Source/Core/DolphinQt/Settings/PathPane.cpp b/Source/Core/DolphinQt/Settings/PathPane.cpp index ab59f185ccf1..e77f4d09c33e 100644 --- a/Source/Core/DolphinQt/Settings/PathPane.cpp +++ b/Source/Core/DolphinQt/Settings/PathPane.cpp @@ -44,7 +44,7 @@ void PathPane::BrowseDefaultGame() { QString file = QDir::toNativeSeparators(QFileDialog::getOpenFileName( this, tr("Select a Game"), Settings::Instance().GetDefaultGame(), - tr("All GC/Wii files (*.elf *.dol *.gcm *.iso *.tgc *.wbfs *.ciso *.gcz *.wad *.m3u);;" + tr("All GC/Wii files (*.elf *.dol *.gcm *.iso *.tgc *.wbfs *.ciso *.gcz *.wia *.wad *.m3u);;" "All Files (*)"))); if (!file.isEmpty()) diff --git a/Source/Core/UICommon/GameFileCache.cpp b/Source/Core/UICommon/GameFileCache.cpp index 9b2729407ed1..bb12eb3ccd50 100644 --- a/Source/Core/UICommon/GameFileCache.cpp +++ b/Source/Core/UICommon/GameFileCache.cpp @@ -33,7 +33,7 @@ std::vector FindAllGamePaths(const std::vector& direct bool recursive_scan) { static const std::vector search_extensions = { - ".gcm", ".tgc", ".iso", ".ciso", ".gcz", ".wbfs", ".wad", ".dol", ".elf"}; + ".gcm", ".tgc", ".iso", ".ciso", ".gcz", ".wbfs", ".wia", ".wad", ".dol", ".elf"}; // TODO: We could process paths iteratively as they are found return Common::DoFileSearch(directories_to_scan, search_extensions, recursive_scan);