diff --git a/Readme.md b/Readme.md index 80f0f1424f..1f1d3c3578 100644 --- a/Readme.md +++ b/Readme.md @@ -1,252 +1,50 @@ -# Dolphin - A GameCube and Wii Emulator +# Dolphin MPN -[Homepage](https://dolphin-emu.org/) | [Project Site](https://github.com/dolphin-emu/dolphin) | [Buildbot](https://dolphin.ci) | [Forums](https://forums.dolphin-emu.org/) | [Wiki](https://wiki.dolphin-emu.org/) | [Issue Tracker](https://bugs.dolphin-emu.org/projects/emulator/issues) | [Coding Style](https://github.com/dolphin-emu/dolphin/blob/master/Contributing.md) | [Transifex Page](https://www.transifex.com/projects/p/dolphin-emu/) +Dolphin MPN is an emulator for running GameCube and Wii games on Windows, macOS, and Linux -Dolphin is an emulator for running GameCube and Wii games on Windows, -Linux, macOS, and recent Android devices. It's licensed under the terms -of the GNU General Public License, version 2 or later (GPLv2+). -Please read the [FAQ](https://dolphin-emu.org/docs/faq/) before using Dolphin. +## Info +This emulator bundled a modified version of Dolphin -## System Requirements +## Installation +(note: ROMs will need to be downloaded separately) -### Desktop +## Compiling -* OS - * Windows (7 SP1 or higher). - * Linux. - * macOS (10.13 High Sierra or higher). - * Unix-like systems other than Linux are not officially supported but might work. -* Processor - * A CPU with SSE2 support. - * A modern CPU (3 GHz and Dual Core, not older than 2008) is highly recommended. -* Graphics - * A reasonably modern graphics card (Direct3D 11.1 / OpenGL 3.3). - * A graphics card that supports Direct3D 11.1 / OpenGL 4.4 is recommended. +Windows: +1) Install [Git](https://gitforwindows.org/) and [Visual Studio 2019](https://visualstudio.microsoft.com/downloads/) (community is fine) if you haven't already +1) Clone the Git Repo `git clone https://github.com/MarioPartyNetplay/dolphin-mpn --recursive` +2) Open the **Source/dolphin-emu.sln** project in Visual Studio 2019 +3) Set Build Path to **Release** -> **x64** +4) Click Build +5: Open Dolphin +**Dolphin is Located at Binaries/x64/** -### Android +Linux: +1) Open Terminal +2) Install Required Packages: + **Ubuntu** `sudo apt install curl ffmpeg git libao-dev libasound-dev libavcodec-dev libavformat-dev libbluetooth-dev libevdev-dev libgtk2.0-dev libhidapi-dev libmbedtls-dev libminiupnpc-dev libopenal-dev libpangocairo-1.0-0 libpulse-dev libsfml-dev libswscale-dev libudev-dev libusb-1.0-0-dev libxrandr-dev qt5-default qtbase5-private-dev` +3) Clone the Git Repo: `git clone https://github.com/MarioPartyNetplay/dolphin-mpn` +4) Enter the Cloned Repo: `cd dolphin-mpn` +5) Make the Build Directory: `mkdir build && cd build` +6) Setup CMake: `cmake .. -G Ninja -DLINUX_LOCAL_DEV=true` +7) Build Dolphin `ninja` +8) Copy Important Files `cp -r ../Data/Sys/ Binaries/ && cp -r ../Data/User/ Binaries/ && cp -r ../Data/dolphin-emu.png Binaries/ && touch Binaries/portable.txt` +9) Run Dolphin +**Dolphin is Located at build/Binaries/dolphin-emu** -* OS - * Android (5.0 Lollipop or higher). -* Processor - * A processor with support for 64-bit applications (either ARMv8 or x86-64). -* Graphics - * A graphics processor that supports OpenGL ES 3.0 or higher. Performance varies heavily with [driver quality](https://dolphin-emu.org/blog/2013/09/26/dolphin-emulator-and-opengl-drivers-hall-fameshame/). - * A graphics processor that supports standard desktop OpenGL features is recommended for best performance. +macOS: +1) Install [Xcode Command Line Tools](https://developer.apple.com/xcode/features/) & [Brew](https://brew.sh/) if you havent +2) Open a Terminal +3) Install Brew: `brew install cmake qt5 ninja pkgconfig git` +4) Clone the Git Repo: `git clone https://github.com/MarioPartyNetplay/dolphin-mpn` +5) Enter the Cloned Repo: `cd dolphin-mpn` +6) Make the Build Directory: `mkdir -p build && cd build` +7) Setup CMake: `cmake .. -G Ninja -DCMAKE_CXX_FLAGS="-Xclang -fcolor-diagnostics" -DCMAKE_PREFIX_PATH=$(brew --prefix qt5)` +8) Build Dolphin: `ninja` +9) Copy Important Files: `cp -r ../Data/Sys/ Binaries/ && cp -r ../Data/User/ Binaries/ && cp -r ../Data/dolphin-emu.png Binaries/ && touch Binaries/portable.txt` +10) Run Dolphin +**Dolphin is Located at build/Binaries/dolphin-emu** -Dolphin can only be installed on devices that satisfy the above requirements. Attempting to install on an unsupported device will fail and display an error message. - -## Building for Windows - -Use the solution file `Source/dolphin-emu.sln` to build Dolphin on Windows. -Visual Studio 2022 17.0 or later is a hard requirement. Other compilers might be -able to build Dolphin on Windows but have not been tested and are not -recommended to be used. Git and Windows 11 SDK must be installed when building. - -Make sure to pull submodules before building: -```sh -git submodule update --init -``` - -The "Release" solution configuration includes performance optimizations for the best user experience but complicates debugging Dolphin. -The "Debug" solution configuration is significantly slower, more verbose and less permissive but makes debugging Dolphin easier. - -An installer can be created by using the `Installer.nsi` script in the -Installer directory. This will require the Nullsoft Scriptable Install System -(NSIS) to be installed. Creating an installer is not necessary to run Dolphin -since the Binary directory contains a working Dolphin distribution. - -## Building for Linux and macOS - -Dolphin requires [CMake](https://cmake.org/) for systems other than Windows. Many libraries are -bundled with Dolphin and used if they're not installed on your system. CMake -will inform you if a bundled library is used or if you need to install any -missing packages yourself. - -Make sure to pull submodules before building: -```sh -git submodule update --init -``` - -### macOS Build Steps: - -A binary supporting a single architecture can be built using the following steps: - -1. `mkdir build` -2. `cd build` -3. `cmake ..` -4. `make` - -An application bundle will be created in `./Binaries`. - -A script is also provided to build universal binaries supporting both x64 and ARM in the same -application bundle using the following steps: - -1. `mkdir build` -2. `cd build` -3. `python ../BuildMacOSUniversalBinary.py` -4. Universal binaries will be available in the `universal` folder - -Doing this is more complex as it requires installation of library dependencies for both x64 and ARM (or universal library -equivalents) and may require specifying additional arguments to point to relevant library locations. -Execute BuildMacOSUniversalBinary.py --help for more details. - -### Linux Global Build Steps: - -To install to your system. - -1. `mkdir build` -2. `cd build` -3. `cmake ..` -4. `make` -5. `sudo make install` - -### Linux Local Build Steps: - -Useful for development as root access is not required. - -1. `mkdir Build` -2. `cd Build` -3. `cmake .. -DLINUX_LOCAL_DEV=true` -4. `make` -5. `ln -s ../../Data/Sys Binaries/` - -### Linux Portable Build Steps: - -Can be stored on external storage and used on different Linux systems. -Or useful for having multiple distinct Dolphin setups for testing/development/TAS. - -1. `mkdir Build` -2. `cd Build` -3. `cmake .. -DLINUX_LOCAL_DEV=true` -4. `make` -5. `cp -r ../Data/Sys/ Binaries/` -6. `touch Binaries/portable.txt` - -## Building for Android - -These instructions assume familiarity with Android development. If you do not have an -Android dev environment set up, see [AndroidSetup.md](AndroidSetup.md). - -Make sure to pull submodules before building: -```sh -git submodule update --init -``` - -If using Android Studio, import the Gradle project located in `./Source/Android`. - -Android apps are compiled using a build system called Gradle. Dolphin's native component, -however, is compiled using CMake. The Gradle script will attempt to run a CMake build -automatically while building the Java code. - -## Uninstalling - -When Dolphin has been installed with the NSIS installer, you can uninstall -Dolphin like any other Windows application. - -Linux users can run `cat install_manifest.txt | xargs -d '\n' rm` as root from the build directory -to uninstall Dolphin from their system. - -macOS users can simply delete Dolphin.app to uninstall it. - -Additionally, you'll want to remove the global user directory (see below to -see where it's stored) if you don't plan to reinstall Dolphin. - -## Command Line Usage - -`Usage: Dolphin [-h] [-d] [-l] [-e ] [-b] [-v ] [-a ]` - -* -h, --help Show this help message -* -d, --debugger Show the debugger pane and additional View menu options -* -l, --logger Open the logger -* -e, --exec= Load the specified file (DOL,ELF,WAD,GCM,ISO) -* -b, --batch Exit Dolphin with emulator -* -v, --video_backend= Specify a video backend -* -a, --audio_emulation= Low level (LLE) or high level (HLE) audio - -Available DSP emulation engines are HLE (High Level Emulation) and -LLE (Low Level Emulation). HLE is faster but less accurate whereas -LLE is slower but close to perfect. Note that LLE has two submodes (Interpreter and Recompiler) -but they cannot be selected from the command line. - -Available video backends are "D3D" and "D3D12" (they are only available on Windows), "OGL", and "Vulkan". -There's also "Null", which will not render anything, and -"Software Renderer", which uses the CPU for rendering and -is intended for debugging purposes only. - -## Sys Files - -* `wiitdb.txt`: Wii title database from [GameTDB](https://www.gametdb.com/) -* `totaldb.dsy`: Database of symbols (for devs only) -* `GC/font_western.bin`: font dumps -* `GC/font_japanese.bin`: font dumps -* `GC/dsp_coef.bin`: DSP dumps -* `GC/dsp_rom.bin`: DSP dumps -* `Wii/clientca.pem`: Wii network certificate -* `Wii/clientcakey.pem`: Wii network certificate key -* `Wii/rootca.pem`: Wii network certificate issuer / CA - -The DSP dumps included with Dolphin have been written from scratch and do not -contain any copyrighted material. They should work for most purposes, however -some games implement copy protection by checksumming the dumps. You will need -to dump the DSP files from a console and replace the default dumps if you want -to fix those issues. - -Wii network certificates must be extracted from a Wii IOS. A guide for that can be found [here](https://wiki.dolphin-emu.org/index.php?title=Wii_Network_Guide). - -## Folder Structure - -These folders are installed read-only and should not be changed: - -* `GameSettings`: per-game default settings database -* `GC`: DSP and font dumps -* `Shaders`: post-processing shaders -* `Themes`: icon themes for GUI -* `Resources`: icons that are theme-agnostic -* `Wii`: default Wii NAND contents - -## Packaging and udev - -The Data folder contains a udev rule file for the official GameCube controller -adapter and the Mayflash DolphinBar. Package maintainers can use that file in their packages for Dolphin. -Users compiling Dolphin on Linux can also just copy the file to their udev -rules folder. - -## User Folder Structure - -A number of user writeable directories are created for caching purposes or for -allowing the user to edit their contents. On macOS and Linux these folders are -stored in `~/Library/Application Support/Dolphin/` and `~/.dolphin-emu` -respectively, but can be overwritten by setting the environment variable -`DOLPHIN_EMU_USERPATH`. On Windows the user directory is stored in the `My Documents` -folder by default, but there are various way to override this behavior: - -* Creating a file called `portable.txt` next to the Dolphin executable will - store the user directory in a local directory called "User" next to the - Dolphin executable. -* If the registry string value `LocalUserConfig` exists in - `HKEY_CURRENT_USER/Software/Dolphin Emulator` and has the value **1**, - Dolphin will always start in portable mode. -* If the registry string value `UserConfigPath` exists in - `HKEY_CURRENT_USER/Software/Dolphin Emulator`, the user folders will be - stored in the directory given by that string. The other two methods will be - prioritized over this setting. - -List of user folders: - -* `Cache`: used to cache the ISO list -* `Config`: configuration files -* `Dump`: anything dumped from Dolphin -* `GameConfig`: additional settings to be applied per-game -* `GC`: memory cards and system BIOS -* `Load`: custom textures -* `Logs`: logs, if enabled -* `ScreenShots`: screenshots taken via Dolphin -* `StateSaves`: save states -* `Wii`: Wii NAND contents - -## Custom Textures - -Custom textures have to be placed in the user directory under -`Load/Textures/[GameID]/`. You can find the Game ID by right-clicking a game -in the ISO list and selecting "ISO Properties". +### License +Dolphin MPN is licensed under the [GNU General Public License v2.0](license.txt) diff --git a/Source/Core/Common/DynamicLibrary.h b/Source/Core/Common/DynamicLibrary.h index 775afce56c..edfcde8ff3 100644 --- a/Source/Core/Common/DynamicLibrary.h +++ b/Source/Core/Common/DynamicLibrary.h @@ -24,6 +24,12 @@ public: // Closes the library. ~DynamicLibrary(); + DynamicLibrary(const DynamicLibrary&) = delete; + DynamicLibrary(DynamicLibrary&&) = delete; + + DynamicLibrary& operator=(const DynamicLibrary&) = delete; + DynamicLibrary& operator=(DynamicLibrary&&) = delete; + // Returns the specified library name with the platform-specific suffix added. static std::string GetUnprefixedFilename(const char* filename); diff --git a/Source/Core/Common/FileUtil.cpp b/Source/Core/Common/FileUtil.cpp index d84b725e70..8715c50a99 100644 --- a/Source/Core/Common/FileUtil.cpp +++ b/Source/Core/Common/FileUtil.cpp @@ -859,7 +859,6 @@ std::string GetExePath() } #elif defined(__APPLE__) result = GetBundleDirectory(); - result = result.substr(0, result.find_last_of("Dolphin.app/Contents/MacOS") + 1); #else char dolphin_exe_path[PATH_MAX]; ssize_t len = ::readlink("/proc/self/exe", dolphin_exe_path, sizeof(dolphin_exe_path)); diff --git a/Source/Core/Core/Debugger/PPCDebugInterface.cpp b/Source/Core/Core/Debugger/PPCDebugInterface.cpp index 2d6e83c7cc..42082a5cfb 100644 --- a/Source/Core/Core/Debugger/PPCDebugInterface.cpp +++ b/Source/Core/Core/Debugger/PPCDebugInterface.cpp @@ -182,7 +182,7 @@ Common::Debug::Threads PPCDebugInterface::GetThreads() const if (!active_thread->IsValid()) return threads; - std::vector visited_addrs{{active_thread->GetAddress()}}; + std::vector visited_addrs{active_thread->GetAddress()}; const auto insert_threads = [&threads, &visited_addrs](u32 addr, auto get_next_addr) { while (addr != 0 && PowerPC::HostIsRAMAddress(addr)) { diff --git a/Source/Core/DiscIO/NANDImporter.cpp b/Source/Core/DiscIO/NANDImporter.cpp index ccd1525b62..4520930ca3 100644 --- a/Source/Core/DiscIO/NANDImporter.cpp +++ b/Source/Core/DiscIO/NANDImporter.cpp @@ -4,17 +4,13 @@ #include "DiscIO/NANDImporter.h" #include -#include #include -#include - #include "Common/Crypto/AES.h" #include "Common/FileUtil.h" #include "Common/IOFile.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" -#include "Common/Swap.h" #include "Core/IOS/ES/Formats.h" namespace DiscIO @@ -22,7 +18,9 @@ namespace DiscIO constexpr size_t NAND_SIZE = 0x20000000; constexpr size_t NAND_KEYS_SIZE = 0x400; -NANDImporter::NANDImporter() = default; +NANDImporter::NANDImporter() : m_nand_root(File::GetUserPath(D_WIIROOT_IDX)) +{ +} NANDImporter::~NANDImporter() = default; void NANDImporter::ImportNANDBin(const std::string& path_to_bin, @@ -33,15 +31,12 @@ void NANDImporter::ImportNANDBin(const std::string& path_to_bin, if (!ReadNANDBin(path_to_bin, get_otp_dump_path)) return; + if (!FindSuperblock()) + return; - std::string nand_root = File::GetUserPath(D_WIIROOT_IDX); - nand_root.pop_back(); // remove trailing path separator - m_nand_root_length = nand_root.length(); - - FindSuperblock(); - ProcessEntry(0, nand_root); - ExportKeys(nand_root); - ExtractCertificates(nand_root); + ExportKeys(); + ProcessEntry(0, ""); + ExtractCertificates(); } bool NANDImporter::ReadNANDBin(const std::string& path_to_bin, @@ -93,32 +88,37 @@ bool NANDImporter::ReadNANDBin(const std::string& path_to_bin, return file.ReadBytes(m_nand_keys.data(), NAND_KEYS_SIZE); } -void NANDImporter::FindSuperblock() +bool NANDImporter::FindSuperblock() { constexpr size_t NAND_SUPERBLOCK_START = 0x1fc00000; - constexpr size_t NAND_SUPERBLOCK_SIZE = 0x40000; - size_t superblock = 0; - u32 newest_version = 0; - for (size_t pos = NAND_SUPERBLOCK_START; pos < NAND_SIZE; pos += NAND_SUPERBLOCK_SIZE) + // There are 16 superblocks, choose the highest/newest version + for (int i = 0; i < 16; i++) { - if (!memcmp(m_nand.data() + pos, "SFFS", 4)) + auto superblock = std::make_unique(); + std::memcpy(superblock.get(), &m_nand[NAND_SUPERBLOCK_START + i * sizeof(NANDSuperblock)], + sizeof(NANDSuperblock)); + + if (std::memcmp(superblock->magic, "SFFS", 4) != 0) { - const u32 version = Common::swap32(&m_nand[pos + 4]); - INFO_LOG_FMT(DISCIO, "Found superblock at {:#x} with version {:#x}", pos, version); - if (superblock == 0 || version > newest_version) - { - superblock = pos; - newest_version = version; - } + ERROR_LOG_FMT(DISCIO, "Superblock #{} does not exist", i); + continue; } + + INFO_LOG_FMT(DISCIO, "Superblock #{} has version {:#x}", i, superblock->version); + + if (!m_superblock || superblock->version > m_superblock->version) + m_superblock = std::move(superblock); } - m_nand_fat_offset = superblock + 0xC; - m_nand_fst_offset = m_nand_fat_offset + 0x10000; - INFO_LOG_FMT(DISCIO, - "Using superblock version {:#x} at position {:#x}. FAT/FST offset: {:#x}/{:#x}", - newest_version, superblock, m_nand_fat_offset, m_nand_fst_offset); + if (!m_superblock) + { + PanicAlertFmtT("This file does not contain a valid Wii filesystem."); + return false; + } + + INFO_LOG_FMT(DISCIO, "Using superblock version {:#x}", m_superblock->version); + return true; } std::string NANDImporter::GetPath(const NANDFSTEntry& entry, const std::string& parent_path) @@ -131,76 +131,65 @@ std::string NANDImporter::GetPath(const NANDFSTEntry& entry, const std::string& return parent_path + '/' + name; } -std::string NANDImporter::FormatDebugString(const NANDFSTEntry& entry) -{ - return fmt::format( - "{:12.12} {:#04x} {:#04x} {:#06x} {:#06x} {:#010x} {:#06x} {:#06x} {:#06x} {:#010x}", - entry.name, entry.mode, entry.attr, entry.sub, entry.sib, entry.size, entry.x1, entry.uid, - entry.gid, entry.x3); -} - void NANDImporter::ProcessEntry(u16 entry_number, const std::string& parent_path) { - NANDFSTEntry entry; - memcpy(&entry, &m_nand[m_nand_fst_offset + sizeof(NANDFSTEntry) * Common::swap16(entry_number)], - sizeof(NANDFSTEntry)); + while (entry_number != 0xffff) + { + const NANDFSTEntry entry = m_superblock->fst[entry_number]; - if (entry.sib != 0xffff) - ProcessEntry(entry.sib, parent_path); + const std::string path = GetPath(entry, parent_path); + INFO_LOG_FMT(DISCIO, "Entry: {} Path: {}", entry, path); + m_update_callback(); - if ((entry.mode & 3) == 1) - ProcessFile(entry, parent_path); - else if ((entry.mode & 3) == 2) - ProcessDirectory(entry, parent_path); - else - ERROR_LOG_FMT(DISCIO, "Unknown mode: {}", FormatDebugString(entry)); + Type type = static_cast(entry.mode & 3); + if (type == Type::File) + { + std::vector data = GetEntryData(entry); + File::IOFile file(m_nand_root + path, "wb"); + file.WriteBytes(data.data(), data.size()); + } + else if (type == Type::Directory) + { + File::CreateDir(m_nand_root + path); + ProcessEntry(entry.sub, path); + } + else + { + ERROR_LOG_FMT(DISCIO, "Ignoring unknown entry type for {}", entry); + } + + entry_number = entry.sib; + } } -void NANDImporter::ProcessDirectory(const NANDFSTEntry& entry, const std::string& parent_path) +std::vector NANDImporter::GetEntryData(const NANDFSTEntry& entry) { - m_update_callback(); - INFO_LOG_FMT(DISCIO, "Path: {}", FormatDebugString(entry)); - - const std::string path = GetPath(entry, parent_path); - File::CreateDir(path); - - if (entry.sub != 0xffff) - ProcessEntry(entry.sub, path); - - INFO_LOG_FMT(DISCIO, "Path: {}", parent_path.data() + m_nand_root_length); -} - -void NANDImporter::ProcessFile(const NANDFSTEntry& entry, const std::string& parent_path) -{ - constexpr size_t NAND_AES_KEY_OFFSET = 0x158; constexpr size_t NAND_FAT_BLOCK_SIZE = 0x4000; - m_update_callback(); - INFO_LOG_FMT(DISCIO, "File: {}", FormatDebugString(entry)); - - const std::string path = GetPath(entry, parent_path); - File::IOFile file(path, "wb"); - std::array key{}; - std::copy(&m_nand_keys[NAND_AES_KEY_OFFSET], &m_nand_keys[NAND_AES_KEY_OFFSET + key.size()], - key.begin()); - u16 sub = Common::swap16(entry.sub); - u32 remaining_bytes = Common::swap32(entry.size); + u16 sub = entry.sub; + size_t remaining_bytes = entry.size; + std::vector data{}; + data.reserve(remaining_bytes); while (remaining_bytes > 0) { std::array iv{}; std::vector block = Common::AES::Decrypt( - key.data(), iv.data(), &m_nand[NAND_FAT_BLOCK_SIZE * sub], NAND_FAT_BLOCK_SIZE); - u32 size = remaining_bytes < NAND_FAT_BLOCK_SIZE ? remaining_bytes : NAND_FAT_BLOCK_SIZE; - file.WriteBytes(block.data(), size); + m_aes_key.data(), iv.data(), &m_nand[NAND_FAT_BLOCK_SIZE * sub], NAND_FAT_BLOCK_SIZE); + + size_t size = std::min(remaining_bytes, block.size()); + data.insert(data.end(), block.begin(), block.begin() + size); remaining_bytes -= size; - sub = Common::swap16(&m_nand[m_nand_fat_offset + 2 * sub]); + + sub = m_superblock->fat[sub]; } + + return data; } -bool NANDImporter::ExtractCertificates(const std::string& nand_root) +bool NANDImporter::ExtractCertificates() { - const std::string content_dir = nand_root + "/title/00000001/0000000d/content/"; + const std::string content_dir = m_nand_root + "/title/00000001/0000000d/content/"; File::IOFile tmd_file(content_dir + "title.tmd", "rb"); std::vector tmd_bytes(tmd_file.GetSize()); @@ -251,7 +240,7 @@ bool NANDImporter::ExtractCertificates(const std::string& nand_root) return false; } - const std::string pem_file_path = nand_root + std::string(certificate.filename); + const std::string pem_file_path = m_nand_root + std::string(certificate.filename); const ptrdiff_t certificate_offset = std::distance(content_bytes.begin(), search_result); const u16 certificate_size = Common::swap16(&content_bytes[certificate_offset - 2]); INFO_LOG_FMT(DISCIO, "ExtractCertificates: '{}' offset: {:#x} size: {:#x}", @@ -267,9 +256,13 @@ bool NANDImporter::ExtractCertificates(const std::string& nand_root) return true; } -void NANDImporter::ExportKeys(const std::string& nand_root) +void NANDImporter::ExportKeys() { - const std::string file_path = nand_root + "/keys.bin"; + constexpr size_t NAND_AES_KEY_OFFSET = 0x158; + + std::copy_n(&m_nand_keys[NAND_AES_KEY_OFFSET], m_aes_key.size(), m_aes_key.begin()); + + const std::string file_path = m_nand_root + "/keys.bin"; File::IOFile file(file_path, "wb"); if (!file.WriteBytes(m_nand_keys.data(), NAND_KEYS_SIZE)) PanicAlertFmtT("Unable to write to file {0}", file_path); diff --git a/Source/Core/DiscIO/NANDImporter.h b/Source/Core/DiscIO/NANDImporter.h index f60a5b06f8..269a6c5483 100644 --- a/Source/Core/DiscIO/NANDImporter.h +++ b/Source/Core/DiscIO/NANDImporter.h @@ -3,11 +3,16 @@ #pragma once +#include #include +#include #include #include +#include + #include "Common/CommonTypes.h" +#include "Common/Swap.h" namespace DiscIO { @@ -22,39 +27,69 @@ public: // get_otp_dump_path will be called to get a path to it. void ImportNANDBin(const std::string& path_to_bin, std::function update_callback, std::function get_otp_dump_path); - bool ExtractCertificates(const std::string& nand_root); + bool ExtractCertificates(); + + enum class Type + { + File = 1, + Directory = 2, + }; -private: #pragma pack(push, 1) struct NANDFSTEntry { char name[12]; - u8 mode; // 0x0C - u8 attr; // 0x0D - u16 sub; // 0x0E - u16 sib; // 0x10 - u32 size; // 0x12 - u16 x1; // 0x16 - u16 uid; // 0x18 - u16 gid; // 0x1A - u32 x3; // 0x1C + u8 mode; + u8 attr; + Common::BigEndianValue sub; + Common::BigEndianValue sib; + Common::BigEndianValue size; + Common::BigEndianValue uid; + Common::BigEndianValue gid; + Common::BigEndianValue x3; }; + static_assert(sizeof(NANDFSTEntry) == 0x20, "Wrong size"); + + struct NANDSuperblock + { + char magic[4]; // "SFFS" + Common::BigEndianValue version; + Common::BigEndianValue unknown; + Common::BigEndianValue fat[0x8000]; + NANDFSTEntry fst[0x17FF]; + u8 pad[0x14]; + }; + static_assert(sizeof(NANDSuperblock) == 0x40000, "Wrong size"); #pragma pack(pop) +private: bool ReadNANDBin(const std::string& path_to_bin, std::function get_otp_dump_path); - void FindSuperblock(); + bool FindSuperblock(); std::string GetPath(const NANDFSTEntry& entry, const std::string& parent_path); std::string FormatDebugString(const NANDFSTEntry& entry); void ProcessEntry(u16 entry_number, const std::string& parent_path); - void ProcessFile(const NANDFSTEntry& entry, const std::string& parent_path); - void ProcessDirectory(const NANDFSTEntry& entry, const std::string& parent_path); - void ExportKeys(const std::string& nand_root); + std::vector GetEntryData(const NANDFSTEntry& entry); + void ExportKeys(); + std::string m_nand_root; std::vector m_nand; std::vector m_nand_keys; - size_t m_nand_fat_offset = 0; - size_t m_nand_fst_offset = 0; + std::array m_aes_key; + std::unique_ptr m_superblock; std::function m_update_callback; - size_t m_nand_root_length = 0; }; } // namespace DiscIO + +template <> +struct fmt::formatter +{ + constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); } + template + auto format(const DiscIO::NANDImporter::NANDFSTEntry& entry, FormatContext& ctx) const + { + return fmt::format_to( + ctx.out(), "{:12.12} {:#010b} {:#04x} {:#06x} {:#06x} {:#010x} {:#010x} {:#06x} {:#010x}", + entry.name, entry.mode, entry.attr, entry.sub, entry.sib, entry.size, entry.uid, entry.gid, + entry.x3); + } +}; diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index 4ca4a216fb..fe7a42604e 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -279,6 +279,7 @@ add_executable(dolphin-emu QtUtils/PartiallyClosableTabWidget.h QtUtils/ImageConverter.cpp QtUtils/ImageConverter.h + QtUtils/SignalBlocking.h QtUtils/UTF8CodePointCountValidator.cpp QtUtils/UTF8CodePointCountValidator.h QtUtils/WindowActivationEventFilter.cpp diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index e675395aa2..2a22d8ad49 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -231,6 +231,7 @@ + diff --git a/Source/Core/DolphinQt/MenuBar.cpp b/Source/Core/DolphinQt/MenuBar.cpp index 68e1263d93..ef1bfdc165 100644 --- a/Source/Core/DolphinQt/MenuBar.cpp +++ b/Source/Core/DolphinQt/MenuBar.cpp @@ -542,16 +542,6 @@ void MenuBar::AddOptionsMenu() m_change_font = options_menu->addAction(tr("&Font..."), this, &MenuBar::ChangeDebugFont); } -void MenuBar::InstallUpdateManually() -{ - QString filename = "Replace.bat"; - (this); - - hide(); // optional - - QDesktopServices::openUrl(QUrl("file:///" + filename, QUrl::TolerantMode)); -} - void MenuBar::AddHelpMenu() { QMenu* help_menu = addMenu(tr("&Help")); @@ -573,13 +563,6 @@ void MenuBar::AddHelpMenu() QUrl(QStringLiteral("https://bugs.dolphin-emu.org/projects/emulator"))); }); - if (AutoUpdateChecker::SystemSupportsAutoUpdates()) - { - help_menu->addSeparator(); - - help_menu->addAction(tr("&Update Emulator..."), this, &MenuBar::InstallUpdateManually); - } - #ifndef __APPLE__ help_menu->addSeparator(); #endif @@ -1168,7 +1151,7 @@ void MenuBar::CheckNAND() void MenuBar::NANDExtractCertificates() { - if (DiscIO::NANDImporter().ExtractCertificates(File::GetUserPath(D_WIIROOT_IDX))) + if (DiscIO::NANDImporter().ExtractCertificates()) { ModalMessageBox::information(this, tr("Success"), tr("Successfully extracted certificates from NAND")); diff --git a/Source/Core/DolphinQt/QtUtils/SignalBlocking.h b/Source/Core/DolphinQt/QtUtils/SignalBlocking.h new file mode 100644 index 0000000000..296ed69f22 --- /dev/null +++ b/Source/Core/DolphinQt/QtUtils/SignalBlocking.h @@ -0,0 +1,32 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +// Helper class for populating a GUI element without triggering its data change signals. + +template +class SignalBlockerProxy +{ +public: + explicit SignalBlockerProxy(T* object) : m_object(object), m_blocker(object) {} + SignalBlockerProxy(const SignalBlockerProxy& other) = delete; + SignalBlockerProxy(SignalBlockerProxy&& other) = default; + SignalBlockerProxy& operator=(const SignalBlockerProxy& other) = delete; + SignalBlockerProxy& operator=(SignalBlockerProxy&& other) = default; + ~SignalBlockerProxy() = default; + + T* operator->() const { return m_object; } + +private: + T* m_object; + QSignalBlocker m_blocker; +}; + +template +SignalBlockerProxy SignalBlocking(T* object) +{ + return SignalBlockerProxy(object); +} diff --git a/Source/Core/DolphinQt/Settings/GeneralPane.cpp b/Source/Core/DolphinQt/Settings/GeneralPane.cpp index 19c0a5d095..a3433deac5 100644 --- a/Source/Core/DolphinQt/Settings/GeneralPane.cpp +++ b/Source/Core/DolphinQt/Settings/GeneralPane.cpp @@ -23,14 +23,15 @@ #include "Core/PowerPC/PowerPC.h" #include "DolphinQt/QtUtils/ModalMessageBox.h" +#include "DolphinQt/QtUtils/SignalBlocking.h" #include "DolphinQt/Settings.h" -//#include "UICommon/AutoUpdate.h" +#include "UICommon/AutoUpdate.h" #ifdef USE_DISCORD_PRESENCE #include "UICommon/DiscordPresence.h" #endif -/* constexpr int AUTO_UPDATE_DISABLE_INDEX = 0; +constexpr int AUTO_UPDATE_DISABLE_INDEX = 0; constexpr int AUTO_UPDATE_STABLE_INDEX = 1; constexpr int AUTO_UPDATE_BETA_INDEX = 2; constexpr int AUTO_UPDATE_DEV_INDEX = 3; @@ -38,7 +39,7 @@ constexpr int AUTO_UPDATE_DEV_INDEX = 3; constexpr const char* AUTO_UPDATE_DISABLE_STRING = ""; constexpr const char* AUTO_UPDATE_STABLE_STRING = "stable"; constexpr const char* AUTO_UPDATE_BETA_STRING = "beta"; -constexpr const char* AUTO_UPDATE_DEV_STRING = "dev";*/ +constexpr const char* AUTO_UPDATE_DEV_STRING = "dev"; constexpr int FALLBACK_REGION_NTSCJ_INDEX = 0; constexpr int FALLBACK_REGION_NTSCU_INDEX = 1; @@ -65,9 +66,6 @@ void GeneralPane::CreateLayout() // Create layout here CreateBasic(); - /* if (AutoUpdateChecker::SystemSupportsAutoUpdates()) - CreateAutoUpdate();*/ - CreateFallbackRegion(); #if defined(USE_ANALYTICS) && USE_ANALYTICS @@ -83,6 +81,7 @@ void GeneralPane::OnEmulationStateChanged(Core::State state) const bool running = state != Core::State::Uninitialized; m_checkbox_dualcore->setEnabled(!running); + m_checkbox_cheats->setEnabled(!running); m_checkbox_override_region_settings->setEnabled(!running); #ifdef USE_DISCORD_PRESENCE m_checkbox_discord_presence->setEnabled(!running); @@ -93,6 +92,7 @@ void GeneralPane::OnEmulationStateChanged(Core::State state) void GeneralPane::ConnectLayout() { connect(m_checkbox_dualcore, &QCheckBox::toggled, this, &GeneralPane::OnSaveConfig); + connect(m_checkbox_cheats, &QCheckBox::toggled, this, &GeneralPane::OnSaveConfig); connect(m_checkbox_override_region_settings, &QCheckBox::stateChanged, this, &GeneralPane::OnSaveConfig); connect(m_checkbox_auto_disc_change, &QCheckBox::toggled, this, &GeneralPane::OnSaveConfig); @@ -100,13 +100,13 @@ void GeneralPane::ConnectLayout() connect(m_checkbox_discord_presence, &QCheckBox::toggled, this, &GeneralPane::OnSaveConfig); #endif - /* if (AutoUpdateChecker::SystemSupportsAutoUpdates()) + if (AutoUpdateChecker::SystemSupportsAutoUpdates()) { connect(m_combobox_update_track, qOverload(&QComboBox::currentIndexChanged), this, &GeneralPane::OnSaveConfig); connect(&Settings::Instance(), &Settings::AutoUpdateTrackChanged, this, &GeneralPane::LoadConfig); - }*/ + } // Advanced connect(m_combobox_speedlimit, qOverload(&QComboBox::currentIndexChanged), @@ -134,6 +134,9 @@ void GeneralPane::CreateBasic() m_checkbox_dualcore = new QCheckBox(tr("Enable Dual Core (speedup)")); basic_group_layout->addWidget(m_checkbox_dualcore); + m_checkbox_cheats = new QCheckBox(tr("Enable Cheats")); + basic_group_layout->addWidget(m_checkbox_cheats); + m_checkbox_override_region_settings = new QCheckBox(tr("Allow Mismatched Region Settings")); basic_group_layout->addWidget(m_checkbox_override_region_settings); @@ -167,25 +170,6 @@ void GeneralPane::CreateBasic() speed_limit_layout->addRow(tr("&Speed Limit:"), m_combobox_speedlimit); } -/* void GeneralPane::CreateAutoUpdate() -{ - auto* auto_update_group = new QGroupBox(tr("Auto Update Settings")); - auto* auto_update_group_layout = new QFormLayout; - auto_update_group->setLayout(auto_update_group_layout); - m_main_layout->addWidget(auto_update_group); - - auto_update_group_layout->setFormAlignment(Qt::AlignLeft | Qt::AlignTop); - auto_update_group_layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); - - m_combobox_update_track = new QComboBox(this); - - auto_update_group_layout->addRow(tr("&Auto Update:"), m_combobox_update_track); - - for (const QString& option : {tr("Don't Update"), tr("Stable (once a year)"), - tr("Beta (once a month)"), tr("Dev (multiple times a day)")}) - m_combobox_update_track->addItem(option); -}*/ - void GeneralPane::CreateFallbackRegion() { auto* fallback_region_group = new QGroupBox(tr("Fallback Region")); @@ -230,50 +214,52 @@ void GeneralPane::LoadConfig() { const QSignalBlocker blocker(this); - /* if (AutoUpdateChecker::SystemSupportsAutoUpdates()) + if (AutoUpdateChecker::SystemSupportsAutoUpdates()) { const auto track = Settings::Instance().GetAutoUpdateTrack().toStdString(); - if (track == AUTO_UPDATE_DISABLE_STRING) - m_combobox_update_track->setCurrentIndex(AUTO_UPDATE_DISABLE_INDEX); + SignalBlocking(m_combobox_update_track)->setCurrentIndex(AUTO_UPDATE_DISABLE_INDEX); else if (track == AUTO_UPDATE_STABLE_STRING) - m_combobox_update_track->setCurrentIndex(AUTO_UPDATE_STABLE_INDEX); + SignalBlocking(m_combobox_update_track)->setCurrentIndex(AUTO_UPDATE_STABLE_INDEX); else if (track == AUTO_UPDATE_BETA_STRING) - m_combobox_update_track->setCurrentIndex(AUTO_UPDATE_BETA_INDEX); + SignalBlocking(m_combobox_update_track)->setCurrentIndex(AUTO_UPDATE_BETA_INDEX); else - m_combobox_update_track->setCurrentIndex(AUTO_UPDATE_DEV_INDEX); - }*/ + SignalBlocking(m_combobox_update_track)->setCurrentIndex(AUTO_UPDATE_DEV_INDEX); + } #if defined(USE_ANALYTICS) && USE_ANALYTICS - m_checkbox_enable_analytics->setChecked(Settings::Instance().IsAnalyticsEnabled()); + SignalBlocking(m_checkbox_enable_analytics) + ->setChecked(Settings::Instance().IsAnalyticsEnabled()); #endif - m_checkbox_dualcore->setChecked(Config::Get(Config::MAIN_CPU_THREAD)); - m_checkbox_override_region_settings->setChecked( - Config::Get(Config::MAIN_OVERRIDE_REGION_SETTINGS)); - m_checkbox_auto_disc_change->setChecked(Config::Get(Config::MAIN_AUTO_DISC_CHANGE)); + SignalBlocking(m_checkbox_dualcore)->setChecked(Config::Get(Config::MAIN_CPU_THREAD)); + SignalBlocking(m_checkbox_cheats)->setChecked(Settings::Instance().GetCheatsEnabled()); + SignalBlocking(m_checkbox_override_region_settings) + ->setChecked(Config::Get(Config::MAIN_OVERRIDE_REGION_SETTINGS)); + SignalBlocking(m_checkbox_auto_disc_change) + ->setChecked(Config::Get(Config::MAIN_AUTO_DISC_CHANGE)); + #ifdef USE_DISCORD_PRESENCE - m_checkbox_discord_presence->setChecked(Config::Get(Config::MAIN_USE_DISCORD_PRESENCE)); + SignalBlocking(m_checkbox_discord_presence) + ->setChecked(Config::Get(Config::MAIN_USE_DISCORD_PRESENCE)); #endif int selection = qRound(Config::Get(Config::MAIN_EMULATION_SPEED) * 10); if (selection < m_combobox_speedlimit->count()) - m_combobox_speedlimit->setCurrentIndex(selection); - m_checkbox_dualcore->setChecked(Config::Get(Config::MAIN_CPU_THREAD)); + SignalBlocking(m_combobox_speedlimit)->setCurrentIndex(selection); const auto fallback = Settings::Instance().GetFallbackRegion(); - if (fallback == DiscIO::Region::NTSC_J) - m_combobox_fallback_region->setCurrentIndex(FALLBACK_REGION_NTSCJ_INDEX); + SignalBlocking(m_combobox_fallback_region)->setCurrentIndex(FALLBACK_REGION_NTSCJ_INDEX); else if (fallback == DiscIO::Region::NTSC_U) - m_combobox_fallback_region->setCurrentIndex(FALLBACK_REGION_NTSCU_INDEX); + SignalBlocking(m_combobox_fallback_region)->setCurrentIndex(FALLBACK_REGION_NTSCU_INDEX); else if (fallback == DiscIO::Region::PAL) - m_combobox_fallback_region->setCurrentIndex(FALLBACK_REGION_PAL_INDEX); + SignalBlocking(m_combobox_fallback_region)->setCurrentIndex(FALLBACK_REGION_PAL_INDEX); else if (fallback == DiscIO::Region::NTSC_K) - m_combobox_fallback_region->setCurrentIndex(FALLBACK_REGION_NTSCK_INDEX); + SignalBlocking(m_combobox_fallback_region)->setCurrentIndex(FALLBACK_REGION_NTSCK_INDEX); else - m_combobox_fallback_region->setCurrentIndex(FALLBACK_REGION_NTSCJ_INDEX); + SignalBlocking(m_combobox_fallback_region)->setCurrentIndex(FALLBACK_REGION_NTSCJ_INDEX); } -/* static QString UpdateTrackFromIndex(int index) +static QString UpdateTrackFromIndex(int index) { QString value; @@ -294,7 +280,7 @@ void GeneralPane::LoadConfig() } return value; -}*/ +} static DiscIO::Region UpdateFallbackRegionFromIndex(int index) { @@ -326,11 +312,11 @@ void GeneralPane::OnSaveConfig() Config::ConfigChangeCallbackGuard config_guard; auto& settings = SConfig::GetInstance(); - /* if (AutoUpdateChecker::SystemSupportsAutoUpdates()) + if (AutoUpdateChecker::SystemSupportsAutoUpdates()) { Settings::Instance().SetAutoUpdateTrack( UpdateTrackFromIndex(m_combobox_update_track->currentIndex())); - }*/ + } #ifdef USE_DISCORD_PRESENCE Discord::SetDiscordPresenceEnabled(m_checkbox_discord_presence->isChecked()); @@ -341,9 +327,11 @@ void GeneralPane::OnSaveConfig() DolphinAnalytics::Instance().ReloadConfig(); #endif Config::SetBaseOrCurrent(Config::MAIN_CPU_THREAD, m_checkbox_dualcore->isChecked()); + Settings::Instance().SetCheatsEnabled(m_checkbox_cheats->isChecked()); Config::SetBaseOrCurrent(Config::MAIN_OVERRIDE_REGION_SETTINGS, m_checkbox_override_region_settings->isChecked()); Config::SetBase(Config::MAIN_AUTO_DISC_CHANGE, m_checkbox_auto_disc_change->isChecked()); + Config::SetBaseOrCurrent(Config::MAIN_ENABLE_CHEATS, m_checkbox_cheats->isChecked()); Config::SetBaseOrCurrent(Config::MAIN_EMULATION_SPEED, m_combobox_speedlimit->currentIndex() * 0.1f); Settings::Instance().SetFallbackRegion( diff --git a/Source/Core/DolphinQt/WiiUpdate.cpp b/Source/Core/DolphinQt/WiiUpdate.cpp index edd3ba9593..0cfc336c51 100644 --- a/Source/Core/DolphinQt/WiiUpdate.cpp +++ b/Source/Core/DolphinQt/WiiUpdate.cpp @@ -30,12 +30,12 @@ static void ShowResult(QWidget* parent, WiiUtils::UpdateResult result) case WiiUtils::UpdateResult::Succeeded: ModalMessageBox::information(parent, QObject::tr("Update completed"), QObject::tr("The emulated Wii console has been updated.")); - DiscIO::NANDImporter().ExtractCertificates(File::GetUserPath(D_WIIROOT_IDX)); + DiscIO::NANDImporter().ExtractCertificates(); break; case WiiUtils::UpdateResult::AlreadyUpToDate: ModalMessageBox::information(parent, QObject::tr("Update completed"), QObject::tr("The emulated Wii console is already up-to-date.")); - DiscIO::NANDImporter().ExtractCertificates(File::GetUserPath(D_WIIROOT_IDX)); + DiscIO::NANDImporter().ExtractCertificates(); break; case WiiUtils::UpdateResult::ServerFailed: ModalMessageBox::critical(parent, QObject::tr("Update failed"), diff --git a/Source/Core/VideoBackends/OGL/OGLRender.cpp b/Source/Core/VideoBackends/OGL/OGLRender.cpp index 123b11b2d6..fae0d7a0ad 100644 --- a/Source/Core/VideoBackends/OGL/OGLRender.cpp +++ b/Source/Core/VideoBackends/OGL/OGLRender.cpp @@ -751,6 +751,8 @@ Renderer::Renderer(std::unique_ptr main_gl_context, float backbuffer_ OSD::AddMessage("This device's performance may be poor.", 60000); } + INFO_LOG_FMT(VIDEO, "Video Info: {}, {}, {}", g_ogl_config.gl_vendor, g_ogl_config.gl_renderer, + g_ogl_config.gl_version); WARN_LOG_FMT(VIDEO, "Missing OGL Extensions: {}{}{}{}{}{}{}{}{}{}{}{}{}{}", g_ActiveConfig.backend_info.bSupportsDualSourceBlend ? "" : "DualSourceBlend ", g_ActiveConfig.backend_info.bSupportsPrimitiveRestart ? "" : "PrimitiveRestart ",