diff --git a/.gitattributes b/.gitattributes index 7200b70d56..3605094f4e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,26 @@ *.sln text eol=crlf + +# Preserve the qualified StarletOS integration bytes across operating systems. +CMakeLists.txt -text +Run-Wii-Starlet-Bootstrap.ps1 -text +Source/Core/Core/Boot/Boot.cpp -text +Source/Core/Core/CMakeLists.txt -text +Source/Core/Core/Config/MainSettings.cpp -text +Source/Core/Core/Config/MainSettings.h -text +Source/Core/Core/ConfigManager.cpp -text +Source/Core/Core/HW/HW.cpp -text +Source/Core/Core/IOS/Starlet/ARMCore.cpp -text +Source/Core/Core/IOS/Starlet/BootstrapELF.cpp -text +Source/Core/Core/IOS/Starlet/BootstrapELF.h -text +Source/Core/Core/IOS/Starlet/Starlet.cpp -text +Source/Core/Core/IOS/Starlet/Starlet.h -text +Source/Core/Core/IOS/Starlet/StarletMemory.cpp -text +Source/Core/Core/IOS/Starlet/StarletMemory.h -text +Source/Core/DolphinNoGUI/PlatformHeadless.cpp -text +Source/UnitTests/CMakeLists.txt -text +Source/UnitTests/Core/CMakeLists.txt -text +Source/UnitTests/Core/IOS/Starlet/BootstrapELFTest.cpp -text +Source/UnitTests/Core/IOS/Starlet/DMATest.cpp -text +Tools/StarletBootstrapProbe/StarletBootstrapProbe.cpp -text +docs/Wii_Starlet_Bootstrap.md -text +docs/StarletOS_Checkpoint.json -text diff --git a/.gitignore b/.gitignore index 1f0c0c8a5a..f31efbe848 100644 --- a/.gitignore +++ b/.gitignore @@ -75,3 +75,6 @@ CMakeUserPresets.json # Generated bytecode from the performance analysis helpers /Tools/PerformanceTrace/__pycache__/ + +# Historical local script backup; active script is tracked. +/Run-Wii-Starlet-Bootstrap.ps1.before-* diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..8ea28b8d3f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,30 @@ +# Instructions for the StarletOS Dolphin fork + +Read `docs/StarletOS_Handoff.md`, `docs/StarletOS_Checkpoint.json` and +`docs/Wii_Starlet_Bootstrap.md` before working on the Starlet integration. +Also read the companion StarletOS HANDOFF and current phase SPEC/STATUS when +changing support for its tests. Current user instructions take precedence. + +- One writer per checkout. Inspect Git status and the latest handoff before + editing; preserve unrelated changes and never overwrite another agent's work. +- Fetch the user's fork before work and inspect divergence. Use fast-forward + pulls only on a clean branch; never reset or force-push to hide divergence. +- The publication destination is `https://git.yaya48.xyz/Yaya48/dolphin`. + On the original checkout it is named `yaya`; on a fresh clone it is normally + `origin`. The github.com/dolphin-emu upstream is NOT the publication target. +- Preserve qualified source bytes/line endings. Do not broadly normalize the + repository. Exact integration files are protected by `.gitattributes`. +- Do not commit dumps, console keys/OTP/SEEPROM, Nintendo binaries, real device + identifiers, credentials, private emulator profiles or diagnostic captures. + Stage explicit paths and inspect staged contents AND pending commits before + publishing. Never use force-add to bypass these exclusions. +- Respect companion phase boundaries and public-source/clean-room rules. A + fake emulated device is supporting test evidence, not the physical contract. +- Update the handoff with changes, reasons, commands/results, limitations and + the next exact action at every stop. Preserve old evidence and distinguish + source inspection, build results, emulation and physical observations. +- Commit and push completed work to the user's fork, verify the remote SHA, + then update StarletOS's pinned Dolphin commit/hashes. Never leave a required + source change only in a private bundle or a copy in another repository. +- Keep generated caches and private evidence locally; no blanket cleanup of + ignored files. Do not modify a removable SD or console for a Git-only task. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..293442ec27 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,3 @@ +# Shared instructions + +Read AGENTS.md and docs/StarletOS_Handoff.md before working in this fork. diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f62d7f847..59c80b9454 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,882 +1,890 @@ -######################################## -# General setup -# -cmake_minimum_required(VERSION 3.25...4.2.1) - -cmake_policy(SET CMP0080 OLD) # allow using BundleUtilities at configure time - -# Minimum OS X version. -# This is inserted into the Info.plist as well. -set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0.0" CACHE STRING "") - -# When we don't set a sysroot, AppleClang will put /usr/local/include at a higher -# priority than -isystem paths, which can cause weird include issues in Externals -set(CMAKE_OSX_SYSROOT macosx CACHE STRING "") - -set(CMAKE_USER_MAKE_RULES_OVERRIDE "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FlagsOverride.cmake") - -# CMake 3.28 and later scan c++ source files for module imports by default. Since we don't use -# modules scanning is pointless, so disable it. This also prevents Clang from generating an error -# if the clang-scan-deps tool isn't installed: -# "CMAKE_CXX_COMPILER_CLANG_SCAN_DEPS-NOTFOUND: not found". -set(CMAKE_CXX_SCAN_FOR_MODULES OFF) - +######################################## +# General setup +# +cmake_minimum_required(VERSION 3.25...4.2.1) + +cmake_policy(SET CMP0080 OLD) # allow using BundleUtilities at configure time + +# Minimum OS X version. +# This is inserted into the Info.plist as well. +set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0.0" CACHE STRING "") + +# When we don't set a sysroot, AppleClang will put /usr/local/include at a higher +# priority than -isystem paths, which can cause weird include issues in Externals +set(CMAKE_OSX_SYSROOT macosx CACHE STRING "") + +set(CMAKE_USER_MAKE_RULES_OVERRIDE "${CMAKE_CURRENT_SOURCE_DIR}/CMake/FlagsOverride.cmake") + +# CMake 3.28 and later scan c++ source files for module imports by default. Since we don't use +# modules scanning is pointless, so disable it. This also prevents Clang from generating an error +# if the clang-scan-deps tool isn't installed: +# "CMAKE_CXX_COMPILER_CLANG_SCAN_DEPS-NOTFOUND: not found". +set(CMAKE_CXX_SCAN_FOR_MODULES OFF) + project(dolphin-emu) +# CMake 4.3 can decode French MSVC's /showIncludes prefix through the console code page, +# turning nonbreaking spaces into "┬á". Ninja then records zero header dependencies and may +# reuse objects after a class layout changes. Give Ninja the actual UTF-8 prefix. +if(MSVC AND CMAKE_GENERATOR STREQUAL "Ninja" AND CMAKE_CL_SHOWINCLUDES_PREFIX MATCHES "^Remarque") + string(ASCII 194 160 _msvc_french_nbsp) + set(CMAKE_CL_SHOWINCLUDES_PREFIX "Remarque${_msvc_french_nbsp}: inclusion du fichier${_msvc_french_nbsp}: ") +endif() + list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/CMake) - -# Support functions -include(CheckAndAddFlag) -include(CheckCCompilerFlag) -include(CheckSymbolExists) -include(DolphinCompileDefinitions) -include(DolphinDisableWarningsMSVC) -include(DolphinLibraryTools) -include(GNUInstallDirs) -include(RemoveCompileFlag) - -# When using the Visual Studio generator, only show our targets and not the ones from Externals. -set_property(DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT dolphin-emu) - -if (MSVC) - set(CMAKE_C_STANDARD 99) - set(CMAKE_CXX_STANDARD 23) - set(CMAKE_CXX_STANDARD_REQUIRED ON) -endif() - -set(COMPILER ${CMAKE_CXX_COMPILER_ID}) -if (COMPILER STREQUAL "GNU") - set(COMPILER "GCC") # prefer printing GCC instead of GNU -endif() - -# Minimum required versions -# Toolsets -set(Xcode_min_version 14.3) -set(MSVC_toolset_min_version 143) -# Compilers -set(GCC_min_version 12) -set(Clang_min_version 15) -set(AppleClang_min_version 14.0.3) -set(MSVC_min_version 19.32) -# Standard libraries -set(libstdc++_min_version 12) # This should match GCC_min_version's major version. -set(libc++_min_version 150000) # This should match Clang_min_version in the format "xxyyzz" instead of "xx.yy.zz" - -dolphin_check_toolset_version("Xcode" XCODE_VERSION ${Xcode_min_version}) -dolphin_check_toolset_version("MSVC Toolset" MSVC_TOOLSET_VERSION ${MSVC_toolset_min_version}) - -message(STATUS "Using ${COMPILER} ${CMAKE_CXX_COMPILER_VERSION}") - -if(NOT DEFINED ${COMPILER}_min_version) - message(WARNING "Unknown compiler ${COMPILER}, assuming it is new enough") -elseif(CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${${COMPILER}_min_version}) - message(FATAL_ERROR "Requires ${COMPILER} ${${COMPILER}_min_version} or higher") -endif() - -# libstdc++ is almost always used on Linux, even when using clang as the compiler. -# libc++ is used on the likes of Android, Apple devices, FreeBSD, and a few (very) rare Linux distros like Chimera Linux. -# Windows uses its own standard library named STL, which we check as part of the toolset above. (outside of MinGW which can use either libstdc++ or libc++) -dolphin_check_std_version("GNU_libstdc++" _GLIBCXX_RELEASE ${libstdc++_min_version}) -dolphin_check_std_version("LLVM_libc++" _LIBCPP_VERSION ${libc++_min_version}) - -# Name of the Dolphin distributor. If you redistribute Dolphin builds (forks, -# unofficial builds) please consider identifying your distribution with a -# unique name here. -set(DISTRIBUTOR "None" CACHE STRING "Name of the distributor.") - -set(DOLPHIN_DEFAULT_UPDATE_TRACK "" CACHE STRING "Name of the default update track. If empty, disables auto-update.") - -if(UNIX AND NOT APPLE AND NOT ANDROID) - option(ENABLE_X11 "Enables X11 Support" ON) -endif() -if(NOT WIN32 AND NOT APPLE AND NOT HAIKU) - option(ENABLE_EGL "Enables EGL OpenGL Interface" ON) -endif() - -if(NOT ANDROID) - option(ENABLE_CLI_TOOL "Enable dolphin-tool, a CLI-based utility for functions such as managing disc images" ON) -endif() - - -set(USE_SYSTEM_LIBS "AUTO" CACHE STRING "Use system libraries instead of bundled libraries. ON - Always use system and fail if unavailable, OFF - Always use bundled, AUTO - Use system if available, otherwise use bundled. Default is AUTO") -if(APPROVED_VENDORED_DEPENDENCIES) - message(WARNING "APPROVED_VENDORED_DEPENDENCIES is deprecated. Please migrate to setting USE_SYSTEM_LIBS to ON and setting USE_SYSTEM_ to either AUTO or OFF to allow bundled libs.") -endif() -option(USE_UPNP "Enables UPnP port mapping support" ON) -option(ENABLE_NOGUI "Enable NoGUI frontend" ON) -option(ENABLE_QT "Enable Qt (Default)" ON) -option(ENABLE_LTO "Enables Link Time Optimization" OFF) -option(ENABLE_GENERIC "Enables generic build that should run on any little-endian host" OFF) -option(ENABLE_HEADLESS "Enables running Dolphin as a headless variant" OFF) -option(ENABLE_ALSA "Enables ALSA sound backend" ON) -option(ENABLE_PULSEAUDIO "Enables PulseAudio sound backend" ON) -option(ENABLE_CUBEB "Enables Cubeb sound backend" ON) -option(ENABLE_LLVM "Enables LLVM support, for disassembly" ON) -option(ENABLE_TESTS "Enables building the unit tests" ON) -option(ENABLE_VULKAN "Enables vulkan video backend" ON) -option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence, show the current game on Discord" ON) -option(USE_MGBA "Enables GBA controllers emulation using libmgba" ON) -option(ENABLE_AUTOUPDATE "Enables support for automatic updates" ON) -option(USE_RETRO_ACHIEVEMENTS "Enables integration with retroachievements.org" ON) -option(ENABLE_CCACHE "Enables CCache compiler cache" OFF) - -# Maintainers: if you consider blanket disabling this for your users, please -# consider the following points: -# * No data is being sent without explicit user approval (pop up box at first -# launch). -# * The Dolphin team relies on the data in order to understand the behavior -# of our software in the wild. -option(ENABLE_ANALYTICS "Enables opt-in Analytics collection" ON) - -option(ENCODE_FRAMEDUMPS "Encode framedumps in AVI format" ON) - -option(ENABLE_GPROF "Enable gprof profiling (must be using Debug build)" OFF) -option(FASTLOG "Enable all logs" OFF) - -# TODO: Add DSPSpy -option(DSPTOOL "Build dsptool" OFF) - -# RetroAchievements developer tools require Windows hooks -if(WIN32) - option(RC_CLIENT_SUPPORTS_RAINTEGRATION "Enables RetroAchievements developer tools" ON) -endif() - -# Enable SDL by default on operating systems that aren't Android. -if(NOT ANDROID) - option(ENABLE_SDL "Enables SDL as a generic controller backend" ON) -else() - option(ENABLE_SDL "Enables SDL as a generic controller backend" OFF) -endif() - -if(APPLE) - enable_language(OBJC) - enable_language(OBJCXX) - option(MACOS_USE_DEFAULT_SEARCH_PATH "Don't prioritize system library paths" OFF) - option(POSTPROCESS_BUNDLE "Postprocess bundle for redistributability" OFF) - # Enable adhoc code signing by default (otherwise makefile builds on ARM will not work) - option(MACOS_CODE_SIGNING "Enable codesigning" ON) - option(USE_BUNDLED_MOLTENVK "Build MoltenVK from Externals with Dolphin-specific patches" ON) - set(MACOS_CODE_SIGNING_IDENTITY "-" CACHE STRING "The identity used for codesigning.") -endif() - -if(CMAKE_SYSTEM_NAME STREQUAL "Linux") - option(ENABLE_VTUNE "Enable Intel VTune integration for JIT code." OFF) - - if(NOT ANDROID) - option(ENABLE_HWDB "Enables the udev hardware database" ON) - option(ENABLE_EVDEV "Enables the evdev controller backend" ON) - endif() -endif() - -# Enable folders for IDE -set_property(GLOBAL PROPERTY USE_FOLDERS ON) - -# Set up paths -set(datadir ${CMAKE_INSTALL_FULL_DATADIR}/dolphin-emu CACHE PATH "datadir") -add_definitions(-DDATA_DIR="${datadir}/") - -if(CMAKE_SYSROOT) - # If we should use a sysroot, tell pkg-config to search for packages in there, not on the host - set(ENV{PKG_CONFIG_LIBDIR} "${CMAKE_SYSROOT}/usr/lib/pkgconfig:${CMAKE_SYSROOT}/usr/share/pkgconfig") - set(ENV{PKG_CONFIG_SYSROOT_DIR} "${CMAKE_SYSROOT}") -endif() - -# Output directory for the Dolphin binary -# We need to use a generator expression here to ensure that multi-config generators -# (like Visual Studio) put the binaries in the right place. -# See https://cmake.org/cmake/help/v4.2/prop_tgt/RUNTIME_OUTPUT_DIRECTORY.html -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY $<1:${CMAKE_BINARY_DIR}/Binaries>) - -# Output directory for PDB files. PDBs are only generated on Windows with MSVC. -# We set a common directory for all PDBs to avoid littering the Binaries/ directory. -set(CMAKE_PDB_OUTPUT_DIRECTORY $<1:${CMAKE_BINARY_DIR}/pdb>) - -if(ENABLE_CCACHE) - include(CCache) - if(NOT CCACHE_BIN) - message(WARNING "Ccache was enabled but not found. This build will not use Ccache.") - endif() -endif() - -# Architecture detection and arch specific settings -message(STATUS "Detected architecture: ${CMAKE_SYSTEM_PROCESSOR}") - -# Detect 64bit or 32bit -# CMake doesn't provide a simple way to determine 32bit or 64bit -# If we ever support a architecture that is 64bit with 32bit pointers then this'll break -# Of course the chances of that are slim(x32?) so who cares -if(CMAKE_SIZEOF_VOID_P EQUAL 8) - set(_ARCH_64 1) - add_definitions(-D_ARCH_64=1) -else() - set(_ARCH_32 1) - add_definitions(-D_ARCH_32=1) -endif() - -if(ENABLE_GENERIC) - if(WIN32) - message(FATAL_ERROR "Generic builds are not supported on Windows!") - endif() - message(STATUS "Warning! Building generic build!") - set(_M_GENERIC 1) - add_definitions(-D_M_GENERIC=1) -elseif(_ARCH_64 AND CMAKE_SYSTEM_PROCESSOR MATCHES "x64|x86_64|amd64|AMD64") - set(_M_X86_64 1) - add_definitions(-D_M_X86_64=1) - check_and_add_flag(HAVE_SSE2 -msse2) -elseif(_ARCH_64 AND CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64|ARM64") - set(_M_ARM_64 1) - add_definitions(-D_M_ARM_64=1) - # CRC instruction set is used in the CRC32 hash function - check_and_add_flag(HAVE_ARCH_ARMV8 -march=armv8-a+crc) -else() - message(FATAL_ERROR "You're building on an unsupported platform: " - "'${CMAKE_SYSTEM_PROCESSOR}' with ${CMAKE_SIZEOF_VOID_P}-byte pointers." - " Enable generic build if you really want a JIT-less binary.") -endif() - -if(CMAKE_GENERATOR MATCHES "Ninja") - check_and_add_flag(DIAGNOSTICS_COLOR -fdiagnostics-color) -elseif(CMAKE_GENERATOR MATCHES "Visual Studio") - # Only MSBuild needs this, other generators will compile one file at a time - add_compile_options("/MP") -endif() - -if(MSVC) - check_and_add_flag(EXCEPTIONS /EHsc) - dolphin_compile_definitions(_DEBUG DEBUG_ONLY) - - # Disable RTTI - add_compile_options(/GR-) - - # Set warning level 4 (the highest) - add_compile_options(/W4) - - # Treat all warnings as errors - add_compile_options(/WX) - - # Disable some warnings - add_compile_options( - /wd4201 # nonstandard extension used : nameless struct/union - /wd4127 # conditional expression is constant - /wd4100 # 'identifier' : unreferenced formal parameter - /wd4200 # InputCommon fix temp. - /wd4244 # 'conversion' conversion from 'type1' to 'type2', possible loss of data - /wd4121 # 'symbol' : alignment of a member was sensitive to packing - /wd4324 # Padding was added at the end of a structure because you specified a __declspec(align) value. - /wd4714 # function 'function' marked as __forceinline not inlined - /wd4351 # new behavior: elements of array 'array' will be default initialized - # TODO: Enable this warning once possible - /wd4245 # conversion from 'type1' to 'type2', signed/unsigned mismatch - # Currently jits use some annoying code patterns which makes this common - ) - - # Additional warnings - add_compile_options( - /w15262 # Unannotated fallthrough between switch labels - /w44263 # Non-virtual member function hides base class virtual function - /w44265 # Class has virtual functions, but destructor is not virtual - /w44946 # Reinterpret cast between related types - ) - - # All files are encoded as UTF-8 - add_compile_options(/utf-8) - - # Ignore warnings in external headers - add_compile_options(/external:anglebrackets) - add_compile_options(/external:W0) - add_compile_options(/external:templates-) - - # Request deterministic builds - add_compile_options(/experimental:deterministic) - add_link_options(/experimental:deterministic) - - # Enable function-level linking - add_compile_options(/Gy) - # Generate intrinsic functions - add_compile_options(/Oi) - # Enable buffer security check on Debug, disable otherwise - add_compile_options($,/GS,/GS->) - # Remove unreferenced inline functions/data to reduce link time and catch bugs - # Note: In msbuild build, this gets set by msbuild by default - add_compile_options(/Zc:inline) - # Fix various other non-conformant behaviors - add_compile_options(/Zc:__cplusplus,enumTypes,externConstexpr,preprocessor,templateScope,throwingNew) - # Enforce strict volatile semantics as per ISO C++ - add_compile_options(/volatile:iso) - - # Use 'precise' floating point model - add_compile_options(/fp:precise) - - string(APPEND CMAKE_EXE_LINKER_FLAGS " /NXCOMPAT") - # Generate debug data - string(APPEND CMAKE_EXE_LINKER_FLAGS " /DEBUG") - # Eliminate dead code and data - string(APPEND CMAKE_EXE_LINKER_FLAGS " /OPT:REF /OPT:ICF") -else() - add_definitions(-D_DEFAULT_SOURCE) - - # gcc uses some optimizations which might break stuff without this flag - check_and_add_flag(NO_STRICT_ALIASING -fno-strict-aliasing) - check_and_add_flag(NO_EXCEPTIONS -fno-exceptions) - - check_and_add_flag(VISIBILITY_INLINES_HIDDEN -fvisibility-inlines-hidden) - check_and_add_flag(VISIBILITY_HIDDEN -fvisibility=hidden) - - check_and_add_flag(FOMIT_FRAME_POINTER -fomit-frame-pointer NO_DEBINFO_ONLY) - - dolphin_compile_definitions(_DEBUG DEBUG_ONLY) - check_and_add_flag(GGDB -ggdb DEBUG_ONLY) - - if(CMAKE_SYSTEM_NAME STREQUAL "Linux") - # GNU ar: Create thin archive files. - # Requires binutils-2.19 or later. - set(CMAKE_C_ARCHIVE_CREATE " qcTP ") - set(CMAKE_C_ARCHIVE_APPEND " qTP ") - set(CMAKE_CXX_ARCHIVE_CREATE " qcTP ") - set(CMAKE_CXX_ARCHIVE_APPEND " qTP ") - endif() -endif() - -if(CMAKE_SYSTEM_NAME MATCHES "Darwin") - if(NOT MACOS_USE_DEFAULT_SEARCH_PATH) - # Hack up the path to prioritize the path to built-in OS libraries to - # increase the chance of not depending on a bunch of copies of them - # installed by MacPorts, Fink, Homebrew, etc, and ending up copying - # them into the bundle. Since we optionally depend on libraries which - # are not part of OS X (ffmpeg, etc.), however, don't remove the default - # path entirely as was done in a previous version of this file. This is - # still kinda evil, since it defeats the user's path settings... - # See http://www.cmake.org/cmake/help/v3.0/command/find_program.html - list(APPEND CMAKE_PREFIX_PATH "/usr") - endif() - - # Prevents Xcode from overriding the -fno-strict-aliasing flag - set(CMAKE_XCODE_ATTRIBUTE_GCC_STRICT_ALIASING NO) - - # Specify target CPUs. - if(_ARCH_64 AND _M_X86_64) - check_and_add_flag(HAVE_MSSSE3 -mssse3) - check_and_add_flag(HAVE_ARCH_CORE2 -march=core2) - endif() - # Linker flags. - # Drop unreachable code and data. - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-dead_strip,-dead_strip_dylibs") - - # Set FMT_EXCEPTIONS = 0, for consistency with -fno-exceptions earlier. - # If we set only -fno-exceptions, fmt fails to compile when included from - # Objective-C++ because fmt tries try to use throw because __EXCEPTIONS is defined. - add_definitions(-DFMT_EXCEPTIONS=0) - - find_library(APPKIT_LIBRARY AppKit) - find_library(APPSERV_LIBRARY ApplicationServices) - find_library(CARBON_LIBRARY Carbon) - find_library(COCOA_LIBRARY Cocoa) - find_library(COREFOUNDATION_LIBRARY CoreFoundation) - find_library(CORESERV_LIBRARY CoreServices) - find_library(FORCEFEEDBACK_LIBRARY ForceFeedback) - find_library(FOUNDATION_LIBRARY Foundation) - find_library(IOB_LIBRARY IOBluetooth) - find_library(IOK_LIBRARY IOKit) - find_library(OPENGL_LIBRARY OpenGL) -endif() - -if(ENABLE_LTO) - include(CheckIPOSupported) - check_ipo_supported(RESULT supported OUTPUT error) - - if(supported) - set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) - else() - message(STATUS "IPO / LTO not supported: <${error}>") - endif() -endif() - -# BSDs put packages in /usr/local instead of /usr, so we need to -# force CMake to look in those directories by default, too. -# All commands and submodule commands also need to see these -# changes, so just setting them in the project scope via -# include_directories and link_directories is not sufficient -if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") - set(CMAKE_PREFIX_PATH "${CMAKE_PREFIX_PATH};/usr/local") - set(CMAKE_REQUIRED_INCLUDES "/usr/local/include") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/usr/local/lib") -endif() - -# Dolphin requires threads. -find_package(Threads) - -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE "Release" CACHE STRING - "Build type (Release/Debug/RelWithDebInfo/MinSizeRel)" FORCE) -endif() - -if(ENABLE_GPROF) - check_and_add_flag(HAVE_PG -pg) - if(NOT FLAG_C_HAVE_PG) - message(FATAL_ERROR "Compiler option -pg is not supported") - endif() - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg") -endif() - -if(FASTLOG) - add_definitions(-DDEBUGFAST) -endif() - -if(ENABLE_VTUNE) - set(VTUNE_DIR "/opt/intel/vtune_amplifier") - add_definitions(-DUSE_VTUNE) - include_directories("${VTUNE_DIR}/include") - set(VTUNE_LIBRARIES - "${VTUNE_DIR}/lib64/libjitprofiling.a" - "${VTUNE_DIR}/lib64/libittnotify.a" - ) -endif() - -if(ANDROID) - message(STATUS "Building for Android") - if(NOT ENABLE_HEADLESS) - add_definitions(-DANDROID) - if(ENABLE_NOGUI) - message(STATUS "Building Android app, disabling NoGUI frontend.") - set(ENABLE_NOGUI 0) - endif() - else() - # Lie to cmake a bit. We are cross compiling to Android - # but not as a shared library. We want an executable. - set(ANDROID 0) - endif() - set(ENABLE_QT 0) - set(USE_DISCORD_PRESENCE 0) - - # We are cross compiling, search only the toolchain for libraries and includes - SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) - SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) - SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) - SET(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) -endif() - -if(ENABLE_HEADLESS) - message(STATUS "Enabling Headless! Disabling GUI.") - set(ENABLE_QT 0) - set(USE_DISCORD_PRESENCE 0) -endif() - -# Set file offset size to 64 bits. On modern Unixes, this is typically already the case. Exceptions: -# -# glibc may default to 32 bits. This can be configured by setting _FILE_OFFSET_BITS=64. -# -# bionic (Android) defaults to 32 bits for 32-bit ABIs. Here too we can use _FILE_OFFSET_BITS=64, -# but only on API 25 and up. Since we're currently supporting older API levels and 32-bit Android -# isn't a build configuration we officially support, let's leave this as it is for now. -# More details: https://android.googlesource.com/platform/bionic/+/master/docs/32-bit-abi.md -if(NOT ANDROID AND NOT CMAKE_SYSTEM_NAME MATCHES "Windows") - add_definitions(-D_FILE_OFFSET_BITS=64) - add_definitions(-D_LARGEFILE_SOURCE) -endif() - -######################################## -# Dependency checking -# -# TODO: We should have options for dependencies included in the externals to -# override autodetection of system libraries and force the usage of the -# externals. -include(CheckLib) -include(CheckCXXSourceRuns) - -set(OpenGL_GL_PREFERENCE GLVND CACHE STRING "Linux-only: if GLVND, use the vendor-neutral GL libraries (default). If LEGACY, use the legacy ones (might be necessary to have optirun/primusrun work)") -set_property(CACHE OpenGL_GL_PREFERENCE PROPERTY STRINGS GLVND LEGACY) -find_package(OpenGL) -if (OPENGL_GL) - include_directories(${OPENGL_INCLUDE_DIR}) -endif() - -if(ENABLE_X11) - pkg_check_modules(X11 x11 IMPORTED_TARGET) - if(X11_FOUND) - add_definitions(-DHAVE_X11=1) - pkg_check_modules(XRANDR xrandr IMPORTED_TARGET) - if(XRANDR_FOUND) - add_definitions(-DHAVE_XRANDR=1) - endif() - pkg_check_modules(X11_INPUT REQUIRED xi>=1.5.0 IMPORTED_TARGET) - message(STATUS "X11 support enabled") - else() - message(WARNING "X11 support enabled but not found. This build will not support X11.") - endif() -endif() - -if(ENABLE_EGL) - find_package(EGL) - if(EGL_FOUND) - add_definitions(-DHAVE_EGL=1) - message(STATUS "EGL OpenGL interface enabled") - else() - message(WARNING "EGL support enabled but not found. This build will not support EGL.") - endif() -endif() - -if(ENCODE_FRAMEDUMPS) - if(WIN32) - if(_M_X86_64) - set(FFMPEG_DIR Externals/FFmpeg-bin/x64) - elseif(_M_ARM_64) - set(FFMPEG_DIR Externals/FFmpeg-bin/ARM64) - endif() - endif() - find_package(FFmpeg COMPONENTS avcodec avformat avutil swresample swscale) - if(FFmpeg_FOUND) - if(APPLE) - find_library(COREMEDIA_LIBRARY CoreMedia) - find_library(VIDEOTOOLBOX_LIBRARY VideoToolbox) - find_library(COREVIDEO_LIBRARY CoreVideo) - find_library(AUDIOTOOLBOX_LIBRARY AudioToolbox) - endif() - message(STATUS "libav/ffmpeg found, enabling AVI frame dumps") - add_definitions(-DHAVE_FFMPEG) - if(WIN32) - # Our prebuilt binaries depend on Bcrypt - set_property(TARGET FFmpeg::avutil APPEND PROPERTY - INTERFACE_LINK_LIBRARIES "Bcrypt.lib" - ) - endif() - else() - message(STATUS "libav/ffmpeg not found, disabling AVI frame dumps") - endif() -endif() - -if(ENABLE_HWDB) - find_package(LIBUDEV REQUIRED) - if(LIBUDEV_FOUND) - message(STATUS "libudev found, enabling hardware database") - add_definitions(-DHAVE_LIBUDEV=1) - else() - message(FATAL_ERROR "Couldn't find libudev. Can't build hardware database.\nDisable ENABLE_HWDB if you wish to build without hardware database support") - endif() -endif() - -if(ENABLE_EVDEV) - find_package(LIBUDEV REQUIRED) - find_package(LIBEVDEV REQUIRED) - if(LIBUDEV_FOUND AND LIBEVDEV_FOUND) - message(STATUS "libevdev/libudev found, enabling evdev controller backend") - add_definitions(-DHAVE_LIBUDEV=1) - add_definitions(-DHAVE_LIBEVDEV=1) - else() - message(FATAL_ERROR "Couldn't find libevdev and/or libudev. Can't build evdev controller backend.\nDisable ENABLE_EVDEV if you wish to build without controller support") - endif() -endif() - -if(UNIX) - message(STATUS "Using named pipes as controller inputs") - add_definitions(-DUSE_PIPES=1) - message(STATUS "Watching game memory for changes") - add_definitions(-DUSE_MEMORYWATCHER=1) -endif() - -if(ENABLE_ANALYTICS) - message(STATUS "Enabling analytics collection (subject to end-user opt-in)") - add_definitions(-DUSE_ANALYTICS=1) -endif() - -if(ENABLE_AUTOUPDATE) - message(STATUS "Enabling automatic update support") - add_definitions(-DAUTOUPDATE=1) -endif() - -######################################## -# Setup include directories (and make sure they are preferred over the Externals) -# -include_directories(Source/Core) -if(ANDROID) - include_directories(Source/Android) -endif() - -######################################## -# Process externals and setup their include directories -# -# NOTES about adding Externals: -# - If an external provides a target, or one can be introduced with find_package, consider using it. -# - If a target doesn't exist, consider introducing a target for it with add_library and adding all necessary -# includes, definitions, etc, to that target. This way, only libraries that need those attributes simply -# need to link that target in, as opposed to them being provided to every library -# (which is the case with the directory-based include_directories, add_definitions, etc) -# -# - make sure to tell cmake to link them statically or dynamically (most -# should be linked statically) -# - place the CMakeLists.txt in the first-level subdirectory, e.g. -# Externals/zlib/CMakeLists.txt (that is: NOT in some Src/ subdirectory) -# -if (_M_X86_64) - add_subdirectory(Externals/Bochs_disasm) -endif() -add_subdirectory(Externals/cpp-optparse) - -dolphin_find_optional_system_library_pkgconfig(FMT - fmt>=10.1 fmt::fmt Externals/fmt -) - -add_subdirectory(Externals/imgui) -add_subdirectory(Externals/implot) - -dolphin_find_optional_system_library(glslang Externals/glslang DOLPHIN_TRY_VERSIONS 15 16) - -# SPIRV-Cross is used on Windows for GLSL to HLSL conversion for the Direct3D 11 and Direct3D 12 -# video backends, and on Apple devices for the Metal video backend. -if(WIN32 OR APPLE) - add_subdirectory(Externals/spirv_cross) -endif() -add_subdirectory(Externals/tinygltf) - -if(ENABLE_VULKAN) - add_definitions(-DHAS_VULKAN) - - if(APPLE AND USE_BUNDLED_MOLTENVK) - add_subdirectory(Externals/MoltenVK) - endif() - - - if (ANDROID AND _M_ARM_64) - add_subdirectory(Externals/libadrenotools) - endif() -endif() - -if(NOT (WIN32 AND _M_ARM_64)) - # OpenGL is available on all platforms except windows-arm64 - add_definitions(-DHAS_OPENGL) -endif() - -if(NOT WIN32) - if(ANDROID) - set(USE_SYSTEM_Iconv OFF) - endif() - dolphin_find_optional_system_library(Iconv Externals/libiconv 1.14) -endif() - -dolphin_find_optional_system_library(pugixml Externals/pugixml) - -dolphin_find_optional_system_library_pkgconfig(ENET libenet>=1.3.18 enet::enet Externals/enet) - -dolphin_find_optional_system_library_pkgconfig(xxhash libxxhash>=0.8.2 xxhash::xxhash Externals/xxhash) - -dolphin_find_optional_system_library(BZip2 Externals/bzip2) - -dolphin_find_optional_system_library(LibLZMA Externals/liblzma) -# Imported target added in CMake 3.14 -dolphin_make_imported_target_if_missing(LibLZMA::LibLZMA LIBLZMA) - -dolphin_find_optional_system_library_pkgconfig(ZSTD libzstd>=1.4.0 zstd::zstd Externals/zstd) - -dolphin_find_optional_system_library_pkgconfig(ZLIB zlib>=1.3.1 ZLIB::ZLIB Externals/zlib-ng) - -dolphin_find_optional_system_library_pkgconfig(minizip-ng - "minizip-ng>=4.0.4" MINIZIP::minizip-ng Externals/minizip-ng -) - -dolphin_find_optional_system_library(LZO Externals/LZO) - -dolphin_find_optional_system_library_pkgconfig(lz4 liblz4>=1.8 LZ4::LZ4 Externals/lz4) - -dolphin_find_optional_system_library_pkgconfig(SPNG spng spng::spng Externals/libspng) - -# Using static FreeSurround from Externals -# There is no system FreeSurround library. -message(STATUS "Using static FreeSurround from Externals") -add_subdirectory(Externals/FreeSurround) - -if (APPLE OR WIN32) - message(STATUS "Using ed25519 from Externals") - add_subdirectory(Externals/ed25519) - include_directories(Externals/ed25519) -endif() - -if(ENABLE_CUBEB) - dolphin_find_optional_system_library(CUBEB Externals/cubeb) - add_definitions(-DHAVE_CUBEB) -endif() - -if(NOT ANDROID) - dolphin_find_optional_system_library(LibUSB Externals/libusb) - add_definitions(-D__LIBUSB__) -endif() - -if(ENABLE_SDL) - dolphin_find_optional_system_library(SDL3 Externals/SDL 3.2.0) - add_definitions(-DHAVE_SDL3) -endif() - -dolphin_find_optional_system_library(SFML Externals/SFML 3.0 COMPONENTS Network System) - -if(USE_UPNP) - dolphin_find_optional_system_library(MINIUPNPC Externals/miniupnpc 1.6) - add_definitions(-DUSE_UPNP) -endif() - -dolphin_find_optional_system_library(MBEDTLS Externals/mbedtls 2.28) - -dolphin_find_optional_system_library(CURL Externals/curl) - -if(NOT ANDROID) - dolphin_find_optional_system_library(HIDAPI Externals/hidapi) -endif() - -if(USE_DISCORD_PRESENCE) - message(STATUS "Using static DiscordRPC from Externals") - add_subdirectory(Externals/discord-rpc EXCLUDE_FROM_ALL) - include_directories(Externals/discord-rpc/include) -endif() - -if(USE_MGBA) - dolphin_find_optional_system_library(LIBMGBA Externals/mGBA 0.11) -endif() - -find_package(SYSTEMD) -if(SYSTEMD_FOUND) - message(STATUS "libsystemd found, enabling traversal server watchdog support") - add_definitions(-DHAVE_LIBSYSTEMD) -else() - message(STATUS "libsystemd not found, disabling traversal server watchdog support") -endif() - -if (WIN32) - include_directories(Externals/wil/include) - include_directories(Externals/OpenAL/include) -endif() - -include_directories(Externals/picojson) - -add_subdirectory(Externals/expr) - -add_subdirectory(Externals/FatFs) - -if (USE_RETRO_ACHIEVEMENTS) - add_subdirectory(Externals/rcheevos) -endif() - -add_subdirectory(Externals/watcher) - -# Any other platform or BSD is currently unsupported by cpp-ipc -if(WIN32 OR LINUX OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR CMAKE_SYSTEM_NAME STREQUAL "QNX") - add_subdirectory(Externals/cpp-ipc) - add_definitions(-DHAVE_CPPIPC) -endif() - -######################################## -# Pre-build events: Define configuration variables and write SCM info header -# - -# Remove in-tree revision information generated by Visual Studio -# This is because the compiler will check in-tree first and use this, even if it is outdated -file(REMOVE "${PROJECT_SOURCE_DIR}/Source/Core/Common/scmrev.h") - -file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/Source/Core/Common) -if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/Source/Core/Common/scmrev.h) - file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/Source/Core/Common/scmrev.h) -endif() - -if(APPLE) - file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/Source/Core) - if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/Source/Core/VersionInfo.plist) - file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/Source/Core/VersionInfo.plist) - endif() -endif() - -if(CMAKE_SYSTEM_NAME STREQUAL "Linux") - file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/Flatpak) - if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/Flatpak/org.DolphinEmu.dolphin-emu.metainfo.xml) - file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/Flatpak/org.DolphinEmu.dolphin-emu.metainfo.xml) - endif() -endif() - -find_package(Git) -if(NOT GIT_FOUND) - set(GIT_EXECUTABLE "") -endif() -add_custom_target( - dolphin_scmrev - ${CMAKE_COMMAND} -DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR} -DPROJECT_BINARY_DIR=${PROJECT_BINARY_DIR} -DDISTRIBUTOR=${DISTRIBUTOR} -DDOLPHIN_DEFAULT_UPDATE_TRACK=${DOLPHIN_DEFAULT_UPDATE_TRACK} -DGIT_FOUND=${GIT_FOUND} -DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DDOLPHIN_WC_REVISION=${DOLPHIN_WC_REVISION} -DDOLPHIN_WC_DESCRIBE=${DOLPHIN_WC_DESCRIBE} -DDOLPHIN_WC_BRANCH=${DOLPHIN_WC_BRANCH} -DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET} -P ${CMAKE_CURRENT_SOURCE_DIR}/CMake/ScmRevGen.cmake - BYPRODUCTS "${CMAKE_CURRENT_BINARY_DIR}/Source/Core/Common/scmrev.h" "${CMAKE_CURRENT_BINARY_DIR}/Source/Core/DolphinQt/Info.plist" "${CMAKE_CURRENT_BINARY_DIR}/Source/Core/MacUpdater/Info.plist" "${CMAKE_CURRENT_BINARY_DIR}/Flatpak/org.DolphinEmu.dolphin-emu.metainfo.xml" - VERBATIM -) - -# This is here so #include "Common/scmrev.h" finds the generated header. -include_directories("${PROJECT_BINARY_DIR}/Source/Core") - -######################################## -# Unit testing. -# -if(ENABLE_TESTS) - dolphin_find_optional_system_library_pkgconfig(GTEST - gtest gtest::gtest Externals/gtest - ) - # dolphin_find_optional_system_library_pkgconfig() doesn't add an alias if it - # uses the bundled libraries, so we add one ourselves. - if (NOT TARGET gtest::gtest) - add_library(gtest::gtest ALIAS gtest) - endif() - # Force gtest to link the C runtime dynamically on Windows in order to avoid - # runtime mismatches. - set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) -else() - message(STATUS "Unit tests are disabled") -endif() - -######################################## -# Process Dolphin source now that all setup is complete -# -add_subdirectory(Source) - -######################################## -# Install shared data files -# -if(NOT CMAKE_SYSTEM_NAME MATCHES "Darwin|Windows") - install(DIRECTORY Data/Sys/ DESTINATION ${datadir}/Sys PATTERN) -endif() -if(NOT CMAKE_SYSTEM_NAME MATCHES "Linux|FreeBSD|OpenBSD|Darwin") - install(FILES COPYING DESTINATION ${datadir}) - install(DIRECTORY LICENSES DESTINATION ${datadir}/LICENSES PATTERN) -endif() -if(CMAKE_SYSTEM_NAME MATCHES "Linux|FreeBSD|OpenBSD") - # Install the application icon and menu item - install(FILES Data/dolphin-emu.svg - DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/scalable/apps) - install(FILES Data/dolphin-emu.png - DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/256x256/apps) - install(FILES Data/dolphin-emu.desktop - DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications) - # Install manpages - install(FILES Data/dolphin-emu.6 - DESTINATION ${CMAKE_INSTALL_MANDIR}/man6) - install(FILES Data/dolphin-emu-nogui.6 - DESTINATION ${CMAKE_INSTALL_MANDIR}/man6) -endif() - -# packaging information -set(CPACK_PACKAGE_NAME "dolphin-emu") -set(CPACK_PACKAGE_VENDOR "Dolphin Team") -set(CPACK_PACKAGE_VERSION_MAJOR ${DOLPHIN_VERSION_MAJOR}) -set(CPACK_PACKAGE_VERSION_MINOR ${DOLPHIN_VERSION_MINOR}) -set(CPACK_PACKAGE_VERSION_PATCH ${DOLPHIN_VERSION_PATCH}) -set(CPACK_PACKAGE_DESCRIPTION_FILE ${PROJECT_SOURCE_DIR}/Data/cpack_package_description.txt) -set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A GameCube and Wii emulator") - -set(CPACK_RPM_PACKAGE_GROUP System/Emulators/Other) -set(CPACK_RPM_PACKAGE_LICENSE GPL-2.0) -# TODO: CPACK_RESOURCE_FILE_README -# TODO: CPACK_RESOURCE_FILE_WELCOME -# TODO: CPACK_PACKAGE_ICON -# TODO: CPACK_NSIS_* -# TODO: Use CPack components for DSPSpy, etc => cpack_add_component - -# Debian Specific. Install dpkg-dev for automatic deps generation -set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) -set(CPACK_DEBIAN_PACKAGE_SECTION "Games") - -set(CPACK_SET_DESTDIR ON) -set(CPACK_SOURCE_GENERATOR "TGZ;TBZ2;ZIP") -set(CPACK_SOURCE_IGNORE_FILES "\\\\.#;/#;.*~;\\\\.swp;/\\\\.git") -list(APPEND CPACK_SOURCE_IGNORE_FILES "${CMAKE_BINARY_DIR}") - -# CPack must be included after the CPACK_* variables are set in order for those -# variables to take effect. -Include(CPack) + +# Support functions +include(CheckAndAddFlag) +include(CheckCCompilerFlag) +include(CheckSymbolExists) +include(DolphinCompileDefinitions) +include(DolphinDisableWarningsMSVC) +include(DolphinLibraryTools) +include(GNUInstallDirs) +include(RemoveCompileFlag) + +# When using the Visual Studio generator, only show our targets and not the ones from Externals. +set_property(DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT dolphin-emu) + +if (MSVC) + set(CMAKE_C_STANDARD 99) + set(CMAKE_CXX_STANDARD 23) + set(CMAKE_CXX_STANDARD_REQUIRED ON) +endif() + +set(COMPILER ${CMAKE_CXX_COMPILER_ID}) +if (COMPILER STREQUAL "GNU") + set(COMPILER "GCC") # prefer printing GCC instead of GNU +endif() + +# Minimum required versions +# Toolsets +set(Xcode_min_version 14.3) +set(MSVC_toolset_min_version 143) +# Compilers +set(GCC_min_version 12) +set(Clang_min_version 15) +set(AppleClang_min_version 14.0.3) +set(MSVC_min_version 19.32) +# Standard libraries +set(libstdc++_min_version 12) # This should match GCC_min_version's major version. +set(libc++_min_version 150000) # This should match Clang_min_version in the format "xxyyzz" instead of "xx.yy.zz" + +dolphin_check_toolset_version("Xcode" XCODE_VERSION ${Xcode_min_version}) +dolphin_check_toolset_version("MSVC Toolset" MSVC_TOOLSET_VERSION ${MSVC_toolset_min_version}) + +message(STATUS "Using ${COMPILER} ${CMAKE_CXX_COMPILER_VERSION}") + +if(NOT DEFINED ${COMPILER}_min_version) + message(WARNING "Unknown compiler ${COMPILER}, assuming it is new enough") +elseif(CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${${COMPILER}_min_version}) + message(FATAL_ERROR "Requires ${COMPILER} ${${COMPILER}_min_version} or higher") +endif() + +# libstdc++ is almost always used on Linux, even when using clang as the compiler. +# libc++ is used on the likes of Android, Apple devices, FreeBSD, and a few (very) rare Linux distros like Chimera Linux. +# Windows uses its own standard library named STL, which we check as part of the toolset above. (outside of MinGW which can use either libstdc++ or libc++) +dolphin_check_std_version("GNU_libstdc++" _GLIBCXX_RELEASE ${libstdc++_min_version}) +dolphin_check_std_version("LLVM_libc++" _LIBCPP_VERSION ${libc++_min_version}) + +# Name of the Dolphin distributor. If you redistribute Dolphin builds (forks, +# unofficial builds) please consider identifying your distribution with a +# unique name here. +set(DISTRIBUTOR "None" CACHE STRING "Name of the distributor.") + +set(DOLPHIN_DEFAULT_UPDATE_TRACK "" CACHE STRING "Name of the default update track. If empty, disables auto-update.") + +if(UNIX AND NOT APPLE AND NOT ANDROID) + option(ENABLE_X11 "Enables X11 Support" ON) +endif() +if(NOT WIN32 AND NOT APPLE AND NOT HAIKU) + option(ENABLE_EGL "Enables EGL OpenGL Interface" ON) +endif() + +if(NOT ANDROID) + option(ENABLE_CLI_TOOL "Enable dolphin-tool, a CLI-based utility for functions such as managing disc images" ON) +endif() + + +set(USE_SYSTEM_LIBS "AUTO" CACHE STRING "Use system libraries instead of bundled libraries. ON - Always use system and fail if unavailable, OFF - Always use bundled, AUTO - Use system if available, otherwise use bundled. Default is AUTO") +if(APPROVED_VENDORED_DEPENDENCIES) + message(WARNING "APPROVED_VENDORED_DEPENDENCIES is deprecated. Please migrate to setting USE_SYSTEM_LIBS to ON and setting USE_SYSTEM_ to either AUTO or OFF to allow bundled libs.") +endif() +option(USE_UPNP "Enables UPnP port mapping support" ON) +option(ENABLE_NOGUI "Enable NoGUI frontend" ON) +option(ENABLE_QT "Enable Qt (Default)" ON) +option(ENABLE_LTO "Enables Link Time Optimization" OFF) +option(ENABLE_GENERIC "Enables generic build that should run on any little-endian host" OFF) +option(ENABLE_HEADLESS "Enables running Dolphin as a headless variant" OFF) +option(ENABLE_ALSA "Enables ALSA sound backend" ON) +option(ENABLE_PULSEAUDIO "Enables PulseAudio sound backend" ON) +option(ENABLE_CUBEB "Enables Cubeb sound backend" ON) +option(ENABLE_LLVM "Enables LLVM support, for disassembly" ON) +option(ENABLE_TESTS "Enables building the unit tests" ON) +option(ENABLE_VULKAN "Enables vulkan video backend" ON) +option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence, show the current game on Discord" ON) +option(USE_MGBA "Enables GBA controllers emulation using libmgba" ON) +option(ENABLE_AUTOUPDATE "Enables support for automatic updates" ON) +option(USE_RETRO_ACHIEVEMENTS "Enables integration with retroachievements.org" ON) +option(ENABLE_CCACHE "Enables CCache compiler cache" OFF) + +# Maintainers: if you consider blanket disabling this for your users, please +# consider the following points: +# * No data is being sent without explicit user approval (pop up box at first +# launch). +# * The Dolphin team relies on the data in order to understand the behavior +# of our software in the wild. +option(ENABLE_ANALYTICS "Enables opt-in Analytics collection" ON) + +option(ENCODE_FRAMEDUMPS "Encode framedumps in AVI format" ON) + +option(ENABLE_GPROF "Enable gprof profiling (must be using Debug build)" OFF) +option(FASTLOG "Enable all logs" OFF) + +# TODO: Add DSPSpy +option(DSPTOOL "Build dsptool" OFF) + +# RetroAchievements developer tools require Windows hooks +if(WIN32) + option(RC_CLIENT_SUPPORTS_RAINTEGRATION "Enables RetroAchievements developer tools" ON) +endif() + +# Enable SDL by default on operating systems that aren't Android. +if(NOT ANDROID) + option(ENABLE_SDL "Enables SDL as a generic controller backend" ON) +else() + option(ENABLE_SDL "Enables SDL as a generic controller backend" OFF) +endif() + +if(APPLE) + enable_language(OBJC) + enable_language(OBJCXX) + option(MACOS_USE_DEFAULT_SEARCH_PATH "Don't prioritize system library paths" OFF) + option(POSTPROCESS_BUNDLE "Postprocess bundle for redistributability" OFF) + # Enable adhoc code signing by default (otherwise makefile builds on ARM will not work) + option(MACOS_CODE_SIGNING "Enable codesigning" ON) + option(USE_BUNDLED_MOLTENVK "Build MoltenVK from Externals with Dolphin-specific patches" ON) + set(MACOS_CODE_SIGNING_IDENTITY "-" CACHE STRING "The identity used for codesigning.") +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + option(ENABLE_VTUNE "Enable Intel VTune integration for JIT code." OFF) + + if(NOT ANDROID) + option(ENABLE_HWDB "Enables the udev hardware database" ON) + option(ENABLE_EVDEV "Enables the evdev controller backend" ON) + endif() +endif() + +# Enable folders for IDE +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +# Set up paths +set(datadir ${CMAKE_INSTALL_FULL_DATADIR}/dolphin-emu CACHE PATH "datadir") +add_definitions(-DDATA_DIR="${datadir}/") + +if(CMAKE_SYSROOT) + # If we should use a sysroot, tell pkg-config to search for packages in there, not on the host + set(ENV{PKG_CONFIG_LIBDIR} "${CMAKE_SYSROOT}/usr/lib/pkgconfig:${CMAKE_SYSROOT}/usr/share/pkgconfig") + set(ENV{PKG_CONFIG_SYSROOT_DIR} "${CMAKE_SYSROOT}") +endif() + +# Output directory for the Dolphin binary +# We need to use a generator expression here to ensure that multi-config generators +# (like Visual Studio) put the binaries in the right place. +# See https://cmake.org/cmake/help/v4.2/prop_tgt/RUNTIME_OUTPUT_DIRECTORY.html +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY $<1:${CMAKE_BINARY_DIR}/Binaries>) + +# Output directory for PDB files. PDBs are only generated on Windows with MSVC. +# We set a common directory for all PDBs to avoid littering the Binaries/ directory. +set(CMAKE_PDB_OUTPUT_DIRECTORY $<1:${CMAKE_BINARY_DIR}/pdb>) + +if(ENABLE_CCACHE) + include(CCache) + if(NOT CCACHE_BIN) + message(WARNING "Ccache was enabled but not found. This build will not use Ccache.") + endif() +endif() + +# Architecture detection and arch specific settings +message(STATUS "Detected architecture: ${CMAKE_SYSTEM_PROCESSOR}") + +# Detect 64bit or 32bit +# CMake doesn't provide a simple way to determine 32bit or 64bit +# If we ever support a architecture that is 64bit with 32bit pointers then this'll break +# Of course the chances of that are slim(x32?) so who cares +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(_ARCH_64 1) + add_definitions(-D_ARCH_64=1) +else() + set(_ARCH_32 1) + add_definitions(-D_ARCH_32=1) +endif() + +if(ENABLE_GENERIC) + if(WIN32) + message(FATAL_ERROR "Generic builds are not supported on Windows!") + endif() + message(STATUS "Warning! Building generic build!") + set(_M_GENERIC 1) + add_definitions(-D_M_GENERIC=1) +elseif(_ARCH_64 AND CMAKE_SYSTEM_PROCESSOR MATCHES "x64|x86_64|amd64|AMD64") + set(_M_X86_64 1) + add_definitions(-D_M_X86_64=1) + check_and_add_flag(HAVE_SSE2 -msse2) +elseif(_ARCH_64 AND CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64|ARM64") + set(_M_ARM_64 1) + add_definitions(-D_M_ARM_64=1) + # CRC instruction set is used in the CRC32 hash function + check_and_add_flag(HAVE_ARCH_ARMV8 -march=armv8-a+crc) +else() + message(FATAL_ERROR "You're building on an unsupported platform: " + "'${CMAKE_SYSTEM_PROCESSOR}' with ${CMAKE_SIZEOF_VOID_P}-byte pointers." + " Enable generic build if you really want a JIT-less binary.") +endif() + +if(CMAKE_GENERATOR MATCHES "Ninja") + check_and_add_flag(DIAGNOSTICS_COLOR -fdiagnostics-color) +elseif(CMAKE_GENERATOR MATCHES "Visual Studio") + # Only MSBuild needs this, other generators will compile one file at a time + add_compile_options("/MP") +endif() + +if(MSVC) + check_and_add_flag(EXCEPTIONS /EHsc) + dolphin_compile_definitions(_DEBUG DEBUG_ONLY) + + # Disable RTTI + add_compile_options(/GR-) + + # Set warning level 4 (the highest) + add_compile_options(/W4) + + # Treat all warnings as errors + add_compile_options(/WX) + + # Disable some warnings + add_compile_options( + /wd4201 # nonstandard extension used : nameless struct/union + /wd4127 # conditional expression is constant + /wd4100 # 'identifier' : unreferenced formal parameter + /wd4200 # InputCommon fix temp. + /wd4244 # 'conversion' conversion from 'type1' to 'type2', possible loss of data + /wd4121 # 'symbol' : alignment of a member was sensitive to packing + /wd4324 # Padding was added at the end of a structure because you specified a __declspec(align) value. + /wd4714 # function 'function' marked as __forceinline not inlined + /wd4351 # new behavior: elements of array 'array' will be default initialized + # TODO: Enable this warning once possible + /wd4245 # conversion from 'type1' to 'type2', signed/unsigned mismatch + # Currently jits use some annoying code patterns which makes this common + ) + + # Additional warnings + add_compile_options( + /w15262 # Unannotated fallthrough between switch labels + /w44263 # Non-virtual member function hides base class virtual function + /w44265 # Class has virtual functions, but destructor is not virtual + /w44946 # Reinterpret cast between related types + ) + + # All files are encoded as UTF-8 + add_compile_options(/utf-8) + + # Ignore warnings in external headers + add_compile_options(/external:anglebrackets) + add_compile_options(/external:W0) + add_compile_options(/external:templates-) + + # Request deterministic builds + add_compile_options(/experimental:deterministic) + add_link_options(/experimental:deterministic) + + # Enable function-level linking + add_compile_options(/Gy) + # Generate intrinsic functions + add_compile_options(/Oi) + # Enable buffer security check on Debug, disable otherwise + add_compile_options($,/GS,/GS->) + # Remove unreferenced inline functions/data to reduce link time and catch bugs + # Note: In msbuild build, this gets set by msbuild by default + add_compile_options(/Zc:inline) + # Fix various other non-conformant behaviors + add_compile_options(/Zc:__cplusplus,enumTypes,externConstexpr,preprocessor,templateScope,throwingNew) + # Enforce strict volatile semantics as per ISO C++ + add_compile_options(/volatile:iso) + + # Use 'precise' floating point model + add_compile_options(/fp:precise) + + string(APPEND CMAKE_EXE_LINKER_FLAGS " /NXCOMPAT") + # Generate debug data + string(APPEND CMAKE_EXE_LINKER_FLAGS " /DEBUG") + # Eliminate dead code and data + string(APPEND CMAKE_EXE_LINKER_FLAGS " /OPT:REF /OPT:ICF") +else() + add_definitions(-D_DEFAULT_SOURCE) + + # gcc uses some optimizations which might break stuff without this flag + check_and_add_flag(NO_STRICT_ALIASING -fno-strict-aliasing) + check_and_add_flag(NO_EXCEPTIONS -fno-exceptions) + + check_and_add_flag(VISIBILITY_INLINES_HIDDEN -fvisibility-inlines-hidden) + check_and_add_flag(VISIBILITY_HIDDEN -fvisibility=hidden) + + check_and_add_flag(FOMIT_FRAME_POINTER -fomit-frame-pointer NO_DEBINFO_ONLY) + + dolphin_compile_definitions(_DEBUG DEBUG_ONLY) + check_and_add_flag(GGDB -ggdb DEBUG_ONLY) + + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + # GNU ar: Create thin archive files. + # Requires binutils-2.19 or later. + set(CMAKE_C_ARCHIVE_CREATE " qcTP ") + set(CMAKE_C_ARCHIVE_APPEND " qTP ") + set(CMAKE_CXX_ARCHIVE_CREATE " qcTP ") + set(CMAKE_CXX_ARCHIVE_APPEND " qTP ") + endif() +endif() + +if(CMAKE_SYSTEM_NAME MATCHES "Darwin") + if(NOT MACOS_USE_DEFAULT_SEARCH_PATH) + # Hack up the path to prioritize the path to built-in OS libraries to + # increase the chance of not depending on a bunch of copies of them + # installed by MacPorts, Fink, Homebrew, etc, and ending up copying + # them into the bundle. Since we optionally depend on libraries which + # are not part of OS X (ffmpeg, etc.), however, don't remove the default + # path entirely as was done in a previous version of this file. This is + # still kinda evil, since it defeats the user's path settings... + # See http://www.cmake.org/cmake/help/v3.0/command/find_program.html + list(APPEND CMAKE_PREFIX_PATH "/usr") + endif() + + # Prevents Xcode from overriding the -fno-strict-aliasing flag + set(CMAKE_XCODE_ATTRIBUTE_GCC_STRICT_ALIASING NO) + + # Specify target CPUs. + if(_ARCH_64 AND _M_X86_64) + check_and_add_flag(HAVE_MSSSE3 -mssse3) + check_and_add_flag(HAVE_ARCH_CORE2 -march=core2) + endif() + # Linker flags. + # Drop unreachable code and data. + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-dead_strip,-dead_strip_dylibs") + + # Set FMT_EXCEPTIONS = 0, for consistency with -fno-exceptions earlier. + # If we set only -fno-exceptions, fmt fails to compile when included from + # Objective-C++ because fmt tries try to use throw because __EXCEPTIONS is defined. + add_definitions(-DFMT_EXCEPTIONS=0) + + find_library(APPKIT_LIBRARY AppKit) + find_library(APPSERV_LIBRARY ApplicationServices) + find_library(CARBON_LIBRARY Carbon) + find_library(COCOA_LIBRARY Cocoa) + find_library(COREFOUNDATION_LIBRARY CoreFoundation) + find_library(CORESERV_LIBRARY CoreServices) + find_library(FORCEFEEDBACK_LIBRARY ForceFeedback) + find_library(FOUNDATION_LIBRARY Foundation) + find_library(IOB_LIBRARY IOBluetooth) + find_library(IOK_LIBRARY IOKit) + find_library(OPENGL_LIBRARY OpenGL) +endif() + +if(ENABLE_LTO) + include(CheckIPOSupported) + check_ipo_supported(RESULT supported OUTPUT error) + + if(supported) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) + else() + message(STATUS "IPO / LTO not supported: <${error}>") + endif() +endif() + +# BSDs put packages in /usr/local instead of /usr, so we need to +# force CMake to look in those directories by default, too. +# All commands and submodule commands also need to see these +# changes, so just setting them in the project scope via +# include_directories and link_directories is not sufficient +if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + set(CMAKE_PREFIX_PATH "${CMAKE_PREFIX_PATH};/usr/local") + set(CMAKE_REQUIRED_INCLUDES "/usr/local/include") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/usr/local/lib") +endif() + +# Dolphin requires threads. +find_package(Threads) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release" CACHE STRING + "Build type (Release/Debug/RelWithDebInfo/MinSizeRel)" FORCE) +endif() + +if(ENABLE_GPROF) + check_and_add_flag(HAVE_PG -pg) + if(NOT FLAG_C_HAVE_PG) + message(FATAL_ERROR "Compiler option -pg is not supported") + endif() + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg") +endif() + +if(FASTLOG) + add_definitions(-DDEBUGFAST) +endif() + +if(ENABLE_VTUNE) + set(VTUNE_DIR "/opt/intel/vtune_amplifier") + add_definitions(-DUSE_VTUNE) + include_directories("${VTUNE_DIR}/include") + set(VTUNE_LIBRARIES + "${VTUNE_DIR}/lib64/libjitprofiling.a" + "${VTUNE_DIR}/lib64/libittnotify.a" + ) +endif() + +if(ANDROID) + message(STATUS "Building for Android") + if(NOT ENABLE_HEADLESS) + add_definitions(-DANDROID) + if(ENABLE_NOGUI) + message(STATUS "Building Android app, disabling NoGUI frontend.") + set(ENABLE_NOGUI 0) + endif() + else() + # Lie to cmake a bit. We are cross compiling to Android + # but not as a shared library. We want an executable. + set(ANDROID 0) + endif() + set(ENABLE_QT 0) + set(USE_DISCORD_PRESENCE 0) + + # We are cross compiling, search only the toolchain for libraries and includes + SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + SET(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) +endif() + +if(ENABLE_HEADLESS) + message(STATUS "Enabling Headless! Disabling GUI.") + set(ENABLE_QT 0) + set(USE_DISCORD_PRESENCE 0) +endif() + +# Set file offset size to 64 bits. On modern Unixes, this is typically already the case. Exceptions: +# +# glibc may default to 32 bits. This can be configured by setting _FILE_OFFSET_BITS=64. +# +# bionic (Android) defaults to 32 bits for 32-bit ABIs. Here too we can use _FILE_OFFSET_BITS=64, +# but only on API 25 and up. Since we're currently supporting older API levels and 32-bit Android +# isn't a build configuration we officially support, let's leave this as it is for now. +# More details: https://android.googlesource.com/platform/bionic/+/master/docs/32-bit-abi.md +if(NOT ANDROID AND NOT CMAKE_SYSTEM_NAME MATCHES "Windows") + add_definitions(-D_FILE_OFFSET_BITS=64) + add_definitions(-D_LARGEFILE_SOURCE) +endif() + +######################################## +# Dependency checking +# +# TODO: We should have options for dependencies included in the externals to +# override autodetection of system libraries and force the usage of the +# externals. +include(CheckLib) +include(CheckCXXSourceRuns) + +set(OpenGL_GL_PREFERENCE GLVND CACHE STRING "Linux-only: if GLVND, use the vendor-neutral GL libraries (default). If LEGACY, use the legacy ones (might be necessary to have optirun/primusrun work)") +set_property(CACHE OpenGL_GL_PREFERENCE PROPERTY STRINGS GLVND LEGACY) +find_package(OpenGL) +if (OPENGL_GL) + include_directories(${OPENGL_INCLUDE_DIR}) +endif() + +if(ENABLE_X11) + pkg_check_modules(X11 x11 IMPORTED_TARGET) + if(X11_FOUND) + add_definitions(-DHAVE_X11=1) + pkg_check_modules(XRANDR xrandr IMPORTED_TARGET) + if(XRANDR_FOUND) + add_definitions(-DHAVE_XRANDR=1) + endif() + pkg_check_modules(X11_INPUT REQUIRED xi>=1.5.0 IMPORTED_TARGET) + message(STATUS "X11 support enabled") + else() + message(WARNING "X11 support enabled but not found. This build will not support X11.") + endif() +endif() + +if(ENABLE_EGL) + find_package(EGL) + if(EGL_FOUND) + add_definitions(-DHAVE_EGL=1) + message(STATUS "EGL OpenGL interface enabled") + else() + message(WARNING "EGL support enabled but not found. This build will not support EGL.") + endif() +endif() + +if(ENCODE_FRAMEDUMPS) + if(WIN32) + if(_M_X86_64) + set(FFMPEG_DIR Externals/FFmpeg-bin/x64) + elseif(_M_ARM_64) + set(FFMPEG_DIR Externals/FFmpeg-bin/ARM64) + endif() + endif() + find_package(FFmpeg COMPONENTS avcodec avformat avutil swresample swscale) + if(FFmpeg_FOUND) + if(APPLE) + find_library(COREMEDIA_LIBRARY CoreMedia) + find_library(VIDEOTOOLBOX_LIBRARY VideoToolbox) + find_library(COREVIDEO_LIBRARY CoreVideo) + find_library(AUDIOTOOLBOX_LIBRARY AudioToolbox) + endif() + message(STATUS "libav/ffmpeg found, enabling AVI frame dumps") + add_definitions(-DHAVE_FFMPEG) + if(WIN32) + # Our prebuilt binaries depend on Bcrypt + set_property(TARGET FFmpeg::avutil APPEND PROPERTY + INTERFACE_LINK_LIBRARIES "Bcrypt.lib" + ) + endif() + else() + message(STATUS "libav/ffmpeg not found, disabling AVI frame dumps") + endif() +endif() + +if(ENABLE_HWDB) + find_package(LIBUDEV REQUIRED) + if(LIBUDEV_FOUND) + message(STATUS "libudev found, enabling hardware database") + add_definitions(-DHAVE_LIBUDEV=1) + else() + message(FATAL_ERROR "Couldn't find libudev. Can't build hardware database.\nDisable ENABLE_HWDB if you wish to build without hardware database support") + endif() +endif() + +if(ENABLE_EVDEV) + find_package(LIBUDEV REQUIRED) + find_package(LIBEVDEV REQUIRED) + if(LIBUDEV_FOUND AND LIBEVDEV_FOUND) + message(STATUS "libevdev/libudev found, enabling evdev controller backend") + add_definitions(-DHAVE_LIBUDEV=1) + add_definitions(-DHAVE_LIBEVDEV=1) + else() + message(FATAL_ERROR "Couldn't find libevdev and/or libudev. Can't build evdev controller backend.\nDisable ENABLE_EVDEV if you wish to build without controller support") + endif() +endif() + +if(UNIX) + message(STATUS "Using named pipes as controller inputs") + add_definitions(-DUSE_PIPES=1) + message(STATUS "Watching game memory for changes") + add_definitions(-DUSE_MEMORYWATCHER=1) +endif() + +if(ENABLE_ANALYTICS) + message(STATUS "Enabling analytics collection (subject to end-user opt-in)") + add_definitions(-DUSE_ANALYTICS=1) +endif() + +if(ENABLE_AUTOUPDATE) + message(STATUS "Enabling automatic update support") + add_definitions(-DAUTOUPDATE=1) +endif() + +######################################## +# Setup include directories (and make sure they are preferred over the Externals) +# +include_directories(Source/Core) +if(ANDROID) + include_directories(Source/Android) +endif() + +######################################## +# Process externals and setup their include directories +# +# NOTES about adding Externals: +# - If an external provides a target, or one can be introduced with find_package, consider using it. +# - If a target doesn't exist, consider introducing a target for it with add_library and adding all necessary +# includes, definitions, etc, to that target. This way, only libraries that need those attributes simply +# need to link that target in, as opposed to them being provided to every library +# (which is the case with the directory-based include_directories, add_definitions, etc) +# +# - make sure to tell cmake to link them statically or dynamically (most +# should be linked statically) +# - place the CMakeLists.txt in the first-level subdirectory, e.g. +# Externals/zlib/CMakeLists.txt (that is: NOT in some Src/ subdirectory) +# +if (_M_X86_64) + add_subdirectory(Externals/Bochs_disasm) +endif() +add_subdirectory(Externals/cpp-optparse) + +dolphin_find_optional_system_library_pkgconfig(FMT + fmt>=10.1 fmt::fmt Externals/fmt +) + +add_subdirectory(Externals/imgui) +add_subdirectory(Externals/implot) + +dolphin_find_optional_system_library(glslang Externals/glslang DOLPHIN_TRY_VERSIONS 15 16) + +# SPIRV-Cross is used on Windows for GLSL to HLSL conversion for the Direct3D 11 and Direct3D 12 +# video backends, and on Apple devices for the Metal video backend. +if(WIN32 OR APPLE) + add_subdirectory(Externals/spirv_cross) +endif() +add_subdirectory(Externals/tinygltf) + +if(ENABLE_VULKAN) + add_definitions(-DHAS_VULKAN) + + if(APPLE AND USE_BUNDLED_MOLTENVK) + add_subdirectory(Externals/MoltenVK) + endif() + + + if (ANDROID AND _M_ARM_64) + add_subdirectory(Externals/libadrenotools) + endif() +endif() + +if(NOT (WIN32 AND _M_ARM_64)) + # OpenGL is available on all platforms except windows-arm64 + add_definitions(-DHAS_OPENGL) +endif() + +if(NOT WIN32) + if(ANDROID) + set(USE_SYSTEM_Iconv OFF) + endif() + dolphin_find_optional_system_library(Iconv Externals/libiconv 1.14) +endif() + +dolphin_find_optional_system_library(pugixml Externals/pugixml) + +dolphin_find_optional_system_library_pkgconfig(ENET libenet>=1.3.18 enet::enet Externals/enet) + +dolphin_find_optional_system_library_pkgconfig(xxhash libxxhash>=0.8.2 xxhash::xxhash Externals/xxhash) + +dolphin_find_optional_system_library(BZip2 Externals/bzip2) + +dolphin_find_optional_system_library(LibLZMA Externals/liblzma) +# Imported target added in CMake 3.14 +dolphin_make_imported_target_if_missing(LibLZMA::LibLZMA LIBLZMA) + +dolphin_find_optional_system_library_pkgconfig(ZSTD libzstd>=1.4.0 zstd::zstd Externals/zstd) + +dolphin_find_optional_system_library_pkgconfig(ZLIB zlib>=1.3.1 ZLIB::ZLIB Externals/zlib-ng) + +dolphin_find_optional_system_library_pkgconfig(minizip-ng + "minizip-ng>=4.0.4" MINIZIP::minizip-ng Externals/minizip-ng +) + +dolphin_find_optional_system_library(LZO Externals/LZO) + +dolphin_find_optional_system_library_pkgconfig(lz4 liblz4>=1.8 LZ4::LZ4 Externals/lz4) + +dolphin_find_optional_system_library_pkgconfig(SPNG spng spng::spng Externals/libspng) + +# Using static FreeSurround from Externals +# There is no system FreeSurround library. +message(STATUS "Using static FreeSurround from Externals") +add_subdirectory(Externals/FreeSurround) + +if (APPLE OR WIN32) + message(STATUS "Using ed25519 from Externals") + add_subdirectory(Externals/ed25519) + include_directories(Externals/ed25519) +endif() + +if(ENABLE_CUBEB) + dolphin_find_optional_system_library(CUBEB Externals/cubeb) + add_definitions(-DHAVE_CUBEB) +endif() + +if(NOT ANDROID) + dolphin_find_optional_system_library(LibUSB Externals/libusb) + add_definitions(-D__LIBUSB__) +endif() + +if(ENABLE_SDL) + dolphin_find_optional_system_library(SDL3 Externals/SDL 3.2.0) + add_definitions(-DHAVE_SDL3) +endif() + +dolphin_find_optional_system_library(SFML Externals/SFML 3.0 COMPONENTS Network System) + +if(USE_UPNP) + dolphin_find_optional_system_library(MINIUPNPC Externals/miniupnpc 1.6) + add_definitions(-DUSE_UPNP) +endif() + +dolphin_find_optional_system_library(MBEDTLS Externals/mbedtls 2.28) + +dolphin_find_optional_system_library(CURL Externals/curl) + +if(NOT ANDROID) + dolphin_find_optional_system_library(HIDAPI Externals/hidapi) +endif() + +if(USE_DISCORD_PRESENCE) + message(STATUS "Using static DiscordRPC from Externals") + add_subdirectory(Externals/discord-rpc EXCLUDE_FROM_ALL) + include_directories(Externals/discord-rpc/include) +endif() + +if(USE_MGBA) + dolphin_find_optional_system_library(LIBMGBA Externals/mGBA 0.11) +endif() + +find_package(SYSTEMD) +if(SYSTEMD_FOUND) + message(STATUS "libsystemd found, enabling traversal server watchdog support") + add_definitions(-DHAVE_LIBSYSTEMD) +else() + message(STATUS "libsystemd not found, disabling traversal server watchdog support") +endif() + +if (WIN32) + include_directories(Externals/wil/include) + include_directories(Externals/OpenAL/include) +endif() + +include_directories(Externals/picojson) + +add_subdirectory(Externals/expr) + +add_subdirectory(Externals/FatFs) + +if (USE_RETRO_ACHIEVEMENTS) + add_subdirectory(Externals/rcheevos) +endif() + +add_subdirectory(Externals/watcher) + +# Any other platform or BSD is currently unsupported by cpp-ipc +if(WIN32 OR LINUX OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR CMAKE_SYSTEM_NAME STREQUAL "QNX") + add_subdirectory(Externals/cpp-ipc) + add_definitions(-DHAVE_CPPIPC) +endif() + +######################################## +# Pre-build events: Define configuration variables and write SCM info header +# + +# Remove in-tree revision information generated by Visual Studio +# This is because the compiler will check in-tree first and use this, even if it is outdated +file(REMOVE "${PROJECT_SOURCE_DIR}/Source/Core/Common/scmrev.h") + +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/Source/Core/Common) +if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/Source/Core/Common/scmrev.h) + file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/Source/Core/Common/scmrev.h) +endif() + +if(APPLE) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/Source/Core) + if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/Source/Core/VersionInfo.plist) + file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/Source/Core/VersionInfo.plist) + endif() +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/Flatpak) + if (NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/Flatpak/org.DolphinEmu.dolphin-emu.metainfo.xml) + file(TOUCH ${CMAKE_CURRENT_BINARY_DIR}/Flatpak/org.DolphinEmu.dolphin-emu.metainfo.xml) + endif() +endif() + +find_package(Git) +if(NOT GIT_FOUND) + set(GIT_EXECUTABLE "") +endif() +add_custom_target( + dolphin_scmrev + ${CMAKE_COMMAND} -DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR} -DPROJECT_BINARY_DIR=${PROJECT_BINARY_DIR} -DDISTRIBUTOR=${DISTRIBUTOR} -DDOLPHIN_DEFAULT_UPDATE_TRACK=${DOLPHIN_DEFAULT_UPDATE_TRACK} -DGIT_FOUND=${GIT_FOUND} -DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DDOLPHIN_WC_REVISION=${DOLPHIN_WC_REVISION} -DDOLPHIN_WC_DESCRIBE=${DOLPHIN_WC_DESCRIBE} -DDOLPHIN_WC_BRANCH=${DOLPHIN_WC_BRANCH} -DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET} -P ${CMAKE_CURRENT_SOURCE_DIR}/CMake/ScmRevGen.cmake + BYPRODUCTS "${CMAKE_CURRENT_BINARY_DIR}/Source/Core/Common/scmrev.h" "${CMAKE_CURRENT_BINARY_DIR}/Source/Core/DolphinQt/Info.plist" "${CMAKE_CURRENT_BINARY_DIR}/Source/Core/MacUpdater/Info.plist" "${CMAKE_CURRENT_BINARY_DIR}/Flatpak/org.DolphinEmu.dolphin-emu.metainfo.xml" + VERBATIM +) + +# This is here so #include "Common/scmrev.h" finds the generated header. +include_directories("${PROJECT_BINARY_DIR}/Source/Core") + +######################################## +# Unit testing. +# +if(ENABLE_TESTS) + dolphin_find_optional_system_library_pkgconfig(GTEST + gtest gtest::gtest Externals/gtest + ) + # dolphin_find_optional_system_library_pkgconfig() doesn't add an alias if it + # uses the bundled libraries, so we add one ourselves. + if (NOT TARGET gtest::gtest) + add_library(gtest::gtest ALIAS gtest) + endif() + # Force gtest to link the C runtime dynamically on Windows in order to avoid + # runtime mismatches. + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +else() + message(STATUS "Unit tests are disabled") +endif() + +######################################## +# Process Dolphin source now that all setup is complete +# +add_subdirectory(Source) + +######################################## +# Install shared data files +# +if(NOT CMAKE_SYSTEM_NAME MATCHES "Darwin|Windows") + install(DIRECTORY Data/Sys/ DESTINATION ${datadir}/Sys PATTERN) +endif() +if(NOT CMAKE_SYSTEM_NAME MATCHES "Linux|FreeBSD|OpenBSD|Darwin") + install(FILES COPYING DESTINATION ${datadir}) + install(DIRECTORY LICENSES DESTINATION ${datadir}/LICENSES PATTERN) +endif() +if(CMAKE_SYSTEM_NAME MATCHES "Linux|FreeBSD|OpenBSD") + # Install the application icon and menu item + install(FILES Data/dolphin-emu.svg + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/scalable/apps) + install(FILES Data/dolphin-emu.png + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/256x256/apps) + install(FILES Data/dolphin-emu.desktop + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications) + # Install manpages + install(FILES Data/dolphin-emu.6 + DESTINATION ${CMAKE_INSTALL_MANDIR}/man6) + install(FILES Data/dolphin-emu-nogui.6 + DESTINATION ${CMAKE_INSTALL_MANDIR}/man6) +endif() + +# packaging information +set(CPACK_PACKAGE_NAME "dolphin-emu") +set(CPACK_PACKAGE_VENDOR "Dolphin Team") +set(CPACK_PACKAGE_VERSION_MAJOR ${DOLPHIN_VERSION_MAJOR}) +set(CPACK_PACKAGE_VERSION_MINOR ${DOLPHIN_VERSION_MINOR}) +set(CPACK_PACKAGE_VERSION_PATCH ${DOLPHIN_VERSION_PATCH}) +set(CPACK_PACKAGE_DESCRIPTION_FILE ${PROJECT_SOURCE_DIR}/Data/cpack_package_description.txt) +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A GameCube and Wii emulator") + +set(CPACK_RPM_PACKAGE_GROUP System/Emulators/Other) +set(CPACK_RPM_PACKAGE_LICENSE GPL-2.0) +# TODO: CPACK_RESOURCE_FILE_README +# TODO: CPACK_RESOURCE_FILE_WELCOME +# TODO: CPACK_PACKAGE_ICON +# TODO: CPACK_NSIS_* +# TODO: Use CPack components for DSPSpy, etc => cpack_add_component + +# Debian Specific. Install dpkg-dev for automatic deps generation +set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) +set(CPACK_DEBIAN_PACKAGE_SECTION "Games") + +set(CPACK_SET_DESTDIR ON) +set(CPACK_SOURCE_GENERATOR "TGZ;TBZ2;ZIP") +set(CPACK_SOURCE_IGNORE_FILES "\\\\.#;/#;.*~;\\\\.swp;/\\\\.git") +list(APPEND CPACK_SOURCE_IGNORE_FILES "${CMAKE_BINARY_DIR}") + +# CPack must be included after the CPACK_* variables are set in order for those +# variables to take effect. +Include(CPack) diff --git a/Run-Wii-Starlet-Bootstrap.ps1 b/Run-Wii-Starlet-Bootstrap.ps1 new file mode 100644 index 0000000000..ccc07a1be1 --- /dev/null +++ b/Run-Wii-Starlet-Bootstrap.ps1 @@ -0,0 +1,322 @@ +param( + [string]$ELF = '../starlet-ios-rust/target/armebv5te-none-eabi/release/revolution-ios-bootstrap', + [string]$BuildDirectory = '.starlet_msvc2', + [string]$UserDirectory = '.starlet_user3', + [ValidateSet('Idle', 'Panic', 'Fatal', 'Fault', 'Phase2', 'Phase3Raw', 'Phase3Ios', 'Phase4Fs', 'Phase5Dir', 'Phase5Es', 'Phase5Client', 'Phase6Write', 'Phase6Verify', 'Phase8Client', 'Phase9Resolve', 'Phase9Launch', 'Phase9Menu', 'Phase9Rescue', 'Phase10AOh1')][string]$ExpectedOutcome = 'Idle', + [string]$PpcDol = '', + [int]$TimeoutSeconds = 30, + # Phase9Launch: the launched program's minimum check count. 5 for the + # P09-H02 payload and a requested P09-H02M launch; 3 for a P09-H02M launch + # made by the kernel's watchdog (no launcher mailbox to check). + [ValidateRange(1, 5)][int]$Phase9MinimumChecks = 5, + # Phase10AOh1: the program's minimum check count. 40 for the P10A-H07 + # reduced smoke; 16 for the P10A-H07B launched payload. + [ValidateRange(1, 1000)][int]$Phase10AMinimumChecks = 40, + [switch]$JIT, + [switch]$FullDolphin +) + +$ErrorActionPreference = 'Stop' +$repoRoot = Split-Path -Parent $MyInvocation.MyCommand.Path +$elfCandidate = if ([System.IO.Path]::IsPathRooted($ELF)) { $ELF } else { Join-Path $repoRoot $ELF } +$elfPath = (Resolve-Path -LiteralPath $elfCandidate).Path +$dolPath = if ($PpcDol) { (Resolve-Path -LiteralPath $PpcDol).Path } else { '' } +$probePath = Join-Path $repoRoot "$BuildDirectory/Binaries/Tests/StarletBootstrapProbe.exe" +if (-not $FullDolphin) { + if ($PpcDol) { throw 'PpcDol requires FullDolphin' } + if (-not (Test-Path -LiteralPath $probePath -PathType Leaf)) { + throw "Build StarletBootstrapProbe first: $probePath" + } + $probeArguments = @($elfPath) + if ($JIT) { $probeArguments += '--jit' } + if ($ExpectedOutcome -eq 'Panic') { $probeArguments += '--expect-panic' } + if ($ExpectedOutcome -eq 'Fatal') { $probeArguments += '--expect-fatal-data' } + if ($ExpectedOutcome -eq 'Fault') { $probeArguments += '--expect-undefined-fault' } + if ($ExpectedOutcome -eq 'Phase2') { $probeArguments += '--expect-phase2' } + & $probePath @probeArguments + exit $LASTEXITCODE +} + +$dolphinPath = Join-Path $repoRoot "$BuildDirectory/Binaries/DolphinNoGUI.exe" +$userPath = (Resolve-Path -LiteralPath (Join-Path $repoRoot $UserDirectory)).Path +$logPath = Join-Path $userPath 'Logs/dolphin.log' + +if (-not (Test-Path -LiteralPath $dolphinPath -PathType Leaf)) { + throw "Build DolphinNoGUI first: $dolphinPath" +} +if ($TimeoutSeconds -lt 1) { throw 'TimeoutSeconds must be positive' } +New-Item -ItemType Directory -Force -Path (Split-Path -Parent $logPath) | Out-Null + +$bootArguments = if ($dolPath) { @('-e', "`"$dolPath`"") } else { @('-n', '0000000100000002') } +$arguments = @('-u', "`"$userPath`"") + $bootArguments + @( + '-C', 'Dolphin.Core.WiiIOSLLE=True', + '-C', 'Dolphin.Core.WiiSDCard=False', + '-C', "Dolphin.Core.WiiStarletJIT=$($JIT.IsPresent)", + '-C', "`"Dolphin.General.WiiStarletBootstrapELF=$elfPath`"", + '-C', 'Logger.Options.WriteToFile=True', + '-C', 'Logger.Options.Verbosity=4', + '-C', 'Logger.Logs.IOS=True', + '-v', 'Null', '-p', 'headless' +) + +$position = if (Test-Path -LiteralPath $logPath) { (Get-Item -LiteralPath $logPath).Length } else { 0 } +$pending = '' +$trace = [System.Collections.Generic.List[string]]::new() +$rescueStarted = $false +$outcome = $null +$faultLine = $null +$probeCounters = $null +$probeChecks = $null +$dolphin = Start-Process -FilePath $dolphinPath -ArgumentList $arguments ` + -WorkingDirectory $repoRoot -PassThru -WindowStyle Hidden + +try { + $deadline = (Get-Date).AddSeconds($TimeoutSeconds) + while ((Get-Date) -lt $deadline) { + if (Test-Path -LiteralPath $logPath) { + $stream = [System.IO.File]::Open($logPath, [System.IO.FileMode]::Open, + [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite) + try { + if ($position -gt $stream.Length) { $position = 0; $pending = '' } + [void]$stream.Seek($position, [System.IO.SeekOrigin]::Begin) + $reader = [System.IO.StreamReader]::new($stream, [System.Text.Encoding]::UTF8, + $false, 4096, $true) + try { $chunk = $reader.ReadToEnd() } finally { $reader.Dispose() } + $position = $stream.Position + } finally { + $stream.Dispose() + } + if ($chunk.Length -gt 0) { + $lines = ($pending + $chunk) -split "`n" + $pending = $lines[-1] + for ($index = 0; $index -lt $lines.Length - 1; ++$index) { + $line = $lines[$index] + if ($line -match 'Starlet bootstrap: (.+)$') { + $message = $Matches[1].TrimEnd("`r") + $trace.Add($message) + Write-Host $message + if ($message -eq 'REV:BOOT:IDLE' -and $ExpectedOutcome -notin @('Phase3Raw', 'Phase3Ios', 'Phase4Fs', 'Phase5Dir', 'Phase5Es', 'Phase5Client', 'Phase6Write', 'Phase6Verify', 'Phase8Client', 'Phase9Launch', 'Phase9Menu', 'Phase9Rescue', 'Phase10AOh1')) { + $outcome = if ($ExpectedOutcome -eq 'Phase2') { 'Phase2' } else { 'Idle' } + } + elseif ($message -like 'REV:PANIC*') { $outcome = 'Panic' } + # Revolution IOS Phase 09 P09-H01: the read-only System Menu resolution ends. + elseif ($ExpectedOutcome -eq 'Phase9Resolve' -and $message -like 'REV:P9:H01 DONE*') { $outcome = 'Phase9Resolve' } + elseif ($ExpectedOutcome -eq 'Phase9Resolve' -and $message -like 'REV:P9:H01 *FAIL*') { $outcome = 'Phase9ResolveFailed' } + # Revolution IOS Phase 09 P09-H04: the System Menu's observation ends with the kernel's + # summary (no request for a while, or none at all); a load failure ends it too. + elseif ($ExpectedOutcome -eq 'Phase9Menu' -and ($message -like 'REV:P9:H04 QUIET *' -or $message -like 'REV:P9:H04 NOIPC *')) { $outcome = 'Phase9Menu' } + elseif ($ExpectedOutcome -eq 'Phase9Menu' -and $message -like 'REV:P9:H0[34] FAIL*') { $outcome = 'Phase9MenuFailed' } + # P09-H04 rescue: the kernel stopped the Menu and launches the rescue screen, whose + # status (0x52500005 once drawn) ends the run below. + elseif ($ExpectedOutcome -eq 'Phase9Rescue' -and $message -like 'REV:P9:H04 RESCUE GO *') { $rescueStarted = $true } + elseif ($ExpectedOutcome -eq 'Phase9Rescue' -and ($message -like 'REV:P9:H0[34] FAIL*' -or $message -like 'REV:P9:H04 RESCUE PLAN FAIL*')) { $outcome = 'Phase9RescueFailed' } + elseif ($message -like 'REV:FATAL*') { $outcome = 'Fatal' } + } elseif ($line -match 'Starlet bootstrap exception|Starlet bootstrap trace length is invalid') { + $outcome = 'Fault' + $faultLine = $line.TrimEnd("`r") + Write-Host $faultLine + } elseif ($line -match 'Broadway ios-probe status=(0x[0-9a-f]+)') { + $probeStatus = $Matches[1] + Write-Host "PPC: $probeStatus" + if ($probeStatus -eq '0x52500002') { $outcome = 'Phase3Raw' } + elseif ($probeStatus -eq '0x52500005' -and $ExpectedOutcome -eq 'Phase10AOh1') { $outcome = 'Phase10AOh1' } + elseif ($probeStatus -eq '0x52500003') { $outcome = 'Phase3Ios' } + # Phase 09 P09-H02: the payload's success is final only with the kernel's + # release and first-request lines (checked after each chunk below). + elseif ($probeStatus -eq '0x52500005' -and $ExpectedOutcome -eq 'Phase9Launch') { $payloadPassed = $true } + elseif ($ExpectedOutcome -eq 'Phase9Rescue') { if ($probeStatus -eq '0x52500005' -and $rescueStarted) { $outcome = 'Phase9Rescue' } } + elseif ($probeStatus -eq '0x52500005') { $outcome = $(if ($ExpectedOutcome -in @('Phase5Dir', 'Phase5Es', 'Phase5Client', 'Phase6Write', 'Phase6Verify', 'Phase8Client')) { $ExpectedOutcome } else { 'Phase4Fs' }) } + # Phase 09 P09-H02M: a later program's status (the launched monitor + # starting) voids an earlier program's pass and its checks. + elseif ($ExpectedOutcome -eq 'Phase9Launch' -and $probeStatus -notmatch '^0x5250e') { $payloadPassed = $false; $probeChecks = $null } + elseif ($probeStatus -match '^0x5250e') { $outcome = 'ProbeFailed' } + } elseif ($line -match 'Broadway ios-probe counters sent=(\d+) received=(\d+) invalid=(\d+) duplicate=(\d+) timeout=(\d+)') { + $probeCounters = @([int]$Matches[1], [int]$Matches[2], [int]$Matches[3], + [int]$Matches[4], [int]$Matches[5]) + Write-Host "PPC counters: $($probeCounters -join '/')" + } elseif ($line -match 'Broadway ios-probe checks=(\d+) failures=(\d+)') { + $probeChecks = @([int]$Matches[1], [int]$Matches[2]) + Write-Host "PPC checks: $($probeChecks -join '/')" + } + } + } + } + if (-not $outcome -and $ExpectedOutcome -eq 'Phase9Launch' -and $payloadPassed -and $probeChecks -and + @($trace | Where-Object { $_ -like 'REV:P9:H02 GO *' }).Count -eq 1 -and + @($trace | Where-Object { $_ -like 'REV:P9:H02 FIRST CMD *' }).Count -eq 1) { + $outcome = 'Phase9Launch' + } + if ($outcome) { break } + if ($dolphin.HasExited) { break } + Start-Sleep -Milliseconds 200 + } + if ($outcome -ne $ExpectedOutcome) { + $exitDetail = if ($dolphin.HasExited) { "Dolphin exited with code $($dolphin.ExitCode). " } else { '' } + throw "$exitDetail Bootstrap outcome '$outcome' (expected '$ExpectedOutcome'); see $logPath" + } + if ($ExpectedOutcome -eq 'Fault') { + if ($faultLine -notmatch 'Starlet bootstrap exception 1: LR=0x[0-9a-f]+ SPSR=0x[0-9a-f]+ PC=0x[0-9a-f]+') { + throw "Expected an undefined-instruction exception with registers; see $logPath" + } + } + if ($ExpectedOutcome -eq 'Fatal') { + if (-not $trace.Contains('REV:BOOT:ENTRY') -or + -not $trace.Contains('REV:BOOT:BSS_OK') -or + -not $trace.Contains('REV:FATAL:DATA') -or + $trace.Contains('REV:BOOT:DATA_OK')) { + throw "Fatal data check did not produce the expected markers; see $logPath" + } + } + if ($ExpectedOutcome -eq 'Phase2') { + $requiredPhase2 = @('REV:P2:START', 'ARCH PASS', 'CRITICAL PASS', 'TIMER PASS', + 'MONOTONIC PASS', 'MEM PASS', 'IRQ PASS', 'BARRIER PASS', 'LOGGING PASS', + 'REV:P2:PASS', 'REV:P2:KERNEL_READY', 'REV:BOOT:IDLE') + $missingPhase2 = @($requiredPhase2 | Where-Object { -not $trace.Contains($_) }) + if ($missingPhase2.Count -gt 0 -or @($trace | Where-Object { $_ -like '* FAIL' }).Count -gt 0) { + throw "Phase 02 self-test markers are incomplete; see $logPath" + } + } + if ($ExpectedOutcome -eq 'Phase3Raw' -and + (-not $trace.Contains('REV:P3A:START') -or + -not $trace.Contains('REV:P3A:ARM_128') -or + -not $trace.Contains('REV:P3A:ARM_PASS') -or + -not $probeCounters -or + $probeCounters[0] -ne 128 -or $probeCounters[1] -ne 128 -or + $probeCounters[2] -ne 0 -or $probeCounters[3] -ne 0 -or + $probeCounters[4] -ne 0)) { + throw "Phase 03A Starlet markers are incomplete; see $logPath" + } + if ($ExpectedOutcome -eq 'Phase3Ios' -and + (-not $trace.Contains('REV:P3B:START') -or + -not $trace.Contains('REV:P3B:OPEN_OK') -or + -not $trace.Contains('REV:P3B:PING_OK') -or + -not $trace.Contains('REV:P3B:CLOSE_OK') -or + -not $probeCounters -or $probeCounters[0] -lt 200 -or + $probeCounters[0] -ne $probeCounters[1] -or + $probeCounters[2] -ne 0 -or $probeCounters[3] -ne 0 -or + $probeCounters[4] -ne 0 -or + -not $probeChecks -or $probeChecks[0] -lt 260 -or + $probeChecks[1] -ne 0)) { + throw "Phase 03B IOS probe markers or counters are incomplete; see $logPath" + } + # Revolution IOS Phase 04D: the shared /dev/fs cases (ppc/common/fs_cases.h). + if ($ExpectedOutcome -eq 'Phase4Fs' -and + (-not $trace.Contains('REV:P3B:START') -or + @($trace | Where-Object { $_ -like 'REV:P4:FS MOUNT GEN * AUTH YES *' }).Count -ne 1 -or + -not $probeCounters -or $probeCounters[0] -lt 100 -or + $probeCounters[0] -ne $probeCounters[1] -or + $probeCounters[2] -ne 0 -or $probeCounters[3] -ne 0 -or + $probeCounters[4] -ne 0 -or + -not $probeChecks -or $probeChecks[0] -lt 30 -or + $probeChecks[1] -ne 0)) { + throw "Phase 04D /dev/fs markers or counters are incomplete; see $logPath" + } + # Revolution IOS Phase 05B: the READDIR/GETUSAGE cases (ppc/common/fs_dir_cases.h). + if ($ExpectedOutcome -eq 'Phase5Dir' -and + (-not $trace.Contains('REV:P3B:START') -or + @($trace | Where-Object { $_ -like 'REV:P4:FS MOUNT GEN * AUTH YES *' }).Count -ne 1 -or + -not $probeCounters -or $probeCounters[0] -lt 600 -or + $probeCounters[0] -ne $probeCounters[1] -or + $probeCounters[2] -ne 0 -or $probeCounters[3] -ne 0 -or + $probeCounters[4] -ne 0 -or + -not $probeChecks -or $probeChecks[0] -lt 13 -or + $probeChecks[1] -ne 0)) { + throw "Phase 05B READDIR/GETUSAGE markers or counters are incomplete; see $logPath" + } + # Revolution IOS Phase 05C/05D: the /dev/es cases (ppc/common/es_cases.h). + if ($ExpectedOutcome -eq 'Phase5Es' -and + (-not $trace.Contains('REV:P3B:START') -or + @($trace | Where-Object { $_ -like 'REV:P4:FS MOUNT GEN * AUTH YES *' }).Count -ne 1 -or + -not $probeCounters -or $probeCounters[0] -lt 600 -or + $probeCounters[0] -ne $probeCounters[1] -or + $probeCounters[2] -ne 0 -or $probeCounters[3] -ne 0 -or + $probeCounters[4] -ne 0 -or + -not $probeChecks -or $probeChecks[0] -lt 18 -or + $probeChecks[1] -ne 0)) { + throw "Phase 05C /dev/es markers or counters are incomplete; see $logPath" + } + # P10A-H07 reduced smoke: real service on the emulator USB double; no + # local loopback and no physical acceptance. No synthetic IPC counters. + if ($ExpectedOutcome -eq 'Phase10AOh1' -and + (-not $trace.Contains('REV:P10A:OH1 READY REAL USB') -or + @($trace | Where-Object { $_ -like 'REV:P4:FS MOUNT GEN * AUTH YES *' }).Count -ne 1 -or + -not $probeChecks -or $probeChecks[0] -lt $Phase10AMinimumChecks -or $probeChecks[1] -ne 0)) { + throw 'Phase 10A OH1 reduced smoke markers or checks are incomplete' + } + # Revolution IOS Phase 05F: the unchanged libogc stock client (ppc/stock-client). + if ($ExpectedOutcome -eq 'Phase5Client' -and + (-not $trace.Contains('REV:P3B:START') -or + @($trace | Where-Object { $_ -like 'REV:P4:FS MOUNT GEN * AUTH YES *' }).Count -ne 1 -or + -not $probeCounters -or $probeCounters[0] -lt 16 -or + $probeCounters[0] -ne $probeCounters[1] -or + -not $probeChecks -or $probeChecks[0] -lt 16 -or + $probeChecks[1] -ne 0)) { + throw "Phase 05F stock client markers or counters are incomplete; see $logPath" + } + # Revolution IOS Phase 06H: the writable scenario (ppc/fs-write-probe, mode Write) on the + # emulator-only writable kernel over the disposable NAND (REV_BOOTSTRAP_NAND_DISPOSABLE). + if ($ExpectedOutcome -eq 'Phase6Write' -and + (-not $trace.Contains('REV:P3B:START') -or + @($trace | Where-Object { $_ -like 'REV:P6:FS MOUNT GEN * WRITABLE' }).Count -ne 1 -or + @($trace | Where-Object { $_ -like 'REV:P6:FS GEN * REMOUNT *' }).Count -ne 0 -or + -not $probeCounters -or $probeCounters[0] -lt 100 -or + $probeCounters[0] -ne $probeCounters[1] -or + $probeCounters[2] -ne 0 -or $probeCounters[3] -ne 0 -or + $probeCounters[4] -ne 0 -or + -not $probeChecks -or $probeChecks[0] -lt 60 -or + $probeChecks[1] -ne 0)) { + throw "Phase 06H writable scenario markers or counters are incomplete; see $logPath" + } + # Revolution IOS Phase 06H: the resulting image after a fresh boot of the read-only kernel + # (ppc/fs-write-probe, mode Verify). + if ($ExpectedOutcome -eq 'Phase6Verify' -and + (-not $trace.Contains('REV:P3B:START') -or + @($trace | Where-Object { $_ -like 'REV:P4:FS MOUNT GEN * AUTH YES *' }).Count -ne 1 -or + -not $probeCounters -or $probeCounters[0] -lt 30 -or + $probeCounters[0] -ne $probeCounters[1] -or + $probeCounters[2] -ne 0 -or $probeCounters[3] -ne 0 -or + $probeCounters[4] -ne 0 -or + -not $probeChecks -or $probeChecks[0] -lt 25 -or + $probeChecks[1] -ne 0)) { + throw "Phase 06H verification markers or counters are incomplete; see $logPath" + } + # Revolution IOS Phase 08 H07R (section 69): the libogc read-after-reboot client + # (ppc/p8-client/reboot.c), the same boot.dol as on the console, on the emulator-only + # writable kernel over the disposable NAND. It writes only its status word (0x52500005 when + # every check of its part passed); the part is proven by the NAND copy, outside this script. + if ($ExpectedOutcome -eq 'Phase8Client' -and + (-not $trace.Contains('REV:P3B:START') -or + @($trace | Where-Object { $_ -like 'REV:P6:FS MOUNT GEN * WRITABLE' }).Count -ne 1)) { + throw "Phase 08 client markers are incomplete; see $logPath" + } + # Revolution IOS Phase 09 P09-H02: Broadway stopped and restarted into the payload while the + # kernel stays resident; the payload's own checks passed. + if ($ExpectedOutcome -eq 'Phase9Launch' -and + (@($trace | Where-Object { $_ -like 'REV:P9:H02 GO *' }).Count -ne 1 -or + @($trace | Where-Object { $_ -like 'REV:P9:H02 FIRST CMD *' }).Count -ne 1 -or + -not $probeChecks -or $probeChecks[0] -lt $Phase9MinimumChecks -or $probeChecks[1] -ne 0)) { + throw "Phase 09 launch markers or checks are incomplete; see $logPath" + } + # Revolution IOS Phase 09 P09-H04: the Menu was resolved, profiled, loaded and released + # (M0-M4) before the observation ended. + if ($ExpectedOutcome -in @('Phase9Menu', 'Phase9Rescue')) { + $missingMenu = @('REV:P9:H04 M0 ', 'REV:P9:H04 M1 ', 'REV:P9:H04 M2 STOPPED LOWMEM VERIFY OK', 'REV:P9:H04 M3 ', 'REV:P9:H04 M4 ') | + Where-Object { $prefix = $_; @($trace | Where-Object { $_.StartsWith($prefix) }).Count -ne 1 } + if (@($missingMenu).Count -gt 0) { + throw "Phase 09 System Menu milestones are incomplete ($($missingMenu -join ', ')); see $logPath" + } + } + $required = @('REV:BOOT:ENTRY', 'REV:BOOT:BSS_OK', 'REV:BOOT:DATA_OK', + 'REV:BOOT:STACK_OK', 'REV:BOOT:RUST', 'Revolution IOS', + 'Starlet Rust bootstrap reached') + $missing = @($required | Where-Object { -not $trace.Contains($_) }) + if ($ExpectedOutcome -in @('Idle', 'Phase2') -and $missing.Count -gt 0) { + throw "Bootstrap reached idle without all expected markers; see $logPath" + } + Write-Host "Starlet bootstrap $outcome passed ($($trace.Count) trace lines)." +} finally { + if (-not $dolphin.HasExited) { + Stop-Process -Id $dolphin.Id + $dolphin.WaitForExit() + } +} diff --git a/Source/Core/Core/Boot/Boot.cpp b/Source/Core/Core/Boot/Boot.cpp index 9316e719ee..868c8ba564 100644 --- a/Source/Core/Core/Boot/Boot.cpp +++ b/Source/Core/Core/Boot/Boot.cpp @@ -1,799 +1,804 @@ -// Copyright 2008 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "Core/Boot/Boot.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "Common/CommonPaths.h" -#include "Common/CommonTypes.h" -#include "Common/Config/Config.h" -#include "Common/FileUtil.h" -#include "Common/Hash.h" -#include "Common/IOFile.h" -#include "Common/Logging/Log.h" -#include "Common/MsgHandler.h" -#include "Common/StringUtil.h" - -#include "Core/AchievementManager.h" -#include "Core/Boot/DolReader.h" -#include "Core/Boot/ElfReader.h" -#include "Core/CommonTitles.h" -#include "Core/Config/MainSettings.h" -#include "Core/Config/SYSCONFSettings.h" -#include "Core/ConfigManager.h" -#include "Core/FifoPlayer/FifoPlayer.h" -#include "Core/HLE/HLE.h" -#include "Core/HW/DVD/AMMediaboard.h" -#include "Core/HW/DVD/DVDInterface.h" -#include "Core/HW/EXI/EXI_DeviceIPL.h" -#include "Core/HW/Memmap.h" -#include "Core/HW/VideoInterface.h" -#include "Core/Host.h" -#include "Core/IOS/ES/ES.h" -#include "Core/IOS/FS/FileSystem.h" -#include "Core/IOS/IOS.h" -#include "Core/IOS/IOSC.h" -#include "Core/IOS/Uids.h" -#include "Core/NetPlayProto.h" -#include "Core/PatchEngine.h" -#include "Core/PowerPC/PPCAnalyst.h" -#include "Core/PowerPC/PPCSymbolDB.h" -#include "Core/PowerPC/PowerPC.h" -#include "Core/System.h" - -#include "DiscIO/Enums.h" -#include "DiscIO/GameModDescriptor.h" -#include "DiscIO/RiivolutionParser.h" -#include "DiscIO/RiivolutionPatcher.h" -#include "DiscIO/VolumeDisc.h" -#include "DiscIO/VolumeWad.h" - -static std::vector ReadM3UFile(const std::string& m3u_path, - const std::string& folder_path) -{ - std::vector result; - std::vector nonexistent; - - std::ifstream s; - File::OpenFStream(s, m3u_path, std::ios_base::in); - - std::string line; - while (std::getline(s, line)) - { - // This is the UTF-8 representation of U+FEFF. - constexpr std::string_view utf8_bom = "\xEF\xBB\xBF"; - -#ifndef _WIN32 - if (line.ends_with('\r')) - line.pop_back(); -#endif - - if (line.starts_with(utf8_bom)) - { - WARN_LOG_FMT(BOOT, "UTF-8 BOM in file: {}", m3u_path); - line.erase(0, utf8_bom.length()); - } - - if (!line.empty() && line.front() != '#') // Comments start with # - { - const std::string path_to_add = PathToString(StringToPath(folder_path) / StringToPath(line)); - (File::Exists(path_to_add) ? result : nonexistent).push_back(path_to_add); - } - } - - if (!nonexistent.empty()) - { - PanicAlertFmtT("Files specified in the M3U file \"{0}\" were not found:\n{1}", m3u_path, - fmt::join(nonexistent, "\n")); - return {}; - } - - if (result.empty()) - PanicAlertFmtT("No paths found in the M3U file \"{0}\"", m3u_path); - - return result; -} - -BootSessionData::BootSessionData() -{ -} - -BootSessionData::BootSessionData(std::optional savestate_path, - DeleteSavestateAfterBoot delete_savestate) - : m_savestate_path(std::move(savestate_path)), m_delete_savestate(delete_savestate) -{ -} - -BootSessionData::BootSessionData(BootSessionData&& other) = default; - -BootSessionData& BootSessionData::operator=(BootSessionData&& other) = default; - -BootSessionData::~BootSessionData() = default; - -const std::optional& BootSessionData::GetSavestatePath() const -{ - return m_savestate_path; -} - -DeleteSavestateAfterBoot BootSessionData::GetDeleteSavestate() const -{ - return m_delete_savestate; -} - -void BootSessionData::SetSavestateData(std::optional savestate_path, - DeleteSavestateAfterBoot delete_savestate) -{ - m_savestate_path = std::move(savestate_path); - m_delete_savestate = delete_savestate; -} - -IOS::HLE::FS::FileSystem* BootSessionData::GetWiiSyncFS() const -{ - return m_wii_sync_fs.get(); -} - -const std::vector& BootSessionData::GetWiiSyncTitles() const -{ - return m_wii_sync_titles; -} - -const std::string& BootSessionData::GetWiiSyncRedirectFolder() const -{ - return m_wii_sync_redirect_folder; -} - -void BootSessionData::InvokeWiiSyncCleanup() const -{ - if (m_wii_sync_cleanup) - m_wii_sync_cleanup(); -} - -void BootSessionData::SetWiiSyncData(std::unique_ptr fs, - std::vector titles, std::string redirect_folder, - WiiSyncCleanupFunction cleanup) -{ - m_wii_sync_fs = std::move(fs); - m_wii_sync_titles = std::move(titles); - m_wii_sync_redirect_folder = std::move(redirect_folder); - m_wii_sync_cleanup = std::move(cleanup); -} - -const NetPlay::NetSettings* BootSessionData::GetNetplaySettings() const -{ - return m_netplay_settings.get(); -} - -void BootSessionData::SetNetplaySettings(std::unique_ptr netplay_settings) -{ - m_netplay_settings = std::move(netplay_settings); -} - -BootParameters::BootParameters(Parameters&& parameters_, BootSessionData boot_session_data_) - : parameters(std::move(parameters_)), boot_session_data(std::move(boot_session_data_)) -{ -} - -std::unique_ptr BootParameters::GenerateFromFile(std::string boot_path, - BootSessionData boot_session_data_) -{ - return GenerateFromFile(std::vector{std::move(boot_path)}, - std::move(boot_session_data_)); -} - -std::unique_ptr BootParameters::GenerateFromFile(std::vector paths, - BootSessionData boot_session_data_) -{ - ASSERT(!paths.empty()); - - for (std::string& path : paths) - UnifyPathSeparators(path); - - // Check if the file exist, we may have gotten it from a --elf command line - // that gave an incorrect file name - if (!File::Exists(paths.front())) - { - PanicAlertFmtT("The specified file \"{0}\" does not exist", paths.front()); - return {}; - } - - std::string folder_path; - std::string extension; - SplitPath(paths.front(), &folder_path, nullptr, &extension); - Common::ToLower(&extension); - - if (extension == ".m3u" || extension == ".m3u8") - { - paths = ReadM3UFile(paths.front(), folder_path); - if (paths.empty()) - return {}; - - for (std::string& path : paths) - UnifyPathSeparators(path); - - SplitPath(paths.front(), nullptr, nullptr, &extension); - Common::ToLower(&extension); - } - - std::string path = paths.front(); - if (paths.size() == 1) - paths.clear(); - -#ifdef ANDROID - if (extension.empty() && IsPathAndroidContent(path)) - { - const std::string display_name = GetAndroidContentDisplayName(path); - SplitPath(display_name, nullptr, nullptr, &extension); - Common::ToLower(&extension); - } -#endif - - static const std::unordered_set disc_image_extensions = { - {".gcm", ".bin", ".iso", ".tgc", ".wbfs", ".ciso", ".gcz", ".wia", ".rvz", ".nfs", ".dol", - ".elf"}}; - if (disc_image_extensions.contains(extension)) - { - std::unique_ptr disc = DiscIO::CreateDiscForCore(path); - if (disc) - { - return std::make_unique(Disc{std::move(path), std::move(disc), paths}, - std::move(boot_session_data_)); - } - - if (extension == ".elf") - { - auto elf_reader = std::make_unique(path); - return std::make_unique(Executable{std::move(path), std::move(elf_reader)}, - std::move(boot_session_data_)); - } - - if (extension == ".dol") - { - auto dol_reader = std::make_unique(path); - return std::make_unique(Executable{std::move(path), std::move(dol_reader)}, - std::move(boot_session_data_)); - } - - PanicAlertFmtT("\"{0}\" is an invalid GCM/ISO file, or is not a GC/Wii ISO.", path); - return {}; - } - - if (extension == ".dff") - return std::make_unique(DFF{std::move(path)}, std::move(boot_session_data_)); - - if (extension == ".wad") - { - std::unique_ptr wad = DiscIO::CreateWAD(path); - if (wad) - return std::make_unique(std::move(*wad), std::move(boot_session_data_)); - } - - if (extension == ".json") - { - auto descriptor = DiscIO::ParseGameModDescriptorFile(path); - if (descriptor) - { - auto boot_params = GenerateFromFile(descriptor->base_file, std::move(boot_session_data_)); - if (!boot_params) - { - PanicAlertFmtT("Could not recognize file {0}", descriptor->base_file); - return nullptr; - } - - if (descriptor->riivolution && std::holds_alternative(boot_params->parameters)) - { - const auto& volume = *std::get(boot_params->parameters).volume; - AddRiivolutionPatches(boot_params.get(), - DiscIO::Riivolution::GenerateRiivolutionPatchesFromGameModDescriptor( - *descriptor->riivolution, volume.GetGameID(), - volume.GetRevision(), volume.GetDiscNumber())); - } - - return boot_params; - } - } - - PanicAlertFmtT("Could not recognize file {0}", path); - return {}; -} - -BootParameters::IPL::IPL(DiscIO::Region region_) : region(region_) -{ - const std::string directory = Config::GetDirectoryForRegion(region); - path = Config::GetBootROMPath(directory); -} - -BootParameters::IPL::IPL(DiscIO::Region region_, Disc&& disc_) : IPL(region_) -{ - disc = std::move(disc_); -} - -// Inserts a disc into the emulated disc drive and returns a pointer to it. -// The returned pointer must only be used while we are still booting, -// because DVDThread can do whatever it wants to the disc after that. -static const DiscIO::VolumeDisc* SetDisc(DVD::DVDInterface& dvd_interface, - std::unique_ptr disc, - std::vector auto_disc_change_paths = {}) -{ - const DiscIO::VolumeDisc* pointer = disc.get(); - dvd_interface.SetDisc(std::move(disc), auto_disc_change_paths); - return pointer; -} - -bool CBoot::DVDRead(Core::System& system, const DiscIO::VolumeDisc& disc, u64 dvd_offset, - u32 output_address, u32 length, const DiscIO::Partition& partition) -{ - std::vector buffer(length); - if (!disc.Read(dvd_offset, length, buffer.data(), partition)) - return false; - - auto& memory = system.GetMemory(); - memory.CopyToEmu(output_address, buffer.data(), length); - return true; -} - -bool CBoot::DVDReadDiscID(Core::System& system, const DiscIO::VolumeDisc& disc, u32 output_address) -{ - std::array buffer; - if (!disc.Read(0, buffer.size(), buffer.data(), DiscIO::PARTITION_NONE)) - return false; - - auto& memory = system.GetMemory(); - memory.CopyToEmu(output_address, buffer.data(), buffer.size()); - - // Transition out of the DiscIdNotRead state (which the drive should be in at this point, - // on the assumption that this is only used for the first read) - system.GetDVDInterface().SetDriveState(DVD::DriveState::ReadyNoReadsMade); - return true; -} - -// If ipl.bin is not found, this function does *some* of what BS1 does: -// loading IPL(BS2) and jumping to it. -// It does not initialize the hardware or anything else like BS1 does. -bool CBoot::Load_BS2(Core::System& system, const std::string& boot_rom_filename) -{ - // CRC32 hashes of the IPL file, obtained from Redump - // DOL-001(USA) / DOL-001(JPN) / DOT-001 / SL-GC10 (NTSC Revision 1.0) - constexpr u32 NTSC_v1_0 = 0x6DAC1F2A; - // DOL-001(USA) / DOL-001(JPN) / DOT-001 (NTSC Revision 1.1) - constexpr u32 NTSC_v1_1 = 0xD5E6FEEA; - // DOL-001(USA) / DOL-001(JPN) (NTSC Revision 1.2) - constexpr u32 NTSC_v1_2_001 = 0xD235E3F9; - // DOL-101(USA) / DOL-101(JPN) (NTSC Revision 1.2) - constexpr u32 NTSC_v1_2_101 = 0x86573808; - // DOL-002(BRA) (MPAL Revision 1.1) - constexpr u32 MPAL_v1_1 = 0x667D0B64; - // DOL-001(EUR) / DOT-001P (PAL Revision 1.0) - constexpr u32 PAL_v1_0 = 0x4F319F43; - // DOL-101(EUR) (PAL Revision 1.2) - constexpr u32 PAL_v1_2 = 0xAD1B7F16; - // Triforce Arcade IPL (DEV Revision 1.0) - constexpr u32 Triforce = 0xD1883221; - - // Load the IPL ROM dump, limited to 2MiB which is the size of the official IPLs. - constexpr size_t max_ipl_size = 2 * 1024 * 1024; - std::vector data; - { - File::IOFile file(boot_rom_filename, "rb"); - if (!file) - return false; - - data.resize(static_cast(std::min(file.GetSize(), max_ipl_size))); - if (!file.ReadArray(data.data(), data.size())) - return false; - } - - const u32 ipl_hash = Common::ComputeCRC32(data.data(), data.size()); - bool known_ipl = false; - bool pal_ipl = false; - switch (ipl_hash) - { - case NTSC_v1_0: - case NTSC_v1_1: - case NTSC_v1_2_001: - case NTSC_v1_2_101: - case MPAL_v1_1: - known_ipl = true; - break; - case PAL_v1_0: - case PAL_v1_2: - pal_ipl = true; - known_ipl = true; - break; - case Triforce: - known_ipl = true; - break; - default: - PanicAlertFmtT("The IPL file is not a known good dump. (CRC32: {0:x})", ipl_hash); - break; - } - - const DiscIO::Region boot_region = SConfig::GetInstance().m_region; - if (known_ipl && pal_ipl != (boot_region == DiscIO::Region::PAL)) - { - PanicAlertFmtT("{0} IPL found in {1} directory. The disc might not be recognized", - pal_ipl ? "PAL" : "NTSC", Config::GetDirectoryForRegion(boot_region)); - } - - // Run the descrambler over the encrypted section containing BS1/BS2 - if (data.size() > 0x100) - { - ExpansionInterface::CEXIIPL::Descrambler( - data.data() + 0x100, static_cast(std::min(data.size() - 0x100, 0x1AFE00))); - } - - // TODO: Execution is supposed to start at 0xFFF00000, not 0x81200000; - // copying the initial boot code to 0x81200000 is a hack. - // For now, HLE the first few instructions and start at 0x81200150 - // to work around this. - auto& memory = system.GetMemory(); - if (data.size() > 0x100) - { - memory.CopyToEmu(0x01200000, data.data() + 0x100, std::min(data.size() - 0x100, 0x700)); - } - if (data.size() > 0x820) - { - memory.CopyToEmu(0x01300000, data.data() + 0x820, - std::min(data.size() - 0x820, 0x1AFE00)); - } - - auto& ppc_state = system.GetPPCState(); - ppc_state.gpr[3] = 0xfff0001f; - ppc_state.gpr[4] = 0x00002030; - ppc_state.gpr[5] = 0x0000009c; - - ppc_state.msr.FP = 1; - ppc_state.msr.DR = 1; - ppc_state.msr.IR = 1; - - ppc_state.spr[SPR_HID0] = 0x0011c464; - ppc_state.spr[SPR_IBAT3U] = 0xfff0001f; - ppc_state.spr[SPR_IBAT3L] = 0xfff00001; - ppc_state.spr[SPR_DBAT3U] = 0xfff0001f; - ppc_state.spr[SPR_DBAT3L] = 0xfff00001; - SetupBAT(system, /*is_wii*/ false); - - if (ipl_hash == Triforce) - { - HLE::Patch(system, 0x813048B8, "OSReport"); - HLE::Patch(system, 0x8130095C, "OSReport"); // Apploader - - AMMediaboard::FirmwareMap(true); - } - - ppc_state.pc = 0x81200150; - - system.GetPowerPC().MSRUpdated(); - - return true; -} - -static void SetDefaultDisc(DVD::DVDInterface& dvd_interface) -{ - const std::string default_iso = Config::Get(Config::MAIN_DEFAULT_ISO); - if (!default_iso.empty()) - SetDisc(dvd_interface, DiscIO::CreateDiscForCore(default_iso)); -} - -static void CopyDefaultExceptionHandlers(Core::System& system) -{ - constexpr u32 EXCEPTION_HANDLER_ADDRESSES[] = {0x00000100, 0x00000200, 0x00000300, 0x00000400, - 0x00000500, 0x00000600, 0x00000700, 0x00000800, - 0x00000900, 0x00000C00, 0x00000D00, 0x00000F00, - 0x00001300, 0x00001400, 0x00001700}; - - auto& memory = system.GetMemory(); - constexpr u32 RFI_INSTRUCTION = 0x4C000064; - for (const u32 address : EXCEPTION_HANDLER_ADDRESSES) - memory.Write_U32(RFI_INSTRUCTION, address); -} - -// Third boot step after BootManager and Core. See Call schedule in BootManager.cpp -bool CBoot::BootUp(Core::System& system, const Core::CPUThreadGuard& guard, - std::unique_ptr boot) -{ - SConfig& config = SConfig::GetInstance(); - - // Triforce systems are region free and always must use the NTSC video mode - if (system.IsTriforce()) - { - config.m_region = DiscIO::Region::NTSC_J; - } - - // PAL Wii uses NTSC framerate and linecount in 60Hz modes - system.GetVideoInterface().Preset(DiscIO::IsNTSC(config.m_region) || - (system.IsWii() && Config::Get(Config::SYSCONF_PAL60))); - - struct BootTitle - { - BootTitle(Core::System& system_, const Core::CPUThreadGuard& guard_, - const std::vector& patches) - : system(system_), guard(guard_), config(SConfig::GetInstance()), - riivolution_patches(patches) - { - } - bool operator()(BootParameters::Disc& disc) const - { - NOTICE_LOG_FMT(BOOT, "Booting from disc: {}", disc.path); - const DiscIO::VolumeDisc* volume = - SetDisc(system.GetDVDInterface(), std::move(disc.volume), disc.auto_disc_change_paths); - AchievementManager::GetInstance().LoadGame(volume); - - if (!volume) - return false; - +// Copyright 2008 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/Boot/Boot.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "Common/CommonPaths.h" +#include "Common/CommonTypes.h" +#include "Common/Config/Config.h" +#include "Common/FileUtil.h" +#include "Common/Hash.h" +#include "Common/IOFile.h" +#include "Common/Logging/Log.h" +#include "Common/MsgHandler.h" +#include "Common/StringUtil.h" + +#include "Core/AchievementManager.h" +#include "Core/Boot/DolReader.h" +#include "Core/Boot/ElfReader.h" +#include "Core/CommonTitles.h" +#include "Core/Config/MainSettings.h" +#include "Core/Config/SYSCONFSettings.h" +#include "Core/ConfigManager.h" +#include "Core/FifoPlayer/FifoPlayer.h" +#include "Core/HLE/HLE.h" +#include "Core/HW/DVD/AMMediaboard.h" +#include "Core/HW/DVD/DVDInterface.h" +#include "Core/HW/EXI/EXI_DeviceIPL.h" +#include "Core/HW/Memmap.h" +#include "Core/HW/VideoInterface.h" +#include "Core/Host.h" +#include "Core/IOS/ES/ES.h" +#include "Core/IOS/FS/FileSystem.h" +#include "Core/IOS/IOS.h" +#include "Core/IOS/IOSC.h" +#include "Core/IOS/Uids.h" +#include "Core/NetPlayProto.h" +#include "Core/PatchEngine.h" +#include "Core/PowerPC/PPCAnalyst.h" +#include "Core/PowerPC/PPCSymbolDB.h" +#include "Core/PowerPC/PowerPC.h" +#include "Core/System.h" + +#include "DiscIO/Enums.h" +#include "DiscIO/GameModDescriptor.h" +#include "DiscIO/RiivolutionParser.h" +#include "DiscIO/RiivolutionPatcher.h" +#include "DiscIO/VolumeDisc.h" +#include "DiscIO/VolumeWad.h" + +static std::vector ReadM3UFile(const std::string& m3u_path, + const std::string& folder_path) +{ + std::vector result; + std::vector nonexistent; + + std::ifstream s; + File::OpenFStream(s, m3u_path, std::ios_base::in); + + std::string line; + while (std::getline(s, line)) + { + // This is the UTF-8 representation of U+FEFF. + constexpr std::string_view utf8_bom = "\xEF\xBB\xBF"; + +#ifndef _WIN32 + if (line.ends_with('\r')) + line.pop_back(); +#endif + + if (line.starts_with(utf8_bom)) + { + WARN_LOG_FMT(BOOT, "UTF-8 BOM in file: {}", m3u_path); + line.erase(0, utf8_bom.length()); + } + + if (!line.empty() && line.front() != '#') // Comments start with # + { + const std::string path_to_add = PathToString(StringToPath(folder_path) / StringToPath(line)); + (File::Exists(path_to_add) ? result : nonexistent).push_back(path_to_add); + } + } + + if (!nonexistent.empty()) + { + PanicAlertFmtT("Files specified in the M3U file \"{0}\" were not found:\n{1}", m3u_path, + fmt::join(nonexistent, "\n")); + return {}; + } + + if (result.empty()) + PanicAlertFmtT("No paths found in the M3U file \"{0}\"", m3u_path); + + return result; +} + +BootSessionData::BootSessionData() +{ +} + +BootSessionData::BootSessionData(std::optional savestate_path, + DeleteSavestateAfterBoot delete_savestate) + : m_savestate_path(std::move(savestate_path)), m_delete_savestate(delete_savestate) +{ +} + +BootSessionData::BootSessionData(BootSessionData&& other) = default; + +BootSessionData& BootSessionData::operator=(BootSessionData&& other) = default; + +BootSessionData::~BootSessionData() = default; + +const std::optional& BootSessionData::GetSavestatePath() const +{ + return m_savestate_path; +} + +DeleteSavestateAfterBoot BootSessionData::GetDeleteSavestate() const +{ + return m_delete_savestate; +} + +void BootSessionData::SetSavestateData(std::optional savestate_path, + DeleteSavestateAfterBoot delete_savestate) +{ + m_savestate_path = std::move(savestate_path); + m_delete_savestate = delete_savestate; +} + +IOS::HLE::FS::FileSystem* BootSessionData::GetWiiSyncFS() const +{ + return m_wii_sync_fs.get(); +} + +const std::vector& BootSessionData::GetWiiSyncTitles() const +{ + return m_wii_sync_titles; +} + +const std::string& BootSessionData::GetWiiSyncRedirectFolder() const +{ + return m_wii_sync_redirect_folder; +} + +void BootSessionData::InvokeWiiSyncCleanup() const +{ + if (m_wii_sync_cleanup) + m_wii_sync_cleanup(); +} + +void BootSessionData::SetWiiSyncData(std::unique_ptr fs, + std::vector titles, std::string redirect_folder, + WiiSyncCleanupFunction cleanup) +{ + m_wii_sync_fs = std::move(fs); + m_wii_sync_titles = std::move(titles); + m_wii_sync_redirect_folder = std::move(redirect_folder); + m_wii_sync_cleanup = std::move(cleanup); +} + +const NetPlay::NetSettings* BootSessionData::GetNetplaySettings() const +{ + return m_netplay_settings.get(); +} + +void BootSessionData::SetNetplaySettings(std::unique_ptr netplay_settings) +{ + m_netplay_settings = std::move(netplay_settings); +} + +BootParameters::BootParameters(Parameters&& parameters_, BootSessionData boot_session_data_) + : parameters(std::move(parameters_)), boot_session_data(std::move(boot_session_data_)) +{ +} + +std::unique_ptr BootParameters::GenerateFromFile(std::string boot_path, + BootSessionData boot_session_data_) +{ + return GenerateFromFile(std::vector{std::move(boot_path)}, + std::move(boot_session_data_)); +} + +std::unique_ptr BootParameters::GenerateFromFile(std::vector paths, + BootSessionData boot_session_data_) +{ + ASSERT(!paths.empty()); + + for (std::string& path : paths) + UnifyPathSeparators(path); + + // Check if the file exist, we may have gotten it from a --elf command line + // that gave an incorrect file name + if (!File::Exists(paths.front())) + { + PanicAlertFmtT("The specified file \"{0}\" does not exist", paths.front()); + return {}; + } + + std::string folder_path; + std::string extension; + SplitPath(paths.front(), &folder_path, nullptr, &extension); + Common::ToLower(&extension); + + if (extension == ".m3u" || extension == ".m3u8") + { + paths = ReadM3UFile(paths.front(), folder_path); + if (paths.empty()) + return {}; + + for (std::string& path : paths) + UnifyPathSeparators(path); + + SplitPath(paths.front(), nullptr, nullptr, &extension); + Common::ToLower(&extension); + } + + std::string path = paths.front(); + if (paths.size() == 1) + paths.clear(); + +#ifdef ANDROID + if (extension.empty() && IsPathAndroidContent(path)) + { + const std::string display_name = GetAndroidContentDisplayName(path); + SplitPath(display_name, nullptr, nullptr, &extension); + Common::ToLower(&extension); + } +#endif + + static const std::unordered_set disc_image_extensions = { + {".gcm", ".bin", ".iso", ".tgc", ".wbfs", ".ciso", ".gcz", ".wia", ".rvz", ".nfs", ".dol", + ".elf"}}; + if (disc_image_extensions.contains(extension)) + { + std::unique_ptr disc = DiscIO::CreateDiscForCore(path); + if (disc) + { + return std::make_unique(Disc{std::move(path), std::move(disc), paths}, + std::move(boot_session_data_)); + } + + if (extension == ".elf") + { + auto elf_reader = std::make_unique(path); + return std::make_unique(Executable{std::move(path), std::move(elf_reader)}, + std::move(boot_session_data_)); + } + + if (extension == ".dol") + { + auto dol_reader = std::make_unique(path); + return std::make_unique(Executable{std::move(path), std::move(dol_reader)}, + std::move(boot_session_data_)); + } + + PanicAlertFmtT("\"{0}\" is an invalid GCM/ISO file, or is not a GC/Wii ISO.", path); + return {}; + } + + if (extension == ".dff") + return std::make_unique(DFF{std::move(path)}, std::move(boot_session_data_)); + + if (extension == ".wad") + { + std::unique_ptr wad = DiscIO::CreateWAD(path); + if (wad) + return std::make_unique(std::move(*wad), std::move(boot_session_data_)); + } + + if (extension == ".json") + { + auto descriptor = DiscIO::ParseGameModDescriptorFile(path); + if (descriptor) + { + auto boot_params = GenerateFromFile(descriptor->base_file, std::move(boot_session_data_)); + if (!boot_params) + { + PanicAlertFmtT("Could not recognize file {0}", descriptor->base_file); + return nullptr; + } + + if (descriptor->riivolution && std::holds_alternative(boot_params->parameters)) + { + const auto& volume = *std::get(boot_params->parameters).volume; + AddRiivolutionPatches(boot_params.get(), + DiscIO::Riivolution::GenerateRiivolutionPatchesFromGameModDescriptor( + *descriptor->riivolution, volume.GetGameID(), + volume.GetRevision(), volume.GetDiscNumber())); + } + + return boot_params; + } + } + + PanicAlertFmtT("Could not recognize file {0}", path); + return {}; +} + +BootParameters::IPL::IPL(DiscIO::Region region_) : region(region_) +{ + const std::string directory = Config::GetDirectoryForRegion(region); + path = Config::GetBootROMPath(directory); +} + +BootParameters::IPL::IPL(DiscIO::Region region_, Disc&& disc_) : IPL(region_) +{ + disc = std::move(disc_); +} + +// Inserts a disc into the emulated disc drive and returns a pointer to it. +// The returned pointer must only be used while we are still booting, +// because DVDThread can do whatever it wants to the disc after that. +static const DiscIO::VolumeDisc* SetDisc(DVD::DVDInterface& dvd_interface, + std::unique_ptr disc, + std::vector auto_disc_change_paths = {}) +{ + const DiscIO::VolumeDisc* pointer = disc.get(); + dvd_interface.SetDisc(std::move(disc), auto_disc_change_paths); + return pointer; +} + +bool CBoot::DVDRead(Core::System& system, const DiscIO::VolumeDisc& disc, u64 dvd_offset, + u32 output_address, u32 length, const DiscIO::Partition& partition) +{ + std::vector buffer(length); + if (!disc.Read(dvd_offset, length, buffer.data(), partition)) + return false; + + auto& memory = system.GetMemory(); + memory.CopyToEmu(output_address, buffer.data(), length); + return true; +} + +bool CBoot::DVDReadDiscID(Core::System& system, const DiscIO::VolumeDisc& disc, u32 output_address) +{ + std::array buffer; + if (!disc.Read(0, buffer.size(), buffer.data(), DiscIO::PARTITION_NONE)) + return false; + + auto& memory = system.GetMemory(); + memory.CopyToEmu(output_address, buffer.data(), buffer.size()); + + // Transition out of the DiscIdNotRead state (which the drive should be in at this point, + // on the assumption that this is only used for the first read) + system.GetDVDInterface().SetDriveState(DVD::DriveState::ReadyNoReadsMade); + return true; +} + +// If ipl.bin is not found, this function does *some* of what BS1 does: +// loading IPL(BS2) and jumping to it. +// It does not initialize the hardware or anything else like BS1 does. +bool CBoot::Load_BS2(Core::System& system, const std::string& boot_rom_filename) +{ + // CRC32 hashes of the IPL file, obtained from Redump + // DOL-001(USA) / DOL-001(JPN) / DOT-001 / SL-GC10 (NTSC Revision 1.0) + constexpr u32 NTSC_v1_0 = 0x6DAC1F2A; + // DOL-001(USA) / DOL-001(JPN) / DOT-001 (NTSC Revision 1.1) + constexpr u32 NTSC_v1_1 = 0xD5E6FEEA; + // DOL-001(USA) / DOL-001(JPN) (NTSC Revision 1.2) + constexpr u32 NTSC_v1_2_001 = 0xD235E3F9; + // DOL-101(USA) / DOL-101(JPN) (NTSC Revision 1.2) + constexpr u32 NTSC_v1_2_101 = 0x86573808; + // DOL-002(BRA) (MPAL Revision 1.1) + constexpr u32 MPAL_v1_1 = 0x667D0B64; + // DOL-001(EUR) / DOT-001P (PAL Revision 1.0) + constexpr u32 PAL_v1_0 = 0x4F319F43; + // DOL-101(EUR) (PAL Revision 1.2) + constexpr u32 PAL_v1_2 = 0xAD1B7F16; + // Triforce Arcade IPL (DEV Revision 1.0) + constexpr u32 Triforce = 0xD1883221; + + // Load the IPL ROM dump, limited to 2MiB which is the size of the official IPLs. + constexpr size_t max_ipl_size = 2 * 1024 * 1024; + std::vector data; + { + File::IOFile file(boot_rom_filename, "rb"); + if (!file) + return false; + + data.resize(static_cast(std::min(file.GetSize(), max_ipl_size))); + if (!file.ReadArray(data.data(), data.size())) + return false; + } + + const u32 ipl_hash = Common::ComputeCRC32(data.data(), data.size()); + bool known_ipl = false; + bool pal_ipl = false; + switch (ipl_hash) + { + case NTSC_v1_0: + case NTSC_v1_1: + case NTSC_v1_2_001: + case NTSC_v1_2_101: + case MPAL_v1_1: + known_ipl = true; + break; + case PAL_v1_0: + case PAL_v1_2: + pal_ipl = true; + known_ipl = true; + break; + case Triforce: + known_ipl = true; + break; + default: + PanicAlertFmtT("The IPL file is not a known good dump. (CRC32: {0:x})", ipl_hash); + break; + } + + const DiscIO::Region boot_region = SConfig::GetInstance().m_region; + if (known_ipl && pal_ipl != (boot_region == DiscIO::Region::PAL)) + { + PanicAlertFmtT("{0} IPL found in {1} directory. The disc might not be recognized", + pal_ipl ? "PAL" : "NTSC", Config::GetDirectoryForRegion(boot_region)); + } + + // Run the descrambler over the encrypted section containing BS1/BS2 + if (data.size() > 0x100) + { + ExpansionInterface::CEXIIPL::Descrambler( + data.data() + 0x100, static_cast(std::min(data.size() - 0x100, 0x1AFE00))); + } + + // TODO: Execution is supposed to start at 0xFFF00000, not 0x81200000; + // copying the initial boot code to 0x81200000 is a hack. + // For now, HLE the first few instructions and start at 0x81200150 + // to work around this. + auto& memory = system.GetMemory(); + if (data.size() > 0x100) + { + memory.CopyToEmu(0x01200000, data.data() + 0x100, std::min(data.size() - 0x100, 0x700)); + } + if (data.size() > 0x820) + { + memory.CopyToEmu(0x01300000, data.data() + 0x820, + std::min(data.size() - 0x820, 0x1AFE00)); + } + + auto& ppc_state = system.GetPPCState(); + ppc_state.gpr[3] = 0xfff0001f; + ppc_state.gpr[4] = 0x00002030; + ppc_state.gpr[5] = 0x0000009c; + + ppc_state.msr.FP = 1; + ppc_state.msr.DR = 1; + ppc_state.msr.IR = 1; + + ppc_state.spr[SPR_HID0] = 0x0011c464; + ppc_state.spr[SPR_IBAT3U] = 0xfff0001f; + ppc_state.spr[SPR_IBAT3L] = 0xfff00001; + ppc_state.spr[SPR_DBAT3U] = 0xfff0001f; + ppc_state.spr[SPR_DBAT3L] = 0xfff00001; + SetupBAT(system, /*is_wii*/ false); + + if (ipl_hash == Triforce) + { + HLE::Patch(system, 0x813048B8, "OSReport"); + HLE::Patch(system, 0x8130095C, "OSReport"); // Apploader + + AMMediaboard::FirmwareMap(true); + } + + ppc_state.pc = 0x81200150; + + system.GetPowerPC().MSRUpdated(); + + return true; +} + +static void SetDefaultDisc(DVD::DVDInterface& dvd_interface) +{ + const std::string default_iso = Config::Get(Config::MAIN_DEFAULT_ISO); + if (!default_iso.empty()) + SetDisc(dvd_interface, DiscIO::CreateDiscForCore(default_iso)); +} + +static void CopyDefaultExceptionHandlers(Core::System& system) +{ + constexpr u32 EXCEPTION_HANDLER_ADDRESSES[] = {0x00000100, 0x00000200, 0x00000300, 0x00000400, + 0x00000500, 0x00000600, 0x00000700, 0x00000800, + 0x00000900, 0x00000C00, 0x00000D00, 0x00000F00, + 0x00001300, 0x00001400, 0x00001700}; + + auto& memory = system.GetMemory(); + constexpr u32 RFI_INSTRUCTION = 0x4C000064; + for (const u32 address : EXCEPTION_HANDLER_ADDRESSES) + memory.Write_U32(RFI_INSTRUCTION, address); +} + +// Third boot step after BootManager and Core. See Call schedule in BootManager.cpp +bool CBoot::BootUp(Core::System& system, const Core::CPUThreadGuard& guard, + std::unique_ptr boot) +{ + SConfig& config = SConfig::GetInstance(); + + // Triforce systems are region free and always must use the NTSC video mode + if (system.IsTriforce()) + { + config.m_region = DiscIO::Region::NTSC_J; + } + + // PAL Wii uses NTSC framerate and linecount in 60Hz modes + system.GetVideoInterface().Preset(DiscIO::IsNTSC(config.m_region) || + (system.IsWii() && Config::Get(Config::SYSCONF_PAL60))); + + struct BootTitle + { + BootTitle(Core::System& system_, const Core::CPUThreadGuard& guard_, + const std::vector& patches) + : system(system_), guard(guard_), config(SConfig::GetInstance()), + riivolution_patches(patches) + { + } + bool operator()(BootParameters::Disc& disc) const + { + NOTICE_LOG_FMT(BOOT, "Booting from disc: {}", disc.path); + const DiscIO::VolumeDisc* volume = + SetDisc(system.GetDVDInterface(), std::move(disc.volume), disc.auto_disc_change_paths); + AchievementManager::GetInstance().LoadGame(volume); + + if (!volume) + return false; + if (system.GetStarlet()) - { - NOTICE_LOG_FMT(BOOT, - "Wii IOS LLE: disc inserted; the original Starlet boot chain will start " - "Broadway instead of Dolphin's emulated BS2"); - return true; - } - - if (!EmulatedBS2(system, guard, system.IsWii(), *volume, riivolution_patches)) - return false; - - SConfig::OnTitleDirectlyBooted(guard); - return true; - } - + { + NOTICE_LOG_FMT(BOOT, + "Wii IOS LLE: disc inserted; the original Starlet boot chain will start " + "Broadway instead of Dolphin's emulated BS2"); + return true; + } + + if (!EmulatedBS2(system, guard, system.IsWii(), *volume, riivolution_patches)) + return false; + + SConfig::OnTitleDirectlyBooted(guard); + return true; + } + bool operator()(const BootParameters::Executable& executable) const - { - NOTICE_LOG_FMT(BOOT, "Booting from executable: {}", executable.path); - - if (!executable.reader->IsValid()) - return false; - - if (system.GetStarlet()) - { - PanicAlertFmtT("Direct executable boot is unavailable with Wii IOS LLE. Boot the NAND " - "through Starlet, then launch the title through the original IOS."); - return false; - } - - SetDefaultDisc(system.GetDVDInterface()); - - auto& ppc_state = system.GetPPCState(); - - SetupMSR(system); - SetupHID(ppc_state, system.IsWii()); - SetupBAT(system, system.IsWii()); - CopyDefaultExceptionHandlers(system); - - if (system.IsWii()) - { - // Set a value for the SP. It doesn't matter where this points to, - // as long as it is a valid location. This value is taken from a homebrew binary. - ppc_state.gpr[1] = 0x8004d4bc; - - // 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(system, IOS::HLE::IOSC::ConsoleType::Retail); - system.GetIOS()->BootIOS(Titles::IOS(58)); - - // The Apploader writes an IOS-like version number into memory. - // Older versions of OSInit read it to check IOS compatibility. - constexpr u32 ADDR_IOS_VERSION = 0x3140; - constexpr u32 ADDR_APPLOADER_VERSION = 0x3188; - const u32 ios_version = system.GetMemory().Read_U32(ADDR_IOS_VERSION); - system.GetMemory().Write_U32(ios_version, ADDR_APPLOADER_VERSION); - } - else - { - SetupGCMemory(system, guard); - } - - if (!executable.reader->LoadIntoMemory(system)) - { - PanicAlertFmtT("Failed to load the executable to memory."); - return false; - } - - AchievementManager::GetInstance().LoadGame(nullptr); - - SConfig::OnTitleDirectlyBooted(guard); - - ppc_state.pc = executable.reader->GetEntryPoint(); - - const std::string filename = PathToFileName(executable.path); - - auto& ppc_symbol_db = system.GetPPCSymbolDB(); - bool symbols_changed = ppc_symbol_db.Clear(); - - if (executable.reader->LoadSymbols(guard, ppc_symbol_db, filename)) - { - symbols_changed = true; - HLE::PatchFunctions(system); - } - - if (symbols_changed) - Host_PPCSymbolsChanged(); - - return true; - } - - bool operator()(const DiscIO::VolumeWAD& wad) const - { - if (system.GetStarlet()) - { - PanicAlertFmtT("Direct WAD boot is unavailable with Wii IOS LLE. Install and launch the " - "title through the original Wii software."); - return false; - } - SetDefaultDisc(system.GetDVDInterface()); - if (!Boot_WiiWAD(system, wad)) - return false; - - AchievementManager::GetInstance().LoadGame(&wad); - - SConfig::OnTitleDirectlyBooted(guard); - return true; - } - - bool operator()(const BootParameters::NANDTitle& nand_title) const - { - if (system.GetStarlet()) - { - constexpr u64 SYSTEM_MENU_TITLE_ID = 0x0000000100000002; - if (nand_title.id != SYSTEM_MENU_TITLE_ID) + { + NOTICE_LOG_FMT(BOOT, "Booting from executable: {}", executable.path); + + if (!executable.reader->IsValid()) + return false; + + const bool paired_starlet_probe = system.GetStarlet() && + !Config::Get(Config::MAIN_WII_STARLET_BOOTSTRAP_ELF).empty(); + if (system.GetStarlet() && !paired_starlet_probe) + { + PanicAlertFmtT("Direct executable boot is unavailable with Wii IOS LLE. Boot the NAND " + "through Starlet, then launch the title through the original IOS."); + return false; + } + + SetDefaultDisc(system.GetDVDInterface()); + + auto& ppc_state = system.GetPPCState(); + + SetupMSR(system); + SetupHID(ppc_state, system.IsWii()); + SetupBAT(system, system.IsWii()); + CopyDefaultExceptionHandlers(system); + + if (system.IsWii()) + { + // Set a value for the SP. It doesn't matter where this points to, + // as long as it is a valid location. This value is taken from a homebrew binary. + ppc_state.gpr[1] = 0x8004d4bc; + + // 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. + if (!paired_starlet_probe) { - PanicAlertFmtT("Direct NAND-title boot is unavailable with Wii IOS LLE. Select the Wii " - "System Menu to start the original NAND boot chain."); - return false; + SetupWiiMemory(system, IOS::HLE::IOSC::ConsoleType::Retail); + system.GetIOS()->BootIOS(Titles::IOS(58)); } - - SetDefaultDisc(system.GetDVDInterface()); - AchievementManager::GetInstance().LoadGame(nullptr); - NOTICE_LOG_FMT(BOOT, "Wii IOS LLE: starting the original NAND boot chain"); - return true; - } - SetDefaultDisc(system.GetDVDInterface()); - if (!BootNANDTitle(system, nand_title.id)) - return false; - - AchievementManager::GetInstance().LoadGame(nullptr); - - SConfig::OnTitleDirectlyBooted(guard); - return true; - } - - bool operator()(const BootParameters::IPL& ipl) const - { - NOTICE_LOG_FMT(BOOT, "Booting GC IPL: {}", ipl.path); - if (!File::Exists(ipl.path)) - { - if (ipl.disc) - PanicAlertFmtT("Cannot start the game, because the GC IPL could not be found."); - else - PanicAlertFmtT("Cannot find the GC IPL."); - return false; - } - - if (!Load_BS2(system, ipl.path)) - return false; - - const DiscIO::VolumeDisc* volume = nullptr; - if (ipl.disc) - { - NOTICE_LOG_FMT(BOOT, "Inserting disc: {}", ipl.disc->path); - - volume = SetDisc(system.GetDVDInterface(), DiscIO::CreateDiscForCore(ipl.disc->path), - ipl.disc->auto_disc_change_paths); - } - - AchievementManager::GetInstance().LoadGame(volume); - - SConfig::OnTitleDirectlyBooted(guard); - return true; - } - - bool operator()(const BootParameters::DFF& dff) const - { - NOTICE_LOG_FMT(BOOT, "Booting DFF: {}", dff.dff_path); - AchievementManager::GetInstance().LoadGame(nullptr); - return system.GetFifoPlayer().Open(dff.dff_path); - } - - private: - Core::System& system; - const Core::CPUThreadGuard& guard; - const SConfig& config; - const std::vector& riivolution_patches; - }; - - if (!std::visit(BootTitle(system, guard, boot->riivolution_patches), boot->parameters)) - return false; - - DiscIO::Riivolution::ApplyGeneralMemoryPatches(guard, boot->riivolution_patches); - - return true; -} - -BootExecutableReader::BootExecutableReader(const std::string& file_name) - : BootExecutableReader(File::IOFile{file_name, "rb"}) -{ -} - -BootExecutableReader::BootExecutableReader(File::IOFile file) -{ - file.Seek(0, File::SeekOrigin::Begin); - m_bytes.resize(file.GetSize()); - file.ReadBytes(m_bytes.data(), m_bytes.size()); -} - -BootExecutableReader::BootExecutableReader(std::vector bytes) : m_bytes(std::move(bytes)) -{ -} - -BootExecutableReader::~BootExecutableReader() = default; - -void StateFlags::UpdateChecksum() -{ - constexpr size_t length_in_bytes = sizeof(StateFlags) - 4; - constexpr size_t num_elements = length_in_bytes / sizeof(u32); - std::array flag_data; - std::memcpy(flag_data.data(), &flags, length_in_bytes); - checksum = std::accumulate(flag_data.cbegin(), flag_data.cend(), 0U); -} - -void UpdateStateFlags(const std::function& update_function) -{ - CreateSystemMenuTitleDirs(); - const std::string file_path = Common::GetTitleDataPath(Titles::SYSTEM_MENU) + "/" WII_STATE; - const auto fs = Core::System::GetInstance().GetIOS()->GetFS(); - constexpr IOS::HLE::FS::Mode rw_mode = IOS::HLE::FS::Mode::ReadWrite; - const auto file = fs->CreateAndOpenFile(IOS::SYSMENU_UID, IOS::SYSMENU_GID, file_path, - {rw_mode, rw_mode, rw_mode}); - if (!file) - return; - - StateFlags state{}; - if (file->GetStatus()->size == sizeof(StateFlags)) - (void)file->Read(&state, 1); - - update_function(&state); - state.UpdateChecksum(); - - (void)file->Seek(0, IOS::HLE::FS::SeekMode::Set); - (void)file->Write(&state, 1); -} - -void CreateSystemMenuTitleDirs() -{ - const auto& es = Core::System::GetInstance().GetIOS()->GetESCore(); - es.CreateTitleDirectories(Titles::SYSTEM_MENU, IOS::SYSMENU_GID); -} - -void AddRiivolutionPatches(BootParameters* boot_params, - std::vector riivolution_patches) -{ - if (riivolution_patches.empty()) - return; - if (!std::holds_alternative(boot_params->parameters)) - return; - - auto& disc = std::get(boot_params->parameters); - disc.volume = DiscIO::CreateDisc(DiscIO::DirectoryBlobReader::Create( - std::move(disc.volume), - [&](std::vector* fst) { - DiscIO::Riivolution::ApplyPatchesToFiles( - riivolution_patches, DiscIO::Riivolution::PatchIndex::DolphinSysFiles, fst, nullptr); - }, - [&](std::vector* fst, DiscIO::FSTBuilderNode* dol_node) { - DiscIO::Riivolution::ApplyPatchesToFiles( - riivolution_patches, DiscIO::Riivolution::PatchIndex::FileSystem, fst, dol_node); - })); - boot_params->riivolution_patches = std::move(riivolution_patches); -} + + // The Apploader writes an IOS-like version number into memory. + // Older versions of OSInit read it to check IOS compatibility. + constexpr u32 ADDR_IOS_VERSION = 0x3140; + constexpr u32 ADDR_APPLOADER_VERSION = 0x3188; + const u32 ios_version = system.GetMemory().Read_U32(ADDR_IOS_VERSION); + system.GetMemory().Write_U32(ios_version, ADDR_APPLOADER_VERSION); + } + else + { + SetupGCMemory(system, guard); + } + + if (!executable.reader->LoadIntoMemory(system)) + { + PanicAlertFmtT("Failed to load the executable to memory."); + return false; + } + + AchievementManager::GetInstance().LoadGame(nullptr); + + SConfig::OnTitleDirectlyBooted(guard); + + ppc_state.pc = executable.reader->GetEntryPoint(); + + const std::string filename = PathToFileName(executable.path); + + auto& ppc_symbol_db = system.GetPPCSymbolDB(); + bool symbols_changed = ppc_symbol_db.Clear(); + + if (executable.reader->LoadSymbols(guard, ppc_symbol_db, filename)) + { + symbols_changed = true; + HLE::PatchFunctions(system); + } + + if (symbols_changed) + Host_PPCSymbolsChanged(); + + return true; + } + + bool operator()(const DiscIO::VolumeWAD& wad) const + { + if (system.GetStarlet()) + { + PanicAlertFmtT("Direct WAD boot is unavailable with Wii IOS LLE. Install and launch the " + "title through the original Wii software."); + return false; + } + SetDefaultDisc(system.GetDVDInterface()); + if (!Boot_WiiWAD(system, wad)) + return false; + + AchievementManager::GetInstance().LoadGame(&wad); + + SConfig::OnTitleDirectlyBooted(guard); + return true; + } + + bool operator()(const BootParameters::NANDTitle& nand_title) const + { + if (system.GetStarlet()) + { + constexpr u64 SYSTEM_MENU_TITLE_ID = 0x0000000100000002; + if (nand_title.id != SYSTEM_MENU_TITLE_ID) + { + PanicAlertFmtT("Direct NAND-title boot is unavailable with Wii IOS LLE. Select the Wii " + "System Menu to start the original NAND boot chain."); + return false; + } + + SetDefaultDisc(system.GetDVDInterface()); + AchievementManager::GetInstance().LoadGame(nullptr); + NOTICE_LOG_FMT(BOOT, "Wii IOS LLE: starting the original NAND boot chain"); + return true; + } + SetDefaultDisc(system.GetDVDInterface()); + if (!BootNANDTitle(system, nand_title.id)) + return false; + + AchievementManager::GetInstance().LoadGame(nullptr); + + SConfig::OnTitleDirectlyBooted(guard); + return true; + } + + bool operator()(const BootParameters::IPL& ipl) const + { + NOTICE_LOG_FMT(BOOT, "Booting GC IPL: {}", ipl.path); + if (!File::Exists(ipl.path)) + { + if (ipl.disc) + PanicAlertFmtT("Cannot start the game, because the GC IPL could not be found."); + else + PanicAlertFmtT("Cannot find the GC IPL."); + return false; + } + + if (!Load_BS2(system, ipl.path)) + return false; + + const DiscIO::VolumeDisc* volume = nullptr; + if (ipl.disc) + { + NOTICE_LOG_FMT(BOOT, "Inserting disc: {}", ipl.disc->path); + + volume = SetDisc(system.GetDVDInterface(), DiscIO::CreateDiscForCore(ipl.disc->path), + ipl.disc->auto_disc_change_paths); + } + + AchievementManager::GetInstance().LoadGame(volume); + + SConfig::OnTitleDirectlyBooted(guard); + return true; + } + + bool operator()(const BootParameters::DFF& dff) const + { + NOTICE_LOG_FMT(BOOT, "Booting DFF: {}", dff.dff_path); + AchievementManager::GetInstance().LoadGame(nullptr); + return system.GetFifoPlayer().Open(dff.dff_path); + } + + private: + Core::System& system; + const Core::CPUThreadGuard& guard; + const SConfig& config; + const std::vector& riivolution_patches; + }; + + if (!std::visit(BootTitle(system, guard, boot->riivolution_patches), boot->parameters)) + return false; + + DiscIO::Riivolution::ApplyGeneralMemoryPatches(guard, boot->riivolution_patches); + + return true; +} + +BootExecutableReader::BootExecutableReader(const std::string& file_name) + : BootExecutableReader(File::IOFile{file_name, "rb"}) +{ +} + +BootExecutableReader::BootExecutableReader(File::IOFile file) +{ + file.Seek(0, File::SeekOrigin::Begin); + m_bytes.resize(file.GetSize()); + file.ReadBytes(m_bytes.data(), m_bytes.size()); +} + +BootExecutableReader::BootExecutableReader(std::vector bytes) : m_bytes(std::move(bytes)) +{ +} + +BootExecutableReader::~BootExecutableReader() = default; + +void StateFlags::UpdateChecksum() +{ + constexpr size_t length_in_bytes = sizeof(StateFlags) - 4; + constexpr size_t num_elements = length_in_bytes / sizeof(u32); + std::array flag_data; + std::memcpy(flag_data.data(), &flags, length_in_bytes); + checksum = std::accumulate(flag_data.cbegin(), flag_data.cend(), 0U); +} + +void UpdateStateFlags(const std::function& update_function) +{ + CreateSystemMenuTitleDirs(); + const std::string file_path = Common::GetTitleDataPath(Titles::SYSTEM_MENU) + "/" WII_STATE; + const auto fs = Core::System::GetInstance().GetIOS()->GetFS(); + constexpr IOS::HLE::FS::Mode rw_mode = IOS::HLE::FS::Mode::ReadWrite; + const auto file = fs->CreateAndOpenFile(IOS::SYSMENU_UID, IOS::SYSMENU_GID, file_path, + {rw_mode, rw_mode, rw_mode}); + if (!file) + return; + + StateFlags state{}; + if (file->GetStatus()->size == sizeof(StateFlags)) + (void)file->Read(&state, 1); + + update_function(&state); + state.UpdateChecksum(); + + (void)file->Seek(0, IOS::HLE::FS::SeekMode::Set); + (void)file->Write(&state, 1); +} + +void CreateSystemMenuTitleDirs() +{ + const auto& es = Core::System::GetInstance().GetIOS()->GetESCore(); + es.CreateTitleDirectories(Titles::SYSTEM_MENU, IOS::SYSMENU_GID); +} + +void AddRiivolutionPatches(BootParameters* boot_params, + std::vector riivolution_patches) +{ + if (riivolution_patches.empty()) + return; + if (!std::holds_alternative(boot_params->parameters)) + return; + + auto& disc = std::get(boot_params->parameters); + disc.volume = DiscIO::CreateDisc(DiscIO::DirectoryBlobReader::Create( + std::move(disc.volume), + [&](std::vector* fst) { + DiscIO::Riivolution::ApplyPatchesToFiles( + riivolution_patches, DiscIO::Riivolution::PatchIndex::DolphinSysFiles, fst, nullptr); + }, + [&](std::vector* fst, DiscIO::FSTBuilderNode* dol_node) { + DiscIO::Riivolution::ApplyPatchesToFiles( + riivolution_patches, DiscIO::Riivolution::PatchIndex::FileSystem, fst, dol_node); + })); + boot_params->riivolution_patches = std::move(riivolution_patches); +} diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index 8b8a73b61f..515d70b261 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -1,875 +1,877 @@ -add_library(core - AchievementManager.cpp - AchievementManager.h - ActionReplay.cpp - ActionReplay.h - ARDecrypt.cpp - ARDecrypt.h - Boot/AncastTypes.h - Boot/Boot_BS2Emu.cpp - Boot/Boot_WiiWAD.cpp - Boot/Boot.cpp - Boot/Boot.h - Boot/DolReader.cpp - Boot/DolReader.h - Boot/ElfReader.cpp - Boot/ElfReader.h - Boot/ElfTypes.h - BootManager.cpp - BootManager.h - CheatCodes.h - CheatGeneration.cpp - CheatGeneration.h - CheatSearch.cpp - CheatSearch.h - CommonTitles.h - Config/AchievementSettings.cpp - Config/AchievementSettings.h - Config/DefaultLocale.cpp - Config/DefaultLocale.h - Config/FreeLookSettings.cpp - Config/FreeLookSettings.h - Config/GraphicsSettings.cpp - Config/GraphicsSettings.h - Config/MainSettings.cpp - Config/MainSettings.h - Config/NetplaySettings.cpp - Config/NetplaySettings.h - Config/SessionSettings.cpp - Config/SessionSettings.h - Config/SYSCONFSettings.cpp - Config/SYSCONFSettings.h - Config/UISettings.cpp - Config/UISettings.h - Config/WiimoteSettings.cpp - Config/WiimoteSettings.h - ConfigLoaders/BaseConfigLoader.cpp - ConfigLoaders/BaseConfigLoader.h - ConfigLoaders/GameConfigLoader.cpp - ConfigLoaders/GameConfigLoader.h - ConfigLoaders/IsSettingSaveable.cpp - ConfigLoaders/IsSettingSaveable.h - ConfigLoaders/MovieConfigLoader.cpp - ConfigLoaders/MovieConfigLoader.h - ConfigLoaders/NetPlayConfigLoader.cpp - ConfigLoaders/NetPlayConfigLoader.h - ConfigManager.cpp - ConfigManager.h - Core.cpp - Core.h - CoreTiming.cpp - CoreTiming.h - CPUThreadConfigCallback.cpp - CPUThreadConfigCallback.h - Debugger/BranchWatch.cpp - Debugger/BranchWatch.h - Debugger/CodeTrace.cpp - Debugger/CodeTrace.h - Debugger/DebugInterface.h - Debugger/Debugger_SymbolMap.cpp - Debugger/Debugger_SymbolMap.h - Debugger/Dump.cpp - Debugger/Dump.h - Debugger/OSThread.cpp - Debugger/OSThread.h - Debugger/PPCDebugInterface.cpp - Debugger/PPCDebugInterface.h - Debugger/RSO.cpp - Debugger/RSO.h - DolphinAnalytics.cpp - DolphinAnalytics.h - DSP/DSPAccelerator.cpp - DSP/DSPAccelerator.h - DSP/DSPAnalyzer.cpp - DSP/DSPAnalyzer.h - DSP/DSPAssembler.cpp - DSP/DSPAssembler.h - DSP/DSPCaptureLogger.cpp - DSP/DSPCaptureLogger.h - DSP/DSPCodeUtil.cpp - DSP/DSPCodeUtil.h - DSP/DSPCommon.h - DSP/DSPCore.cpp - DSP/DSPCore.h - DSP/DSPDisassembler.cpp - DSP/DSPDisassembler.h - DSP/DSPHWInterface.cpp - DSP/DSPMemoryMap.cpp - DSP/DSPStacks.cpp - DSP/DSPTables.cpp - DSP/DSPTables.h - DSP/Interpreter/DSPIntArithmetic.cpp - DSP/Interpreter/DSPIntBranch.cpp - DSP/Interpreter/DSPIntCCUtil.h - DSP/Interpreter/DSPInterpreter.cpp - DSP/Interpreter/DSPInterpreter.h - DSP/Interpreter/DSPIntExtOps.cpp - DSP/Interpreter/DSPIntLoadStore.cpp - DSP/Interpreter/DSPIntMisc.cpp - DSP/Interpreter/DSPIntMultiplier.cpp - DSP/Interpreter/DSPIntTables.cpp - DSP/Interpreter/DSPIntTables.h - DSP/Interpreter/DSPIntUtil.h - DSP/Jit/DSPEmitterBase.cpp - DSP/Jit/DSPEmitterBase.h - DSP/LabelMap.cpp - DSP/LabelMap.h - DSPEmulator.cpp - DSPEmulator.h - FifoPlayer/FifoDataFile.cpp - FifoPlayer/FifoDataFile.h - FifoPlayer/FifoPlayer.cpp - FifoPlayer/FifoPlayer.h - FifoPlayer/FifoRecorder.cpp - FifoPlayer/FifoRecorder.h - FreeLookManager.cpp - FreeLookManager.h - GeckoCode.cpp - GeckoCode.h - GeckoCodeConfig.cpp - GeckoCodeConfig.h - HLE/HLE_Misc.cpp - HLE/HLE_Misc.h - HLE/HLE_OS.cpp - HLE/HLE_OS.h - HLE/HLE_VarArgs.cpp - HLE/HLE_VarArgs.h - HLE/HLE.cpp - HLE/HLE.h - Host.h - HotkeyManager.cpp - HotkeyManager.h - HW/AddressSpace.cpp - HW/AddressSpace.h - HW/AudioInterface.cpp - HW/AudioInterface.h - HW/CPU.cpp - HW/CPU.h - HW/DSP.cpp - HW/DSP.h - HW/DSPHLE/DSPHLE.cpp - HW/DSPHLE/DSPHLE.h - HW/DSPHLE/MailHandler.cpp - HW/DSPHLE/MailHandler.h - HW/DSPHLE/UCodes/ASnd.cpp - HW/DSPHLE/UCodes/ASnd.h - HW/DSPHLE/UCodes/AESnd.cpp - HW/DSPHLE/UCodes/AESnd.h - HW/DSPHLE/UCodes/AX.cpp - HW/DSPHLE/UCodes/AX.h - HW/DSPHLE/UCodes/AXStructs.h - HW/DSPHLE/UCodes/AXVoice.h - HW/DSPHLE/UCodes/AXWii.cpp - HW/DSPHLE/UCodes/AXWii.h - HW/DSPHLE/UCodes/CARD.cpp - HW/DSPHLE/UCodes/CARD.h - HW/DSPHLE/UCodes/GBA.cpp - HW/DSPHLE/UCodes/GBA.h - HW/DSPHLE/UCodes/INIT.cpp - HW/DSPHLE/UCodes/INIT.h - HW/DSPHLE/UCodes/ROM.cpp - HW/DSPHLE/UCodes/ROM.h - HW/DSPHLE/UCodes/UCodes.cpp - HW/DSPHLE/UCodes/UCodes.h - HW/DSPHLE/UCodes/Zelda.cpp - HW/DSPHLE/UCodes/Zelda.h - HW/DSPLLE/DSPHost.cpp - HW/DSPLLE/DSPLLE.cpp - HW/DSPLLE/DSPLLE.h - HW/DSPLLE/DSPSymbols.cpp - HW/DSPLLE/DSPSymbols.h - HW/DVD/DVDInterface.cpp - HW/DVD/DVDInterface.h - HW/DVD/AMMediaboard.cpp - HW/DVD/AMMediaboard.h - HW/DVD/DVDMath.cpp - HW/DVD/DVDMath.h - HW/DVD/DVDThread.cpp - HW/DVD/DVDThread.h - HW/DVD/FileMonitor.cpp - HW/DVD/FileMonitor.h - HW/EXI/BBA/TAPServerConnection.cpp - HW/EXI/BBA/TAPServerBBA.cpp - HW/EXI/BBA/XLINK_KAI_BBA.cpp - HW/EXI/BBA/BuiltIn.cpp - HW/EXI/BBA/BuiltIn.h - HW/EXI/Modem/TAPServerModem.cpp - HW/EXI/EXI_Channel.cpp - HW/EXI/EXI_Channel.h - HW/EXI/EXI_Device.cpp - HW/EXI/EXI_Device.h - HW/EXI/EXI_DeviceAD16.cpp - HW/EXI/EXI_DeviceAD16.h - HW/EXI/EXI_DeviceAGP.cpp - HW/EXI/EXI_DeviceAGP.h - HW/EXI/EXI_DeviceBaseboard.cpp - HW/EXI/EXI_DeviceBaseboard.h - HW/EXI/EXI_DeviceDummy.cpp - HW/EXI/EXI_DeviceDummy.h - HW/EXI/EXI_DeviceEthernet.cpp - HW/EXI/EXI_DeviceEthernet.h - HW/EXI/EXI_DeviceGecko.cpp - HW/EXI/EXI_DeviceGecko.h - HW/EXI/EXI_DeviceIPL.cpp - HW/EXI/EXI_DeviceIPL.h - HW/EXI/EXI_DeviceMemoryCard.cpp - HW/EXI/EXI_DeviceMemoryCard.h - HW/EXI/EXI_DeviceModem.cpp - HW/EXI/EXI_DeviceModem.h - HW/EXI/EXI.cpp - HW/EXI/EXI.h - HW/GBAPad.cpp - HW/GBAPad.h - HW/GBAPadEmu.cpp - HW/GBAPadEmu.h - HW/GCKeyboard.cpp - HW/GCKeyboard.h - HW/GCKeyboardEmu.cpp - HW/GCKeyboardEmu.h - HW/GCMemcard/GCIFile.cpp - HW/GCMemcard/GCIFile.h - HW/GCMemcard/GCMemcard.cpp - HW/GCMemcard/GCMemcard.h - HW/GCMemcard/GCMemcardBase.h - HW/GCMemcard/GCMemcardDirectory.cpp - HW/GCMemcard/GCMemcardDirectory.h - HW/GCMemcard/GCMemcardRaw.cpp - HW/GCMemcard/GCMemcardRaw.h - HW/GCMemcard/GCMemcardUtils.cpp - HW/GCMemcard/GCMemcardUtils.h - HW/GCPad.cpp - HW/GCPad.h - HW/GCPadEmu.cpp - HW/GCPadEmu.h - HW/GPFifo.cpp - HW/GPFifo.h - HW/HSP/HSP.cpp - HW/HSP/HSP.h - HW/HSP/HSP_Device.cpp - HW/HSP/HSP_Device.h - HW/HSP/HSP_DeviceARAMExpansion.cpp - HW/HSP/HSP_DeviceARAMExpansion.h - HW/HSP/HSP_DeviceGBPlayer.cpp - HW/HSP/HSP_DeviceGBPlayer.h - HW/HSP/HSP_DeviceNull.cpp - HW/HSP/HSP_DeviceNull.h - HW/HW.cpp - HW/HW.h - HW/MagCard/C1231BR.cpp - HW/MagCard/C1231BR.h - HW/MagCard/C1231LR.cpp - HW/MagCard/C1231LR.h - HW/MagCard/MagneticCardReader.cpp - HW/MagCard/MagneticCardReader.h - HW/Memmap.cpp - HW/Memmap.h - HW/MemoryInterface.cpp - HW/MemoryInterface.h - HW/MMIO.cpp - HW/MMIO.h - HW/ProcessorInterface.cpp - HW/ProcessorInterface.h - HW/SI/SI_Device.cpp - HW/SI/SI_Device.h - HW/SI/SI_DeviceDanceMat.cpp - HW/SI/SI_DeviceDanceMat.h - HW/SI/SI_DeviceGBA.cpp - HW/SI/SI_DeviceGBA.h - HW/SI/SI_DeviceGCAdapter.cpp - HW/SI/SI_DeviceGCAdapter.h - HW/SI/SI_DeviceGCController.cpp - HW/SI/SI_DeviceGCController.h - HW/SI/SI_DeviceGCSteeringWheel.cpp - HW/SI/SI_DeviceGCSteeringWheel.h - HW/SI/SI_DeviceKeyboard.cpp - HW/SI/SI_DeviceKeyboard.h - HW/SI/SI_DeviceAMBaseboard.cpp - HW/SI/SI_DeviceAMBaseboard.h - HW/SI/SI_DeviceNull.cpp - HW/SI/SI_DeviceNull.h - HW/SI/SI.cpp - HW/SI/SI.h - HW/Sram.cpp - HW/Sram.h - HW/StreamADPCM.cpp - HW/StreamADPCM.h - HW/SystemTimers.cpp - HW/SystemTimers.h - HW/Triforce/DeckReader.cpp - HW/Triforce/DeckReader.h - HW/Triforce/FZeroAX.cpp - HW/Triforce/FZeroAX.h - HW/Triforce/ICCardReader.cpp - HW/Triforce/ICCardReader.h - HW/Triforce/IOPorts.cpp - HW/Triforce/IOPorts.h - HW/Triforce/JVSIO.cpp - HW/Triforce/JVSIO.h - HW/Triforce/MarioKartGP.cpp - HW/Triforce/MarioKartGP.h - HW/Triforce/SerialDevice.cpp - HW/Triforce/SerialDevice.h - HW/Triforce/Touchscreen.cpp - HW/Triforce/Touchscreen.h - HW/VideoInterface.cpp - HW/VideoInterface.h - HW/WII_IPC.cpp - HW/WII_IPC.h - HW/Wiimote.cpp - HW/Wiimote.h - HW/WiimoteCommon/DataReport.cpp - HW/WiimoteCommon/DataReport.h - HW/WiimoteCommon/WiimoteConstants.h - HW/WiimoteCommon/WiimoteHid.h - HW/WiimoteCommon/WiimoteReport.h - HW/WiimoteEmu/Camera.cpp - HW/WiimoteEmu/Camera.h - HW/WiimoteEmu/DesiredWiimoteState.cpp - HW/WiimoteEmu/DesiredWiimoteState.h - HW/WiimoteEmu/Dynamics.cpp - HW/WiimoteEmu/Dynamics.h - HW/WiimoteEmu/EmuSubroutines.cpp - HW/WiimoteEmu/Encryption.cpp - HW/WiimoteEmu/Encryption.h - HW/WiimoteEmu/Extension/Classic.cpp - HW/WiimoteEmu/Extension/Classic.h - HW/WiimoteEmu/Extension/DesiredExtensionState.h - HW/WiimoteEmu/Extension/DrawsomeTablet.cpp - HW/WiimoteEmu/Extension/DrawsomeTablet.h - HW/WiimoteEmu/Extension/Drums.cpp - HW/WiimoteEmu/Extension/Drums.h - HW/WiimoteEmu/Extension/Extension.cpp - HW/WiimoteEmu/Extension/Extension.h - HW/WiimoteEmu/Extension/Guitar.cpp - HW/WiimoteEmu/Extension/Guitar.h - HW/WiimoteEmu/Extension/Nunchuk.cpp - HW/WiimoteEmu/Extension/Nunchuk.h - HW/WiimoteEmu/Extension/Shinkansen.cpp - HW/WiimoteEmu/Extension/Shinkansen.h - HW/WiimoteEmu/Extension/TaTaCon.cpp - HW/WiimoteEmu/Extension/TaTaCon.h - HW/WiimoteEmu/Extension/Turntable.cpp - HW/WiimoteEmu/Extension/Turntable.h - HW/WiimoteEmu/Extension/UDrawTablet.cpp - HW/WiimoteEmu/Extension/UDrawTablet.h - HW/WiimoteEmu/ExtensionPort.cpp - HW/WiimoteEmu/ExtensionPort.h - HW/WiimoteEmu/I2CBus.cpp - HW/WiimoteEmu/I2CBus.h - HW/WiimoteEmu/MotionPlus.cpp - HW/WiimoteEmu/MotionPlus.h - HW/WiimoteEmu/Speaker.cpp - HW/WiimoteEmu/Speaker.h - HW/WiimoteEmu/WiimoteEmu.cpp - HW/WiimoteEmu/WiimoteEmu.h - HW/WiimoteReal/WiimoteReal.cpp - HW/WiimoteReal/WiimoteReal.h - HW/WiiSave.cpp - HW/WiiSave.h - HW/WiiSaveStructs.h - IOS/Device.cpp - IOS/Device.h - IOS/DeviceStub.cpp - IOS/DeviceStub.h - IOS/Crypto/AesDevice.cpp - IOS/Crypto/AesDevice.h - IOS/Crypto/Sha.cpp - IOS/Crypto/Sha.h - IOS/DI/DI.cpp - IOS/DI/DI.h - IOS/DolphinDevice.cpp - IOS/DolphinDevice.h - IOS/ES/ES.cpp - IOS/ES/ES.h - IOS/ES/Formats.cpp - IOS/ES/Formats.h - IOS/ES/Identity.cpp - IOS/ES/NandUtils.cpp - IOS/ES/TitleContents.cpp - IOS/ES/TitleInformation.cpp - IOS/ES/TitleManagement.cpp - IOS/ES/Views.cpp - IOS/FS/FileSystem.h - IOS/FS/FileSystemCommon.cpp - IOS/FS/FileSystemProxy.cpp - IOS/FS/FileSystemProxy.h - IOS/FS/HostBackend/File.cpp - IOS/FS/HostBackend/FS.cpp - IOS/FS/HostBackend/FS.h - IOS/IOS.cpp - IOS/IOS.h - IOS/IOSC.cpp - IOS/IOSC.h - IOS/MIOS.cpp - IOS/MIOS.h - IOS/Starlet/ARMCore.cpp +add_library(core + AchievementManager.cpp + AchievementManager.h + ActionReplay.cpp + ActionReplay.h + ARDecrypt.cpp + ARDecrypt.h + Boot/AncastTypes.h + Boot/Boot_BS2Emu.cpp + Boot/Boot_WiiWAD.cpp + Boot/Boot.cpp + Boot/Boot.h + Boot/DolReader.cpp + Boot/DolReader.h + Boot/ElfReader.cpp + Boot/ElfReader.h + Boot/ElfTypes.h + BootManager.cpp + BootManager.h + CheatCodes.h + CheatGeneration.cpp + CheatGeneration.h + CheatSearch.cpp + CheatSearch.h + CommonTitles.h + Config/AchievementSettings.cpp + Config/AchievementSettings.h + Config/DefaultLocale.cpp + Config/DefaultLocale.h + Config/FreeLookSettings.cpp + Config/FreeLookSettings.h + Config/GraphicsSettings.cpp + Config/GraphicsSettings.h + Config/MainSettings.cpp + Config/MainSettings.h + Config/NetplaySettings.cpp + Config/NetplaySettings.h + Config/SessionSettings.cpp + Config/SessionSettings.h + Config/SYSCONFSettings.cpp + Config/SYSCONFSettings.h + Config/UISettings.cpp + Config/UISettings.h + Config/WiimoteSettings.cpp + Config/WiimoteSettings.h + ConfigLoaders/BaseConfigLoader.cpp + ConfigLoaders/BaseConfigLoader.h + ConfigLoaders/GameConfigLoader.cpp + ConfigLoaders/GameConfigLoader.h + ConfigLoaders/IsSettingSaveable.cpp + ConfigLoaders/IsSettingSaveable.h + ConfigLoaders/MovieConfigLoader.cpp + ConfigLoaders/MovieConfigLoader.h + ConfigLoaders/NetPlayConfigLoader.cpp + ConfigLoaders/NetPlayConfigLoader.h + ConfigManager.cpp + ConfigManager.h + Core.cpp + Core.h + CoreTiming.cpp + CoreTiming.h + CPUThreadConfigCallback.cpp + CPUThreadConfigCallback.h + Debugger/BranchWatch.cpp + Debugger/BranchWatch.h + Debugger/CodeTrace.cpp + Debugger/CodeTrace.h + Debugger/DebugInterface.h + Debugger/Debugger_SymbolMap.cpp + Debugger/Debugger_SymbolMap.h + Debugger/Dump.cpp + Debugger/Dump.h + Debugger/OSThread.cpp + Debugger/OSThread.h + Debugger/PPCDebugInterface.cpp + Debugger/PPCDebugInterface.h + Debugger/RSO.cpp + Debugger/RSO.h + DolphinAnalytics.cpp + DolphinAnalytics.h + DSP/DSPAccelerator.cpp + DSP/DSPAccelerator.h + DSP/DSPAnalyzer.cpp + DSP/DSPAnalyzer.h + DSP/DSPAssembler.cpp + DSP/DSPAssembler.h + DSP/DSPCaptureLogger.cpp + DSP/DSPCaptureLogger.h + DSP/DSPCodeUtil.cpp + DSP/DSPCodeUtil.h + DSP/DSPCommon.h + DSP/DSPCore.cpp + DSP/DSPCore.h + DSP/DSPDisassembler.cpp + DSP/DSPDisassembler.h + DSP/DSPHWInterface.cpp + DSP/DSPMemoryMap.cpp + DSP/DSPStacks.cpp + DSP/DSPTables.cpp + DSP/DSPTables.h + DSP/Interpreter/DSPIntArithmetic.cpp + DSP/Interpreter/DSPIntBranch.cpp + DSP/Interpreter/DSPIntCCUtil.h + DSP/Interpreter/DSPInterpreter.cpp + DSP/Interpreter/DSPInterpreter.h + DSP/Interpreter/DSPIntExtOps.cpp + DSP/Interpreter/DSPIntLoadStore.cpp + DSP/Interpreter/DSPIntMisc.cpp + DSP/Interpreter/DSPIntMultiplier.cpp + DSP/Interpreter/DSPIntTables.cpp + DSP/Interpreter/DSPIntTables.h + DSP/Interpreter/DSPIntUtil.h + DSP/Jit/DSPEmitterBase.cpp + DSP/Jit/DSPEmitterBase.h + DSP/LabelMap.cpp + DSP/LabelMap.h + DSPEmulator.cpp + DSPEmulator.h + FifoPlayer/FifoDataFile.cpp + FifoPlayer/FifoDataFile.h + FifoPlayer/FifoPlayer.cpp + FifoPlayer/FifoPlayer.h + FifoPlayer/FifoRecorder.cpp + FifoPlayer/FifoRecorder.h + FreeLookManager.cpp + FreeLookManager.h + GeckoCode.cpp + GeckoCode.h + GeckoCodeConfig.cpp + GeckoCodeConfig.h + HLE/HLE_Misc.cpp + HLE/HLE_Misc.h + HLE/HLE_OS.cpp + HLE/HLE_OS.h + HLE/HLE_VarArgs.cpp + HLE/HLE_VarArgs.h + HLE/HLE.cpp + HLE/HLE.h + Host.h + HotkeyManager.cpp + HotkeyManager.h + HW/AddressSpace.cpp + HW/AddressSpace.h + HW/AudioInterface.cpp + HW/AudioInterface.h + HW/CPU.cpp + HW/CPU.h + HW/DSP.cpp + HW/DSP.h + HW/DSPHLE/DSPHLE.cpp + HW/DSPHLE/DSPHLE.h + HW/DSPHLE/MailHandler.cpp + HW/DSPHLE/MailHandler.h + HW/DSPHLE/UCodes/ASnd.cpp + HW/DSPHLE/UCodes/ASnd.h + HW/DSPHLE/UCodes/AESnd.cpp + HW/DSPHLE/UCodes/AESnd.h + HW/DSPHLE/UCodes/AX.cpp + HW/DSPHLE/UCodes/AX.h + HW/DSPHLE/UCodes/AXStructs.h + HW/DSPHLE/UCodes/AXVoice.h + HW/DSPHLE/UCodes/AXWii.cpp + HW/DSPHLE/UCodes/AXWii.h + HW/DSPHLE/UCodes/CARD.cpp + HW/DSPHLE/UCodes/CARD.h + HW/DSPHLE/UCodes/GBA.cpp + HW/DSPHLE/UCodes/GBA.h + HW/DSPHLE/UCodes/INIT.cpp + HW/DSPHLE/UCodes/INIT.h + HW/DSPHLE/UCodes/ROM.cpp + HW/DSPHLE/UCodes/ROM.h + HW/DSPHLE/UCodes/UCodes.cpp + HW/DSPHLE/UCodes/UCodes.h + HW/DSPHLE/UCodes/Zelda.cpp + HW/DSPHLE/UCodes/Zelda.h + HW/DSPLLE/DSPHost.cpp + HW/DSPLLE/DSPLLE.cpp + HW/DSPLLE/DSPLLE.h + HW/DSPLLE/DSPSymbols.cpp + HW/DSPLLE/DSPSymbols.h + HW/DVD/DVDInterface.cpp + HW/DVD/DVDInterface.h + HW/DVD/AMMediaboard.cpp + HW/DVD/AMMediaboard.h + HW/DVD/DVDMath.cpp + HW/DVD/DVDMath.h + HW/DVD/DVDThread.cpp + HW/DVD/DVDThread.h + HW/DVD/FileMonitor.cpp + HW/DVD/FileMonitor.h + HW/EXI/BBA/TAPServerConnection.cpp + HW/EXI/BBA/TAPServerBBA.cpp + HW/EXI/BBA/XLINK_KAI_BBA.cpp + HW/EXI/BBA/BuiltIn.cpp + HW/EXI/BBA/BuiltIn.h + HW/EXI/Modem/TAPServerModem.cpp + HW/EXI/EXI_Channel.cpp + HW/EXI/EXI_Channel.h + HW/EXI/EXI_Device.cpp + HW/EXI/EXI_Device.h + HW/EXI/EXI_DeviceAD16.cpp + HW/EXI/EXI_DeviceAD16.h + HW/EXI/EXI_DeviceAGP.cpp + HW/EXI/EXI_DeviceAGP.h + HW/EXI/EXI_DeviceBaseboard.cpp + HW/EXI/EXI_DeviceBaseboard.h + HW/EXI/EXI_DeviceDummy.cpp + HW/EXI/EXI_DeviceDummy.h + HW/EXI/EXI_DeviceEthernet.cpp + HW/EXI/EXI_DeviceEthernet.h + HW/EXI/EXI_DeviceGecko.cpp + HW/EXI/EXI_DeviceGecko.h + HW/EXI/EXI_DeviceIPL.cpp + HW/EXI/EXI_DeviceIPL.h + HW/EXI/EXI_DeviceMemoryCard.cpp + HW/EXI/EXI_DeviceMemoryCard.h + HW/EXI/EXI_DeviceModem.cpp + HW/EXI/EXI_DeviceModem.h + HW/EXI/EXI.cpp + HW/EXI/EXI.h + HW/GBAPad.cpp + HW/GBAPad.h + HW/GBAPadEmu.cpp + HW/GBAPadEmu.h + HW/GCKeyboard.cpp + HW/GCKeyboard.h + HW/GCKeyboardEmu.cpp + HW/GCKeyboardEmu.h + HW/GCMemcard/GCIFile.cpp + HW/GCMemcard/GCIFile.h + HW/GCMemcard/GCMemcard.cpp + HW/GCMemcard/GCMemcard.h + HW/GCMemcard/GCMemcardBase.h + HW/GCMemcard/GCMemcardDirectory.cpp + HW/GCMemcard/GCMemcardDirectory.h + HW/GCMemcard/GCMemcardRaw.cpp + HW/GCMemcard/GCMemcardRaw.h + HW/GCMemcard/GCMemcardUtils.cpp + HW/GCMemcard/GCMemcardUtils.h + HW/GCPad.cpp + HW/GCPad.h + HW/GCPadEmu.cpp + HW/GCPadEmu.h + HW/GPFifo.cpp + HW/GPFifo.h + HW/HSP/HSP.cpp + HW/HSP/HSP.h + HW/HSP/HSP_Device.cpp + HW/HSP/HSP_Device.h + HW/HSP/HSP_DeviceARAMExpansion.cpp + HW/HSP/HSP_DeviceARAMExpansion.h + HW/HSP/HSP_DeviceGBPlayer.cpp + HW/HSP/HSP_DeviceGBPlayer.h + HW/HSP/HSP_DeviceNull.cpp + HW/HSP/HSP_DeviceNull.h + HW/HW.cpp + HW/HW.h + HW/MagCard/C1231BR.cpp + HW/MagCard/C1231BR.h + HW/MagCard/C1231LR.cpp + HW/MagCard/C1231LR.h + HW/MagCard/MagneticCardReader.cpp + HW/MagCard/MagneticCardReader.h + HW/Memmap.cpp + HW/Memmap.h + HW/MemoryInterface.cpp + HW/MemoryInterface.h + HW/MMIO.cpp + HW/MMIO.h + HW/ProcessorInterface.cpp + HW/ProcessorInterface.h + HW/SI/SI_Device.cpp + HW/SI/SI_Device.h + HW/SI/SI_DeviceDanceMat.cpp + HW/SI/SI_DeviceDanceMat.h + HW/SI/SI_DeviceGBA.cpp + HW/SI/SI_DeviceGBA.h + HW/SI/SI_DeviceGCAdapter.cpp + HW/SI/SI_DeviceGCAdapter.h + HW/SI/SI_DeviceGCController.cpp + HW/SI/SI_DeviceGCController.h + HW/SI/SI_DeviceGCSteeringWheel.cpp + HW/SI/SI_DeviceGCSteeringWheel.h + HW/SI/SI_DeviceKeyboard.cpp + HW/SI/SI_DeviceKeyboard.h + HW/SI/SI_DeviceAMBaseboard.cpp + HW/SI/SI_DeviceAMBaseboard.h + HW/SI/SI_DeviceNull.cpp + HW/SI/SI_DeviceNull.h + HW/SI/SI.cpp + HW/SI/SI.h + HW/Sram.cpp + HW/Sram.h + HW/StreamADPCM.cpp + HW/StreamADPCM.h + HW/SystemTimers.cpp + HW/SystemTimers.h + HW/Triforce/DeckReader.cpp + HW/Triforce/DeckReader.h + HW/Triforce/FZeroAX.cpp + HW/Triforce/FZeroAX.h + HW/Triforce/ICCardReader.cpp + HW/Triforce/ICCardReader.h + HW/Triforce/IOPorts.cpp + HW/Triforce/IOPorts.h + HW/Triforce/JVSIO.cpp + HW/Triforce/JVSIO.h + HW/Triforce/MarioKartGP.cpp + HW/Triforce/MarioKartGP.h + HW/Triforce/SerialDevice.cpp + HW/Triforce/SerialDevice.h + HW/Triforce/Touchscreen.cpp + HW/Triforce/Touchscreen.h + HW/VideoInterface.cpp + HW/VideoInterface.h + HW/WII_IPC.cpp + HW/WII_IPC.h + HW/Wiimote.cpp + HW/Wiimote.h + HW/WiimoteCommon/DataReport.cpp + HW/WiimoteCommon/DataReport.h + HW/WiimoteCommon/WiimoteConstants.h + HW/WiimoteCommon/WiimoteHid.h + HW/WiimoteCommon/WiimoteReport.h + HW/WiimoteEmu/Camera.cpp + HW/WiimoteEmu/Camera.h + HW/WiimoteEmu/DesiredWiimoteState.cpp + HW/WiimoteEmu/DesiredWiimoteState.h + HW/WiimoteEmu/Dynamics.cpp + HW/WiimoteEmu/Dynamics.h + HW/WiimoteEmu/EmuSubroutines.cpp + HW/WiimoteEmu/Encryption.cpp + HW/WiimoteEmu/Encryption.h + HW/WiimoteEmu/Extension/Classic.cpp + HW/WiimoteEmu/Extension/Classic.h + HW/WiimoteEmu/Extension/DesiredExtensionState.h + HW/WiimoteEmu/Extension/DrawsomeTablet.cpp + HW/WiimoteEmu/Extension/DrawsomeTablet.h + HW/WiimoteEmu/Extension/Drums.cpp + HW/WiimoteEmu/Extension/Drums.h + HW/WiimoteEmu/Extension/Extension.cpp + HW/WiimoteEmu/Extension/Extension.h + HW/WiimoteEmu/Extension/Guitar.cpp + HW/WiimoteEmu/Extension/Guitar.h + HW/WiimoteEmu/Extension/Nunchuk.cpp + HW/WiimoteEmu/Extension/Nunchuk.h + HW/WiimoteEmu/Extension/Shinkansen.cpp + HW/WiimoteEmu/Extension/Shinkansen.h + HW/WiimoteEmu/Extension/TaTaCon.cpp + HW/WiimoteEmu/Extension/TaTaCon.h + HW/WiimoteEmu/Extension/Turntable.cpp + HW/WiimoteEmu/Extension/Turntable.h + HW/WiimoteEmu/Extension/UDrawTablet.cpp + HW/WiimoteEmu/Extension/UDrawTablet.h + HW/WiimoteEmu/ExtensionPort.cpp + HW/WiimoteEmu/ExtensionPort.h + HW/WiimoteEmu/I2CBus.cpp + HW/WiimoteEmu/I2CBus.h + HW/WiimoteEmu/MotionPlus.cpp + HW/WiimoteEmu/MotionPlus.h + HW/WiimoteEmu/Speaker.cpp + HW/WiimoteEmu/Speaker.h + HW/WiimoteEmu/WiimoteEmu.cpp + HW/WiimoteEmu/WiimoteEmu.h + HW/WiimoteReal/WiimoteReal.cpp + HW/WiimoteReal/WiimoteReal.h + HW/WiiSave.cpp + HW/WiiSave.h + HW/WiiSaveStructs.h + IOS/Device.cpp + IOS/Device.h + IOS/DeviceStub.cpp + IOS/DeviceStub.h + IOS/Crypto/AesDevice.cpp + IOS/Crypto/AesDevice.h + IOS/Crypto/Sha.cpp + IOS/Crypto/Sha.h + IOS/DI/DI.cpp + IOS/DI/DI.h + IOS/DolphinDevice.cpp + IOS/DolphinDevice.h + IOS/ES/ES.cpp + IOS/ES/ES.h + IOS/ES/Formats.cpp + IOS/ES/Formats.h + IOS/ES/Identity.cpp + IOS/ES/NandUtils.cpp + IOS/ES/TitleContents.cpp + IOS/ES/TitleInformation.cpp + IOS/ES/TitleManagement.cpp + IOS/ES/Views.cpp + IOS/FS/FileSystem.h + IOS/FS/FileSystemCommon.cpp + IOS/FS/FileSystemProxy.cpp + IOS/FS/FileSystemProxy.h + IOS/FS/HostBackend/File.cpp + IOS/FS/HostBackend/FS.cpp + IOS/FS/HostBackend/FS.h + IOS/IOS.cpp + IOS/IOS.h + IOS/IOSC.cpp + IOS/IOSC.h + IOS/MIOS.cpp + IOS/MIOS.h + IOS/Starlet/ARMCore.cpp IOS/Starlet/ARMCore.h + IOS/Starlet/BootstrapELF.cpp + IOS/Starlet/BootstrapELF.h IOS/Starlet/AX88772.cpp IOS/Starlet/AX88772.h IOS/Starlet/NANDJournal.cpp IOS/Starlet/NANDJournal.h IOS/Starlet/SlirpNetwork.cpp IOS/Starlet/SlirpNetwork.h - IOS/Starlet/Starlet.cpp - IOS/Starlet/Starlet.h - IOS/Starlet/StarletMemory.cpp - IOS/Starlet/StarletMemory.h - IOS/Network/ICMP.h - IOS/Network/ICMPLin.cpp - IOS/Network/IP/Top.cpp - IOS/Network/IP/Top.h - IOS/Network/KD/NetKDRequest.cpp - IOS/Network/KD/NetKDRequest.h - IOS/Network/KD/NetKDTime.cpp - IOS/Network/KD/NetKDTime.h - IOS/Network/KD/NWC24Config.cpp - IOS/Network/KD/NWC24Config.h - IOS/Network/KD/NWC24DL.cpp - IOS/Network/KD/NWC24DL.h - IOS/Network/KD/VFF/VFFUtil.cpp - IOS/Network/KD/VFF/VFFUtil.h - IOS/Network/KD/WC24File.h - IOS/Network/KD/Mail/MailCommon.h - IOS/Network/KD/Mail/WC24FriendList.cpp - IOS/Network/KD/Mail/WC24FriendList.h - IOS/Network/KD/Mail/WC24Send.cpp - IOS/Network/KD/Mail/WC24Send.h - IOS/Network/MACUtils.cpp - IOS/Network/MACUtils.h - IOS/Network/NCD/Manage.cpp - IOS/Network/NCD/Manage.h - IOS/Network/NCD/WiiNetConfig.cpp - IOS/Network/NCD/WiiNetConfig.h - IOS/Network/Socket.cpp - IOS/Network/Socket.h - IOS/Network/SSL.cpp - IOS/Network/SSL.h - IOS/Network/WD/Command.cpp - IOS/Network/WD/Command.h - IOS/SDIO/SDIOSlot0.cpp - IOS/SDIO/SDIOSlot0.h - IOS/STM/STM.cpp - IOS/STM/STM.h - IOS/USB/Bluetooth/BTBase.cpp - IOS/USB/Bluetooth/BTBase.h - IOS/USB/Bluetooth/BTEmu.cpp - IOS/USB/Bluetooth/BTEmu.h - IOS/USB/Bluetooth/BTStub.cpp - IOS/USB/Bluetooth/BTStub.h - IOS/USB/Bluetooth/hci.h - IOS/USB/Bluetooth/l2cap.h - IOS/USB/Bluetooth/WiimoteDevice.cpp - IOS/USB/Bluetooth/WiimoteDevice.h - IOS/USB/Bluetooth/WiimoteHIDAttr.cpp - IOS/USB/Bluetooth/WiimoteHIDAttr.h - IOS/USB/Common.cpp - IOS/USB/Common.h - IOS/USB/Emulated/Infinity.cpp - IOS/USB/Emulated/Infinity.h - IOS/USB/Emulated/Microphone.cpp - IOS/USB/Emulated/Microphone.h - IOS/USB/Emulated/Skylanders/Skylander.cpp - IOS/USB/Emulated/Skylanders/Skylander.h - IOS/USB/Emulated/Skylanders/SkylanderCrypto.cpp - IOS/USB/Emulated/Skylanders/SkylanderCrypto.h - IOS/USB/Emulated/Skylanders/SkylanderFigure.cpp - IOS/USB/Emulated/Skylanders/SkylanderFigure.h - IOS/USB/Emulated/WiiSpeak.cpp - IOS/USB/Emulated/WiiSpeak.h - IOS/USB/Emulated/LogitechMic.cpp - IOS/USB/Emulated/LogitechMic.h - IOS/USB/Host.cpp - IOS/USB/Host.h - IOS/USB/OH0/OH0.cpp - IOS/USB/OH0/OH0.h - IOS/USB/OH0/OH0Device.cpp - IOS/USB/OH0/OH0Device.h - IOS/USB/USB_HID/HIDv4.cpp - IOS/USB/USB_HID/HIDv4.h - IOS/USB/USB_HID/HIDv5.cpp - IOS/USB/USB_HID/HIDv5.h - IOS/USB/USB_KBD.cpp - IOS/USB/USB_KBD.h - IOS/USB/USB_VEN/VEN.cpp - IOS/USB/USB_VEN/VEN.h - IOS/USB/USBScanner.cpp - IOS/USB/USBScanner.h - IOS/USB/USBV0.cpp - IOS/USB/USBV0.h - IOS/USB/USBV4.cpp - IOS/USB/USBV4.h - IOS/USB/USBV5.cpp - IOS/USB/USBV5.h - IOS/VersionInfo.cpp - IOS/VersionInfo.h - IOS/WFS/WFSI.cpp - IOS/WFS/WFSI.h - IOS/WFS/WFSSRV.cpp - IOS/WFS/WFSSRV.h - LibusbUtils.cpp - LibusbUtils.h - MemTools.cpp - MemTools.h - Movie.cpp - Movie.h - NetPlayClient.cpp - NetPlayClient.h - NetPlayCommon.cpp - NetPlayCommon.h - NetPlayServer.cpp - NetPlayServer.h - NetworkCaptureLogger.cpp - NetworkCaptureLogger.h - PatchEngine.cpp - PatchEngine.h - PowerPC/BreakPoints.cpp - PowerPC/BreakPoints.h - PowerPC/CachedInterpreter/CachedInterpreter_Disassembler.cpp - PowerPC/CachedInterpreter/CachedInterpreter.cpp - PowerPC/CachedInterpreter/CachedInterpreter.h - PowerPC/CachedInterpreter/CachedInterpreterBlockCache.cpp - PowerPC/CachedInterpreter/CachedInterpreterBlockCache.h - PowerPC/CachedInterpreter/CachedInterpreterEmitter.cpp - PowerPC/CachedInterpreter/CachedInterpreterEmitter.h - PowerPC/ConditionRegister.cpp - PowerPC/ConditionRegister.h - PowerPC/Expression.cpp - PowerPC/Expression.h - PowerPC/Interpreter/ExceptionUtils.h - PowerPC/Interpreter/Interpreter_Branch.cpp - PowerPC/Interpreter/Interpreter_FloatingPoint.cpp - PowerPC/Interpreter/Interpreter_FPUtils.h - PowerPC/Interpreter/Interpreter_Integer.cpp - PowerPC/Interpreter/Interpreter_LoadStore.cpp - PowerPC/Interpreter/Interpreter_LoadStorePaired.cpp - PowerPC/Interpreter/Interpreter_Paired.cpp - PowerPC/Interpreter/Interpreter_SystemRegisters.cpp - PowerPC/Interpreter/Interpreter_Tables.cpp - PowerPC/Interpreter/Interpreter.cpp - PowerPC/Interpreter/Interpreter.h - PowerPC/JitCommon/ConstantPropagation.cpp - PowerPC/JitCommon/ConstantPropagation.h - PowerPC/JitCommon/DivUtils.cpp - PowerPC/JitCommon/DivUtils.h - PowerPC/JitCommon/JitAsmCommon.cpp - PowerPC/JitCommon/JitAsmCommon.h - PowerPC/JitCommon/JitBase.cpp - PowerPC/JitCommon/JitBase.h - PowerPC/JitCommon/JitCache.cpp - PowerPC/JitCommon/JitCache.h - PowerPC/JitInterface.cpp - PowerPC/JitInterface.h - PowerPC/GDBStub.cpp - PowerPC/GDBStub.h - PowerPC/MMU.cpp - PowerPC/MMU.h - PowerPC/PowerPC.cpp - PowerPC/PowerPC.h - PowerPC/PPCAnalyst.cpp - PowerPC/PPCAnalyst.h - PowerPC/PPCCache.cpp - PowerPC/PPCCache.h - PowerPC/PPCSymbolDB.cpp - PowerPC/PPCSymbolDB.h - PowerPC/PPCTables.cpp - PowerPC/PPCTables.h - PowerPC/SignatureDB/CSVSignatureDB.cpp - PowerPC/SignatureDB/CSVSignatureDB.h - PowerPC/SignatureDB/DSYSignatureDB.cpp - PowerPC/SignatureDB/DSYSignatureDB.h - PowerPC/SignatureDB/MEGASignatureDB.cpp - PowerPC/SignatureDB/MEGASignatureDB.h - PowerPC/SignatureDB/SignatureDB.cpp - PowerPC/SignatureDB/SignatureDB.h - State.cpp - State.h - SyncIdentifier.h - SysConf.cpp - SysConf.h - System.cpp - System.h - TimePlayed.cpp - TimePlayed.h - TitleDatabase.cpp - TitleDatabase.h - USBUtils.cpp - USBUtils.h - WC24PatchEngine.cpp - WC24PatchEngine.h - WiiRoot.cpp - WiiRoot.h - WiiUtils.cpp - WiiUtils.h -) - -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/AchievementApprovedHash.h - COMMAND ${CMAKE_COMMAND} - -DJSON_FILE=${CMAKE_SOURCE_DIR}/Data/Sys/ApprovedInis.json - -DTEMPLATE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/AchievementApprovedHash.h.in - -DOUTPUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/AchievementApprovedHash.h - -P ${CMAKE_SOURCE_DIR}/CMake/GenerateAchievementHash.cmake - DEPENDS ${CMAKE_SOURCE_DIR}/Data/Sys/ApprovedInis.json - ${CMAKE_CURRENT_SOURCE_DIR}/AchievementApprovedHash.h.in - COMMENT "Generating AchievementApprovedHash.h" -) - -target_sources(core PRIVATE AchievementApprovedHash.h) - -if(_M_X86_64) - target_sources(core PRIVATE - IOS/Starlet/ARMJitX64.cpp - IOS/Starlet/ARMJitX64.h - DSP/Jit/x64/DSPEmitter.cpp - DSP/Jit/x64/DSPEmitter.h - DSP/Jit/x64/DSPJitArithmetic.cpp - DSP/Jit/x64/DSPJitBranch.cpp - DSP/Jit/x64/DSPJitCCUtil.cpp - DSP/Jit/x64/DSPJitExtOps.cpp - DSP/Jit/x64/DSPJitLoadStore.cpp - DSP/Jit/x64/DSPJitMisc.cpp - DSP/Jit/x64/DSPJitMultiplier.cpp - DSP/Jit/x64/DSPJitRegCache.cpp - DSP/Jit/x64/DSPJitRegCache.h - DSP/Jit/x64/DSPJitTables.cpp - DSP/Jit/x64/DSPJitTables.h - DSP/Jit/x64/DSPJitUtil.cpp - PowerPC/Jit64/Jit.cpp - PowerPC/Jit64/Jit.h - PowerPC/Jit64/Jit64_Tables.cpp - PowerPC/Jit64/Jit_Branch.cpp - PowerPC/Jit64/Jit_FloatingPoint.cpp - PowerPC/Jit64/Jit_Integer.cpp - PowerPC/Jit64/Jit_LoadStore.cpp - PowerPC/Jit64/Jit_LoadStoreFloating.cpp - PowerPC/Jit64/Jit_LoadStorePaired.cpp - PowerPC/Jit64/Jit_Paired.cpp - PowerPC/Jit64/Jit_SystemRegisters.cpp - PowerPC/Jit64/JitAsm.cpp - PowerPC/Jit64/JitAsm.h - PowerPC/Jit64/RegCache/CachedReg.h - PowerPC/Jit64/RegCache/FPURegCache.cpp - PowerPC/Jit64/RegCache/FPURegCache.h - PowerPC/Jit64/RegCache/GPRRegCache.cpp - PowerPC/Jit64/RegCache/GPRRegCache.h - PowerPC/Jit64/RegCache/JitRegCache.cpp - PowerPC/Jit64/RegCache/JitRegCache.h - PowerPC/Jit64/RegCache/RCMode.h - PowerPC/Jit64Common/BlockCache.cpp - PowerPC/Jit64Common/BlockCache.h - PowerPC/Jit64Common/ConstantPool.cpp - PowerPC/Jit64Common/ConstantPool.h - PowerPC/Jit64Common/EmuCodeBlock.cpp - PowerPC/Jit64Common/EmuCodeBlock.h - PowerPC/Jit64Common/FarCodeCache.cpp - PowerPC/Jit64Common/FarCodeCache.h - PowerPC/Jit64Common/Jit64AsmCommon.cpp - PowerPC/Jit64Common/Jit64AsmCommon.h - PowerPC/Jit64Common/Jit64Constants.h - PowerPC/Jit64Common/Jit64PowerPCState.h - PowerPC/Jit64Common/TrampolineCache.cpp - PowerPC/Jit64Common/TrampolineCache.h - PowerPC/Jit64Common/TrampolineInfo.h - ) -elseif(_M_ARM_64) - target_sources(core PRIVATE - PowerPC/JitArm64/Jit.cpp - PowerPC/JitArm64/Jit.h - PowerPC/JitArm64/JitAsm.cpp - PowerPC/JitArm64/JitArm64Cache.cpp - PowerPC/JitArm64/JitArm64_BackPatch.cpp - PowerPC/JitArm64/JitArm64_Branch.cpp - PowerPC/JitArm64/JitArm64_FloatingPoint.cpp - PowerPC/JitArm64/JitArm64_Integer.cpp - PowerPC/JitArm64/JitArm64_LoadStore.cpp - PowerPC/JitArm64/JitArm64_LoadStoreFloating.cpp - PowerPC/JitArm64/JitArm64_LoadStorePaired.cpp - PowerPC/JitArm64/JitArm64_Paired.cpp - PowerPC/JitArm64/JitArm64_RegCache.cpp - PowerPC/JitArm64/JitArm64_RegCache.h - PowerPC/JitArm64/JitArm64_SystemRegisters.cpp - PowerPC/JitArm64/Jit_Util.cpp - PowerPC/JitArm64/Jit_Util.h - PowerPC/JitArm64/JitArm64_Tables.cpp - PowerPC/JitArmCommon/BackPatch.h - ) -endif() - -target_link_libraries(core -PUBLIC - audiocommon - common - discio - enet::enet - expr - inputcommon - MbedTLS::mbedtls - pugixml - sfml-network - videonull - videoogl - videosoftware - -PRIVATE - FatFs - fmt::fmt - LZO::LZO - LZ4::LZ4 - ZLIB::ZLIB -) - -if(LIBUDEV_FOUND) - target_include_directories(core PRIVATE ${LIBUDEV_INCLUDE_DIR}) - target_link_libraries(core PRIVATE ${LIBUDEV_LIBRARY}) -endif() - -if (APPLE) - target_link_libraries(core - PRIVATE - ${CORESERV_LIBRARY} - ${IOB_LIBRARY} - ${IOK_LIBRARY} - resolv - ) -elseif (ANDROID) - target_link_libraries(core - PRIVATE - androidcommon - ) -endif() - -if(ENABLE_CUBEB) - target_link_libraries(core PUBLIC cubeb) - target_sources(core PRIVATE - HW/EXI/EXI_DeviceMic.cpp - HW/EXI/EXI_DeviceMic.h - ) -endif() - -if(TARGET LibUSB::LibUSB) - target_link_libraries(core PUBLIC LibUSB::LibUSB) - target_sources(core PRIVATE - IOS/USB/LibusbDevice.cpp - IOS/USB/LibusbDevice.h - IOS/USB/Bluetooth/BTReal.cpp - IOS/USB/Bluetooth/BTReal.h - IOS/USB/Bluetooth/LibUSBBluetoothAdapter.cpp - IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h - IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp - IOS/USB/Bluetooth/RealtekFirmwareLoader.h - ) -endif() - -if(ENABLE_VULKAN) - target_link_libraries(core PUBLIC videovulkan) -endif() - -if(APPLE) - target_link_libraries(core PUBLIC videometal) -endif() - -if(USE_MGBA) - target_sources(core PRIVATE - HW/GBACore.cpp - HW/GBACore.h - HW/SI/SI_DeviceGBAEmu.cpp - HW/SI/SI_DeviceGBAEmu.h - ) - target_link_libraries(core PUBLIC mGBA::mgba) - target_compile_definitions(core PUBLIC -DHAS_LIBMGBA) -endif() - -if(WIN32) - target_sources(core PRIVATE - HW/EXI/BBA/TAP_Win32.cpp - HW/EXI/BBA/TAP_Win32.h - HW/WiimoteReal/IOWin.cpp - HW/WiimoteReal/IOWin.h - ) - target_link_libraries(core PUBLIC - videod3d - videod3d12 - setupapi.lib - iphlpapi.lib - ) - target_compile_definitions(core PRIVATE "-D_WINSOCK_DEPRECATED_NO_WARNINGS") -elseif(APPLE) - target_sources(core PRIVATE - HW/EXI/BBA/TAP_Apple.cpp - ) - target_link_libraries(core PUBLIC ${IOB_LIBRARY}) -elseif(UNIX) - target_sources(core PRIVATE - HW/EXI/BBA/TAP_Unix.cpp - ) - if(ANDROID) - target_sources(core PRIVATE - HW/WiimoteReal/IOAndroid.cpp - HW/WiimoteReal/IOAndroid.h - ) - endif() -endif() - -# Bluez doesn't support all the communication modes on FreeBSD, so only using it on Linux -if(LINUX) - option(ENABLE_BLUEZ "Enables bluetooth support" ON) - if(ENABLE_BLUEZ) - find_package(BlueZ) - if(BlueZ_FOUND) - message(STATUS "BlueZ found, enabling bluetooth support") - target_sources(core PRIVATE - HW/WiimoteReal/IOLinux.cpp - HW/WiimoteReal/IOLinux.h - ) - target_link_libraries(core PUBLIC BlueZ::BlueZ) - target_compile_definitions(core PRIVATE -DHAVE_BLUEZ=1) - else() - message(STATUS "BlueZ NOT found, disabling bluetooth support") - endif() - else() - message(STATUS "BlueZ explicitly disabled, disabling bluetooth support") - endif() -endif() - -if(CMAKE_SYSTEM_NAME MATCHES "OpenBSD") - # OpenBSD doesn't allow memory to be both writable and executable by default. - # The JIT currently needs this. - target_link_options(core PUBLIC -Wl,-zwxneeded) -endif() - -if(TARGET Hidapi::Hidapi) - target_sources(core PRIVATE - HW/WiimoteReal/IOhidapi.cpp - HW/WiimoteReal/IOhidapi.h - ) - target_link_libraries(core PUBLIC Hidapi::Hidapi) - target_compile_definitions(core PRIVATE -DHAVE_HIDAPI=1) -endif() - -if(UNIX) - target_sources(core PRIVATE - MemoryWatcher.cpp - MemoryWatcher.h - ) -endif() - -if(TARGET cpp-ipc::ipc) - target_sources(core PRIVATE HW/EXI/BBA/IPC.cpp) - target_link_libraries(core PRIVATE cpp-ipc::ipc) -endif() - + IOS/Starlet/Starlet.cpp + IOS/Starlet/Starlet.h + IOS/Starlet/StarletMemory.cpp + IOS/Starlet/StarletMemory.h + IOS/Network/ICMP.h + IOS/Network/ICMPLin.cpp + IOS/Network/IP/Top.cpp + IOS/Network/IP/Top.h + IOS/Network/KD/NetKDRequest.cpp + IOS/Network/KD/NetKDRequest.h + IOS/Network/KD/NetKDTime.cpp + IOS/Network/KD/NetKDTime.h + IOS/Network/KD/NWC24Config.cpp + IOS/Network/KD/NWC24Config.h + IOS/Network/KD/NWC24DL.cpp + IOS/Network/KD/NWC24DL.h + IOS/Network/KD/VFF/VFFUtil.cpp + IOS/Network/KD/VFF/VFFUtil.h + IOS/Network/KD/WC24File.h + IOS/Network/KD/Mail/MailCommon.h + IOS/Network/KD/Mail/WC24FriendList.cpp + IOS/Network/KD/Mail/WC24FriendList.h + IOS/Network/KD/Mail/WC24Send.cpp + IOS/Network/KD/Mail/WC24Send.h + IOS/Network/MACUtils.cpp + IOS/Network/MACUtils.h + IOS/Network/NCD/Manage.cpp + IOS/Network/NCD/Manage.h + IOS/Network/NCD/WiiNetConfig.cpp + IOS/Network/NCD/WiiNetConfig.h + IOS/Network/Socket.cpp + IOS/Network/Socket.h + IOS/Network/SSL.cpp + IOS/Network/SSL.h + IOS/Network/WD/Command.cpp + IOS/Network/WD/Command.h + IOS/SDIO/SDIOSlot0.cpp + IOS/SDIO/SDIOSlot0.h + IOS/STM/STM.cpp + IOS/STM/STM.h + IOS/USB/Bluetooth/BTBase.cpp + IOS/USB/Bluetooth/BTBase.h + IOS/USB/Bluetooth/BTEmu.cpp + IOS/USB/Bluetooth/BTEmu.h + IOS/USB/Bluetooth/BTStub.cpp + IOS/USB/Bluetooth/BTStub.h + IOS/USB/Bluetooth/hci.h + IOS/USB/Bluetooth/l2cap.h + IOS/USB/Bluetooth/WiimoteDevice.cpp + IOS/USB/Bluetooth/WiimoteDevice.h + IOS/USB/Bluetooth/WiimoteHIDAttr.cpp + IOS/USB/Bluetooth/WiimoteHIDAttr.h + IOS/USB/Common.cpp + IOS/USB/Common.h + IOS/USB/Emulated/Infinity.cpp + IOS/USB/Emulated/Infinity.h + IOS/USB/Emulated/Microphone.cpp + IOS/USB/Emulated/Microphone.h + IOS/USB/Emulated/Skylanders/Skylander.cpp + IOS/USB/Emulated/Skylanders/Skylander.h + IOS/USB/Emulated/Skylanders/SkylanderCrypto.cpp + IOS/USB/Emulated/Skylanders/SkylanderCrypto.h + IOS/USB/Emulated/Skylanders/SkylanderFigure.cpp + IOS/USB/Emulated/Skylanders/SkylanderFigure.h + IOS/USB/Emulated/WiiSpeak.cpp + IOS/USB/Emulated/WiiSpeak.h + IOS/USB/Emulated/LogitechMic.cpp + IOS/USB/Emulated/LogitechMic.h + IOS/USB/Host.cpp + IOS/USB/Host.h + IOS/USB/OH0/OH0.cpp + IOS/USB/OH0/OH0.h + IOS/USB/OH0/OH0Device.cpp + IOS/USB/OH0/OH0Device.h + IOS/USB/USB_HID/HIDv4.cpp + IOS/USB/USB_HID/HIDv4.h + IOS/USB/USB_HID/HIDv5.cpp + IOS/USB/USB_HID/HIDv5.h + IOS/USB/USB_KBD.cpp + IOS/USB/USB_KBD.h + IOS/USB/USB_VEN/VEN.cpp + IOS/USB/USB_VEN/VEN.h + IOS/USB/USBScanner.cpp + IOS/USB/USBScanner.h + IOS/USB/USBV0.cpp + IOS/USB/USBV0.h + IOS/USB/USBV4.cpp + IOS/USB/USBV4.h + IOS/USB/USBV5.cpp + IOS/USB/USBV5.h + IOS/VersionInfo.cpp + IOS/VersionInfo.h + IOS/WFS/WFSI.cpp + IOS/WFS/WFSI.h + IOS/WFS/WFSSRV.cpp + IOS/WFS/WFSSRV.h + LibusbUtils.cpp + LibusbUtils.h + MemTools.cpp + MemTools.h + Movie.cpp + Movie.h + NetPlayClient.cpp + NetPlayClient.h + NetPlayCommon.cpp + NetPlayCommon.h + NetPlayServer.cpp + NetPlayServer.h + NetworkCaptureLogger.cpp + NetworkCaptureLogger.h + PatchEngine.cpp + PatchEngine.h + PowerPC/BreakPoints.cpp + PowerPC/BreakPoints.h + PowerPC/CachedInterpreter/CachedInterpreter_Disassembler.cpp + PowerPC/CachedInterpreter/CachedInterpreter.cpp + PowerPC/CachedInterpreter/CachedInterpreter.h + PowerPC/CachedInterpreter/CachedInterpreterBlockCache.cpp + PowerPC/CachedInterpreter/CachedInterpreterBlockCache.h + PowerPC/CachedInterpreter/CachedInterpreterEmitter.cpp + PowerPC/CachedInterpreter/CachedInterpreterEmitter.h + PowerPC/ConditionRegister.cpp + PowerPC/ConditionRegister.h + PowerPC/Expression.cpp + PowerPC/Expression.h + PowerPC/Interpreter/ExceptionUtils.h + PowerPC/Interpreter/Interpreter_Branch.cpp + PowerPC/Interpreter/Interpreter_FloatingPoint.cpp + PowerPC/Interpreter/Interpreter_FPUtils.h + PowerPC/Interpreter/Interpreter_Integer.cpp + PowerPC/Interpreter/Interpreter_LoadStore.cpp + PowerPC/Interpreter/Interpreter_LoadStorePaired.cpp + PowerPC/Interpreter/Interpreter_Paired.cpp + PowerPC/Interpreter/Interpreter_SystemRegisters.cpp + PowerPC/Interpreter/Interpreter_Tables.cpp + PowerPC/Interpreter/Interpreter.cpp + PowerPC/Interpreter/Interpreter.h + PowerPC/JitCommon/ConstantPropagation.cpp + PowerPC/JitCommon/ConstantPropagation.h + PowerPC/JitCommon/DivUtils.cpp + PowerPC/JitCommon/DivUtils.h + PowerPC/JitCommon/JitAsmCommon.cpp + PowerPC/JitCommon/JitAsmCommon.h + PowerPC/JitCommon/JitBase.cpp + PowerPC/JitCommon/JitBase.h + PowerPC/JitCommon/JitCache.cpp + PowerPC/JitCommon/JitCache.h + PowerPC/JitInterface.cpp + PowerPC/JitInterface.h + PowerPC/GDBStub.cpp + PowerPC/GDBStub.h + PowerPC/MMU.cpp + PowerPC/MMU.h + PowerPC/PowerPC.cpp + PowerPC/PowerPC.h + PowerPC/PPCAnalyst.cpp + PowerPC/PPCAnalyst.h + PowerPC/PPCCache.cpp + PowerPC/PPCCache.h + PowerPC/PPCSymbolDB.cpp + PowerPC/PPCSymbolDB.h + PowerPC/PPCTables.cpp + PowerPC/PPCTables.h + PowerPC/SignatureDB/CSVSignatureDB.cpp + PowerPC/SignatureDB/CSVSignatureDB.h + PowerPC/SignatureDB/DSYSignatureDB.cpp + PowerPC/SignatureDB/DSYSignatureDB.h + PowerPC/SignatureDB/MEGASignatureDB.cpp + PowerPC/SignatureDB/MEGASignatureDB.h + PowerPC/SignatureDB/SignatureDB.cpp + PowerPC/SignatureDB/SignatureDB.h + State.cpp + State.h + SyncIdentifier.h + SysConf.cpp + SysConf.h + System.cpp + System.h + TimePlayed.cpp + TimePlayed.h + TitleDatabase.cpp + TitleDatabase.h + USBUtils.cpp + USBUtils.h + WC24PatchEngine.cpp + WC24PatchEngine.h + WiiRoot.cpp + WiiRoot.h + WiiUtils.cpp + WiiUtils.h +) + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/AchievementApprovedHash.h + COMMAND ${CMAKE_COMMAND} + -DJSON_FILE=${CMAKE_SOURCE_DIR}/Data/Sys/ApprovedInis.json + -DTEMPLATE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/AchievementApprovedHash.h.in + -DOUTPUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/AchievementApprovedHash.h + -P ${CMAKE_SOURCE_DIR}/CMake/GenerateAchievementHash.cmake + DEPENDS ${CMAKE_SOURCE_DIR}/Data/Sys/ApprovedInis.json + ${CMAKE_CURRENT_SOURCE_DIR}/AchievementApprovedHash.h.in + COMMENT "Generating AchievementApprovedHash.h" +) + +target_sources(core PRIVATE AchievementApprovedHash.h) + +if(_M_X86_64) + target_sources(core PRIVATE + IOS/Starlet/ARMJitX64.cpp + IOS/Starlet/ARMJitX64.h + DSP/Jit/x64/DSPEmitter.cpp + DSP/Jit/x64/DSPEmitter.h + DSP/Jit/x64/DSPJitArithmetic.cpp + DSP/Jit/x64/DSPJitBranch.cpp + DSP/Jit/x64/DSPJitCCUtil.cpp + DSP/Jit/x64/DSPJitExtOps.cpp + DSP/Jit/x64/DSPJitLoadStore.cpp + DSP/Jit/x64/DSPJitMisc.cpp + DSP/Jit/x64/DSPJitMultiplier.cpp + DSP/Jit/x64/DSPJitRegCache.cpp + DSP/Jit/x64/DSPJitRegCache.h + DSP/Jit/x64/DSPJitTables.cpp + DSP/Jit/x64/DSPJitTables.h + DSP/Jit/x64/DSPJitUtil.cpp + PowerPC/Jit64/Jit.cpp + PowerPC/Jit64/Jit.h + PowerPC/Jit64/Jit64_Tables.cpp + PowerPC/Jit64/Jit_Branch.cpp + PowerPC/Jit64/Jit_FloatingPoint.cpp + PowerPC/Jit64/Jit_Integer.cpp + PowerPC/Jit64/Jit_LoadStore.cpp + PowerPC/Jit64/Jit_LoadStoreFloating.cpp + PowerPC/Jit64/Jit_LoadStorePaired.cpp + PowerPC/Jit64/Jit_Paired.cpp + PowerPC/Jit64/Jit_SystemRegisters.cpp + PowerPC/Jit64/JitAsm.cpp + PowerPC/Jit64/JitAsm.h + PowerPC/Jit64/RegCache/CachedReg.h + PowerPC/Jit64/RegCache/FPURegCache.cpp + PowerPC/Jit64/RegCache/FPURegCache.h + PowerPC/Jit64/RegCache/GPRRegCache.cpp + PowerPC/Jit64/RegCache/GPRRegCache.h + PowerPC/Jit64/RegCache/JitRegCache.cpp + PowerPC/Jit64/RegCache/JitRegCache.h + PowerPC/Jit64/RegCache/RCMode.h + PowerPC/Jit64Common/BlockCache.cpp + PowerPC/Jit64Common/BlockCache.h + PowerPC/Jit64Common/ConstantPool.cpp + PowerPC/Jit64Common/ConstantPool.h + PowerPC/Jit64Common/EmuCodeBlock.cpp + PowerPC/Jit64Common/EmuCodeBlock.h + PowerPC/Jit64Common/FarCodeCache.cpp + PowerPC/Jit64Common/FarCodeCache.h + PowerPC/Jit64Common/Jit64AsmCommon.cpp + PowerPC/Jit64Common/Jit64AsmCommon.h + PowerPC/Jit64Common/Jit64Constants.h + PowerPC/Jit64Common/Jit64PowerPCState.h + PowerPC/Jit64Common/TrampolineCache.cpp + PowerPC/Jit64Common/TrampolineCache.h + PowerPC/Jit64Common/TrampolineInfo.h + ) +elseif(_M_ARM_64) + target_sources(core PRIVATE + PowerPC/JitArm64/Jit.cpp + PowerPC/JitArm64/Jit.h + PowerPC/JitArm64/JitAsm.cpp + PowerPC/JitArm64/JitArm64Cache.cpp + PowerPC/JitArm64/JitArm64_BackPatch.cpp + PowerPC/JitArm64/JitArm64_Branch.cpp + PowerPC/JitArm64/JitArm64_FloatingPoint.cpp + PowerPC/JitArm64/JitArm64_Integer.cpp + PowerPC/JitArm64/JitArm64_LoadStore.cpp + PowerPC/JitArm64/JitArm64_LoadStoreFloating.cpp + PowerPC/JitArm64/JitArm64_LoadStorePaired.cpp + PowerPC/JitArm64/JitArm64_Paired.cpp + PowerPC/JitArm64/JitArm64_RegCache.cpp + PowerPC/JitArm64/JitArm64_RegCache.h + PowerPC/JitArm64/JitArm64_SystemRegisters.cpp + PowerPC/JitArm64/Jit_Util.cpp + PowerPC/JitArm64/Jit_Util.h + PowerPC/JitArm64/JitArm64_Tables.cpp + PowerPC/JitArmCommon/BackPatch.h + ) +endif() + +target_link_libraries(core +PUBLIC + audiocommon + common + discio + enet::enet + expr + inputcommon + MbedTLS::mbedtls + pugixml + sfml-network + videonull + videoogl + videosoftware + +PRIVATE + FatFs + fmt::fmt + LZO::LZO + LZ4::LZ4 + ZLIB::ZLIB +) + +if(LIBUDEV_FOUND) + target_include_directories(core PRIVATE ${LIBUDEV_INCLUDE_DIR}) + target_link_libraries(core PRIVATE ${LIBUDEV_LIBRARY}) +endif() + +if (APPLE) + target_link_libraries(core + PRIVATE + ${CORESERV_LIBRARY} + ${IOB_LIBRARY} + ${IOK_LIBRARY} + resolv + ) +elseif (ANDROID) + target_link_libraries(core + PRIVATE + androidcommon + ) +endif() + +if(ENABLE_CUBEB) + target_link_libraries(core PUBLIC cubeb) + target_sources(core PRIVATE + HW/EXI/EXI_DeviceMic.cpp + HW/EXI/EXI_DeviceMic.h + ) +endif() + +if(TARGET LibUSB::LibUSB) + target_link_libraries(core PUBLIC LibUSB::LibUSB) + target_sources(core PRIVATE + IOS/USB/LibusbDevice.cpp + IOS/USB/LibusbDevice.h + IOS/USB/Bluetooth/BTReal.cpp + IOS/USB/Bluetooth/BTReal.h + IOS/USB/Bluetooth/LibUSBBluetoothAdapter.cpp + IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h + IOS/USB/Bluetooth/RealtekFirmwareLoader.cpp + IOS/USB/Bluetooth/RealtekFirmwareLoader.h + ) +endif() + +if(ENABLE_VULKAN) + target_link_libraries(core PUBLIC videovulkan) +endif() + +if(APPLE) + target_link_libraries(core PUBLIC videometal) +endif() + +if(USE_MGBA) + target_sources(core PRIVATE + HW/GBACore.cpp + HW/GBACore.h + HW/SI/SI_DeviceGBAEmu.cpp + HW/SI/SI_DeviceGBAEmu.h + ) + target_link_libraries(core PUBLIC mGBA::mgba) + target_compile_definitions(core PUBLIC -DHAS_LIBMGBA) +endif() + +if(WIN32) + target_sources(core PRIVATE + HW/EXI/BBA/TAP_Win32.cpp + HW/EXI/BBA/TAP_Win32.h + HW/WiimoteReal/IOWin.cpp + HW/WiimoteReal/IOWin.h + ) + target_link_libraries(core PUBLIC + videod3d + videod3d12 + setupapi.lib + iphlpapi.lib + ) + target_compile_definitions(core PRIVATE "-D_WINSOCK_DEPRECATED_NO_WARNINGS") +elseif(APPLE) + target_sources(core PRIVATE + HW/EXI/BBA/TAP_Apple.cpp + ) + target_link_libraries(core PUBLIC ${IOB_LIBRARY}) +elseif(UNIX) + target_sources(core PRIVATE + HW/EXI/BBA/TAP_Unix.cpp + ) + if(ANDROID) + target_sources(core PRIVATE + HW/WiimoteReal/IOAndroid.cpp + HW/WiimoteReal/IOAndroid.h + ) + endif() +endif() + +# Bluez doesn't support all the communication modes on FreeBSD, so only using it on Linux +if(LINUX) + option(ENABLE_BLUEZ "Enables bluetooth support" ON) + if(ENABLE_BLUEZ) + find_package(BlueZ) + if(BlueZ_FOUND) + message(STATUS "BlueZ found, enabling bluetooth support") + target_sources(core PRIVATE + HW/WiimoteReal/IOLinux.cpp + HW/WiimoteReal/IOLinux.h + ) + target_link_libraries(core PUBLIC BlueZ::BlueZ) + target_compile_definitions(core PRIVATE -DHAVE_BLUEZ=1) + else() + message(STATUS "BlueZ NOT found, disabling bluetooth support") + endif() + else() + message(STATUS "BlueZ explicitly disabled, disabling bluetooth support") + endif() +endif() + +if(CMAKE_SYSTEM_NAME MATCHES "OpenBSD") + # OpenBSD doesn't allow memory to be both writable and executable by default. + # The JIT currently needs this. + target_link_options(core PUBLIC -Wl,-zwxneeded) +endif() + +if(TARGET Hidapi::Hidapi) + target_sources(core PRIVATE + HW/WiimoteReal/IOhidapi.cpp + HW/WiimoteReal/IOhidapi.h + ) + target_link_libraries(core PUBLIC Hidapi::Hidapi) + target_compile_definitions(core PRIVATE -DHAVE_HIDAPI=1) +endif() + +if(UNIX) + target_sources(core PRIVATE + MemoryWatcher.cpp + MemoryWatcher.h + ) +endif() + +if(TARGET cpp-ipc::ipc) + target_sources(core PRIVATE HW/EXI/BBA/IPC.cpp) + target_link_libraries(core PRIVATE cpp-ipc::ipc) +endif() + if(MSVC) # Add precompiled header - target_link_libraries(core PRIVATE use_pch) + target_link_libraries(core PRIVATE use_pch) endif() target_include_directories(core PRIVATE ${PROJECT_SOURCE_DIR}/Externals/libslirp/include) - -if(USE_RETRO_ACHIEVEMENTS) - target_link_libraries(core PUBLIC rcheevos) - target_compile_definitions(core PUBLIC -DUSE_RETRO_ACHIEVEMENTS) - target_compile_definitions(core PUBLIC -DRC_CLIENT_SUPPORTS_HASH) - if(RC_CLIENT_SUPPORTS_RAINTEGRATION) - target_compile_definitions(core PUBLIC -DRC_CLIENT_SUPPORTS_RAINTEGRATION) - endif() -endif() + +if(USE_RETRO_ACHIEVEMENTS) + target_link_libraries(core PUBLIC rcheevos) + target_compile_definitions(core PUBLIC -DUSE_RETRO_ACHIEVEMENTS) + target_compile_definitions(core PUBLIC -DRC_CLIENT_SUPPORTS_HASH) + if(RC_CLIENT_SUPPORTS_RAINTEGRATION) + target_compile_definitions(core PUBLIC -DRC_CLIENT_SUPPORTS_RAINTEGRATION) + endif() +endif() diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index ee701a1ab4..92cfdfb37d 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -1,926 +1,928 @@ -// Copyright 2017 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "Core/Config/MainSettings.h" - -#include - -#include -#include - -#include "AudioCommon/AudioCommon.h" -#include "Common/Assert.h" -#include "Common/CommonPaths.h" -#include "Common/CommonTypes.h" -#include "Common/Config/Config.h" -#include "Common/EnumMap.h" -#include "Common/FileUtil.h" -#include "Common/Logging/Log.h" -#include "Common/MathUtil.h" -#include "Common/StringUtil.h" -#include "Common/Version.h" -#include "Core/AchievementManager.h" -#include "Core/Config/DefaultLocale.h" -#include "Core/Core.h" -#include "Core/HW/EXI/EXI.h" -#include "Core/HW/EXI/EXI_Device.h" -#include "Core/HW/GCMemcard/GCMemcard.h" -#include "Core/HW/HSP/HSP_Device.h" -#include "Core/HW/Memmap.h" -#include "Core/HW/SI/SI_Device.h" -#include "Core/IOS/Network/Socket.h" -#include "Core/PowerPC/PowerPC.h" -#include "Core/System.h" -#include "Core/USBUtils.h" -#include "DiscIO/Enums.h" -#include "VideoCommon/VideoBackendBase.h" - -namespace Config -{ -// Main.Core - -const Info MAIN_SKIP_IPL{{System::Main, "Core", "SkipIPL"}, true}; -const Info MAIN_CPU_CORE{{System::Main, "Core", "CPUCore"}, - PowerPC::DefaultCPUCore()}; -const Info MAIN_JIT_FOLLOW_BRANCH{{System::Main, "Core", "JITFollowBranch"}, true}; -const Info MAIN_FASTMEM{{System::Main, "Core", "Fastmem"}, true}; -const Info MAIN_PAGE_TABLE_FASTMEM{{System::Main, "Core", "PageTableFastmem"}, true}; -const Info MAIN_FASTMEM_ARENA{{System::Main, "Core", "FastmemArena"}, true}; -const Info MAIN_LARGE_ENTRY_POINTS_MAP{{System::Main, "Core", "LargeEntryPointsMap"}, true}; -const Info MAIN_ACCURATE_CPU_CACHE{{System::Main, "Core", "AccurateCPUCache"}, false}; -const Info MAIN_DSP_HLE{{System::Main, "Core", "DSPHLE"}, true}; -const Info MAIN_MAX_FALLBACK{{System::Main, "Core", "MaxFallback"}, 100}; -const Info MAIN_TIMING_VARIANCE{{System::Main, "Core", "TimingVariance"}, 40}; -const Info MAIN_CORRECT_TIME_DRIFT{{System::Main, "Core", "CorrectTimeDrift"}, false}; -const Info MAIN_RUSH_FRAME_PRESENTATION{{System::Main, "Core", "RushFramePresentation"}, - false}; -const Info MAIN_SMOOTH_EARLY_PRESENTATION{{System::Main, "Core", "SmoothEarlyPresentation"}, - false}; -#if defined(ANDROID) -// Currently enabled by default on Android because the performance boost is really needed. -constexpr bool DEFAULT_CPU_THREAD = true; -#else -constexpr bool DEFAULT_CPU_THREAD = false; -#endif -const Info MAIN_CPU_THREAD{{System::Main, "Core", "CPUThread"}, DEFAULT_CPU_THREAD}; -const Info MAIN_LOAD_GAME_INTO_MEMORY{{System::Main, "Core", "LoadGameIntoMemory"}, false}; -const Info MAIN_SYNC_ON_SKIP_IDLE{{System::Main, "Core", "SyncOnSkipIdle"}, true}; -const Info MAIN_DEFAULT_ISO{{System::Main, "Core", "DefaultISO"}, ""}; -const Info MAIN_ENABLE_CHEATS{{System::Main, "Core", "EnableCheats"}, false}; -const Info MAIN_GC_LANGUAGE{{System::Main, "Core", "SelectedLanguage"}, 0}; -const Info MAIN_OVERRIDE_REGION_SETTINGS{{System::Main, "Core", "OverrideRegionSettings"}, - false}; -const Info MAIN_DPL2_DECODER{{System::Main, "Core", "DPL2Decoder"}, false}; -const Info MAIN_DPL2_QUALITY{{System::Main, "Core", "DPL2Quality"}, - AudioCommon::GetDefaultDPL2Quality()}; -const Info MAIN_AUDIO_LATENCY{{System::Main, "Core", "AudioLatency"}, 20}; -const Info MAIN_AUDIO_BUFFER_SIZE{{System::Main, "Core", "AudioBufferSize"}, 80}; -const Info MAIN_AUDIO_FILL_GAPS{{System::Main, "Core", "AudioFillGaps"}, true}; -const Info MAIN_AUDIO_PRESERVE_PITCH{{System::Main, "Core", "AudioPreservePitch"}, false}; -const Info MAIN_MEMCARD_A_PATH{{System::Main, "Core", "MemcardAPath"}, ""}; -const Info MAIN_MEMCARD_B_PATH{{System::Main, "Core", "MemcardBPath"}, ""}; -const Info& GetInfoForMemcardPath(ExpansionInterface::Slot slot) -{ - ASSERT(ExpansionInterface::IsMemcardSlot(slot)); - static constexpr Common::EnumMap*, ExpansionInterface::MAX_MEMCARD_SLOT> - infos{ - &MAIN_MEMCARD_A_PATH, - &MAIN_MEMCARD_B_PATH, - }; - return *infos[slot]; -} -const Info MAIN_AGP_CART_A_PATH{{System::Main, "Core", "AgpCartAPath"}, ""}; -const Info MAIN_AGP_CART_B_PATH{{System::Main, "Core", "AgpCartBPath"}, ""}; -const Info& GetInfoForAGPCartPath(ExpansionInterface::Slot slot) -{ - ASSERT(ExpansionInterface::IsMemcardSlot(slot)); - static constexpr Common::EnumMap*, ExpansionInterface::MAX_MEMCARD_SLOT> - infos{ - &MAIN_AGP_CART_A_PATH, - &MAIN_AGP_CART_B_PATH, - }; - return *infos[slot]; -} -const Info MAIN_GCI_FOLDER_A_PATH{{System::Main, "Core", "GCIFolderAPath"}, ""}; -const Info MAIN_GCI_FOLDER_B_PATH{{System::Main, "Core", "GCIFolderBPath"}, ""}; -const Info& GetInfoForGCIPath(ExpansionInterface::Slot slot) -{ - ASSERT(ExpansionInterface::IsMemcardSlot(slot)); - static constexpr Common::EnumMap*, ExpansionInterface::MAX_MEMCARD_SLOT> - infos{ - &MAIN_GCI_FOLDER_A_PATH, - &MAIN_GCI_FOLDER_B_PATH, - }; - return *infos[slot]; -} -const Info MAIN_GCI_FOLDER_A_PATH_OVERRIDE{ - {System::Main, "Core", "GCIFolderAPathOverride"}, ""}; -const Info MAIN_GCI_FOLDER_B_PATH_OVERRIDE{ - {System::Main, "Core", "GCIFolderBPathOverride"}, ""}; -const Info& GetInfoForGCIPathOverride(ExpansionInterface::Slot slot) -{ - ASSERT(ExpansionInterface::IsMemcardSlot(slot)); - static constexpr Common::EnumMap*, ExpansionInterface::MAX_MEMCARD_SLOT> - infos{ - &MAIN_GCI_FOLDER_A_PATH_OVERRIDE, - &MAIN_GCI_FOLDER_B_PATH_OVERRIDE, - }; - return *infos[slot]; -} - -const Info MAIN_MEMORY_CARD_SIZE{{System::Main, "Core", "MemoryCardSize"}, -1}; - -const Info MAIN_SLOT_A{ - {System::Main, "Core", "SlotA"}, ExpansionInterface::EXIDeviceType::MemoryCardFolder}; -const Info MAIN_SLOT_B{{System::Main, "Core", "SlotB"}, - ExpansionInterface::EXIDeviceType::None}; -const Info MAIN_SERIAL_PORT_1{ - {System::Main, "Core", "SerialPort1"}, ExpansionInterface::EXIDeviceType::None}; -const Info MAIN_SERIAL_PORT_2{ - {System::Main, "Core", "SerialPort2"}, ExpansionInterface::EXIDeviceType::None}; - -const Info& GetInfoForEXIDevice(ExpansionInterface::Slot slot) -{ - static constexpr Common::EnumMap*, - ExpansionInterface::MAX_SLOT> - infos{ - &MAIN_SLOT_A, - &MAIN_SLOT_B, - &MAIN_SERIAL_PORT_1, - &MAIN_SERIAL_PORT_2, - }; - return *infos[slot]; -} - -const Info MAIN_BBA_MAC{{System::Main, "Core", "BBA_MAC"}, ""}; -const Info MAIN_BBA_XLINK_IP{{System::Main, "Core", "BBA_XLINK_IP"}, "127.0.0.1"}; -const Info MAIN_BBA_XLINK_CHAT_OSD{{System::Main, "Core", "BBA_XLINK_CHAT_OSD"}, true}; - -// Schthack PSO Server - https://schtserv.com/ -const Info MAIN_BBA_BUILTIN_DNS{{System::Main, "Core", "BBA_BUILTIN_DNS"}, - "3.18.217.27"}; -const Info MAIN_BBA_TAPSERVER_DESTINATION{ - {System::Main, "Core", "BBA_TAPSERVER_DESTINATION"}, "/tmp/dolphin-tap"}; -const Info MAIN_MODEM_TAPSERVER_DESTINATION{ - {System::Main, "Core", "MODEM_TAPSERVER_DESTINATION"}, "/tmp/dolphin-modem-tap"}; -const Info MAIN_BBA_BUILTIN_IP{{System::Main, "Core", "BBA_BUILTIN_IP"}, ""}; - -const Info& GetInfoForSIDevice(int channel) -{ - static const std::array, 4> infos{ - Info{{System::Main, "Core", "SIDevice0"}, - SerialInterface::SIDEVICE_GC_CONTROLLER}, - Info{{System::Main, "Core", "SIDevice1"}, - SerialInterface::SIDEVICE_NONE}, - Info{{System::Main, "Core", "SIDevice2"}, - SerialInterface::SIDEVICE_NONE}, - Info{{System::Main, "Core", "SIDevice3"}, - SerialInterface::SIDEVICE_NONE}, - }; - return infos[channel]; -} - -const Info& GetInfoForAdapterRumble(int channel) -{ - static const std::array, 4> infos{ - Info{{System::Main, "Core", "AdapterRumble0"}, true}, - Info{{System::Main, "Core", "AdapterRumble1"}, true}, - Info{{System::Main, "Core", "AdapterRumble2"}, true}, - Info{{System::Main, "Core", "AdapterRumble3"}, true}, - }; - return infos[channel]; -} - -const Info& GetInfoForSimulateKonga(int channel) -{ - static const std::array, 4> infos{ - Info{{System::Main, "Core", "SimulateKonga0"}, false}, - Info{{System::Main, "Core", "SimulateKonga1"}, false}, - Info{{System::Main, "Core", "SimulateKonga2"}, false}, - Info{{System::Main, "Core", "SimulateKonga3"}, false}, - }; - return infos[channel]; -} - -const Info MAIN_WII_SD_CARD{{System::Main, "Core", "WiiSDCard"}, true}; -const Info MAIN_WII_SD_CARD_ENABLE_FOLDER_SYNC{ - {System::Main, "Core", "WiiSDCardEnableFolderSync"}, false}; -const Info MAIN_WII_SD_CARD_FILESIZE{{System::Main, "Core", "WiiSDCardFilesize"}, 0}; -const Info MAIN_WII_KEYBOARD{{System::Main, "Core", "WiiKeyboard"}, false}; -const Info MAIN_WIIMOTE_CONTINUOUS_SCANNING{ - {System::Main, "Core", "WiimoteContinuousScanning"}, false}; -const Info MAIN_WIIMOTE_AUTO_CONNECT_ADDRESSES{ - {System::Main, "Core", "WiimoteAutoConnectAddresses"}, ""}; -const Info MAIN_WIIMOTE_ENABLE_SPEAKER{{System::Main, "Core", "WiimoteEnableSpeaker"}, false}; -const Info MAIN_CONNECT_WIIMOTES_FOR_CONTROLLER_INTERFACE{ - {System::Main, "Core", "WiimoteControllerInterface"}, false}; -const Info MAIN_MMU{{System::Main, "Core", "MMU"}, false}; -const Info MAIN_PAUSE_ON_PANIC{{System::Main, "Core", "PauseOnPanic"}, false}; -const Info MAIN_BB_DUMP_PORT{{System::Main, "Core", "BBDumpPort"}, -1}; -const Info MAIN_SYNC_GPU{{System::Main, "Core", "SyncGPU"}, false}; -const Info MAIN_SYNC_GPU_MAX_DISTANCE{{System::Main, "Core", "SyncGpuMaxDistance"}, 200000}; -const Info MAIN_SYNC_GPU_MIN_DISTANCE{{System::Main, "Core", "SyncGpuMinDistance"}, -200000}; -const Info MAIN_SYNC_GPU_OVERCLOCK{{System::Main, "Core", "SyncGpuOverclock"}, 1.0f}; -const Info MAIN_FAST_DISC_SPEED{{System::Main, "Core", "FastDiscSpeed"}, false}; -const Info MAIN_LOW_DCBZ_HACK{{System::Main, "Core", "LowDCBZHack"}, false}; -const Info MAIN_FLOAT_EXCEPTIONS{{System::Main, "Core", "FloatExceptions"}, false}; -const Info MAIN_DIVIDE_BY_ZERO_EXCEPTIONS{{System::Main, "Core", "DivByZeroExceptions"}, - false}; -const Info MAIN_FPRF{{System::Main, "Core", "FPRF"}, false}; -const Info MAIN_ACCURATE_NANS{{System::Main, "Core", "AccurateNaNs"}, false}; -const Info MAIN_ACCURATE_FMADDS{{System::Main, "Core", "AccurateFmadds"}, true}; -const Info MAIN_DISABLE_ICACHE{{System::Main, "Core", "DisableICache"}, false}; -const Info MAIN_EMULATION_SPEED{{System::Main, "Core", "EmulationSpeed"}, 1.0f}; -#if defined(ANDROID) -// Currently disabled by default on Android for concern of increased power usage while on battery. -// It is also not yet exposed in the UI on Android. -constexpr bool DEFAULT_PRECISION_FRAME_TIMING = false; -#else -constexpr bool DEFAULT_PRECISION_FRAME_TIMING = true; -#endif -const Info MAIN_PRECISION_FRAME_TIMING{{System::Main, "Core", "PrecisionFrameTiming"}, - DEFAULT_PRECISION_FRAME_TIMING}; -const Info MAIN_OVERCLOCK{{System::Main, "Core", "Overclock"}, 1.0f}; -const Info MAIN_OVERCLOCK_ENABLE{{System::Main, "Core", "OverclockEnable"}, false}; -const Info MAIN_VI_OVERCLOCK{{System::Main, "Core", "VIOverclock"}, 1.0f}; -const Info MAIN_VI_OVERCLOCK_ENABLE{{System::Main, "Core", "VIOverclockEnable"}, false}; -const Info MAIN_RAM_OVERRIDE_ENABLE{{System::Main, "Core", "RAMOverrideEnable"}, false}; -const Info MAIN_MEM1_SIZE{{System::Main, "Core", "MEM1Size"}, Memory::MEM1_SIZE_RETAIL}; -const Info MAIN_MEM2_SIZE{{System::Main, "Core", "MEM2Size"}, Memory::MEM2_SIZE_RETAIL}; -const Info MAIN_GFX_BACKEND{{System::Main, "Core", "GFXBackend"}, - VideoBackendBase::GetDefaultBackendConfigName()}; -const Info MAIN_HSP_DEVICE{{System::Main, "Core", "HSPDevice"}, - HSP::HSPDeviceType::None}; -const Info MAIN_ARAM_EXPANSION_SIZE{{System::Main, "Core", "ARAMExpansionSize"}, 0x400000}; - -const Info MAIN_GPU_DETERMINISM_MODE{{System::Main, "Core", "GPUDeterminismMode"}, - "auto"}; -const Info MAIN_OVERRIDE_BOOT_IOS{{System::Main, "Core", "OverrideBootIOS"}, -1}; -const Info MAIN_WII_IOS_LLE{{System::Main, "Core", "WiiIOSLLE"}, false}; +// Copyright 2017 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/Config/MainSettings.h" + +#include + +#include +#include + +#include "AudioCommon/AudioCommon.h" +#include "Common/Assert.h" +#include "Common/CommonPaths.h" +#include "Common/CommonTypes.h" +#include "Common/Config/Config.h" +#include "Common/EnumMap.h" +#include "Common/FileUtil.h" +#include "Common/Logging/Log.h" +#include "Common/MathUtil.h" +#include "Common/StringUtil.h" +#include "Common/Version.h" +#include "Core/AchievementManager.h" +#include "Core/Config/DefaultLocale.h" +#include "Core/Core.h" +#include "Core/HW/EXI/EXI.h" +#include "Core/HW/EXI/EXI_Device.h" +#include "Core/HW/GCMemcard/GCMemcard.h" +#include "Core/HW/HSP/HSP_Device.h" +#include "Core/HW/Memmap.h" +#include "Core/HW/SI/SI_Device.h" +#include "Core/IOS/Network/Socket.h" +#include "Core/PowerPC/PowerPC.h" +#include "Core/System.h" +#include "Core/USBUtils.h" +#include "DiscIO/Enums.h" +#include "VideoCommon/VideoBackendBase.h" + +namespace Config +{ +// Main.Core + +const Info MAIN_SKIP_IPL{{System::Main, "Core", "SkipIPL"}, true}; +const Info MAIN_CPU_CORE{{System::Main, "Core", "CPUCore"}, + PowerPC::DefaultCPUCore()}; +const Info MAIN_JIT_FOLLOW_BRANCH{{System::Main, "Core", "JITFollowBranch"}, true}; +const Info MAIN_FASTMEM{{System::Main, "Core", "Fastmem"}, true}; +const Info MAIN_PAGE_TABLE_FASTMEM{{System::Main, "Core", "PageTableFastmem"}, true}; +const Info MAIN_FASTMEM_ARENA{{System::Main, "Core", "FastmemArena"}, true}; +const Info MAIN_LARGE_ENTRY_POINTS_MAP{{System::Main, "Core", "LargeEntryPointsMap"}, true}; +const Info MAIN_ACCURATE_CPU_CACHE{{System::Main, "Core", "AccurateCPUCache"}, false}; +const Info MAIN_DSP_HLE{{System::Main, "Core", "DSPHLE"}, true}; +const Info MAIN_MAX_FALLBACK{{System::Main, "Core", "MaxFallback"}, 100}; +const Info MAIN_TIMING_VARIANCE{{System::Main, "Core", "TimingVariance"}, 40}; +const Info MAIN_CORRECT_TIME_DRIFT{{System::Main, "Core", "CorrectTimeDrift"}, false}; +const Info MAIN_RUSH_FRAME_PRESENTATION{{System::Main, "Core", "RushFramePresentation"}, + false}; +const Info MAIN_SMOOTH_EARLY_PRESENTATION{{System::Main, "Core", "SmoothEarlyPresentation"}, + false}; +#if defined(ANDROID) +// Currently enabled by default on Android because the performance boost is really needed. +constexpr bool DEFAULT_CPU_THREAD = true; +#else +constexpr bool DEFAULT_CPU_THREAD = false; +#endif +const Info MAIN_CPU_THREAD{{System::Main, "Core", "CPUThread"}, DEFAULT_CPU_THREAD}; +const Info MAIN_LOAD_GAME_INTO_MEMORY{{System::Main, "Core", "LoadGameIntoMemory"}, false}; +const Info MAIN_SYNC_ON_SKIP_IDLE{{System::Main, "Core", "SyncOnSkipIdle"}, true}; +const Info MAIN_DEFAULT_ISO{{System::Main, "Core", "DefaultISO"}, ""}; +const Info MAIN_ENABLE_CHEATS{{System::Main, "Core", "EnableCheats"}, false}; +const Info MAIN_GC_LANGUAGE{{System::Main, "Core", "SelectedLanguage"}, 0}; +const Info MAIN_OVERRIDE_REGION_SETTINGS{{System::Main, "Core", "OverrideRegionSettings"}, + false}; +const Info MAIN_DPL2_DECODER{{System::Main, "Core", "DPL2Decoder"}, false}; +const Info MAIN_DPL2_QUALITY{{System::Main, "Core", "DPL2Quality"}, + AudioCommon::GetDefaultDPL2Quality()}; +const Info MAIN_AUDIO_LATENCY{{System::Main, "Core", "AudioLatency"}, 20}; +const Info MAIN_AUDIO_BUFFER_SIZE{{System::Main, "Core", "AudioBufferSize"}, 80}; +const Info MAIN_AUDIO_FILL_GAPS{{System::Main, "Core", "AudioFillGaps"}, true}; +const Info MAIN_AUDIO_PRESERVE_PITCH{{System::Main, "Core", "AudioPreservePitch"}, false}; +const Info MAIN_MEMCARD_A_PATH{{System::Main, "Core", "MemcardAPath"}, ""}; +const Info MAIN_MEMCARD_B_PATH{{System::Main, "Core", "MemcardBPath"}, ""}; +const Info& GetInfoForMemcardPath(ExpansionInterface::Slot slot) +{ + ASSERT(ExpansionInterface::IsMemcardSlot(slot)); + static constexpr Common::EnumMap*, ExpansionInterface::MAX_MEMCARD_SLOT> + infos{ + &MAIN_MEMCARD_A_PATH, + &MAIN_MEMCARD_B_PATH, + }; + return *infos[slot]; +} +const Info MAIN_AGP_CART_A_PATH{{System::Main, "Core", "AgpCartAPath"}, ""}; +const Info MAIN_AGP_CART_B_PATH{{System::Main, "Core", "AgpCartBPath"}, ""}; +const Info& GetInfoForAGPCartPath(ExpansionInterface::Slot slot) +{ + ASSERT(ExpansionInterface::IsMemcardSlot(slot)); + static constexpr Common::EnumMap*, ExpansionInterface::MAX_MEMCARD_SLOT> + infos{ + &MAIN_AGP_CART_A_PATH, + &MAIN_AGP_CART_B_PATH, + }; + return *infos[slot]; +} +const Info MAIN_GCI_FOLDER_A_PATH{{System::Main, "Core", "GCIFolderAPath"}, ""}; +const Info MAIN_GCI_FOLDER_B_PATH{{System::Main, "Core", "GCIFolderBPath"}, ""}; +const Info& GetInfoForGCIPath(ExpansionInterface::Slot slot) +{ + ASSERT(ExpansionInterface::IsMemcardSlot(slot)); + static constexpr Common::EnumMap*, ExpansionInterface::MAX_MEMCARD_SLOT> + infos{ + &MAIN_GCI_FOLDER_A_PATH, + &MAIN_GCI_FOLDER_B_PATH, + }; + return *infos[slot]; +} +const Info MAIN_GCI_FOLDER_A_PATH_OVERRIDE{ + {System::Main, "Core", "GCIFolderAPathOverride"}, ""}; +const Info MAIN_GCI_FOLDER_B_PATH_OVERRIDE{ + {System::Main, "Core", "GCIFolderBPathOverride"}, ""}; +const Info& GetInfoForGCIPathOverride(ExpansionInterface::Slot slot) +{ + ASSERT(ExpansionInterface::IsMemcardSlot(slot)); + static constexpr Common::EnumMap*, ExpansionInterface::MAX_MEMCARD_SLOT> + infos{ + &MAIN_GCI_FOLDER_A_PATH_OVERRIDE, + &MAIN_GCI_FOLDER_B_PATH_OVERRIDE, + }; + return *infos[slot]; +} + +const Info MAIN_MEMORY_CARD_SIZE{{System::Main, "Core", "MemoryCardSize"}, -1}; + +const Info MAIN_SLOT_A{ + {System::Main, "Core", "SlotA"}, ExpansionInterface::EXIDeviceType::MemoryCardFolder}; +const Info MAIN_SLOT_B{{System::Main, "Core", "SlotB"}, + ExpansionInterface::EXIDeviceType::None}; +const Info MAIN_SERIAL_PORT_1{ + {System::Main, "Core", "SerialPort1"}, ExpansionInterface::EXIDeviceType::None}; +const Info MAIN_SERIAL_PORT_2{ + {System::Main, "Core", "SerialPort2"}, ExpansionInterface::EXIDeviceType::None}; + +const Info& GetInfoForEXIDevice(ExpansionInterface::Slot slot) +{ + static constexpr Common::EnumMap*, + ExpansionInterface::MAX_SLOT> + infos{ + &MAIN_SLOT_A, + &MAIN_SLOT_B, + &MAIN_SERIAL_PORT_1, + &MAIN_SERIAL_PORT_2, + }; + return *infos[slot]; +} + +const Info MAIN_BBA_MAC{{System::Main, "Core", "BBA_MAC"}, ""}; +const Info MAIN_BBA_XLINK_IP{{System::Main, "Core", "BBA_XLINK_IP"}, "127.0.0.1"}; +const Info MAIN_BBA_XLINK_CHAT_OSD{{System::Main, "Core", "BBA_XLINK_CHAT_OSD"}, true}; + +// Schthack PSO Server - https://schtserv.com/ +const Info MAIN_BBA_BUILTIN_DNS{{System::Main, "Core", "BBA_BUILTIN_DNS"}, + "3.18.217.27"}; +const Info MAIN_BBA_TAPSERVER_DESTINATION{ + {System::Main, "Core", "BBA_TAPSERVER_DESTINATION"}, "/tmp/dolphin-tap"}; +const Info MAIN_MODEM_TAPSERVER_DESTINATION{ + {System::Main, "Core", "MODEM_TAPSERVER_DESTINATION"}, "/tmp/dolphin-modem-tap"}; +const Info MAIN_BBA_BUILTIN_IP{{System::Main, "Core", "BBA_BUILTIN_IP"}, ""}; + +const Info& GetInfoForSIDevice(int channel) +{ + static const std::array, 4> infos{ + Info{{System::Main, "Core", "SIDevice0"}, + SerialInterface::SIDEVICE_GC_CONTROLLER}, + Info{{System::Main, "Core", "SIDevice1"}, + SerialInterface::SIDEVICE_NONE}, + Info{{System::Main, "Core", "SIDevice2"}, + SerialInterface::SIDEVICE_NONE}, + Info{{System::Main, "Core", "SIDevice3"}, + SerialInterface::SIDEVICE_NONE}, + }; + return infos[channel]; +} + +const Info& GetInfoForAdapterRumble(int channel) +{ + static const std::array, 4> infos{ + Info{{System::Main, "Core", "AdapterRumble0"}, true}, + Info{{System::Main, "Core", "AdapterRumble1"}, true}, + Info{{System::Main, "Core", "AdapterRumble2"}, true}, + Info{{System::Main, "Core", "AdapterRumble3"}, true}, + }; + return infos[channel]; +} + +const Info& GetInfoForSimulateKonga(int channel) +{ + static const std::array, 4> infos{ + Info{{System::Main, "Core", "SimulateKonga0"}, false}, + Info{{System::Main, "Core", "SimulateKonga1"}, false}, + Info{{System::Main, "Core", "SimulateKonga2"}, false}, + Info{{System::Main, "Core", "SimulateKonga3"}, false}, + }; + return infos[channel]; +} + +const Info MAIN_WII_SD_CARD{{System::Main, "Core", "WiiSDCard"}, true}; +const Info MAIN_WII_SD_CARD_ENABLE_FOLDER_SYNC{ + {System::Main, "Core", "WiiSDCardEnableFolderSync"}, false}; +const Info MAIN_WII_SD_CARD_FILESIZE{{System::Main, "Core", "WiiSDCardFilesize"}, 0}; +const Info MAIN_WII_KEYBOARD{{System::Main, "Core", "WiiKeyboard"}, false}; +const Info MAIN_WIIMOTE_CONTINUOUS_SCANNING{ + {System::Main, "Core", "WiimoteContinuousScanning"}, false}; +const Info MAIN_WIIMOTE_AUTO_CONNECT_ADDRESSES{ + {System::Main, "Core", "WiimoteAutoConnectAddresses"}, ""}; +const Info MAIN_WIIMOTE_ENABLE_SPEAKER{{System::Main, "Core", "WiimoteEnableSpeaker"}, false}; +const Info MAIN_CONNECT_WIIMOTES_FOR_CONTROLLER_INTERFACE{ + {System::Main, "Core", "WiimoteControllerInterface"}, false}; +const Info MAIN_MMU{{System::Main, "Core", "MMU"}, false}; +const Info MAIN_PAUSE_ON_PANIC{{System::Main, "Core", "PauseOnPanic"}, false}; +const Info MAIN_BB_DUMP_PORT{{System::Main, "Core", "BBDumpPort"}, -1}; +const Info MAIN_SYNC_GPU{{System::Main, "Core", "SyncGPU"}, false}; +const Info MAIN_SYNC_GPU_MAX_DISTANCE{{System::Main, "Core", "SyncGpuMaxDistance"}, 200000}; +const Info MAIN_SYNC_GPU_MIN_DISTANCE{{System::Main, "Core", "SyncGpuMinDistance"}, -200000}; +const Info MAIN_SYNC_GPU_OVERCLOCK{{System::Main, "Core", "SyncGpuOverclock"}, 1.0f}; +const Info MAIN_FAST_DISC_SPEED{{System::Main, "Core", "FastDiscSpeed"}, false}; +const Info MAIN_LOW_DCBZ_HACK{{System::Main, "Core", "LowDCBZHack"}, false}; +const Info MAIN_FLOAT_EXCEPTIONS{{System::Main, "Core", "FloatExceptions"}, false}; +const Info MAIN_DIVIDE_BY_ZERO_EXCEPTIONS{{System::Main, "Core", "DivByZeroExceptions"}, + false}; +const Info MAIN_FPRF{{System::Main, "Core", "FPRF"}, false}; +const Info MAIN_ACCURATE_NANS{{System::Main, "Core", "AccurateNaNs"}, false}; +const Info MAIN_ACCURATE_FMADDS{{System::Main, "Core", "AccurateFmadds"}, true}; +const Info MAIN_DISABLE_ICACHE{{System::Main, "Core", "DisableICache"}, false}; +const Info MAIN_EMULATION_SPEED{{System::Main, "Core", "EmulationSpeed"}, 1.0f}; +#if defined(ANDROID) +// Currently disabled by default on Android for concern of increased power usage while on battery. +// It is also not yet exposed in the UI on Android. +constexpr bool DEFAULT_PRECISION_FRAME_TIMING = false; +#else +constexpr bool DEFAULT_PRECISION_FRAME_TIMING = true; +#endif +const Info MAIN_PRECISION_FRAME_TIMING{{System::Main, "Core", "PrecisionFrameTiming"}, + DEFAULT_PRECISION_FRAME_TIMING}; +const Info MAIN_OVERCLOCK{{System::Main, "Core", "Overclock"}, 1.0f}; +const Info MAIN_OVERCLOCK_ENABLE{{System::Main, "Core", "OverclockEnable"}, false}; +const Info MAIN_VI_OVERCLOCK{{System::Main, "Core", "VIOverclock"}, 1.0f}; +const Info MAIN_VI_OVERCLOCK_ENABLE{{System::Main, "Core", "VIOverclockEnable"}, false}; +const Info MAIN_RAM_OVERRIDE_ENABLE{{System::Main, "Core", "RAMOverrideEnable"}, false}; +const Info MAIN_MEM1_SIZE{{System::Main, "Core", "MEM1Size"}, Memory::MEM1_SIZE_RETAIL}; +const Info MAIN_MEM2_SIZE{{System::Main, "Core", "MEM2Size"}, Memory::MEM2_SIZE_RETAIL}; +const Info MAIN_GFX_BACKEND{{System::Main, "Core", "GFXBackend"}, + VideoBackendBase::GetDefaultBackendConfigName()}; +const Info MAIN_HSP_DEVICE{{System::Main, "Core", "HSPDevice"}, + HSP::HSPDeviceType::None}; +const Info MAIN_ARAM_EXPANSION_SIZE{{System::Main, "Core", "ARAMExpansionSize"}, 0x400000}; + +const Info MAIN_GPU_DETERMINISM_MODE{{System::Main, "Core", "GPUDeterminismMode"}, + "auto"}; +const Info MAIN_OVERRIDE_BOOT_IOS{{System::Main, "Core", "OverrideBootIOS"}, -1}; +const Info MAIN_WII_IOS_LLE{{System::Main, "Core", "WiiIOSLLE"}, false}; const Info MAIN_WII_STARLET_JIT{{System::Main, "Core", "WiiStarletJIT"}, true}; const Info MAIN_WII_STARLET_JIT_MEMORY_PROFILING{ {System::Main, "Core", "WiiStarletJITMemoryProfiling"}, false}; const Info MAIN_WII_LLE_ETHERNET{{System::Main, "Core", "WiiLLEEthernet"}, false}; - -GPUDeterminismMode GetGPUDeterminismMode() -{ - auto mode = Config::Get(Config::MAIN_GPU_DETERMINISM_MODE); - if (mode == "auto") - return GPUDeterminismMode::Auto; - if (mode == "none") - return GPUDeterminismMode::Disabled; - if (mode == "fake-completion") - return GPUDeterminismMode::FakeCompletion; - - NOTICE_LOG_FMT(CORE, "Unknown GPU determinism mode {}", mode); - return GPUDeterminismMode::Auto; -} - -const Info MAIN_PERF_MAP_DIR{{System::Main, "Core", "PerfMapDir"}, ""}; -const Info MAIN_CUSTOM_RTC_ENABLE{{System::Main, "Core", "EnableCustomRTC"}, false}; -// Measured in seconds since the unix epoch (1.1.1970). Default is 1.1.2000; there are 7 leap years -// between those dates. -const Info MAIN_CUSTOM_RTC_VALUE{{System::Main, "Core", "CustomRTCValue"}, - (30 * 365 + 7) * 24 * 60 * 60}; -const Info MAIN_FALLBACK_REGION{{System::Main, "Core", "FallbackRegion"}, - GetDefaultRegion()}; -const Info MAIN_AUTO_DISC_CHANGE{{System::Main, "Core", "AutoDiscChange"}, false}; -const Info MAIN_ALLOW_SD_WRITES{{System::Main, "Core", "WiiSDCardAllowWrites"}, true}; -const Info MAIN_ENABLE_SAVESTATES{{System::Main, "Core", "EnableSaveStates"}, false}; -const Info MAIN_REAL_WII_REMOTE_REPEAT_REPORTS{ - {System::Main, "Core", "RealWiiRemoteRepeatReports"}, true}; -const Info MAIN_WII_WIILINK_ENABLE{{System::Main, "Core", "EnableWiiLink"}, false}; - -// Empty means use the Dolphin default URL -const Info MAIN_WII_NUS_SHOP_URL{{System::Main, "Core", "WiiNusShopUrl"}, ""}; - -// Main.Display - -const Info MAIN_FULLSCREEN_DISPLAY_RES{ - {System::Main, "Display", "FullscreenDisplayRes"}, "Auto"}; -const Info MAIN_FULLSCREEN{{System::Main, "Display", "Fullscreen"}, false}; -const Info MAIN_RENDER_TO_MAIN{{System::Main, "Display", "RenderToMain"}, false}; -const Info MAIN_RENDER_WINDOW_XPOS{{System::Main, "Display", "RenderWindowXPos"}, -1}; -const Info MAIN_RENDER_WINDOW_YPOS{{System::Main, "Display", "RenderWindowYPos"}, -1}; -const Info MAIN_RENDER_WINDOW_WIDTH{{System::Main, "Display", "RenderWindowWidth"}, 640}; -const Info MAIN_RENDER_WINDOW_HEIGHT{{System::Main, "Display", "RenderWindowHeight"}, 480}; -const Info MAIN_RENDER_WINDOW_AUTOSIZE{{System::Main, "Display", "RenderWindowAutoSize"}, - false}; -const Info MAIN_KEEP_WINDOW_ON_TOP{{System::Main, "Display", "KeepWindowOnTop"}, false}; -const Info MAIN_DISABLE_SCREENSAVER{{System::Main, "Display", "DisableScreenSaver"}, true}; - -// Main.DSP - -const Info MAIN_DSP_THREAD{{System::Main, "DSP", "DSPThread"}, false}; -const Info MAIN_DSP_CAPTURE_LOG{{System::Main, "DSP", "CaptureLog"}, false}; -const Info MAIN_DSP_JIT{{System::Main, "DSP", "EnableJIT"}, true}; -const Info MAIN_DUMP_AUDIO{{System::Main, "DSP", "DumpAudio"}, false}; -const Info MAIN_DUMP_AUDIO_SILENT{{System::Main, "DSP", "DumpAudioSilent"}, false}; -const Info MAIN_DUMP_UCODE{{System::Main, "DSP", "DumpUCode"}, false}; -const Info MAIN_AUDIO_BACKEND{{System::Main, "DSP", "Backend"}, - AudioCommon::GetDefaultSoundBackend()}; -const Info MAIN_AUDIO_VOLUME{{System::Main, "DSP", "Volume"}, 100}; -const Info MAIN_AUDIO_MUTED{{System::Main, "DSP", "Muted"}, false}; -const Info MAIN_AUDIO_MUTE_ON_DISABLED_SPEED_LIMIT{ - {System::Main, "DSP", "MuteOnDisabledSpeedLimit"}, false}; -#ifdef _WIN32 -const Info MAIN_WASAPI_DEVICE{{System::Main, "DSP", "WASAPIDevice"}, "default"}; -#endif - -bool ShouldUseDPL2Decoder() -{ - return Get(MAIN_DPL2_DECODER) && !Get(MAIN_DSP_HLE); -} - -// Main.General - -const Info MAIN_DUMP_PATH{{System::Main, "General", "DumpPath"}, ""}; -const Info MAIN_LOAD_PATH{{System::Main, "General", "LoadPath"}, ""}; -const Info MAIN_RESOURCEPACK_PATH{{System::Main, "General", "ResourcePackPath"}, ""}; -const Info MAIN_FS_PATH{{System::Main, "General", "NANDRootPath"}, ""}; + +GPUDeterminismMode GetGPUDeterminismMode() +{ + auto mode = Config::Get(Config::MAIN_GPU_DETERMINISM_MODE); + if (mode == "auto") + return GPUDeterminismMode::Auto; + if (mode == "none") + return GPUDeterminismMode::Disabled; + if (mode == "fake-completion") + return GPUDeterminismMode::FakeCompletion; + + NOTICE_LOG_FMT(CORE, "Unknown GPU determinism mode {}", mode); + return GPUDeterminismMode::Auto; +} + +const Info MAIN_PERF_MAP_DIR{{System::Main, "Core", "PerfMapDir"}, ""}; +const Info MAIN_CUSTOM_RTC_ENABLE{{System::Main, "Core", "EnableCustomRTC"}, false}; +// Measured in seconds since the unix epoch (1.1.1970). Default is 1.1.2000; there are 7 leap years +// between those dates. +const Info MAIN_CUSTOM_RTC_VALUE{{System::Main, "Core", "CustomRTCValue"}, + (30 * 365 + 7) * 24 * 60 * 60}; +const Info MAIN_FALLBACK_REGION{{System::Main, "Core", "FallbackRegion"}, + GetDefaultRegion()}; +const Info MAIN_AUTO_DISC_CHANGE{{System::Main, "Core", "AutoDiscChange"}, false}; +const Info MAIN_ALLOW_SD_WRITES{{System::Main, "Core", "WiiSDCardAllowWrites"}, true}; +const Info MAIN_ENABLE_SAVESTATES{{System::Main, "Core", "EnableSaveStates"}, false}; +const Info MAIN_REAL_WII_REMOTE_REPEAT_REPORTS{ + {System::Main, "Core", "RealWiiRemoteRepeatReports"}, true}; +const Info MAIN_WII_WIILINK_ENABLE{{System::Main, "Core", "EnableWiiLink"}, false}; + +// Empty means use the Dolphin default URL +const Info MAIN_WII_NUS_SHOP_URL{{System::Main, "Core", "WiiNusShopUrl"}, ""}; + +// Main.Display + +const Info MAIN_FULLSCREEN_DISPLAY_RES{ + {System::Main, "Display", "FullscreenDisplayRes"}, "Auto"}; +const Info MAIN_FULLSCREEN{{System::Main, "Display", "Fullscreen"}, false}; +const Info MAIN_RENDER_TO_MAIN{{System::Main, "Display", "RenderToMain"}, false}; +const Info MAIN_RENDER_WINDOW_XPOS{{System::Main, "Display", "RenderWindowXPos"}, -1}; +const Info MAIN_RENDER_WINDOW_YPOS{{System::Main, "Display", "RenderWindowYPos"}, -1}; +const Info MAIN_RENDER_WINDOW_WIDTH{{System::Main, "Display", "RenderWindowWidth"}, 640}; +const Info MAIN_RENDER_WINDOW_HEIGHT{{System::Main, "Display", "RenderWindowHeight"}, 480}; +const Info MAIN_RENDER_WINDOW_AUTOSIZE{{System::Main, "Display", "RenderWindowAutoSize"}, + false}; +const Info MAIN_KEEP_WINDOW_ON_TOP{{System::Main, "Display", "KeepWindowOnTop"}, false}; +const Info MAIN_DISABLE_SCREENSAVER{{System::Main, "Display", "DisableScreenSaver"}, true}; + +// Main.DSP + +const Info MAIN_DSP_THREAD{{System::Main, "DSP", "DSPThread"}, false}; +const Info MAIN_DSP_CAPTURE_LOG{{System::Main, "DSP", "CaptureLog"}, false}; +const Info MAIN_DSP_JIT{{System::Main, "DSP", "EnableJIT"}, true}; +const Info MAIN_DUMP_AUDIO{{System::Main, "DSP", "DumpAudio"}, false}; +const Info MAIN_DUMP_AUDIO_SILENT{{System::Main, "DSP", "DumpAudioSilent"}, false}; +const Info MAIN_DUMP_UCODE{{System::Main, "DSP", "DumpUCode"}, false}; +const Info MAIN_AUDIO_BACKEND{{System::Main, "DSP", "Backend"}, + AudioCommon::GetDefaultSoundBackend()}; +const Info MAIN_AUDIO_VOLUME{{System::Main, "DSP", "Volume"}, 100}; +const Info MAIN_AUDIO_MUTED{{System::Main, "DSP", "Muted"}, false}; +const Info MAIN_AUDIO_MUTE_ON_DISABLED_SPEED_LIMIT{ + {System::Main, "DSP", "MuteOnDisabledSpeedLimit"}, false}; +#ifdef _WIN32 +const Info MAIN_WASAPI_DEVICE{{System::Main, "DSP", "WASAPIDevice"}, "default"}; +#endif + +bool ShouldUseDPL2Decoder() +{ + return Get(MAIN_DPL2_DECODER) && !Get(MAIN_DSP_HLE); +} + +// Main.General + +const Info MAIN_DUMP_PATH{{System::Main, "General", "DumpPath"}, ""}; +const Info MAIN_LOAD_PATH{{System::Main, "General", "LoadPath"}, ""}; +const Info MAIN_RESOURCEPACK_PATH{{System::Main, "General", "ResourcePackPath"}, ""}; +const Info MAIN_FS_PATH{{System::Main, "General", "NANDRootPath"}, ""}; const Info MAIN_WII_LLE_DUMP_PATH{{System::Main, "General", "WiiIOSLLEDumpPath"}, ""}; -const Info MAIN_WII_SD_CARD_IMAGE_PATH{{System::Main, "General", "WiiSDCardPath"}, ""}; -const Info MAIN_WII_SD_CARD_SYNC_FOLDER_PATH{ - {System::Main, "General", "WiiSDCardSyncFolder"}, ""}; -const Info MAIN_WFS_PATH{{System::Main, "General", "WFSPath"}, ""}; -const Info MAIN_SHOW_LAG{{System::Main, "General", "ShowLag"}, false}; -const Info MAIN_SHOW_FRAME_COUNT{{System::Main, "General", "ShowFrameCount"}, false}; -const Info MAIN_WIRELESS_MAC{{System::Main, "General", "WirelessMac"}, ""}; -const Info MAIN_GDB_SOCKET{{System::Main, "General", "GDBSocket"}, ""}; -const Info MAIN_GDB_PORT{{System::Main, "General", "GDBPort"}, -1}; -const Info MAIN_ISO_PATH_COUNT{{System::Main, "General", "ISOPaths"}, 0}; -const Info MAIN_SKYLANDERS_PATH{{System::Main, "General", "SkylandersCollectionPath"}, - ""}; -const Info MAIN_TIME_TRACKING{{System::Main, "General", "EnablePlayTimeTracking"}, true}; - -static Info MakeISOPathConfigInfo(size_t idx) -{ - return Config::Info{{Config::System::Main, "General", fmt::format("ISOPath{}", idx)}, - ""}; -} - -std::vector GetIsoPaths() -{ - size_t count = MathUtil::SaturatingCast(Config::Get(Config::MAIN_ISO_PATH_COUNT)); - std::vector paths; - paths.reserve(count); - for (size_t i = 0; i < count; ++i) - { - std::string iso_path = Config::Get(MakeISOPathConfigInfo(i)); - if (!iso_path.empty()) - paths.emplace_back(std::move(iso_path)); - } - return paths; -} - -void SetIsoPaths(std::span paths) -{ - size_t old_size = MathUtil::SaturatingCast(Config::Get(Config::MAIN_ISO_PATH_COUNT)); - size_t new_size = paths.size(); - - size_t current_path_idx = 0; - for (const std::string& p : paths) - { - if (p.empty()) - { - --new_size; - continue; - } - - Config::SetBase(MakeISOPathConfigInfo(current_path_idx), p); - ++current_path_idx; - } - - for (size_t i = current_path_idx; i < old_size; ++i) - { - // TODO: This actually needs a Config::Erase(). - Config::SetBase(MakeISOPathConfigInfo(i), ""); - } - - Config::SetBase(Config::MAIN_ISO_PATH_COUNT, MathUtil::SaturatingCast(new_size)); -} - -// Main.GBA - -#ifdef HAS_LIBMGBA -const Info MAIN_GBA_BIOS_PATH{{System::Main, "GBA", "BIOS"}, ""}; -const std::array, 5> MAIN_GBA_ROM_PATHS{ - Info{{System::Main, "GBA", "Rom1"}, ""}, - Info{{System::Main, "GBA", "Rom2"}, ""}, - Info{{System::Main, "GBA", "Rom3"}, ""}, - Info{{System::Main, "GBA", "Rom4"}, ""}, - Info{{System::Main, "GBA", "GBPlayerRom"}, ""}, -}; -const Info MAIN_GBA_SAVES_PATH{{System::Main, "GBA", "SavesPath"}, ""}; -const Info MAIN_GBA_SAVES_IN_ROM_PATH{{System::Main, "GBA", "SavesInRomPath"}, false}; -#endif - -// Main.Network - -const Info MAIN_NETWORK_SSL_DUMP_READ{{System::Main, "Network", "SSLDumpRead"}, false}; -const Info MAIN_NETWORK_SSL_DUMP_WRITE{{System::Main, "Network", "SSLDumpWrite"}, false}; -const Info MAIN_NETWORK_SSL_VERIFY_CERTIFICATES{ - {System::Main, "Network", "SSLVerifyCertificates"}, true}; -const Info MAIN_NETWORK_SSL_DUMP_ROOT_CA{{System::Main, "Network", "SSLDumpRootCA"}, false}; -const Info MAIN_NETWORK_SSL_DUMP_PEER_CERT{{System::Main, "Network", "SSLDumpPeerCert"}, - false}; -const Info MAIN_NETWORK_DUMP_BBA{{System::Main, "Network", "DumpBBA"}, false}; -const Info MAIN_NETWORK_DUMP_AS_PCAP{{System::Main, "Network", "DumpAsPCAP"}, false}; -// Default value based on: -// - [RFC 1122] 4.2.3.5 TCP Connection Failures (at least 3 minutes) -// - https://dolp.in/pr8759 hwtest (3 minutes and 10 seconds) -const Info MAIN_NETWORK_TIMEOUT{{System::Main, "Network", "NetworkTimeout"}, 190}; - -// Main.Interface - -const Info MAIN_USE_HIGH_CONTRAST_TOOLTIPS{ - {System::Main, "Interface", "UseHighContrastTooltips"}, true}; -const Info MAIN_USE_PANIC_HANDLERS{{System::Main, "Interface", "UsePanicHandlers"}, true}; -const Info MAIN_ABORT_ON_PANIC_ALERT{{System::Main, "Interface", "AbortOnPanicAlert"}, false}; -const Info MAIN_OSD_MESSAGES{{System::Main, "Interface", "OnScreenDisplayMessages"}, true}; -const Info MAIN_OSD_FONT_SIZE{{System::Main, "Settings", "OSDFontSize"}, 13}; -const Info MAIN_SKIP_NKIT_WARNING{{System::Main, "Interface", "SkipNKitWarning"}, false}; -const Info MAIN_CONFIRM_ON_STOP{{System::Main, "Interface", "ConfirmStop"}, true}; -const Info MAIN_SHOW_CURSOR{{System::Main, "Interface", "CursorVisibility"}, - ShowCursor::OnMovement}; -const Info MAIN_LOCK_CURSOR{{System::Main, "Interface", "LockCursor"}, false}; -const Info MAIN_INTERFACE_LANGUAGE{{System::Main, "Interface", "LanguageCode"}, ""}; -const Info MAIN_SHOW_ACTIVE_TITLE{{System::Main, "Interface", "ShowActiveTitle"}, true}; -const Info MAIN_USE_BUILT_IN_TITLE_DATABASE{ - {System::Main, "Interface", "UseBuiltinTitleDatabase"}, true}; -const Info MAIN_THEME_NAME{{System::Main, "Interface", "ThemeName"}, - DEFAULT_THEME_DIR}; -const Info MAIN_PAUSE_ON_FOCUS_LOST{{System::Main, "Interface", "PauseOnFocusLost"}, false}; -const Info MAIN_ENABLE_DEBUGGING{{System::Main, "Interface", "DebugModeEnabled"}, false}; - -// Main.Analytics - -const Info MAIN_ANALYTICS_ID{{System::Main, "Analytics", "ID"}, ""}; -const Info MAIN_ANALYTICS_ENABLED{{System::Main, "Analytics", "Enabled"}, false}; -const Info MAIN_ANALYTICS_PERMISSION_ASKED{{System::Main, "Analytics", "PermissionAsked"}, - false}; - -// Main.GameList - -const Info MAIN_GAMELIST_LIST_DRIVES{{System::Main, "GameList", "ListDrives"}, false}; -const Info MAIN_GAMELIST_LIST_WAD{{System::Main, "GameList", "ListWad"}, true}; -const Info MAIN_GAMELIST_LIST_ELF_DOL{{System::Main, "GameList", "ListElfDol"}, true}; -const Info MAIN_GAMELIST_LIST_WII{{System::Main, "GameList", "ListWii"}, true}; -const Info MAIN_GAMELIST_LIST_GC{{System::Main, "GameList", "ListGC"}, true}; -const Info MAIN_GAMELIST_LIST_TRI{{System::Main, "GameList", "ListTriforce"}, true}; -const Info MAIN_GAMELIST_LIST_JPN{{System::Main, "GameList", "ListJap"}, true}; -const Info MAIN_GAMELIST_LIST_PAL{{System::Main, "GameList", "ListPal"}, true}; -const Info MAIN_GAMELIST_LIST_USA{{System::Main, "GameList", "ListUsa"}, true}; -const Info MAIN_GAMELIST_LIST_AUSTRALIA{{System::Main, "GameList", "ListAustralia"}, true}; -const Info MAIN_GAMELIST_LIST_FRANCE{{System::Main, "GameList", "ListFrance"}, true}; -const Info MAIN_GAMELIST_LIST_GERMANY{{System::Main, "GameList", "ListGermany"}, true}; -const Info MAIN_GAMELIST_LIST_ITALY{{System::Main, "GameList", "ListItaly"}, true}; -const Info MAIN_GAMELIST_LIST_KOREA{{System::Main, "GameList", "ListKorea"}, true}; -const Info MAIN_GAMELIST_LIST_NETHERLANDS{{System::Main, "GameList", "ListNetherlands"}, - true}; -const Info MAIN_GAMELIST_LIST_RUSSIA{{System::Main, "GameList", "ListRussia"}, true}; -const Info MAIN_GAMELIST_LIST_SPAIN{{System::Main, "GameList", "ListSpain"}, true}; -const Info MAIN_GAMELIST_LIST_TAIWAN{{System::Main, "GameList", "ListTaiwan"}, true}; -const Info MAIN_GAMELIST_LIST_WORLD{{System::Main, "GameList", "ListWorld"}, true}; -const Info MAIN_GAMELIST_LIST_UNKNOWN{{System::Main, "GameList", "ListUnknown"}, true}; -const Info MAIN_GAMELIST_LIST_SORT{{System::Main, "GameList", "ListSort"}, 3}; -const Info MAIN_GAMELIST_LIST_SORT_SECONDARY{{System::Main, "GameList", "ListSortSecondary"}, - 0}; -const Info MAIN_GAMELIST_COLUMN_PLATFORM{{System::Main, "GameList", "ColumnPlatform"}, true}; -const Info MAIN_GAMELIST_COLUMN_DESCRIPTION{{System::Main, "GameList", "ColumnDescription"}, - false}; -const Info MAIN_GAMELIST_COLUMN_BANNER{{System::Main, "GameList", "ColumnBanner"}, true}; -const Info MAIN_GAMELIST_COLUMN_TITLE{{System::Main, "GameList", "ColumnTitle"}, true}; -const Info MAIN_GAMELIST_COLUMN_MAKER{{System::Main, "GameList", "ColumnNotes"}, true}; -const Info MAIN_GAMELIST_COLUMN_FILE_NAME{{System::Main, "GameList", "ColumnFileName"}, - false}; -const Info MAIN_GAMELIST_COLUMN_FILE_PATH{{System::Main, "GameList", "ColumnFilePath"}, - false}; -const Info MAIN_GAMELIST_COLUMN_GAME_ID{{System::Main, "GameList", "ColumnID"}, false}; -const Info MAIN_GAMELIST_COLUMN_REGION{{System::Main, "GameList", "ColumnRegion"}, true}; -const Info MAIN_GAMELIST_COLUMN_FILE_SIZE{{System::Main, "GameList", "ColumnSize"}, true}; -const Info MAIN_GAMELIST_COLUMN_FILE_FORMAT{{System::Main, "GameList", "ColumnFileFormat"}, - false}; -const Info MAIN_GAMELIST_COLUMN_BLOCK_SIZE{{System::Main, "GameList", "ColumnBlockSize"}, - false}; -const Info MAIN_GAMELIST_COLUMN_COMPRESSION{{System::Main, "GameList", "ColumnCompression"}, - false}; -const Info MAIN_GAMELIST_COLUMN_TIME_PLAYED{{System::Main, "GameList", "ColumnTimePlayed"}, - true}; -const Info MAIN_GAMELIST_COLUMN_TAGS{{System::Main, "GameList", "ColumnTags"}, false}; - -// Main.FifoPlayer - -const Info MAIN_FIFOPLAYER_LOOP_REPLAY{{System::Main, "FifoPlayer", "LoopReplay"}, true}; -const Info MAIN_FIFOPLAYER_EARLY_MEMORY_UPDATES{ - {System::Main, "FifoPlayer", "EarlyMemoryUpdates"}, false}; - -// Main.AutoUpdate - -const Info MAIN_AUTOUPDATE_UPDATE_TRACK{{System::Main, "AutoUpdate", "UpdateTrack"}, - Common::GetScmUpdateTrackStr()}; -const Info MAIN_AUTOUPDATE_HASH_OVERRIDE{{System::Main, "AutoUpdate", "HashOverride"}, - ""}; - -// Main.Movie - -const Info MAIN_MOVIE_PAUSE_MOVIE{{System::Main, "Movie", "PauseMovie"}, false}; -const Info MAIN_MOVIE_MOVIE_AUTHOR{{System::Main, "Movie", "Author"}, ""}; -const Info MAIN_MOVIE_DUMP_FRAMES{{System::Main, "Movie", "DumpFrames"}, false}; -const Info MAIN_MOVIE_DUMP_FRAMES_SILENT{{System::Main, "Movie", "DumpFramesSilent"}, false}; -const Info MAIN_MOVIE_SHOW_INPUT_DISPLAY{{System::Main, "Movie", "ShowInputDisplay"}, false}; -const Info MAIN_MOVIE_SHOW_RTC{{System::Main, "Movie", "ShowRTC"}, false}; -const Info MAIN_MOVIE_SHOW_RERECORD{{System::Main, "Movie", "ShowRerecord"}, false}; -const Info MAIN_MOVIE_SHOW_OSD{{System::Main, "Movie", "ShowMovieWindow"}, false}; - -// Main.Input - -const Info MAIN_INPUT_BACKGROUND_INPUT{{System::Main, "Input", "BackgroundInput"}, false}; - -// Main.SDL_Hints - -// Defaults for these values are written in SDL.cpp so they appear in the config file, and thus show -// up in the SDL Hints config window (default values defined here would not be written). -const Info MAIN_SDL_HINT_JOYSTICK_ENHANCED_REPORTS{ - {System::Main, "SDL_Hints", "SDL_JOYSTICK_ENHANCED_REPORTS"}, ""}; -const Info MAIN_SDL_HINT_JOYSTICK_WGI{{System::Main, "SDL_Hints", "SDL_JOYSTICK_WGI"}, - ""}; -const Info MAIN_SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED{ - {System::Main, "SDL_Hints", "SDL_JOYSTICK_HIDAPI_PS5_PLAYER_LED"}, ""}; -const Info MAIN_SDL_HINT_JOYSTICK_DIRECTINPUT{ - {System::Main, "SDL_Hints", "SDL_JOYSTICK_DIRECTINPUT"}, ""}; -const Info MAIN_SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS{ - {System::Main, "SDL_Hints", "SDL_JOYSTICK_HIDAPI_COMBINE_JOY_CONS"}, ""}; -const Info MAIN_SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS{ - {System::Main, "SDL_Hints", "SDL_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS"}, ""}; - -// Main.Debug - -const Info MAIN_DEBUG_JIT_OFF{{System::Main, "Debug", "JitOff"}, false}; -const Info MAIN_DEBUG_JIT_LOAD_STORE_OFF{{System::Main, "Debug", "JitLoadStoreOff"}, false}; -const Info MAIN_DEBUG_JIT_LOAD_STORE_LXZ_OFF{{System::Main, "Debug", "JitLoadStorelXzOff"}, - false}; -const Info MAIN_DEBUG_JIT_LOAD_STORE_LWZ_OFF{{System::Main, "Debug", "JitLoadStorelwzOff"}, - false}; -const Info MAIN_DEBUG_JIT_LOAD_STORE_LBZX_OFF{{System::Main, "Debug", "JitLoadStorelbzxOff"}, - false}; -const Info MAIN_DEBUG_JIT_LOAD_STORE_FLOATING_OFF{ - {System::Main, "Debug", "JitLoadStoreFloatingOff"}, false}; -const Info MAIN_DEBUG_JIT_LOAD_STORE_PAIRED_OFF{ - {System::Main, "Debug", "JitLoadStorePairedOff"}, false}; -const Info MAIN_DEBUG_JIT_FLOATING_POINT_OFF{{System::Main, "Debug", "JitFloatingPointOff"}, - false}; -const Info MAIN_DEBUG_JIT_INTEGER_OFF{{System::Main, "Debug", "JitIntegerOff"}, false}; -const Info MAIN_DEBUG_JIT_PAIRED_OFF{{System::Main, "Debug", "JitPairedOff"}, false}; -const Info MAIN_DEBUG_JIT_SYSTEM_REGISTERS_OFF{ - {System::Main, "Debug", "JitSystemRegistersOff"}, false}; -const Info MAIN_DEBUG_JIT_BRANCH_OFF{{System::Main, "Debug", "JitBranchOff"}, false}; -const Info MAIN_DEBUG_JIT_REGISTER_CACHE_OFF{{System::Main, "Debug", "JitRegisterCacheOff"}, - false}; -const Info MAIN_DEBUG_JIT_ENABLE_PROFILING{{System::Main, "Debug", "JitEnableProfiling"}, - false}; - -// Main.BluetoothPassthrough - -const Info MAIN_BLUETOOTH_PASSTHROUGH_ENABLED{ - {System::Main, "BluetoothPassthrough", "Enabled"}, false}; -const Info MAIN_BLUETOOTH_PASSTHROUGH_VID{{System::Main, "BluetoothPassthrough", "VID"}, -1}; -const Info MAIN_BLUETOOTH_PASSTHROUGH_PID{{System::Main, "BluetoothPassthrough", "PID"}, -1}; -const Info MAIN_BLUETOOTH_PASSTHROUGH_LINK_KEYS{ - {System::Main, "BluetoothPassthrough", "LinkKeys"}, ""}; - -// Main.USBPassthrough - -const Info MAIN_USB_PASSTHROUGH_DISGUISE_PLAYSTATION_AS_WII{ - {System::Main, "USBPassthrough", "DisguisePlayStationAsWii"}, true}; -const Info MAIN_USB_PASSTHROUGH_DEVICES{{System::Main, "USBPassthrough", "Devices"}, - ""}; - -static std::set LoadUSBWhitelistFromString(const std::string& devices_string) -{ - std::set devices; - for (const auto& pair : SplitString(devices_string, ',')) - { - auto device = USBUtils::DeviceInfo::FromString(pair); - if (device) - devices.emplace(*device); - } - return devices; -} - -static std::string SaveUSBWhitelistToString(const std::set& devices) -{ - std::ostringstream oss; - for (const auto& device : devices) - oss << device.ToString() << ','; - std::string devices_string = oss.str(); - if (!devices_string.empty()) - devices_string.pop_back(); - return devices_string; -} - -std::set GetUSBDeviceWhitelist() -{ - return LoadUSBWhitelistFromString(Config::Get(Config::MAIN_USB_PASSTHROUGH_DEVICES)); -} - -void SetUSBDeviceWhitelist(const std::set& devices) -{ - Config::SetBase(Config::MAIN_USB_PASSTHROUGH_DEVICES, SaveUSBWhitelistToString(devices)); -} - -// Main.EmulatedUSBDevices - -const Info MAIN_EMULATE_SKYLANDER_PORTAL{ - {System::Main, "EmulatedUSBDevices", "EmulateSkylanderPortal"}, false}; - -const Info MAIN_EMULATE_INFINITY_BASE{ - {System::Main, "EmulatedUSBDevices", "EmulateInfinityBase"}, false}; - -const Info MAIN_EMULATE_WII_SPEAK{{System::Main, "EmulatedUSBDevices", "EmulateWiiSpeak"}, - false}; - -const Info MAIN_WII_SPEAK_MICROPHONE{ - {System::Main, "EmulatedUSBDevices", "WiiSpeakMicrophone"}, ""}; - -const Info MAIN_WII_SPEAK_MUTED{{System::Main, "EmulatedUSBDevices", "WiiSpeakMuted"}, true}; -const Info MAIN_WII_SPEAK_VOLUME_MODIFIER{ - {System::Main, "EmulatedUSBDevices", "WiiSpeakVolumeModifier"}, 0}; - -const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_EMULATE_LOGITECH_MIC{ - Info{{System::Main, "EmulatedUSBDevices", "EmulateLogitechMic1"}, false}, - Info{{System::Main, "EmulatedUSBDevices", "EmulateLogitechMic2"}, false}, - Info{{System::Main, "EmulatedUSBDevices", "EmulateLogitechMic3"}, false}, - Info{{System::Main, "EmulatedUSBDevices", "EmulateLogitechMic4"}, false}}; -const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_MICROPHONE{ - Info{{System::Main, "EmulatedUSBDevices", "LogitechMic1Microphone"}, ""}, - Info{{System::Main, "EmulatedUSBDevices", "LogitechMic2Microphone"}, ""}, - Info{{System::Main, "EmulatedUSBDevices", "LogitechMic3Microphone"}, ""}, - Info{{System::Main, "EmulatedUSBDevices", "LogitechMic4Microphone"}, ""}}; -const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_MUTED{ - Info{{System::Main, "EmulatedUSBDevices", "LogitechMic1Muted"}, false}, - Info{{System::Main, "EmulatedUSBDevices", "LogitechMic2Muted"}, false}, - Info{{System::Main, "EmulatedUSBDevices", "LogitechMic3Muted"}, false}, - Info{{System::Main, "EmulatedUSBDevices", "LogitechMic4Muted"}, false}}; -const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_VOLUME_MODIFIER{ - Info{{System::Main, "EmulatedUSBDevices", "LogitechMic1VolumeModifier"}, 0}, - Info{{System::Main, "EmulatedUSBDevices", "LogitechMic2VolumeModifier"}, 0}, - Info{{System::Main, "EmulatedUSBDevices", "LogitechMic3VolumeModifier"}, 0}, - Info{{System::Main, "EmulatedUSBDevices", "LogitechMic4VolumeModifier"}, 0}}; - -static std::string GetDefaultTriforceIPRedirections() -{ -#if defined(ANDROID) - return "0.0.0.0/0=127.0.0.1"; -#else - constexpr std::string_view entries[] = { - - // Mario Kart Arcade GP 1 + 2 - // This config allows for 4x Multicabinet on the same PC. - "192.168.29.150:5000-5008=127.0.0.1:5000-5008 MarioKart Seat #1 Static", - "192.168.29.151:5000-5008=127.0.0.1:5010-5018 MarioKart Seat #2 Static", - "192.168.29.152:5000-5008=127.0.0.1:5020-5028 MarioKart Seat #3 Static", - "192.168.29.153:5000-5008=127.0.0.1:5030-5038 MarioKart Seat #4 Static", - // Ephemeral ports are constrained to differentiate incoming connections. - "192.168.29.150=127.0.0.1:50000-50999 MarioKart Seat #1 Ephemeral", - "192.168.29.151=127.0.0.1:51000-51999 MarioKart Seat #2 Ephemeral", - "192.168.29.152=127.0.0.1:52000-52999 MarioKart Seat #3 Ephemeral", - "192.168.29.153=127.0.0.1:53000-53999 MarioKart Seat #4 Ephemeral", - // The cameras. - "192.168.29.104-107=127.0.0.1 MarioKart namcam2", - - // CyCraft Connect IP - "192.168.11.0/24=127.0.0.1 CyCraft", - - // The Key of Avalon - // This config isn't usable as-is. It's just here for reference. - "192.168.13.0/24=127.0.0.1 The Key of Avalon", - - // Sega ALL.Net - "192.168.150.0/24=127.0.0.1 ALL.Net", - }; - - return fmt::format("{}", fmt::join(entries, ",")); -#endif -} - -const Info MAIN_TRIFORCE_IP_REDIRECTIONS{ - {System::Main, "Core", "TriforceIPRedirections"}, GetDefaultTriforceIPRedirections()}; - -// Main.WiimoteAudioRouting - -const Info MAIN_WIIMOTE_AUDIO_ROUTING_ENABLED{ - {System::Main, "Core", "WiimoteAudioRoutingEnabled"}, false}; -const std::array, WIIMOTE_SPEAKER_COUNT> MAIN_WIIMOTE_AUDIO_OUTPUT_ENABLED{ - Info{{System::Main, "Core", "Wiimote1AudioOutputEnabled"}, false}, - Info{{System::Main, "Core", "Wiimote2AudioOutputEnabled"}, false}, - Info{{System::Main, "Core", "Wiimote3AudioOutputEnabled"}, false}, - Info{{System::Main, "Core", "Wiimote4AudioOutputEnabled"}, false}}; -const std::array, WIIMOTE_SPEAKER_COUNT> MAIN_WIIMOTE_AUDIO_OUTPUT_DEVICE{ - Info{{System::Main, "Core", "Wiimote1AudioOutputDevice"}, ""}, - Info{{System::Main, "Core", "Wiimote2AudioOutputDevice"}, ""}, - Info{{System::Main, "Core", "Wiimote3AudioOutputDevice"}, ""}, - Info{{System::Main, "Core", "Wiimote4AudioOutputDevice"}, ""}}; - -// The reason we need this function is because some memory card code -// expects to get a non-NTSC-K region even if we're emulating an NTSC-K Wii. -DiscIO::Region ToGameCubeRegion(DiscIO::Region region) -{ - if (region != DiscIO::Region::NTSC_K) - return region; - - // GameCube has no NTSC-K region. No choice of replacement value is completely - // non-arbitrary, but let's go with NTSC-J since Korean GameCubes are NTSC-J. - return DiscIO::Region::NTSC_J; -} - -const char* GetDirectoryForRegion(DiscIO::Region region, RegionDirectoryStyle style) -{ - if (region == DiscIO::Region::Unknown) - region = ToGameCubeRegion(Config::Get(Config::MAIN_FALLBACK_REGION)); - - switch (region) - { - case DiscIO::Region::NTSC_J: - return style == RegionDirectoryStyle::Legacy ? JAP_DIR : JPN_DIR; - - case DiscIO::Region::NTSC_U: - return USA_DIR; - - case DiscIO::Region::PAL: - return EUR_DIR; - - case DiscIO::Region::NTSC_K: - // See ToGameCubeRegion - ASSERT_MSG(BOOT, false, "NTSC-K is not a valid GameCube region"); - return style == RegionDirectoryStyle::Legacy ? JAP_DIR : JPN_DIR; - - case DiscIO::Region::DEV: - return DEV_DIR; - - default: - ASSERT_MSG(BOOT, false, "Default case should not be reached"); - return EUR_DIR; - } -} - -std::string GetBootROMPath(const std::string& region_directory) -{ - const std::string path = - File::GetUserPath(D_GCUSER_IDX) + DIR_SEP + region_directory + DIR_SEP GC_IPL; - if (!File::Exists(path)) - return File::GetSysDirectory() + GC_SYS_DIR + DIR_SEP + region_directory + DIR_SEP GC_IPL; - return path; -} - -std::string GetMemcardPath(ExpansionInterface::Slot slot, std::optional region, - u16 size_mb) -{ - return GetMemcardPath(Config::Get(GetInfoForMemcardPath(slot)), slot, region, size_mb); -} - -std::string GetMemcardPath(std::string configured_filename, ExpansionInterface::Slot slot, - std::optional region, u16 size_mb) -{ - const std::string blocks_string = size_mb < Memcard::MBIT_SIZE_MEMORY_CARD_2043 ? - fmt::format(".{}", Memcard::MbitToFreeBlocks(size_mb)) : - ""; - - if (configured_filename.empty()) - { - // Use default memcard path if there is no user defined one. - const bool is_slot_a = slot == ExpansionInterface::Slot::A; - const std::string region_string = Config::GetDirectoryForRegion( - Config::ToGameCubeRegion(region ? *region : Config::Get(Config::MAIN_FALLBACK_REGION))); - return fmt::format("{}{}.{}{}.raw", File::GetUserPath(D_GCUSER_IDX), - is_slot_a ? GC_MEMCARDA : GC_MEMCARDB, region_string, blocks_string); - } - - // Custom path is expected to be stored in the form of - // "/path/to/file.{region_code}.raw" - // with an arbitrary but supported region code. - // Try to extract and replace that region code. - // If there's no region code just insert one before the extension. - - std::string dir; - std::string name; - std::string ext; - UnifyPathSeparators(configured_filename); - SplitPath(configured_filename, &dir, &name, &ext); - - constexpr std::string_view us_region = "." USA_DIR; - constexpr std::string_view jp_region = "." JAP_DIR; - constexpr std::string_view eu_region = "." EUR_DIR; - constexpr std::string_view dv_region = "." DEV_DIR; - std::optional path_region = std::nullopt; - if (name.ends_with(us_region)) - { - name = name.substr(0, name.size() - us_region.size()); - path_region = DiscIO::Region::NTSC_U; - } - else if (name.ends_with(jp_region)) - { - name = name.substr(0, name.size() - jp_region.size()); - path_region = DiscIO::Region::NTSC_J; - } - else if (name.ends_with(eu_region)) - { - name = name.substr(0, name.size() - eu_region.size()); - path_region = DiscIO::Region::PAL; - } - else if (name.ends_with(dv_region)) - { - name = name.substr(0, name.size() - dv_region.size()); - path_region = DiscIO::Region::DEV; - } - - const DiscIO::Region used_region = - region ? *region : (path_region ? *path_region : Config::Get(Config::MAIN_FALLBACK_REGION)); - return fmt::format("{}{}.{}{}{}", dir, name, - Config::GetDirectoryForRegion(Config::ToGameCubeRegion(used_region)), - blocks_string, ext); -} - -bool IsDefaultMemcardPathConfigured(ExpansionInterface::Slot slot) -{ - return Config::Get(GetInfoForMemcardPath(slot)).empty(); -} - -std::string GetGCIFolderPath(ExpansionInterface::Slot slot, std::optional region) -{ - return GetGCIFolderPath(Config::Get(GetInfoForGCIPath(slot)), slot, region); -} - -std::string GetGCIFolderPath(std::string configured_folder, ExpansionInterface::Slot slot, - std::optional region) -{ - if (configured_folder.empty()) - { - const auto region_dir = Config::GetDirectoryForRegion( - Config::ToGameCubeRegion(region ? *region : Config::Get(Config::MAIN_FALLBACK_REGION))); - const bool is_slot_a = slot == ExpansionInterface::Slot::A; - return fmt::format("{}{}/Card {}", File::GetUserPath(D_GCUSER_IDX), region_dir, - is_slot_a ? 'A' : 'B'); - } - - // Custom path is expected to be stored in the form of - // "/path/to/folder/{region_code}" - // with an arbitrary but supported region code. - // Try to extract and replace that region code. - // If there's no region code just insert one at the end. - - UnifyPathSeparators(configured_folder); - while (configured_folder.ends_with('/')) - configured_folder.pop_back(); - - constexpr std::string_view us_region = "/" USA_DIR; - constexpr std::string_view jp_region = "/" JPN_DIR; - constexpr std::string_view eu_region = "/" EUR_DIR; - constexpr std::string_view dv_region = "/" DEV_DIR; - std::string_view base_path = configured_folder; - std::optional path_region = std::nullopt; - if (base_path.ends_with(us_region)) - { - base_path = base_path.substr(0, base_path.size() - us_region.size()); - path_region = DiscIO::Region::NTSC_U; - } - else if (base_path.ends_with(jp_region)) - { - base_path = base_path.substr(0, base_path.size() - jp_region.size()); - path_region = DiscIO::Region::NTSC_J; - } - else if (base_path.ends_with(eu_region)) - { - base_path = base_path.substr(0, base_path.size() - eu_region.size()); - path_region = DiscIO::Region::PAL; - } - else if (base_path.ends_with(dv_region)) - { - base_path = base_path.substr(0, base_path.size() - dv_region.size()); - path_region = DiscIO::Region::DEV; - } - - const DiscIO::Region used_region = - region ? *region : (path_region ? *path_region : Config::Get(Config::MAIN_FALLBACK_REGION)); - return fmt::format("{}/{}", base_path, - Config::GetDirectoryForRegion(Config::ToGameCubeRegion(used_region), - Config::RegionDirectoryStyle::Modern)); -} - -bool IsDefaultGCIFolderPathConfigured(ExpansionInterface::Slot slot) -{ - return Config::Get(GetInfoForGCIPath(slot)).empty(); -} - -bool AreCheatsEnabled() -{ - return Config::Get(::Config::MAIN_ENABLE_CHEATS); -} - -bool IsDebuggingEnabled() -{ - return Config::Get(::Config::MAIN_ENABLE_DEBUGGING) && - !AchievementManager::GetInstance().IsHardcoreModeActive(); -} - -} // namespace Config +const Info MAIN_WII_STARLET_BOOTSTRAP_ELF{ + {System::Main, "General", "WiiStarletBootstrapELF"}, ""}; +const Info MAIN_WII_SD_CARD_IMAGE_PATH{{System::Main, "General", "WiiSDCardPath"}, ""}; +const Info MAIN_WII_SD_CARD_SYNC_FOLDER_PATH{ + {System::Main, "General", "WiiSDCardSyncFolder"}, ""}; +const Info MAIN_WFS_PATH{{System::Main, "General", "WFSPath"}, ""}; +const Info MAIN_SHOW_LAG{{System::Main, "General", "ShowLag"}, false}; +const Info MAIN_SHOW_FRAME_COUNT{{System::Main, "General", "ShowFrameCount"}, false}; +const Info MAIN_WIRELESS_MAC{{System::Main, "General", "WirelessMac"}, ""}; +const Info MAIN_GDB_SOCKET{{System::Main, "General", "GDBSocket"}, ""}; +const Info MAIN_GDB_PORT{{System::Main, "General", "GDBPort"}, -1}; +const Info MAIN_ISO_PATH_COUNT{{System::Main, "General", "ISOPaths"}, 0}; +const Info MAIN_SKYLANDERS_PATH{{System::Main, "General", "SkylandersCollectionPath"}, + ""}; +const Info MAIN_TIME_TRACKING{{System::Main, "General", "EnablePlayTimeTracking"}, true}; + +static Info MakeISOPathConfigInfo(size_t idx) +{ + return Config::Info{{Config::System::Main, "General", fmt::format("ISOPath{}", idx)}, + ""}; +} + +std::vector GetIsoPaths() +{ + size_t count = MathUtil::SaturatingCast(Config::Get(Config::MAIN_ISO_PATH_COUNT)); + std::vector paths; + paths.reserve(count); + for (size_t i = 0; i < count; ++i) + { + std::string iso_path = Config::Get(MakeISOPathConfigInfo(i)); + if (!iso_path.empty()) + paths.emplace_back(std::move(iso_path)); + } + return paths; +} + +void SetIsoPaths(std::span paths) +{ + size_t old_size = MathUtil::SaturatingCast(Config::Get(Config::MAIN_ISO_PATH_COUNT)); + size_t new_size = paths.size(); + + size_t current_path_idx = 0; + for (const std::string& p : paths) + { + if (p.empty()) + { + --new_size; + continue; + } + + Config::SetBase(MakeISOPathConfigInfo(current_path_idx), p); + ++current_path_idx; + } + + for (size_t i = current_path_idx; i < old_size; ++i) + { + // TODO: This actually needs a Config::Erase(). + Config::SetBase(MakeISOPathConfigInfo(i), ""); + } + + Config::SetBase(Config::MAIN_ISO_PATH_COUNT, MathUtil::SaturatingCast(new_size)); +} + +// Main.GBA + +#ifdef HAS_LIBMGBA +const Info MAIN_GBA_BIOS_PATH{{System::Main, "GBA", "BIOS"}, ""}; +const std::array, 5> MAIN_GBA_ROM_PATHS{ + Info{{System::Main, "GBA", "Rom1"}, ""}, + Info{{System::Main, "GBA", "Rom2"}, ""}, + Info{{System::Main, "GBA", "Rom3"}, ""}, + Info{{System::Main, "GBA", "Rom4"}, ""}, + Info{{System::Main, "GBA", "GBPlayerRom"}, ""}, +}; +const Info MAIN_GBA_SAVES_PATH{{System::Main, "GBA", "SavesPath"}, ""}; +const Info MAIN_GBA_SAVES_IN_ROM_PATH{{System::Main, "GBA", "SavesInRomPath"}, false}; +#endif + +// Main.Network + +const Info MAIN_NETWORK_SSL_DUMP_READ{{System::Main, "Network", "SSLDumpRead"}, false}; +const Info MAIN_NETWORK_SSL_DUMP_WRITE{{System::Main, "Network", "SSLDumpWrite"}, false}; +const Info MAIN_NETWORK_SSL_VERIFY_CERTIFICATES{ + {System::Main, "Network", "SSLVerifyCertificates"}, true}; +const Info MAIN_NETWORK_SSL_DUMP_ROOT_CA{{System::Main, "Network", "SSLDumpRootCA"}, false}; +const Info MAIN_NETWORK_SSL_DUMP_PEER_CERT{{System::Main, "Network", "SSLDumpPeerCert"}, + false}; +const Info MAIN_NETWORK_DUMP_BBA{{System::Main, "Network", "DumpBBA"}, false}; +const Info MAIN_NETWORK_DUMP_AS_PCAP{{System::Main, "Network", "DumpAsPCAP"}, false}; +// Default value based on: +// - [RFC 1122] 4.2.3.5 TCP Connection Failures (at least 3 minutes) +// - https://dolp.in/pr8759 hwtest (3 minutes and 10 seconds) +const Info MAIN_NETWORK_TIMEOUT{{System::Main, "Network", "NetworkTimeout"}, 190}; + +// Main.Interface + +const Info MAIN_USE_HIGH_CONTRAST_TOOLTIPS{ + {System::Main, "Interface", "UseHighContrastTooltips"}, true}; +const Info MAIN_USE_PANIC_HANDLERS{{System::Main, "Interface", "UsePanicHandlers"}, true}; +const Info MAIN_ABORT_ON_PANIC_ALERT{{System::Main, "Interface", "AbortOnPanicAlert"}, false}; +const Info MAIN_OSD_MESSAGES{{System::Main, "Interface", "OnScreenDisplayMessages"}, true}; +const Info MAIN_OSD_FONT_SIZE{{System::Main, "Settings", "OSDFontSize"}, 13}; +const Info MAIN_SKIP_NKIT_WARNING{{System::Main, "Interface", "SkipNKitWarning"}, false}; +const Info MAIN_CONFIRM_ON_STOP{{System::Main, "Interface", "ConfirmStop"}, true}; +const Info MAIN_SHOW_CURSOR{{System::Main, "Interface", "CursorVisibility"}, + ShowCursor::OnMovement}; +const Info MAIN_LOCK_CURSOR{{System::Main, "Interface", "LockCursor"}, false}; +const Info MAIN_INTERFACE_LANGUAGE{{System::Main, "Interface", "LanguageCode"}, ""}; +const Info MAIN_SHOW_ACTIVE_TITLE{{System::Main, "Interface", "ShowActiveTitle"}, true}; +const Info MAIN_USE_BUILT_IN_TITLE_DATABASE{ + {System::Main, "Interface", "UseBuiltinTitleDatabase"}, true}; +const Info MAIN_THEME_NAME{{System::Main, "Interface", "ThemeName"}, + DEFAULT_THEME_DIR}; +const Info MAIN_PAUSE_ON_FOCUS_LOST{{System::Main, "Interface", "PauseOnFocusLost"}, false}; +const Info MAIN_ENABLE_DEBUGGING{{System::Main, "Interface", "DebugModeEnabled"}, false}; + +// Main.Analytics + +const Info MAIN_ANALYTICS_ID{{System::Main, "Analytics", "ID"}, ""}; +const Info MAIN_ANALYTICS_ENABLED{{System::Main, "Analytics", "Enabled"}, false}; +const Info MAIN_ANALYTICS_PERMISSION_ASKED{{System::Main, "Analytics", "PermissionAsked"}, + false}; + +// Main.GameList + +const Info MAIN_GAMELIST_LIST_DRIVES{{System::Main, "GameList", "ListDrives"}, false}; +const Info MAIN_GAMELIST_LIST_WAD{{System::Main, "GameList", "ListWad"}, true}; +const Info MAIN_GAMELIST_LIST_ELF_DOL{{System::Main, "GameList", "ListElfDol"}, true}; +const Info MAIN_GAMELIST_LIST_WII{{System::Main, "GameList", "ListWii"}, true}; +const Info MAIN_GAMELIST_LIST_GC{{System::Main, "GameList", "ListGC"}, true}; +const Info MAIN_GAMELIST_LIST_TRI{{System::Main, "GameList", "ListTriforce"}, true}; +const Info MAIN_GAMELIST_LIST_JPN{{System::Main, "GameList", "ListJap"}, true}; +const Info MAIN_GAMELIST_LIST_PAL{{System::Main, "GameList", "ListPal"}, true}; +const Info MAIN_GAMELIST_LIST_USA{{System::Main, "GameList", "ListUsa"}, true}; +const Info MAIN_GAMELIST_LIST_AUSTRALIA{{System::Main, "GameList", "ListAustralia"}, true}; +const Info MAIN_GAMELIST_LIST_FRANCE{{System::Main, "GameList", "ListFrance"}, true}; +const Info MAIN_GAMELIST_LIST_GERMANY{{System::Main, "GameList", "ListGermany"}, true}; +const Info MAIN_GAMELIST_LIST_ITALY{{System::Main, "GameList", "ListItaly"}, true}; +const Info MAIN_GAMELIST_LIST_KOREA{{System::Main, "GameList", "ListKorea"}, true}; +const Info MAIN_GAMELIST_LIST_NETHERLANDS{{System::Main, "GameList", "ListNetherlands"}, + true}; +const Info MAIN_GAMELIST_LIST_RUSSIA{{System::Main, "GameList", "ListRussia"}, true}; +const Info MAIN_GAMELIST_LIST_SPAIN{{System::Main, "GameList", "ListSpain"}, true}; +const Info MAIN_GAMELIST_LIST_TAIWAN{{System::Main, "GameList", "ListTaiwan"}, true}; +const Info MAIN_GAMELIST_LIST_WORLD{{System::Main, "GameList", "ListWorld"}, true}; +const Info MAIN_GAMELIST_LIST_UNKNOWN{{System::Main, "GameList", "ListUnknown"}, true}; +const Info MAIN_GAMELIST_LIST_SORT{{System::Main, "GameList", "ListSort"}, 3}; +const Info MAIN_GAMELIST_LIST_SORT_SECONDARY{{System::Main, "GameList", "ListSortSecondary"}, + 0}; +const Info MAIN_GAMELIST_COLUMN_PLATFORM{{System::Main, "GameList", "ColumnPlatform"}, true}; +const Info MAIN_GAMELIST_COLUMN_DESCRIPTION{{System::Main, "GameList", "ColumnDescription"}, + false}; +const Info MAIN_GAMELIST_COLUMN_BANNER{{System::Main, "GameList", "ColumnBanner"}, true}; +const Info MAIN_GAMELIST_COLUMN_TITLE{{System::Main, "GameList", "ColumnTitle"}, true}; +const Info MAIN_GAMELIST_COLUMN_MAKER{{System::Main, "GameList", "ColumnNotes"}, true}; +const Info MAIN_GAMELIST_COLUMN_FILE_NAME{{System::Main, "GameList", "ColumnFileName"}, + false}; +const Info MAIN_GAMELIST_COLUMN_FILE_PATH{{System::Main, "GameList", "ColumnFilePath"}, + false}; +const Info MAIN_GAMELIST_COLUMN_GAME_ID{{System::Main, "GameList", "ColumnID"}, false}; +const Info MAIN_GAMELIST_COLUMN_REGION{{System::Main, "GameList", "ColumnRegion"}, true}; +const Info MAIN_GAMELIST_COLUMN_FILE_SIZE{{System::Main, "GameList", "ColumnSize"}, true}; +const Info MAIN_GAMELIST_COLUMN_FILE_FORMAT{{System::Main, "GameList", "ColumnFileFormat"}, + false}; +const Info MAIN_GAMELIST_COLUMN_BLOCK_SIZE{{System::Main, "GameList", "ColumnBlockSize"}, + false}; +const Info MAIN_GAMELIST_COLUMN_COMPRESSION{{System::Main, "GameList", "ColumnCompression"}, + false}; +const Info MAIN_GAMELIST_COLUMN_TIME_PLAYED{{System::Main, "GameList", "ColumnTimePlayed"}, + true}; +const Info MAIN_GAMELIST_COLUMN_TAGS{{System::Main, "GameList", "ColumnTags"}, false}; + +// Main.FifoPlayer + +const Info MAIN_FIFOPLAYER_LOOP_REPLAY{{System::Main, "FifoPlayer", "LoopReplay"}, true}; +const Info MAIN_FIFOPLAYER_EARLY_MEMORY_UPDATES{ + {System::Main, "FifoPlayer", "EarlyMemoryUpdates"}, false}; + +// Main.AutoUpdate + +const Info MAIN_AUTOUPDATE_UPDATE_TRACK{{System::Main, "AutoUpdate", "UpdateTrack"}, + Common::GetScmUpdateTrackStr()}; +const Info MAIN_AUTOUPDATE_HASH_OVERRIDE{{System::Main, "AutoUpdate", "HashOverride"}, + ""}; + +// Main.Movie + +const Info MAIN_MOVIE_PAUSE_MOVIE{{System::Main, "Movie", "PauseMovie"}, false}; +const Info MAIN_MOVIE_MOVIE_AUTHOR{{System::Main, "Movie", "Author"}, ""}; +const Info MAIN_MOVIE_DUMP_FRAMES{{System::Main, "Movie", "DumpFrames"}, false}; +const Info MAIN_MOVIE_DUMP_FRAMES_SILENT{{System::Main, "Movie", "DumpFramesSilent"}, false}; +const Info MAIN_MOVIE_SHOW_INPUT_DISPLAY{{System::Main, "Movie", "ShowInputDisplay"}, false}; +const Info MAIN_MOVIE_SHOW_RTC{{System::Main, "Movie", "ShowRTC"}, false}; +const Info MAIN_MOVIE_SHOW_RERECORD{{System::Main, "Movie", "ShowRerecord"}, false}; +const Info MAIN_MOVIE_SHOW_OSD{{System::Main, "Movie", "ShowMovieWindow"}, false}; + +// Main.Input + +const Info MAIN_INPUT_BACKGROUND_INPUT{{System::Main, "Input", "BackgroundInput"}, false}; + +// Main.SDL_Hints + +// Defaults for these values are written in SDL.cpp so they appear in the config file, and thus show +// up in the SDL Hints config window (default values defined here would not be written). +const Info MAIN_SDL_HINT_JOYSTICK_ENHANCED_REPORTS{ + {System::Main, "SDL_Hints", "SDL_JOYSTICK_ENHANCED_REPORTS"}, ""}; +const Info MAIN_SDL_HINT_JOYSTICK_WGI{{System::Main, "SDL_Hints", "SDL_JOYSTICK_WGI"}, + ""}; +const Info MAIN_SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED{ + {System::Main, "SDL_Hints", "SDL_JOYSTICK_HIDAPI_PS5_PLAYER_LED"}, ""}; +const Info MAIN_SDL_HINT_JOYSTICK_DIRECTINPUT{ + {System::Main, "SDL_Hints", "SDL_JOYSTICK_DIRECTINPUT"}, ""}; +const Info MAIN_SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS{ + {System::Main, "SDL_Hints", "SDL_JOYSTICK_HIDAPI_COMBINE_JOY_CONS"}, ""}; +const Info MAIN_SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS{ + {System::Main, "SDL_Hints", "SDL_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS"}, ""}; + +// Main.Debug + +const Info MAIN_DEBUG_JIT_OFF{{System::Main, "Debug", "JitOff"}, false}; +const Info MAIN_DEBUG_JIT_LOAD_STORE_OFF{{System::Main, "Debug", "JitLoadStoreOff"}, false}; +const Info MAIN_DEBUG_JIT_LOAD_STORE_LXZ_OFF{{System::Main, "Debug", "JitLoadStorelXzOff"}, + false}; +const Info MAIN_DEBUG_JIT_LOAD_STORE_LWZ_OFF{{System::Main, "Debug", "JitLoadStorelwzOff"}, + false}; +const Info MAIN_DEBUG_JIT_LOAD_STORE_LBZX_OFF{{System::Main, "Debug", "JitLoadStorelbzxOff"}, + false}; +const Info MAIN_DEBUG_JIT_LOAD_STORE_FLOATING_OFF{ + {System::Main, "Debug", "JitLoadStoreFloatingOff"}, false}; +const Info MAIN_DEBUG_JIT_LOAD_STORE_PAIRED_OFF{ + {System::Main, "Debug", "JitLoadStorePairedOff"}, false}; +const Info MAIN_DEBUG_JIT_FLOATING_POINT_OFF{{System::Main, "Debug", "JitFloatingPointOff"}, + false}; +const Info MAIN_DEBUG_JIT_INTEGER_OFF{{System::Main, "Debug", "JitIntegerOff"}, false}; +const Info MAIN_DEBUG_JIT_PAIRED_OFF{{System::Main, "Debug", "JitPairedOff"}, false}; +const Info MAIN_DEBUG_JIT_SYSTEM_REGISTERS_OFF{ + {System::Main, "Debug", "JitSystemRegistersOff"}, false}; +const Info MAIN_DEBUG_JIT_BRANCH_OFF{{System::Main, "Debug", "JitBranchOff"}, false}; +const Info MAIN_DEBUG_JIT_REGISTER_CACHE_OFF{{System::Main, "Debug", "JitRegisterCacheOff"}, + false}; +const Info MAIN_DEBUG_JIT_ENABLE_PROFILING{{System::Main, "Debug", "JitEnableProfiling"}, + false}; + +// Main.BluetoothPassthrough + +const Info MAIN_BLUETOOTH_PASSTHROUGH_ENABLED{ + {System::Main, "BluetoothPassthrough", "Enabled"}, false}; +const Info MAIN_BLUETOOTH_PASSTHROUGH_VID{{System::Main, "BluetoothPassthrough", "VID"}, -1}; +const Info MAIN_BLUETOOTH_PASSTHROUGH_PID{{System::Main, "BluetoothPassthrough", "PID"}, -1}; +const Info MAIN_BLUETOOTH_PASSTHROUGH_LINK_KEYS{ + {System::Main, "BluetoothPassthrough", "LinkKeys"}, ""}; + +// Main.USBPassthrough + +const Info MAIN_USB_PASSTHROUGH_DISGUISE_PLAYSTATION_AS_WII{ + {System::Main, "USBPassthrough", "DisguisePlayStationAsWii"}, true}; +const Info MAIN_USB_PASSTHROUGH_DEVICES{{System::Main, "USBPassthrough", "Devices"}, + ""}; + +static std::set LoadUSBWhitelistFromString(const std::string& devices_string) +{ + std::set devices; + for (const auto& pair : SplitString(devices_string, ',')) + { + auto device = USBUtils::DeviceInfo::FromString(pair); + if (device) + devices.emplace(*device); + } + return devices; +} + +static std::string SaveUSBWhitelistToString(const std::set& devices) +{ + std::ostringstream oss; + for (const auto& device : devices) + oss << device.ToString() << ','; + std::string devices_string = oss.str(); + if (!devices_string.empty()) + devices_string.pop_back(); + return devices_string; +} + +std::set GetUSBDeviceWhitelist() +{ + return LoadUSBWhitelistFromString(Config::Get(Config::MAIN_USB_PASSTHROUGH_DEVICES)); +} + +void SetUSBDeviceWhitelist(const std::set& devices) +{ + Config::SetBase(Config::MAIN_USB_PASSTHROUGH_DEVICES, SaveUSBWhitelistToString(devices)); +} + +// Main.EmulatedUSBDevices + +const Info MAIN_EMULATE_SKYLANDER_PORTAL{ + {System::Main, "EmulatedUSBDevices", "EmulateSkylanderPortal"}, false}; + +const Info MAIN_EMULATE_INFINITY_BASE{ + {System::Main, "EmulatedUSBDevices", "EmulateInfinityBase"}, false}; + +const Info MAIN_EMULATE_WII_SPEAK{{System::Main, "EmulatedUSBDevices", "EmulateWiiSpeak"}, + false}; + +const Info MAIN_WII_SPEAK_MICROPHONE{ + {System::Main, "EmulatedUSBDevices", "WiiSpeakMicrophone"}, ""}; + +const Info MAIN_WII_SPEAK_MUTED{{System::Main, "EmulatedUSBDevices", "WiiSpeakMuted"}, true}; +const Info MAIN_WII_SPEAK_VOLUME_MODIFIER{ + {System::Main, "EmulatedUSBDevices", "WiiSpeakVolumeModifier"}, 0}; + +const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_EMULATE_LOGITECH_MIC{ + Info{{System::Main, "EmulatedUSBDevices", "EmulateLogitechMic1"}, false}, + Info{{System::Main, "EmulatedUSBDevices", "EmulateLogitechMic2"}, false}, + Info{{System::Main, "EmulatedUSBDevices", "EmulateLogitechMic3"}, false}, + Info{{System::Main, "EmulatedUSBDevices", "EmulateLogitechMic4"}, false}}; +const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_MICROPHONE{ + Info{{System::Main, "EmulatedUSBDevices", "LogitechMic1Microphone"}, ""}, + Info{{System::Main, "EmulatedUSBDevices", "LogitechMic2Microphone"}, ""}, + Info{{System::Main, "EmulatedUSBDevices", "LogitechMic3Microphone"}, ""}, + Info{{System::Main, "EmulatedUSBDevices", "LogitechMic4Microphone"}, ""}}; +const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_MUTED{ + Info{{System::Main, "EmulatedUSBDevices", "LogitechMic1Muted"}, false}, + Info{{System::Main, "EmulatedUSBDevices", "LogitechMic2Muted"}, false}, + Info{{System::Main, "EmulatedUSBDevices", "LogitechMic3Muted"}, false}, + Info{{System::Main, "EmulatedUSBDevices", "LogitechMic4Muted"}, false}}; +const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_VOLUME_MODIFIER{ + Info{{System::Main, "EmulatedUSBDevices", "LogitechMic1VolumeModifier"}, 0}, + Info{{System::Main, "EmulatedUSBDevices", "LogitechMic2VolumeModifier"}, 0}, + Info{{System::Main, "EmulatedUSBDevices", "LogitechMic3VolumeModifier"}, 0}, + Info{{System::Main, "EmulatedUSBDevices", "LogitechMic4VolumeModifier"}, 0}}; + +static std::string GetDefaultTriforceIPRedirections() +{ +#if defined(ANDROID) + return "0.0.0.0/0=127.0.0.1"; +#else + constexpr std::string_view entries[] = { + + // Mario Kart Arcade GP 1 + 2 + // This config allows for 4x Multicabinet on the same PC. + "192.168.29.150:5000-5008=127.0.0.1:5000-5008 MarioKart Seat #1 Static", + "192.168.29.151:5000-5008=127.0.0.1:5010-5018 MarioKart Seat #2 Static", + "192.168.29.152:5000-5008=127.0.0.1:5020-5028 MarioKart Seat #3 Static", + "192.168.29.153:5000-5008=127.0.0.1:5030-5038 MarioKart Seat #4 Static", + // Ephemeral ports are constrained to differentiate incoming connections. + "192.168.29.150=127.0.0.1:50000-50999 MarioKart Seat #1 Ephemeral", + "192.168.29.151=127.0.0.1:51000-51999 MarioKart Seat #2 Ephemeral", + "192.168.29.152=127.0.0.1:52000-52999 MarioKart Seat #3 Ephemeral", + "192.168.29.153=127.0.0.1:53000-53999 MarioKart Seat #4 Ephemeral", + // The cameras. + "192.168.29.104-107=127.0.0.1 MarioKart namcam2", + + // CyCraft Connect IP + "192.168.11.0/24=127.0.0.1 CyCraft", + + // The Key of Avalon + // This config isn't usable as-is. It's just here for reference. + "192.168.13.0/24=127.0.0.1 The Key of Avalon", + + // Sega ALL.Net + "192.168.150.0/24=127.0.0.1 ALL.Net", + }; + + return fmt::format("{}", fmt::join(entries, ",")); +#endif +} + +const Info MAIN_TRIFORCE_IP_REDIRECTIONS{ + {System::Main, "Core", "TriforceIPRedirections"}, GetDefaultTriforceIPRedirections()}; + +// Main.WiimoteAudioRouting + +const Info MAIN_WIIMOTE_AUDIO_ROUTING_ENABLED{ + {System::Main, "Core", "WiimoteAudioRoutingEnabled"}, false}; +const std::array, WIIMOTE_SPEAKER_COUNT> MAIN_WIIMOTE_AUDIO_OUTPUT_ENABLED{ + Info{{System::Main, "Core", "Wiimote1AudioOutputEnabled"}, false}, + Info{{System::Main, "Core", "Wiimote2AudioOutputEnabled"}, false}, + Info{{System::Main, "Core", "Wiimote3AudioOutputEnabled"}, false}, + Info{{System::Main, "Core", "Wiimote4AudioOutputEnabled"}, false}}; +const std::array, WIIMOTE_SPEAKER_COUNT> MAIN_WIIMOTE_AUDIO_OUTPUT_DEVICE{ + Info{{System::Main, "Core", "Wiimote1AudioOutputDevice"}, ""}, + Info{{System::Main, "Core", "Wiimote2AudioOutputDevice"}, ""}, + Info{{System::Main, "Core", "Wiimote3AudioOutputDevice"}, ""}, + Info{{System::Main, "Core", "Wiimote4AudioOutputDevice"}, ""}}; + +// The reason we need this function is because some memory card code +// expects to get a non-NTSC-K region even if we're emulating an NTSC-K Wii. +DiscIO::Region ToGameCubeRegion(DiscIO::Region region) +{ + if (region != DiscIO::Region::NTSC_K) + return region; + + // GameCube has no NTSC-K region. No choice of replacement value is completely + // non-arbitrary, but let's go with NTSC-J since Korean GameCubes are NTSC-J. + return DiscIO::Region::NTSC_J; +} + +const char* GetDirectoryForRegion(DiscIO::Region region, RegionDirectoryStyle style) +{ + if (region == DiscIO::Region::Unknown) + region = ToGameCubeRegion(Config::Get(Config::MAIN_FALLBACK_REGION)); + + switch (region) + { + case DiscIO::Region::NTSC_J: + return style == RegionDirectoryStyle::Legacy ? JAP_DIR : JPN_DIR; + + case DiscIO::Region::NTSC_U: + return USA_DIR; + + case DiscIO::Region::PAL: + return EUR_DIR; + + case DiscIO::Region::NTSC_K: + // See ToGameCubeRegion + ASSERT_MSG(BOOT, false, "NTSC-K is not a valid GameCube region"); + return style == RegionDirectoryStyle::Legacy ? JAP_DIR : JPN_DIR; + + case DiscIO::Region::DEV: + return DEV_DIR; + + default: + ASSERT_MSG(BOOT, false, "Default case should not be reached"); + return EUR_DIR; + } +} + +std::string GetBootROMPath(const std::string& region_directory) +{ + const std::string path = + File::GetUserPath(D_GCUSER_IDX) + DIR_SEP + region_directory + DIR_SEP GC_IPL; + if (!File::Exists(path)) + return File::GetSysDirectory() + GC_SYS_DIR + DIR_SEP + region_directory + DIR_SEP GC_IPL; + return path; +} + +std::string GetMemcardPath(ExpansionInterface::Slot slot, std::optional region, + u16 size_mb) +{ + return GetMemcardPath(Config::Get(GetInfoForMemcardPath(slot)), slot, region, size_mb); +} + +std::string GetMemcardPath(std::string configured_filename, ExpansionInterface::Slot slot, + std::optional region, u16 size_mb) +{ + const std::string blocks_string = size_mb < Memcard::MBIT_SIZE_MEMORY_CARD_2043 ? + fmt::format(".{}", Memcard::MbitToFreeBlocks(size_mb)) : + ""; + + if (configured_filename.empty()) + { + // Use default memcard path if there is no user defined one. + const bool is_slot_a = slot == ExpansionInterface::Slot::A; + const std::string region_string = Config::GetDirectoryForRegion( + Config::ToGameCubeRegion(region ? *region : Config::Get(Config::MAIN_FALLBACK_REGION))); + return fmt::format("{}{}.{}{}.raw", File::GetUserPath(D_GCUSER_IDX), + is_slot_a ? GC_MEMCARDA : GC_MEMCARDB, region_string, blocks_string); + } + + // Custom path is expected to be stored in the form of + // "/path/to/file.{region_code}.raw" + // with an arbitrary but supported region code. + // Try to extract and replace that region code. + // If there's no region code just insert one before the extension. + + std::string dir; + std::string name; + std::string ext; + UnifyPathSeparators(configured_filename); + SplitPath(configured_filename, &dir, &name, &ext); + + constexpr std::string_view us_region = "." USA_DIR; + constexpr std::string_view jp_region = "." JAP_DIR; + constexpr std::string_view eu_region = "." EUR_DIR; + constexpr std::string_view dv_region = "." DEV_DIR; + std::optional path_region = std::nullopt; + if (name.ends_with(us_region)) + { + name = name.substr(0, name.size() - us_region.size()); + path_region = DiscIO::Region::NTSC_U; + } + else if (name.ends_with(jp_region)) + { + name = name.substr(0, name.size() - jp_region.size()); + path_region = DiscIO::Region::NTSC_J; + } + else if (name.ends_with(eu_region)) + { + name = name.substr(0, name.size() - eu_region.size()); + path_region = DiscIO::Region::PAL; + } + else if (name.ends_with(dv_region)) + { + name = name.substr(0, name.size() - dv_region.size()); + path_region = DiscIO::Region::DEV; + } + + const DiscIO::Region used_region = + region ? *region : (path_region ? *path_region : Config::Get(Config::MAIN_FALLBACK_REGION)); + return fmt::format("{}{}.{}{}{}", dir, name, + Config::GetDirectoryForRegion(Config::ToGameCubeRegion(used_region)), + blocks_string, ext); +} + +bool IsDefaultMemcardPathConfigured(ExpansionInterface::Slot slot) +{ + return Config::Get(GetInfoForMemcardPath(slot)).empty(); +} + +std::string GetGCIFolderPath(ExpansionInterface::Slot slot, std::optional region) +{ + return GetGCIFolderPath(Config::Get(GetInfoForGCIPath(slot)), slot, region); +} + +std::string GetGCIFolderPath(std::string configured_folder, ExpansionInterface::Slot slot, + std::optional region) +{ + if (configured_folder.empty()) + { + const auto region_dir = Config::GetDirectoryForRegion( + Config::ToGameCubeRegion(region ? *region : Config::Get(Config::MAIN_FALLBACK_REGION))); + const bool is_slot_a = slot == ExpansionInterface::Slot::A; + return fmt::format("{}{}/Card {}", File::GetUserPath(D_GCUSER_IDX), region_dir, + is_slot_a ? 'A' : 'B'); + } + + // Custom path is expected to be stored in the form of + // "/path/to/folder/{region_code}" + // with an arbitrary but supported region code. + // Try to extract and replace that region code. + // If there's no region code just insert one at the end. + + UnifyPathSeparators(configured_folder); + while (configured_folder.ends_with('/')) + configured_folder.pop_back(); + + constexpr std::string_view us_region = "/" USA_DIR; + constexpr std::string_view jp_region = "/" JPN_DIR; + constexpr std::string_view eu_region = "/" EUR_DIR; + constexpr std::string_view dv_region = "/" DEV_DIR; + std::string_view base_path = configured_folder; + std::optional path_region = std::nullopt; + if (base_path.ends_with(us_region)) + { + base_path = base_path.substr(0, base_path.size() - us_region.size()); + path_region = DiscIO::Region::NTSC_U; + } + else if (base_path.ends_with(jp_region)) + { + base_path = base_path.substr(0, base_path.size() - jp_region.size()); + path_region = DiscIO::Region::NTSC_J; + } + else if (base_path.ends_with(eu_region)) + { + base_path = base_path.substr(0, base_path.size() - eu_region.size()); + path_region = DiscIO::Region::PAL; + } + else if (base_path.ends_with(dv_region)) + { + base_path = base_path.substr(0, base_path.size() - dv_region.size()); + path_region = DiscIO::Region::DEV; + } + + const DiscIO::Region used_region = + region ? *region : (path_region ? *path_region : Config::Get(Config::MAIN_FALLBACK_REGION)); + return fmt::format("{}/{}", base_path, + Config::GetDirectoryForRegion(Config::ToGameCubeRegion(used_region), + Config::RegionDirectoryStyle::Modern)); +} + +bool IsDefaultGCIFolderPathConfigured(ExpansionInterface::Slot slot) +{ + return Config::Get(GetInfoForGCIPath(slot)).empty(); +} + +bool AreCheatsEnabled() +{ + return Config::Get(::Config::MAIN_ENABLE_CHEATS); +} + +bool IsDebuggingEnabled() +{ + return Config::Get(::Config::MAIN_ENABLE_DEBUGGING) && + !AchievementManager::GetInstance().IsHardcoreModeActive(); +} + +} // namespace Config diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index c1df9b4ddf..26e1d95164 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -1,442 +1,443 @@ -// Copyright 2017 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include - -#include "Common/Common.h" -#include "Common/CommonTypes.h" -#include "Common/Config/Config.h" -#include "Core/USBUtils.h" -#include "DiscIO/Enums.h" - -// DSP Backend Types -#define BACKEND_NULLSOUND _trans("No Audio Output") -#define BACKEND_ALSA "ALSA" -#define BACKEND_CUBEB "Cubeb" -#define BACKEND_OPENAL "OpenAL" -#define BACKEND_PULSEAUDIO "Pulse" -#define BACKEND_OPENSLES "OpenSLES" -#define BACKEND_WASAPI _trans("WASAPI (Exclusive Mode)") - -namespace PowerPC -{ -enum class CPUCore; -} - -namespace AudioCommon -{ -enum class DPL2Quality; -} - -namespace ExpansionInterface -{ -enum class EXIDeviceType : int; -enum class Slot : int; -} // namespace ExpansionInterface - -namespace SerialInterface -{ -enum SIDevices : int; -} - -namespace HSP -{ -enum class HSPDeviceType : int; -} - -namespace Config -{ -// Main.Core - -extern const Info MAIN_SKIP_IPL; -extern const Info MAIN_CPU_CORE; -extern const Info MAIN_JIT_FOLLOW_BRANCH; -extern const Info MAIN_FASTMEM; -extern const Info MAIN_PAGE_TABLE_FASTMEM; -extern const Info MAIN_FASTMEM_ARENA; -extern const Info MAIN_LARGE_ENTRY_POINTS_MAP; -extern const Info MAIN_ACCURATE_CPU_CACHE; -// Should really be in the DSP section, but we're kind of stuck with bad decisions made in the past. -extern const Info MAIN_DSP_HLE; -extern const Info MAIN_MAX_FALLBACK; -extern const Info MAIN_TIMING_VARIANCE; -extern const Info MAIN_CORRECT_TIME_DRIFT; -extern const Info MAIN_RUSH_FRAME_PRESENTATION; -extern const Info MAIN_SMOOTH_EARLY_PRESENTATION; -extern const Info MAIN_CPU_THREAD; -extern const Info MAIN_LOAD_GAME_INTO_MEMORY; -extern const Info MAIN_SYNC_ON_SKIP_IDLE; -extern const Info MAIN_DEFAULT_ISO; -extern const Info MAIN_ENABLE_CHEATS; -extern const Info MAIN_GC_LANGUAGE; -extern const Info MAIN_OVERRIDE_REGION_SETTINGS; -extern const Info MAIN_DPL2_DECODER; -extern const Info MAIN_DPL2_QUALITY; -extern const Info MAIN_AUDIO_LATENCY; -extern const Info MAIN_AUDIO_BUFFER_SIZE; -extern const Info MAIN_AUDIO_FILL_GAPS; -extern const Info MAIN_AUDIO_PRESERVE_PITCH; -extern const Info MAIN_MEMCARD_A_PATH; -extern const Info MAIN_MEMCARD_B_PATH; -const Info& GetInfoForMemcardPath(ExpansionInterface::Slot slot); -extern const Info MAIN_AGP_CART_A_PATH; -extern const Info MAIN_AGP_CART_B_PATH; -const Info& GetInfoForAGPCartPath(ExpansionInterface::Slot slot); -extern const Info MAIN_GCI_FOLDER_A_PATH; -extern const Info MAIN_GCI_FOLDER_B_PATH; -const Info& GetInfoForGCIPath(ExpansionInterface::Slot slot); -extern const Info MAIN_GCI_FOLDER_A_PATH_OVERRIDE; -extern const Info MAIN_GCI_FOLDER_B_PATH_OVERRIDE; -const Info& GetInfoForGCIPathOverride(ExpansionInterface::Slot slot); -extern const Info MAIN_MEMORY_CARD_SIZE; -extern const Info MAIN_SLOT_A; -extern const Info MAIN_SLOT_B; -extern const Info MAIN_SERIAL_PORT_1; -extern const Info MAIN_SERIAL_PORT_2; -const Info& GetInfoForEXIDevice(ExpansionInterface::Slot slot); -extern const Info MAIN_BBA_MAC; -extern const Info MAIN_BBA_XLINK_IP; -extern const Info MAIN_BBA_XLINK_CHAT_OSD; -extern const Info MAIN_BBA_BUILTIN_DNS; -extern const Info MAIN_BBA_BUILTIN_IP; -extern const Info MAIN_BBA_TAPSERVER_DESTINATION; -extern const Info MAIN_MODEM_TAPSERVER_DESTINATION; -const Info& GetInfoForSIDevice(int channel); -const Info& GetInfoForAdapterRumble(int channel); -const Info& GetInfoForSimulateKonga(int channel); -extern const Info MAIN_WII_SD_CARD; -extern const Info MAIN_WII_SD_CARD_ENABLE_FOLDER_SYNC; -extern const Info MAIN_WII_SD_CARD_FILESIZE; -extern const Info MAIN_WII_KEYBOARD; -extern const Info MAIN_WIIMOTE_CONTINUOUS_SCANNING; -extern const Info MAIN_WIIMOTE_AUTO_CONNECT_ADDRESSES; -extern const Info MAIN_WIIMOTE_ENABLE_SPEAKER; -extern const Info MAIN_CONNECT_WIIMOTES_FOR_CONTROLLER_INTERFACE; -extern const Info MAIN_MMU; -extern const Info MAIN_PAUSE_ON_PANIC; -extern const Info MAIN_BB_DUMP_PORT; -extern const Info MAIN_SYNC_GPU; -extern const Info MAIN_SYNC_GPU_MAX_DISTANCE; -extern const Info MAIN_SYNC_GPU_MIN_DISTANCE; -extern const Info MAIN_SYNC_GPU_OVERCLOCK; -extern const Info MAIN_FAST_DISC_SPEED; -extern const Info MAIN_LOW_DCBZ_HACK; -extern const Info MAIN_FLOAT_EXCEPTIONS; -extern const Info MAIN_DIVIDE_BY_ZERO_EXCEPTIONS; -extern const Info MAIN_FPRF; -extern const Info MAIN_ACCURATE_NANS; -extern const Info MAIN_ACCURATE_FMADDS; -extern const Info MAIN_DISABLE_ICACHE; -extern const Info MAIN_EMULATION_SPEED; -extern const Info MAIN_PRECISION_FRAME_TIMING; -extern const Info MAIN_OVERCLOCK; -extern const Info MAIN_OVERCLOCK_ENABLE; -extern const Info MAIN_VI_OVERCLOCK; -extern const Info MAIN_VI_OVERCLOCK_ENABLE; -extern const Info MAIN_RAM_OVERRIDE_ENABLE; -extern const Info MAIN_MEM1_SIZE; -extern const Info MAIN_MEM2_SIZE; -// Should really be part of System::GFX, but again, we're stuck with past mistakes. -extern const Info MAIN_GFX_BACKEND; -extern const Info MAIN_HSP_DEVICE; -extern const Info MAIN_ARAM_EXPANSION_SIZE; - -enum class GPUDeterminismMode -{ - Auto, - Disabled, - // This is currently the only mode. There will probably be at least - // one more at some point. - FakeCompletion, -}; -extern const Info MAIN_GPU_DETERMINISM_MODE; -GPUDeterminismMode GetGPUDeterminismMode(); - -extern const Info MAIN_PERF_MAP_DIR; -extern const Info MAIN_CUSTOM_RTC_ENABLE; -extern const Info MAIN_CUSTOM_RTC_VALUE; -extern const Info MAIN_AUTO_DISC_CHANGE; -extern const Info MAIN_ALLOW_SD_WRITES; -extern const Info MAIN_ENABLE_SAVESTATES; -extern const Info MAIN_FALLBACK_REGION; -extern const Info MAIN_REAL_WII_REMOTE_REPEAT_REPORTS; -extern const Info MAIN_OVERRIDE_BOOT_IOS; -extern const Info MAIN_WII_IOS_LLE; +// Copyright 2017 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include "Common/Common.h" +#include "Common/CommonTypes.h" +#include "Common/Config/Config.h" +#include "Core/USBUtils.h" +#include "DiscIO/Enums.h" + +// DSP Backend Types +#define BACKEND_NULLSOUND _trans("No Audio Output") +#define BACKEND_ALSA "ALSA" +#define BACKEND_CUBEB "Cubeb" +#define BACKEND_OPENAL "OpenAL" +#define BACKEND_PULSEAUDIO "Pulse" +#define BACKEND_OPENSLES "OpenSLES" +#define BACKEND_WASAPI _trans("WASAPI (Exclusive Mode)") + +namespace PowerPC +{ +enum class CPUCore; +} + +namespace AudioCommon +{ +enum class DPL2Quality; +} + +namespace ExpansionInterface +{ +enum class EXIDeviceType : int; +enum class Slot : int; +} // namespace ExpansionInterface + +namespace SerialInterface +{ +enum SIDevices : int; +} + +namespace HSP +{ +enum class HSPDeviceType : int; +} + +namespace Config +{ +// Main.Core + +extern const Info MAIN_SKIP_IPL; +extern const Info MAIN_CPU_CORE; +extern const Info MAIN_JIT_FOLLOW_BRANCH; +extern const Info MAIN_FASTMEM; +extern const Info MAIN_PAGE_TABLE_FASTMEM; +extern const Info MAIN_FASTMEM_ARENA; +extern const Info MAIN_LARGE_ENTRY_POINTS_MAP; +extern const Info MAIN_ACCURATE_CPU_CACHE; +// Should really be in the DSP section, but we're kind of stuck with bad decisions made in the past. +extern const Info MAIN_DSP_HLE; +extern const Info MAIN_MAX_FALLBACK; +extern const Info MAIN_TIMING_VARIANCE; +extern const Info MAIN_CORRECT_TIME_DRIFT; +extern const Info MAIN_RUSH_FRAME_PRESENTATION; +extern const Info MAIN_SMOOTH_EARLY_PRESENTATION; +extern const Info MAIN_CPU_THREAD; +extern const Info MAIN_LOAD_GAME_INTO_MEMORY; +extern const Info MAIN_SYNC_ON_SKIP_IDLE; +extern const Info MAIN_DEFAULT_ISO; +extern const Info MAIN_ENABLE_CHEATS; +extern const Info MAIN_GC_LANGUAGE; +extern const Info MAIN_OVERRIDE_REGION_SETTINGS; +extern const Info MAIN_DPL2_DECODER; +extern const Info MAIN_DPL2_QUALITY; +extern const Info MAIN_AUDIO_LATENCY; +extern const Info MAIN_AUDIO_BUFFER_SIZE; +extern const Info MAIN_AUDIO_FILL_GAPS; +extern const Info MAIN_AUDIO_PRESERVE_PITCH; +extern const Info MAIN_MEMCARD_A_PATH; +extern const Info MAIN_MEMCARD_B_PATH; +const Info& GetInfoForMemcardPath(ExpansionInterface::Slot slot); +extern const Info MAIN_AGP_CART_A_PATH; +extern const Info MAIN_AGP_CART_B_PATH; +const Info& GetInfoForAGPCartPath(ExpansionInterface::Slot slot); +extern const Info MAIN_GCI_FOLDER_A_PATH; +extern const Info MAIN_GCI_FOLDER_B_PATH; +const Info& GetInfoForGCIPath(ExpansionInterface::Slot slot); +extern const Info MAIN_GCI_FOLDER_A_PATH_OVERRIDE; +extern const Info MAIN_GCI_FOLDER_B_PATH_OVERRIDE; +const Info& GetInfoForGCIPathOverride(ExpansionInterface::Slot slot); +extern const Info MAIN_MEMORY_CARD_SIZE; +extern const Info MAIN_SLOT_A; +extern const Info MAIN_SLOT_B; +extern const Info MAIN_SERIAL_PORT_1; +extern const Info MAIN_SERIAL_PORT_2; +const Info& GetInfoForEXIDevice(ExpansionInterface::Slot slot); +extern const Info MAIN_BBA_MAC; +extern const Info MAIN_BBA_XLINK_IP; +extern const Info MAIN_BBA_XLINK_CHAT_OSD; +extern const Info MAIN_BBA_BUILTIN_DNS; +extern const Info MAIN_BBA_BUILTIN_IP; +extern const Info MAIN_BBA_TAPSERVER_DESTINATION; +extern const Info MAIN_MODEM_TAPSERVER_DESTINATION; +const Info& GetInfoForSIDevice(int channel); +const Info& GetInfoForAdapterRumble(int channel); +const Info& GetInfoForSimulateKonga(int channel); +extern const Info MAIN_WII_SD_CARD; +extern const Info MAIN_WII_SD_CARD_ENABLE_FOLDER_SYNC; +extern const Info MAIN_WII_SD_CARD_FILESIZE; +extern const Info MAIN_WII_KEYBOARD; +extern const Info MAIN_WIIMOTE_CONTINUOUS_SCANNING; +extern const Info MAIN_WIIMOTE_AUTO_CONNECT_ADDRESSES; +extern const Info MAIN_WIIMOTE_ENABLE_SPEAKER; +extern const Info MAIN_CONNECT_WIIMOTES_FOR_CONTROLLER_INTERFACE; +extern const Info MAIN_MMU; +extern const Info MAIN_PAUSE_ON_PANIC; +extern const Info MAIN_BB_DUMP_PORT; +extern const Info MAIN_SYNC_GPU; +extern const Info MAIN_SYNC_GPU_MAX_DISTANCE; +extern const Info MAIN_SYNC_GPU_MIN_DISTANCE; +extern const Info MAIN_SYNC_GPU_OVERCLOCK; +extern const Info MAIN_FAST_DISC_SPEED; +extern const Info MAIN_LOW_DCBZ_HACK; +extern const Info MAIN_FLOAT_EXCEPTIONS; +extern const Info MAIN_DIVIDE_BY_ZERO_EXCEPTIONS; +extern const Info MAIN_FPRF; +extern const Info MAIN_ACCURATE_NANS; +extern const Info MAIN_ACCURATE_FMADDS; +extern const Info MAIN_DISABLE_ICACHE; +extern const Info MAIN_EMULATION_SPEED; +extern const Info MAIN_PRECISION_FRAME_TIMING; +extern const Info MAIN_OVERCLOCK; +extern const Info MAIN_OVERCLOCK_ENABLE; +extern const Info MAIN_VI_OVERCLOCK; +extern const Info MAIN_VI_OVERCLOCK_ENABLE; +extern const Info MAIN_RAM_OVERRIDE_ENABLE; +extern const Info MAIN_MEM1_SIZE; +extern const Info MAIN_MEM2_SIZE; +// Should really be part of System::GFX, but again, we're stuck with past mistakes. +extern const Info MAIN_GFX_BACKEND; +extern const Info MAIN_HSP_DEVICE; +extern const Info MAIN_ARAM_EXPANSION_SIZE; + +enum class GPUDeterminismMode +{ + Auto, + Disabled, + // This is currently the only mode. There will probably be at least + // one more at some point. + FakeCompletion, +}; +extern const Info MAIN_GPU_DETERMINISM_MODE; +GPUDeterminismMode GetGPUDeterminismMode(); + +extern const Info MAIN_PERF_MAP_DIR; +extern const Info MAIN_CUSTOM_RTC_ENABLE; +extern const Info MAIN_CUSTOM_RTC_VALUE; +extern const Info MAIN_AUTO_DISC_CHANGE; +extern const Info MAIN_ALLOW_SD_WRITES; +extern const Info MAIN_ENABLE_SAVESTATES; +extern const Info MAIN_FALLBACK_REGION; +extern const Info MAIN_REAL_WII_REMOTE_REPEAT_REPORTS; +extern const Info MAIN_OVERRIDE_BOOT_IOS; +extern const Info MAIN_WII_IOS_LLE; extern const Info MAIN_WII_STARLET_JIT; extern const Info MAIN_WII_STARLET_JIT_MEMORY_PROFILING; extern const Info MAIN_WII_LLE_ETHERNET; -extern const Info MAIN_WII_NUS_SHOP_URL; -extern const Info MAIN_WII_WIILINK_ENABLE; - -// Main.DSP - -extern const Info MAIN_DSP_THREAD; -extern const Info MAIN_DSP_CAPTURE_LOG; -extern const Info MAIN_DSP_JIT; -extern const Info MAIN_DUMP_AUDIO; -extern const Info MAIN_DUMP_AUDIO_SILENT; -extern const Info MAIN_DUMP_UCODE; -extern const Info MAIN_AUDIO_BACKEND; -extern const Info MAIN_AUDIO_VOLUME; -extern const Info MAIN_AUDIO_MUTED; -extern const Info MAIN_AUDIO_MUTE_ON_DISABLED_SPEED_LIMIT; -#ifdef _WIN32 -extern const Info MAIN_WASAPI_DEVICE; -#endif - -bool ShouldUseDPL2Decoder(); - -// Main.Display - -extern const Info MAIN_FULLSCREEN_DISPLAY_RES; -extern const Info MAIN_FULLSCREEN; -extern const Info MAIN_RENDER_TO_MAIN; -extern const Info MAIN_RENDER_WINDOW_XPOS; -extern const Info MAIN_RENDER_WINDOW_YPOS; -extern const Info MAIN_RENDER_WINDOW_WIDTH; -extern const Info MAIN_RENDER_WINDOW_HEIGHT; -extern const Info MAIN_RENDER_WINDOW_AUTOSIZE; -extern const Info MAIN_KEEP_WINDOW_ON_TOP; -extern const Info MAIN_DISABLE_SCREENSAVER; -extern const Info MAIN_TIME_TRACKING; - -// Main.General - -extern const Info MAIN_DUMP_PATH; -extern const Info MAIN_LOAD_PATH; -extern const Info MAIN_RESOURCEPACK_PATH; -extern const Info MAIN_FS_PATH; +extern const Info MAIN_WII_NUS_SHOP_URL; +extern const Info MAIN_WII_WIILINK_ENABLE; + +// Main.DSP + +extern const Info MAIN_DSP_THREAD; +extern const Info MAIN_DSP_CAPTURE_LOG; +extern const Info MAIN_DSP_JIT; +extern const Info MAIN_DUMP_AUDIO; +extern const Info MAIN_DUMP_AUDIO_SILENT; +extern const Info MAIN_DUMP_UCODE; +extern const Info MAIN_AUDIO_BACKEND; +extern const Info MAIN_AUDIO_VOLUME; +extern const Info MAIN_AUDIO_MUTED; +extern const Info MAIN_AUDIO_MUTE_ON_DISABLED_SPEED_LIMIT; +#ifdef _WIN32 +extern const Info MAIN_WASAPI_DEVICE; +#endif + +bool ShouldUseDPL2Decoder(); + +// Main.Display + +extern const Info MAIN_FULLSCREEN_DISPLAY_RES; +extern const Info MAIN_FULLSCREEN; +extern const Info MAIN_RENDER_TO_MAIN; +extern const Info MAIN_RENDER_WINDOW_XPOS; +extern const Info MAIN_RENDER_WINDOW_YPOS; +extern const Info MAIN_RENDER_WINDOW_WIDTH; +extern const Info MAIN_RENDER_WINDOW_HEIGHT; +extern const Info MAIN_RENDER_WINDOW_AUTOSIZE; +extern const Info MAIN_KEEP_WINDOW_ON_TOP; +extern const Info MAIN_DISABLE_SCREENSAVER; +extern const Info MAIN_TIME_TRACKING; + +// Main.General + +extern const Info MAIN_DUMP_PATH; +extern const Info MAIN_LOAD_PATH; +extern const Info MAIN_RESOURCEPACK_PATH; +extern const Info MAIN_FS_PATH; extern const Info MAIN_WII_LLE_DUMP_PATH; -extern const Info MAIN_WII_SD_CARD_IMAGE_PATH; -extern const Info MAIN_WII_SD_CARD_SYNC_FOLDER_PATH; -extern const Info MAIN_WFS_PATH; -extern const Info MAIN_SHOW_LAG; -extern const Info MAIN_SHOW_FRAME_COUNT; -extern const Info MAIN_WIRELESS_MAC; -extern const Info MAIN_GDB_SOCKET; -extern const Info MAIN_GDB_PORT; -extern const Info MAIN_ISO_PATH_COUNT; -extern const Info MAIN_SKYLANDERS_PATH; -std::vector GetIsoPaths(); -void SetIsoPaths(std::span paths); - -// Main.GBA - -#ifdef HAS_LIBMGBA -extern const Info MAIN_GBA_BIOS_PATH; -extern const std::array, 5> MAIN_GBA_ROM_PATHS; -extern const Info MAIN_GBA_SAVES_PATH; -extern const Info MAIN_GBA_SAVES_IN_ROM_PATH; - -static constexpr std::size_t GBPLAYER_GBA_INDEX = 4; -#endif - -// Main.Network - -extern const Info MAIN_NETWORK_SSL_DUMP_READ; -extern const Info MAIN_NETWORK_SSL_DUMP_WRITE; -extern const Info MAIN_NETWORK_SSL_VERIFY_CERTIFICATES; -extern const Info MAIN_NETWORK_SSL_DUMP_ROOT_CA; -extern const Info MAIN_NETWORK_SSL_DUMP_PEER_CERT; -extern const Info MAIN_NETWORK_DUMP_BBA; -extern const Info MAIN_NETWORK_DUMP_AS_PCAP; -extern const Info MAIN_NETWORK_TIMEOUT; - -// Main.Interface - -extern const Info MAIN_USE_HIGH_CONTRAST_TOOLTIPS; -extern const Info MAIN_USE_PANIC_HANDLERS; -extern const Info MAIN_ABORT_ON_PANIC_ALERT; -extern const Info MAIN_OSD_MESSAGES; -extern const Info MAIN_OSD_FONT_SIZE; -extern const Info MAIN_SKIP_NKIT_WARNING; -extern const Info MAIN_CONFIRM_ON_STOP; - -enum class ShowCursor -{ - Never, - Constantly, - OnMovement, -}; -extern const Info MAIN_SHOW_CURSOR; - -extern const Info MAIN_LOCK_CURSOR; -extern const Info MAIN_INTERFACE_LANGUAGE; -extern const Info MAIN_SHOW_ACTIVE_TITLE; -extern const Info MAIN_USE_BUILT_IN_TITLE_DATABASE; -extern const Info MAIN_THEME_NAME; -extern const Info MAIN_PAUSE_ON_FOCUS_LOST; -extern const Info MAIN_ENABLE_DEBUGGING; - -// Main.Analytics - -extern const Info MAIN_ANALYTICS_ID; -extern const Info MAIN_ANALYTICS_ENABLED; -extern const Info MAIN_ANALYTICS_PERMISSION_ASKED; - -// Main.GameList - -extern const Info MAIN_GAMELIST_LIST_DRIVES; -extern const Info MAIN_GAMELIST_LIST_WAD; -extern const Info MAIN_GAMELIST_LIST_ELF_DOL; -extern const Info MAIN_GAMELIST_LIST_WII; -extern const Info MAIN_GAMELIST_LIST_GC; -extern const Info MAIN_GAMELIST_LIST_TRI; -extern const Info MAIN_GAMELIST_LIST_JPN; -extern const Info MAIN_GAMELIST_LIST_PAL; -extern const Info MAIN_GAMELIST_LIST_USA; -extern const Info MAIN_GAMELIST_LIST_AUSTRALIA; -extern const Info MAIN_GAMELIST_LIST_FRANCE; -extern const Info MAIN_GAMELIST_LIST_GERMANY; -extern const Info MAIN_GAMELIST_LIST_ITALY; -extern const Info MAIN_GAMELIST_LIST_KOREA; -extern const Info MAIN_GAMELIST_LIST_NETHERLANDS; -extern const Info MAIN_GAMELIST_LIST_RUSSIA; -extern const Info MAIN_GAMELIST_LIST_SPAIN; -extern const Info MAIN_GAMELIST_LIST_TAIWAN; -extern const Info MAIN_GAMELIST_LIST_WORLD; -extern const Info MAIN_GAMELIST_LIST_UNKNOWN; -extern const Info MAIN_GAMELIST_LIST_SORT; -extern const Info MAIN_GAMELIST_LIST_SORT_SECONDARY; - -extern const Info MAIN_GAMELIST_COLUMN_PLATFORM; -extern const Info MAIN_GAMELIST_COLUMN_DESCRIPTION; -extern const Info MAIN_GAMELIST_COLUMN_BANNER; -extern const Info MAIN_GAMELIST_COLUMN_TITLE; -extern const Info MAIN_GAMELIST_COLUMN_MAKER; -extern const Info MAIN_GAMELIST_COLUMN_FILE_NAME; -extern const Info MAIN_GAMELIST_COLUMN_FILE_PATH; -extern const Info MAIN_GAMELIST_COLUMN_GAME_ID; -extern const Info MAIN_GAMELIST_COLUMN_REGION; -extern const Info MAIN_GAMELIST_COLUMN_FILE_SIZE; -extern const Info MAIN_GAMELIST_COLUMN_FILE_FORMAT; -extern const Info MAIN_GAMELIST_COLUMN_BLOCK_SIZE; -extern const Info MAIN_GAMELIST_COLUMN_COMPRESSION; -extern const Info MAIN_GAMELIST_COLUMN_TIME_PLAYED; -extern const Info MAIN_GAMELIST_COLUMN_TAGS; - -// Main.FifoPlayer - -extern const Info MAIN_FIFOPLAYER_LOOP_REPLAY; -extern const Info MAIN_FIFOPLAYER_EARLY_MEMORY_UPDATES; - -// Main.AutoUpdate - -extern const Info MAIN_AUTOUPDATE_UPDATE_TRACK; -extern const Info MAIN_AUTOUPDATE_HASH_OVERRIDE; - -// Main.Movie - -extern const Info MAIN_MOVIE_PAUSE_MOVIE; -extern const Info MAIN_MOVIE_MOVIE_AUTHOR; -extern const Info MAIN_MOVIE_DUMP_FRAMES; -extern const Info MAIN_MOVIE_DUMP_FRAMES_SILENT; -extern const Info MAIN_MOVIE_SHOW_INPUT_DISPLAY; -extern const Info MAIN_MOVIE_SHOW_RTC; -extern const Info MAIN_MOVIE_SHOW_RERECORD; -extern const Info MAIN_MOVIE_SHOW_OSD; - -// Main.Input - -extern const Info MAIN_INPUT_BACKGROUND_INPUT; - -extern const Config::Info MAIN_SDL_HINT_JOYSTICK_ENHANCED_REPORTS; -extern const Config::Info MAIN_SDL_HINT_JOYSTICK_WGI; -extern const Config::Info MAIN_SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED; -extern const Config::Info MAIN_SDL_HINT_JOYSTICK_DIRECTINPUT; -extern const Config::Info MAIN_SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS; -extern const Config::Info MAIN_SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS; - -// Main.Debug - -extern const Info MAIN_DEBUG_JIT_OFF; -extern const Info MAIN_DEBUG_JIT_LOAD_STORE_OFF; -extern const Info MAIN_DEBUG_JIT_LOAD_STORE_LXZ_OFF; -extern const Info MAIN_DEBUG_JIT_LOAD_STORE_LWZ_OFF; -extern const Info MAIN_DEBUG_JIT_LOAD_STORE_LBZX_OFF; -extern const Info MAIN_DEBUG_JIT_LOAD_STORE_FLOATING_OFF; -extern const Info MAIN_DEBUG_JIT_LOAD_STORE_PAIRED_OFF; -extern const Info MAIN_DEBUG_JIT_FLOATING_POINT_OFF; -extern const Info MAIN_DEBUG_JIT_INTEGER_OFF; -extern const Info MAIN_DEBUG_JIT_PAIRED_OFF; -extern const Info MAIN_DEBUG_JIT_SYSTEM_REGISTERS_OFF; -extern const Info MAIN_DEBUG_JIT_BRANCH_OFF; -extern const Info MAIN_DEBUG_JIT_REGISTER_CACHE_OFF; -extern const Info MAIN_DEBUG_JIT_ENABLE_PROFILING; - -// Main.BluetoothPassthrough - -extern const Info MAIN_BLUETOOTH_PASSTHROUGH_ENABLED; -extern const Info MAIN_BLUETOOTH_PASSTHROUGH_VID; -extern const Info MAIN_BLUETOOTH_PASSTHROUGH_PID; -extern const Info MAIN_BLUETOOTH_PASSTHROUGH_LINK_KEYS; - -// Main.USBPassthrough - -extern const Info MAIN_USB_PASSTHROUGH_DISGUISE_PLAYSTATION_AS_WII; -extern const Info MAIN_USB_PASSTHROUGH_DEVICES; -std::set GetUSBDeviceWhitelist(); -void SetUSBDeviceWhitelist(const std::set& devices); - -// Main.EmulatedUSBDevices - -extern const Info MAIN_EMULATE_SKYLANDER_PORTAL; -extern const Info MAIN_EMULATE_INFINITY_BASE; -extern const Info MAIN_EMULATE_WII_SPEAK; -extern const Info MAIN_WII_SPEAK_MICROPHONE; -extern const Info MAIN_WII_SPEAK_MUTED; -extern const Info MAIN_WII_SPEAK_VOLUME_MODIFIER; - -static constexpr std::size_t EMULATED_LOGITECH_MIC_COUNT = 4; - -extern const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_EMULATE_LOGITECH_MIC; -extern const std::array, EMULATED_LOGITECH_MIC_COUNT> - MAIN_LOGITECH_MIC_MICROPHONE; -extern const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_MUTED; -extern const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_VOLUME_MODIFIER; - -extern const Info MAIN_TRIFORCE_IP_REDIRECTIONS; - -// Main.WiimoteAudioRouting - -static constexpr std::size_t WIIMOTE_SPEAKER_COUNT = 4; - -extern const Info MAIN_WIIMOTE_AUDIO_ROUTING_ENABLED; -extern const std::array, WIIMOTE_SPEAKER_COUNT> MAIN_WIIMOTE_AUDIO_OUTPUT_ENABLED; -extern const std::array, WIIMOTE_SPEAKER_COUNT> MAIN_WIIMOTE_AUDIO_OUTPUT_DEVICE; - -// GameCube path utility functions - -// Replaces NTSC-K with some other region, and doesn't replace non-NTSC-K regions -DiscIO::Region ToGameCubeRegion(DiscIO::Region region); - -// The region argument must be valid for GameCube (i.e. must not be NTSC-K) -enum class RegionDirectoryStyle -{ - Legacy, - Modern, -}; -const char* GetDirectoryForRegion(DiscIO::Region region, - RegionDirectoryStyle style = RegionDirectoryStyle::Legacy); - -std::string GetBootROMPath(const std::string& region_directory); -// Builds the memory card according to the configuration with the given region and size. If the -// given region is std::nullopt, the region in the configured path is used if there is one, or the -// fallback region otherwise. -std::string GetMemcardPath(ExpansionInterface::Slot slot, std::optional region, - u16 size_mb = 0x80); -std::string GetMemcardPath(std::string configured_filename, ExpansionInterface::Slot slot, - std::optional region, u16 size_mb = 0x80); -bool IsDefaultMemcardPathConfigured(ExpansionInterface::Slot slot); -std::string GetGCIFolderPath(ExpansionInterface::Slot slot, std::optional region); -std::string GetGCIFolderPath(std::string configured_folder, ExpansionInterface::Slot slot, - std::optional region); -bool IsDefaultGCIFolderPathConfigured(ExpansionInterface::Slot slot); -bool AreCheatsEnabled(); -bool IsDebuggingEnabled(); -} // namespace Config +extern const Info MAIN_WII_STARLET_BOOTSTRAP_ELF; +extern const Info MAIN_WII_SD_CARD_IMAGE_PATH; +extern const Info MAIN_WII_SD_CARD_SYNC_FOLDER_PATH; +extern const Info MAIN_WFS_PATH; +extern const Info MAIN_SHOW_LAG; +extern const Info MAIN_SHOW_FRAME_COUNT; +extern const Info MAIN_WIRELESS_MAC; +extern const Info MAIN_GDB_SOCKET; +extern const Info MAIN_GDB_PORT; +extern const Info MAIN_ISO_PATH_COUNT; +extern const Info MAIN_SKYLANDERS_PATH; +std::vector GetIsoPaths(); +void SetIsoPaths(std::span paths); + +// Main.GBA + +#ifdef HAS_LIBMGBA +extern const Info MAIN_GBA_BIOS_PATH; +extern const std::array, 5> MAIN_GBA_ROM_PATHS; +extern const Info MAIN_GBA_SAVES_PATH; +extern const Info MAIN_GBA_SAVES_IN_ROM_PATH; + +static constexpr std::size_t GBPLAYER_GBA_INDEX = 4; +#endif + +// Main.Network + +extern const Info MAIN_NETWORK_SSL_DUMP_READ; +extern const Info MAIN_NETWORK_SSL_DUMP_WRITE; +extern const Info MAIN_NETWORK_SSL_VERIFY_CERTIFICATES; +extern const Info MAIN_NETWORK_SSL_DUMP_ROOT_CA; +extern const Info MAIN_NETWORK_SSL_DUMP_PEER_CERT; +extern const Info MAIN_NETWORK_DUMP_BBA; +extern const Info MAIN_NETWORK_DUMP_AS_PCAP; +extern const Info MAIN_NETWORK_TIMEOUT; + +// Main.Interface + +extern const Info MAIN_USE_HIGH_CONTRAST_TOOLTIPS; +extern const Info MAIN_USE_PANIC_HANDLERS; +extern const Info MAIN_ABORT_ON_PANIC_ALERT; +extern const Info MAIN_OSD_MESSAGES; +extern const Info MAIN_OSD_FONT_SIZE; +extern const Info MAIN_SKIP_NKIT_WARNING; +extern const Info MAIN_CONFIRM_ON_STOP; + +enum class ShowCursor +{ + Never, + Constantly, + OnMovement, +}; +extern const Info MAIN_SHOW_CURSOR; + +extern const Info MAIN_LOCK_CURSOR; +extern const Info MAIN_INTERFACE_LANGUAGE; +extern const Info MAIN_SHOW_ACTIVE_TITLE; +extern const Info MAIN_USE_BUILT_IN_TITLE_DATABASE; +extern const Info MAIN_THEME_NAME; +extern const Info MAIN_PAUSE_ON_FOCUS_LOST; +extern const Info MAIN_ENABLE_DEBUGGING; + +// Main.Analytics + +extern const Info MAIN_ANALYTICS_ID; +extern const Info MAIN_ANALYTICS_ENABLED; +extern const Info MAIN_ANALYTICS_PERMISSION_ASKED; + +// Main.GameList + +extern const Info MAIN_GAMELIST_LIST_DRIVES; +extern const Info MAIN_GAMELIST_LIST_WAD; +extern const Info MAIN_GAMELIST_LIST_ELF_DOL; +extern const Info MAIN_GAMELIST_LIST_WII; +extern const Info MAIN_GAMELIST_LIST_GC; +extern const Info MAIN_GAMELIST_LIST_TRI; +extern const Info MAIN_GAMELIST_LIST_JPN; +extern const Info MAIN_GAMELIST_LIST_PAL; +extern const Info MAIN_GAMELIST_LIST_USA; +extern const Info MAIN_GAMELIST_LIST_AUSTRALIA; +extern const Info MAIN_GAMELIST_LIST_FRANCE; +extern const Info MAIN_GAMELIST_LIST_GERMANY; +extern const Info MAIN_GAMELIST_LIST_ITALY; +extern const Info MAIN_GAMELIST_LIST_KOREA; +extern const Info MAIN_GAMELIST_LIST_NETHERLANDS; +extern const Info MAIN_GAMELIST_LIST_RUSSIA; +extern const Info MAIN_GAMELIST_LIST_SPAIN; +extern const Info MAIN_GAMELIST_LIST_TAIWAN; +extern const Info MAIN_GAMELIST_LIST_WORLD; +extern const Info MAIN_GAMELIST_LIST_UNKNOWN; +extern const Info MAIN_GAMELIST_LIST_SORT; +extern const Info MAIN_GAMELIST_LIST_SORT_SECONDARY; + +extern const Info MAIN_GAMELIST_COLUMN_PLATFORM; +extern const Info MAIN_GAMELIST_COLUMN_DESCRIPTION; +extern const Info MAIN_GAMELIST_COLUMN_BANNER; +extern const Info MAIN_GAMELIST_COLUMN_TITLE; +extern const Info MAIN_GAMELIST_COLUMN_MAKER; +extern const Info MAIN_GAMELIST_COLUMN_FILE_NAME; +extern const Info MAIN_GAMELIST_COLUMN_FILE_PATH; +extern const Info MAIN_GAMELIST_COLUMN_GAME_ID; +extern const Info MAIN_GAMELIST_COLUMN_REGION; +extern const Info MAIN_GAMELIST_COLUMN_FILE_SIZE; +extern const Info MAIN_GAMELIST_COLUMN_FILE_FORMAT; +extern const Info MAIN_GAMELIST_COLUMN_BLOCK_SIZE; +extern const Info MAIN_GAMELIST_COLUMN_COMPRESSION; +extern const Info MAIN_GAMELIST_COLUMN_TIME_PLAYED; +extern const Info MAIN_GAMELIST_COLUMN_TAGS; + +// Main.FifoPlayer + +extern const Info MAIN_FIFOPLAYER_LOOP_REPLAY; +extern const Info MAIN_FIFOPLAYER_EARLY_MEMORY_UPDATES; + +// Main.AutoUpdate + +extern const Info MAIN_AUTOUPDATE_UPDATE_TRACK; +extern const Info MAIN_AUTOUPDATE_HASH_OVERRIDE; + +// Main.Movie + +extern const Info MAIN_MOVIE_PAUSE_MOVIE; +extern const Info MAIN_MOVIE_MOVIE_AUTHOR; +extern const Info MAIN_MOVIE_DUMP_FRAMES; +extern const Info MAIN_MOVIE_DUMP_FRAMES_SILENT; +extern const Info MAIN_MOVIE_SHOW_INPUT_DISPLAY; +extern const Info MAIN_MOVIE_SHOW_RTC; +extern const Info MAIN_MOVIE_SHOW_RERECORD; +extern const Info MAIN_MOVIE_SHOW_OSD; + +// Main.Input + +extern const Info MAIN_INPUT_BACKGROUND_INPUT; + +extern const Config::Info MAIN_SDL_HINT_JOYSTICK_ENHANCED_REPORTS; +extern const Config::Info MAIN_SDL_HINT_JOYSTICK_WGI; +extern const Config::Info MAIN_SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED; +extern const Config::Info MAIN_SDL_HINT_JOYSTICK_DIRECTINPUT; +extern const Config::Info MAIN_SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS; +extern const Config::Info MAIN_SDL_HINT_JOYSTICK_HIDAPI_VERTICAL_JOY_CONS; + +// Main.Debug + +extern const Info MAIN_DEBUG_JIT_OFF; +extern const Info MAIN_DEBUG_JIT_LOAD_STORE_OFF; +extern const Info MAIN_DEBUG_JIT_LOAD_STORE_LXZ_OFF; +extern const Info MAIN_DEBUG_JIT_LOAD_STORE_LWZ_OFF; +extern const Info MAIN_DEBUG_JIT_LOAD_STORE_LBZX_OFF; +extern const Info MAIN_DEBUG_JIT_LOAD_STORE_FLOATING_OFF; +extern const Info MAIN_DEBUG_JIT_LOAD_STORE_PAIRED_OFF; +extern const Info MAIN_DEBUG_JIT_FLOATING_POINT_OFF; +extern const Info MAIN_DEBUG_JIT_INTEGER_OFF; +extern const Info MAIN_DEBUG_JIT_PAIRED_OFF; +extern const Info MAIN_DEBUG_JIT_SYSTEM_REGISTERS_OFF; +extern const Info MAIN_DEBUG_JIT_BRANCH_OFF; +extern const Info MAIN_DEBUG_JIT_REGISTER_CACHE_OFF; +extern const Info MAIN_DEBUG_JIT_ENABLE_PROFILING; + +// Main.BluetoothPassthrough + +extern const Info MAIN_BLUETOOTH_PASSTHROUGH_ENABLED; +extern const Info MAIN_BLUETOOTH_PASSTHROUGH_VID; +extern const Info MAIN_BLUETOOTH_PASSTHROUGH_PID; +extern const Info MAIN_BLUETOOTH_PASSTHROUGH_LINK_KEYS; + +// Main.USBPassthrough + +extern const Info MAIN_USB_PASSTHROUGH_DISGUISE_PLAYSTATION_AS_WII; +extern const Info MAIN_USB_PASSTHROUGH_DEVICES; +std::set GetUSBDeviceWhitelist(); +void SetUSBDeviceWhitelist(const std::set& devices); + +// Main.EmulatedUSBDevices + +extern const Info MAIN_EMULATE_SKYLANDER_PORTAL; +extern const Info MAIN_EMULATE_INFINITY_BASE; +extern const Info MAIN_EMULATE_WII_SPEAK; +extern const Info MAIN_WII_SPEAK_MICROPHONE; +extern const Info MAIN_WII_SPEAK_MUTED; +extern const Info MAIN_WII_SPEAK_VOLUME_MODIFIER; + +static constexpr std::size_t EMULATED_LOGITECH_MIC_COUNT = 4; + +extern const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_EMULATE_LOGITECH_MIC; +extern const std::array, EMULATED_LOGITECH_MIC_COUNT> + MAIN_LOGITECH_MIC_MICROPHONE; +extern const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_MUTED; +extern const std::array, EMULATED_LOGITECH_MIC_COUNT> MAIN_LOGITECH_MIC_VOLUME_MODIFIER; + +extern const Info MAIN_TRIFORCE_IP_REDIRECTIONS; + +// Main.WiimoteAudioRouting + +static constexpr std::size_t WIIMOTE_SPEAKER_COUNT = 4; + +extern const Info MAIN_WIIMOTE_AUDIO_ROUTING_ENABLED; +extern const std::array, WIIMOTE_SPEAKER_COUNT> MAIN_WIIMOTE_AUDIO_OUTPUT_ENABLED; +extern const std::array, WIIMOTE_SPEAKER_COUNT> MAIN_WIIMOTE_AUDIO_OUTPUT_DEVICE; + +// GameCube path utility functions + +// Replaces NTSC-K with some other region, and doesn't replace non-NTSC-K regions +DiscIO::Region ToGameCubeRegion(DiscIO::Region region); + +// The region argument must be valid for GameCube (i.e. must not be NTSC-K) +enum class RegionDirectoryStyle +{ + Legacy, + Modern, +}; +const char* GetDirectoryForRegion(DiscIO::Region region, + RegionDirectoryStyle style = RegionDirectoryStyle::Legacy); + +std::string GetBootROMPath(const std::string& region_directory); +// Builds the memory card according to the configuration with the given region and size. If the +// given region is std::nullopt, the region in the configured path is used if there is one, or the +// fallback region otherwise. +std::string GetMemcardPath(ExpansionInterface::Slot slot, std::optional region, + u16 size_mb = 0x80); +std::string GetMemcardPath(std::string configured_filename, ExpansionInterface::Slot slot, + std::optional region, u16 size_mb = 0x80); +bool IsDefaultMemcardPathConfigured(ExpansionInterface::Slot slot); +std::string GetGCIFolderPath(ExpansionInterface::Slot slot, std::optional region); +std::string GetGCIFolderPath(std::string configured_folder, ExpansionInterface::Slot slot, + std::optional region); +bool IsDefaultGCIFolderPathConfigured(ExpansionInterface::Slot slot); +bool AreCheatsEnabled(); +bool IsDebuggingEnabled(); +} // namespace Config diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index cdb47e98ab..a8bed005ff 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -1,652 +1,656 @@ -// Copyright 2008 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "Core/ConfigManager.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "AudioCommon/AudioCommon.h" - -#include "Common/CommonPaths.h" -#include "Common/CommonTypes.h" -#include "Common/Config/Config.h" -#include "Common/FileUtil.h" -#include "Common/IniFile.h" -#include "Common/Logging/Log.h" -#include "Common/MsgHandler.h" -#include "Common/StringUtil.h" - -#include "Core/AchievementManager.h" -#include "Core/Boot/Boot.h" -#include "Core/Config/DefaultLocale.h" -#include "Core/Config/MainSettings.h" -#include "Core/Config/SYSCONFSettings.h" -#include "Core/ConfigLoaders/GameConfigLoader.h" -#include "Core/Core.h" -#include "Core/DolphinAnalytics.h" -#include "Core/FifoPlayer/FifoDataFile.h" -#include "Core/HLE/HLE.h" -#include "Core/HW/DVD/DVDInterface.h" -#include "Core/HW/EXI/EXI_Device.h" -#include "Core/HW/GCKeyboard.h" -#include "Core/HW/GCPad.h" -#include "Core/HW/SI/SI.h" -#include "Core/HW/SI/SI_Device.h" -#include "Core/HW/Wiimote.h" -#include "Core/Host.h" -#include "Core/IOS/ES/ES.h" -#include "Core/IOS/ES/Formats.h" -#include "Core/PatchEngine.h" -#include "Core/PowerPC/PPCSymbolDB.h" -#include "Core/System.h" -#include "Core/TitleDatabase.h" -#include "Core/WC24PatchEngine.h" - -#include "VideoCommon/HiresTextures.h" - -#include "DiscIO/Enums.h" -#include "DiscIO/Volume.h" -#include "DiscIO/VolumeWad.h" - -SConfig* SConfig::m_Instance; - -SConfig::SConfig() -{ - LoadDefaults(); - // Make sure we have log manager - LoadSettings(); -} - -void SConfig::Init() -{ - m_Instance = new SConfig; -} - -void SConfig::Shutdown() -{ - delete m_Instance; - m_Instance = nullptr; -} - -SConfig::~SConfig() -{ - SaveSettings(); -} - -void SConfig::SaveSettings() -{ - NOTICE_LOG_FMT(BOOT, "Saving settings to {}", File::GetUserPath(F_DOLPHINCONFIG_IDX)); - Config::Save(); -} - -void SConfig::LoadSettings() -{ - INFO_LOG_FMT(BOOT, "Loading Settings from {}", File::GetUserPath(F_DOLPHINCONFIG_IDX)); - Config::Load(); -} - -void SConfig::ResetAllSettings() -{ - Config::ConfigChangeCallbackGuard config_guard; - - File::Delete(File::GetUserPath(F_DOLPHINCONFIG_IDX)); - File::Delete(File::GetUserPath(F_GFXCONFIG_IDX)); - File::Delete(File::GetUserPath(F_LOGGERCONFIG_IDX)); - File::Delete(File::GetUserPath(F_DUALSHOCKUDPCLIENTCONFIG_IDX)); - File::Delete(File::GetUserPath(F_FREELOOKCONFIG_IDX)); - File::Delete(File::GetUserPath(F_RETROACHIEVEMENTSCONFIG_IDX)); - File::Delete(File::GetUserPath(F_WIISYSCONF_IDX)); - - for (Config::LayerType layer_type : Config::SEARCH_ORDER) - { - const std::shared_ptr layer = Config::GetLayer(layer_type); - if (!layer) - continue; - layer->DeleteAllKeys(); - } - - Config::OnConfigChanged(); -} - -const std::string SConfig::GetGameID() const -{ - std::lock_guard lock(m_metadata_lock); - return m_game_id; -} - -const std::string SConfig::GetGameTDBID() const -{ - std::lock_guard lock(m_metadata_lock); - return m_gametdb_id; -} - -const std::string SConfig::GetGameIDElfDol() const -{ - std::lock_guard lock(m_metadata_lock); - return m_game_id_elf_dol; -} - -const std::string SConfig::GetGameIDForTextures() const -{ - return GetGameIDsForTextures().front(); -} - -const std::vector SConfig::GetGameIDsForTextures() const -{ - std::lock_guard lock(m_metadata_lock); - - if (m_game_id_elf_dol.empty()) - return {m_game_id}; - - return {fmt::format("{}-{}", m_game_id_elf_dol, m_game_id), m_game_id}; -} - -const std::string SConfig::GetTitleName() const -{ - std::lock_guard lock(m_metadata_lock); - return m_title_name; -} - -const std::string SConfig::GetTitleDescription() const -{ - std::lock_guard lock(m_metadata_lock); - return m_title_description; -} - -u64 SConfig::GetTitleID() const -{ - std::lock_guard lock(m_metadata_lock); - return m_title_id; -} - -u16 SConfig::GetRevision() const -{ - std::lock_guard lock(m_metadata_lock); - return m_revision; -} - -u32 SConfig::GetSimulatedMemorySize() const -{ - std::lock_guard lock(m_metadata_lock); - return m_simulated_memory_size; -} - -void SConfig::ResetRunningGameMetadata() -{ - std::lock_guard lock(m_metadata_lock); - SetRunningGameMetadata("00000000", "", 0, 0, DiscIO::Region::Unknown, 0); - SetElfDolID(""); -} - -void SConfig::SetRunningGameMetadata(const DiscIO::Volume& volume, - const DiscIO::Partition& partition) -{ - std::lock_guard lock(m_metadata_lock); - - if (partition == volume.GetGamePartition()) - { - SetRunningGameMetadata(volume.GetGameID(), volume.GetGameTDBID(), - volume.GetTitleID().value_or(0), volume.GetRevision().value_or(0), - volume.GetRegion(), volume.GetSimulatedMemorySize()); - } - else - { - SetRunningGameMetadata(volume.GetGameID(partition), volume.GetGameTDBID(partition), - volume.GetTitleID(partition).value_or(0), - volume.GetRevision(partition).value_or(0), volume.GetRegion(), - volume.GetSimulatedMemorySize()); - } -} - -void SConfig::SetRunningGameMetadata(const IOS::ES::TMDReader& tmd, DiscIO::Platform platform) -{ - std::lock_guard lock(m_metadata_lock); - const u64 tmd_title_id = tmd.GetTitleId(); - - // If we're launching a disc game, we want to read the revision from - // the disc header instead of the TMD. They can differ. - // (IOS HLE ES calls us with a TMDReader rather than a volume when launching - // a disc game, because ES has no reason to be accessing the disc directly.) - if (platform == DiscIO::Platform::WiiWAD || - !Core::System::GetInstance().GetDVDInterface().UpdateRunningGameMetadata(tmd_title_id)) - { - // If not launching a disc game, just read everything from the TMD. - SetRunningGameMetadata(tmd.GetGameID(), tmd.GetGameTDBID(), tmd_title_id, tmd.GetTitleVersion(), - tmd.GetRegion(), 0); - } -} - -void SConfig::SetRunningGameMetadata(const std::string& game_id) -{ - std::lock_guard lock(m_metadata_lock); - SetRunningGameMetadata(game_id, "", 0, 0, DiscIO::Region::Unknown, 0); -} - -void SConfig::SetRunningGameMetadata(const std::string& game_id, const std::string& gametdb_id, - u64 title_id, u16 revision, DiscIO::Region region, - u32 simulated_memory_size) -{ - std::lock_guard lock(m_metadata_lock); - const bool was_changed = m_game_id != game_id || m_gametdb_id != gametdb_id || - m_title_id != title_id || m_revision != revision || - m_simulated_memory_size != simulated_memory_size; - m_game_id = game_id; - m_gametdb_id = gametdb_id; - m_title_id = title_id; - m_revision = revision; - m_simulated_memory_size = simulated_memory_size; - - if (game_id.length() == 6) - { - m_debugger_game_id = game_id; - } - else if (title_id != 0) - { - m_debugger_game_id = - fmt::format("{:08X}_{:08X}", static_cast(title_id >> 32), static_cast(title_id)); - } - else - { - m_debugger_game_id.clear(); - } - - if (!was_changed) - return; - - if (game_id == "00000000") - { - m_title_name.clear(); - m_title_description.clear(); - return; - } - - AchievementManager::GetInstance().CloseGame(); - - const Core::TitleDatabase title_database; - auto& system = Core::System::GetInstance(); - const DiscIO::Language language = GetLanguageAdjustedForRegion(system.IsWii(), region); - m_title_name = title_database.GetTitleName(m_gametdb_id, language); - m_title_description = title_database.Describe(m_gametdb_id, language); - NOTICE_LOG_FMT(CORE, "Active title: {}", m_title_description); - Host_TitleChanged(); - - const bool is_running_or_starting = Core::IsRunningOrStarting(system); - if (is_running_or_starting) - Core::UpdateTitle(system); - - Config::AddLayer(ConfigLoaders::GenerateGlobalGameConfigLoader(game_id, revision)); - Config::AddLayer(ConfigLoaders::GenerateLocalGameConfigLoader(game_id, revision)); - - if (is_running_or_starting) - DolphinAnalytics::Instance().ReportGameStart(); -} - -void SConfig::SetElfDolID(const std::string& game_id) -{ - std::lock_guard lock(m_metadata_lock); - m_game_id_elf_dol = game_id; -} - -void SConfig::OnESTitleChanged() -{ - auto& system = Core::System::GetInstance(); - Pad::LoadConfig(); - Keyboard::LoadConfig(); - if (system.IsWii() && !Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_ENABLED)) - { - Wiimote::LoadConfig(); - } - - ReloadTextures(system); -} - -void SConfig::OnTitleDirectlyBooted(const Core::CPUThreadGuard& guard) -{ - auto& system = guard.GetSystem(); - if (!Core::IsRunningOrStarting(system)) - return; - - auto& ppc_symbol_db = system.GetPPCSymbolDB(); - - if (ppc_symbol_db.LoadMapOnBoot(guard)) - Host_PPCSymbolsChanged(); - HLE::Reload(system); - - PatchEngine::Reload(system); - WC24PatchEngine::Reload(); - - // Note: Wii is handled by ES title change - if (!system.IsWii()) - { - ReloadTextures(system); - } -} - -void SConfig::ReloadTextures(Core::System& system) -{ - Pad::GenerateDynamicInputTextures(); - Keyboard::GenerateDynamicInputTextures(); - if (system.IsWii() && !Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_ENABLED)) - { - Wiimote::GenerateDynamicInputTextures(); - } - - HiresTexture::Update(); -} - -void SConfig::LoadDefaults() -{ - bBootToPause = false; - - auto& system = Core::System::GetInstance(); - system.SetIsWii(false); - system.SetIsTriforce(false); - - ResetRunningGameMetadata(); -} - -// Static method to make a simple game ID for elf/dol files -std::string SConfig::MakeGameID(std::string_view file_name) -{ - size_t lastdot = file_name.find_last_of("."); - if (lastdot == std::string::npos) - return "ID-" + std::string(file_name); - return "ID-" + std::string(file_name.substr(0, lastdot)); -} - -struct SetGameMetadata -{ - SetGameMetadata(SConfig* config_, Core::System& system_, DiscIO::Region* region_) - : config(config_), system(system_), region(region_) - { - } - bool operator()(const BootParameters::Disc& disc) const - { - *region = disc.volume->GetRegion(); - system.SetIsWii(disc.volume->GetVolumeType() == DiscIO::Platform::WiiDisc); - system.SetIsTriforce(disc.volume->GetVolumeType() == DiscIO::Platform::Triforce); - config->m_disc_booted_from_game_list = true; - config->SetRunningGameMetadata(*disc.volume, disc.volume->GetGamePartition()); - return true; - } - - bool operator()(const BootParameters::Executable& executable) const - { - if (!executable.reader->IsValid()) - return false; - - *region = DiscIO::Region::Unknown; - system.SetIsWii(executable.reader->IsWii()); - - // Strip the .elf/.dol file extension and directories before the name - SplitPath(executable.path, nullptr, &config->m_debugger_game_id, nullptr); - - // Set DOL/ELF game ID appropriately - std::string executable_path = executable.path; - constexpr char BACKSLASH = '\\'; - constexpr char FORWARDSLASH = '/'; - std::ranges::replace(executable_path, BACKSLASH, FORWARDSLASH); - const std::string made_game_id = SConfig::MakeGameID(PathToFileName(executable_path)); - config->SetRunningGameMetadata(made_game_id); - config->SetElfDolID(made_game_id); - - Host_TitleChanged(); - - return true; - } - - bool operator()(const DiscIO::VolumeWAD& wad) const - { - if (!wad.GetTMD().IsValid()) - { - PanicAlertFmtT("This WAD is not valid."); - return false; - } - if (!IOS::ES::IsChannel(wad.GetTMD().GetTitleId())) - { - PanicAlertFmtT("This WAD is not bootable."); - return false; - } - - const IOS::ES::TMDReader& tmd = wad.GetTMD(); - *region = tmd.GetRegion(); - system.SetIsWii(true); - config->SetRunningGameMetadata(tmd, DiscIO::Platform::WiiWAD); - - return true; - } - - bool operator()(const BootParameters::NANDTitle& nand_title) const - { - constexpr u64 SYSTEM_MENU_TITLE_ID = 0x0000000100000002; - if (Config::Get(Config::MAIN_WII_IOS_LLE)) - { - if (nand_title.id != SYSTEM_MENU_TITLE_ID) - { - PanicAlertFmtT("Direct NAND-title boot is unavailable with Wii IOS LLE. Select the Wii " - "System Menu to start the original NAND boot chain."); - return false; - } - - // The LLE NAND remains a raw, read-only dump with a copy-on-write overlay. Do not ask the - // HLE filesystem for a TMD here: boot2 and the original IOS will select and authenticate the - // System Menu themselves. - *region = DiscIO::Region::Unknown; - system.SetIsWii(true); - config->SetRunningGameMetadata("WIILLE"); - Host_TitleChanged(); - return true; - } - - IOS::HLE::Kernel ios; - const IOS::ES::TMDReader tmd = ios.GetESCore().FindInstalledTMD(nand_title.id); - if (!tmd.IsValid() || !IOS::ES::IsChannel(nand_title.id)) - { - PanicAlertFmtT("This title cannot be booted."); - return false; - } - - *region = tmd.GetRegion(); - system.SetIsWii(true); - config->SetRunningGameMetadata(tmd, DiscIO::Platform::WiiWAD); - - return true; - } - - bool operator()(const BootParameters::IPL& ipl) const - { - *region = ipl.region; - system.SetIsWii(false); - Host_TitleChanged(); - - return true; - } - - bool operator()(const BootParameters::DFF& dff) const - { - std::unique_ptr dff_file(FifoDataFile::Load(dff.dff_path, true)); - if (!dff_file) - return false; - - *region = DiscIO::Region::NTSC_U; - system.SetIsWii(dff_file->GetIsWii()); - - const std::string& game_id = dff_file->GetGameId(); - if (game_id == DEFAULT_GAME_ID) - { - Host_TitleChanged(); - } - else - { - config->SetRunningGameMetadata(game_id); - } - - return true; - } - -private: - SConfig* config; - Core::System& system; - DiscIO::Region* region; -}; - -bool SConfig::SetPathsAndGameMetadata(Core::System& system, const BootParameters& boot) -{ - system.SetIsMIOS(false); - m_disc_booted_from_game_list = false; - if (!std::visit(SetGameMetadata(this, system, &m_region), boot.parameters)) - return false; - - if (m_region == DiscIO::Region::Unknown) - m_region = Config::Get(Config::MAIN_FALLBACK_REGION); - - if (system.IsTriforce()) - m_region = DiscIO::Region::DEV; - - // Set up paths - const std::string region_dir = Config::GetDirectoryForRegion(Config::ToGameCubeRegion(m_region)); - m_strSRAM = File::GetUserPath(F_GCSRAM_IDX); - m_strBootROM = Config::GetBootROMPath(region_dir); - - return true; -} - -DiscIO::Language SConfig::GetCurrentLanguage(bool wii) const -{ - DiscIO::Language language; - if (wii) - language = static_cast(Config::Get(Config::SYSCONF_LANGUAGE)); - else - language = DiscIO::FromGameCubeLanguage(Config::Get(Config::MAIN_GC_LANGUAGE)); - - // Get rid of invalid values (probably doesn't matter, but might as well do it) - if (language > DiscIO::Language::Unknown || language < DiscIO::Language::Japanese) - language = DiscIO::Language::Unknown; - return language; -} - -DiscIO::Language SConfig::GetLanguageAdjustedForRegion(bool wii, DiscIO::Region region) const -{ - const DiscIO::Language language = GetCurrentLanguage(wii); - - if (!wii && region == DiscIO::Region::NTSC_K) - region = DiscIO::Region::NTSC_J; // NTSC-K only exists on Wii, so use a fallback - - if (!wii && region == DiscIO::Region::NTSC_J && language == DiscIO::Language::English) - return DiscIO::Language::Japanese; // English and Japanese both use the value 0 in GC SRAM - - if (!Config::Get(Config::MAIN_OVERRIDE_REGION_SETTINGS)) - { - if (region == DiscIO::Region::NTSC_J) - return DiscIO::Language::Japanese; - - if (region == DiscIO::Region::NTSC_U && language != DiscIO::Language::English && - (!wii || (language != DiscIO::Language::French && language != DiscIO::Language::Spanish))) - { - return DiscIO::Language::English; - } - - if (region == DiscIO::Region::PAL && - (language < DiscIO::Language::English || language > DiscIO::Language::Dutch)) - { - return DiscIO::Language::English; - } - - if (region == DiscIO::Region::NTSC_K) - return DiscIO::Language::Korean; - } - - return language; -} - -Common::IniFile SConfig::LoadDefaultGameIni() const -{ - return LoadDefaultGameIni(GetGameID(), m_revision); -} - -Common::IniFile SConfig::LoadLocalGameIni() const -{ - return LoadLocalGameIni(GetGameID(), m_revision); -} - -Common::IniFile SConfig::LoadGameIni() const -{ - return LoadGameIni(GetGameID(), m_revision); -} - -Common::IniFile SConfig::LoadDefaultGameIni(std::string_view id, std::optional revision) -{ - Common::IniFile game_ini; - for (const std::string& filename : ConfigLoaders::GetGameIniFilenames(id, revision)) - game_ini.Load(File::GetSysDirectory() + GAMESETTINGS_DIR DIR_SEP + filename, true); - return game_ini; -} - -Common::IniFile SConfig::LoadLocalGameIni(std::string_view id, std::optional revision) -{ - Common::IniFile game_ini; - for (const std::string& filename : ConfigLoaders::GetGameIniFilenames(id, revision)) - game_ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + filename, true); - return game_ini; -} - -Common::IniFile SConfig::LoadGameIni(std::string_view id, std::optional revision) -{ - Common::IniFile game_ini; - for (const std::string& filename : ConfigLoaders::GetGameIniFilenames(id, revision)) - game_ini.Load(File::GetSysDirectory() + GAMESETTINGS_DIR DIR_SEP + filename, true); - for (const std::string& filename : ConfigLoaders::GetGameIniFilenames(id, revision)) - game_ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + filename, true); - return game_ini; -} - -std::string SConfig::GetGameTDBImageRegionCode(bool wii, DiscIO::Region region) const -{ - switch (region) - { - case DiscIO::Region::NTSC_J: - { - // Taiwanese games share the Japanese region code however their title ID ends with 'W'. - // GameTDB differentiates the covers using the code "ZH". - if (m_game_id.size() >= 4 && m_game_id.at(3) == 'W') - return "ZH"; - - return "JA"; - } - case DiscIO::Region::NTSC_U: - return "US"; - case DiscIO::Region::NTSC_K: - return "KO"; - case DiscIO::Region::PAL: - { - const auto user_lang = GetCurrentLanguage(wii); - switch (user_lang) - { - case DiscIO::Language::German: - return "DE"; - case DiscIO::Language::French: - return "FR"; - case DiscIO::Language::Spanish: - return "ES"; - case DiscIO::Language::Italian: - return "IT"; - case DiscIO::Language::Dutch: - return "NL"; - case DiscIO::Language::English: - default: - return "EN"; - } - } - default: - return "EN"; - } -} +// Copyright 2008 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/ConfigManager.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "AudioCommon/AudioCommon.h" + +#include "Common/CommonPaths.h" +#include "Common/CommonTypes.h" +#include "Common/Config/Config.h" +#include "Common/FileUtil.h" +#include "Common/IniFile.h" +#include "Common/Logging/Log.h" +#include "Common/MsgHandler.h" +#include "Common/StringUtil.h" + +#include "Core/AchievementManager.h" +#include "Core/Boot/Boot.h" +#include "Core/Config/DefaultLocale.h" +#include "Core/Config/MainSettings.h" +#include "Core/Config/SYSCONFSettings.h" +#include "Core/ConfigLoaders/GameConfigLoader.h" +#include "Core/Core.h" +#include "Core/DolphinAnalytics.h" +#include "Core/FifoPlayer/FifoDataFile.h" +#include "Core/HLE/HLE.h" +#include "Core/HW/DVD/DVDInterface.h" +#include "Core/HW/EXI/EXI_Device.h" +#include "Core/HW/GCKeyboard.h" +#include "Core/HW/GCPad.h" +#include "Core/HW/SI/SI.h" +#include "Core/HW/SI/SI_Device.h" +#include "Core/HW/Wiimote.h" +#include "Core/Host.h" +#include "Core/IOS/ES/ES.h" +#include "Core/IOS/ES/Formats.h" +#include "Core/PatchEngine.h" +#include "Core/PowerPC/PPCSymbolDB.h" +#include "Core/System.h" +#include "Core/TitleDatabase.h" +#include "Core/WC24PatchEngine.h" + +#include "VideoCommon/HiresTextures.h" + +#include "DiscIO/Enums.h" +#include "DiscIO/Volume.h" +#include "DiscIO/VolumeWad.h" + +SConfig* SConfig::m_Instance; + +SConfig::SConfig() +{ + LoadDefaults(); + // Make sure we have log manager + LoadSettings(); +} + +void SConfig::Init() +{ + m_Instance = new SConfig; +} + +void SConfig::Shutdown() +{ + delete m_Instance; + m_Instance = nullptr; +} + +SConfig::~SConfig() +{ + SaveSettings(); +} + +void SConfig::SaveSettings() +{ + NOTICE_LOG_FMT(BOOT, "Saving settings to {}", File::GetUserPath(F_DOLPHINCONFIG_IDX)); + Config::Save(); +} + +void SConfig::LoadSettings() +{ + INFO_LOG_FMT(BOOT, "Loading Settings from {}", File::GetUserPath(F_DOLPHINCONFIG_IDX)); + Config::Load(); +} + +void SConfig::ResetAllSettings() +{ + Config::ConfigChangeCallbackGuard config_guard; + + File::Delete(File::GetUserPath(F_DOLPHINCONFIG_IDX)); + File::Delete(File::GetUserPath(F_GFXCONFIG_IDX)); + File::Delete(File::GetUserPath(F_LOGGERCONFIG_IDX)); + File::Delete(File::GetUserPath(F_DUALSHOCKUDPCLIENTCONFIG_IDX)); + File::Delete(File::GetUserPath(F_FREELOOKCONFIG_IDX)); + File::Delete(File::GetUserPath(F_RETROACHIEVEMENTSCONFIG_IDX)); + File::Delete(File::GetUserPath(F_WIISYSCONF_IDX)); + + for (Config::LayerType layer_type : Config::SEARCH_ORDER) + { + const std::shared_ptr layer = Config::GetLayer(layer_type); + if (!layer) + continue; + layer->DeleteAllKeys(); + } + + Config::OnConfigChanged(); +} + +const std::string SConfig::GetGameID() const +{ + std::lock_guard lock(m_metadata_lock); + return m_game_id; +} + +const std::string SConfig::GetGameTDBID() const +{ + std::lock_guard lock(m_metadata_lock); + return m_gametdb_id; +} + +const std::string SConfig::GetGameIDElfDol() const +{ + std::lock_guard lock(m_metadata_lock); + return m_game_id_elf_dol; +} + +const std::string SConfig::GetGameIDForTextures() const +{ + return GetGameIDsForTextures().front(); +} + +const std::vector SConfig::GetGameIDsForTextures() const +{ + std::lock_guard lock(m_metadata_lock); + + if (m_game_id_elf_dol.empty()) + return {m_game_id}; + + return {fmt::format("{}-{}", m_game_id_elf_dol, m_game_id), m_game_id}; +} + +const std::string SConfig::GetTitleName() const +{ + std::lock_guard lock(m_metadata_lock); + return m_title_name; +} + +const std::string SConfig::GetTitleDescription() const +{ + std::lock_guard lock(m_metadata_lock); + return m_title_description; +} + +u64 SConfig::GetTitleID() const +{ + std::lock_guard lock(m_metadata_lock); + return m_title_id; +} + +u16 SConfig::GetRevision() const +{ + std::lock_guard lock(m_metadata_lock); + return m_revision; +} + +u32 SConfig::GetSimulatedMemorySize() const +{ + std::lock_guard lock(m_metadata_lock); + return m_simulated_memory_size; +} + +void SConfig::ResetRunningGameMetadata() +{ + std::lock_guard lock(m_metadata_lock); + SetRunningGameMetadata("00000000", "", 0, 0, DiscIO::Region::Unknown, 0); + SetElfDolID(""); +} + +void SConfig::SetRunningGameMetadata(const DiscIO::Volume& volume, + const DiscIO::Partition& partition) +{ + std::lock_guard lock(m_metadata_lock); + + if (partition == volume.GetGamePartition()) + { + SetRunningGameMetadata(volume.GetGameID(), volume.GetGameTDBID(), + volume.GetTitleID().value_or(0), volume.GetRevision().value_or(0), + volume.GetRegion(), volume.GetSimulatedMemorySize()); + } + else + { + SetRunningGameMetadata(volume.GetGameID(partition), volume.GetGameTDBID(partition), + volume.GetTitleID(partition).value_or(0), + volume.GetRevision(partition).value_or(0), volume.GetRegion(), + volume.GetSimulatedMemorySize()); + } +} + +void SConfig::SetRunningGameMetadata(const IOS::ES::TMDReader& tmd, DiscIO::Platform platform) +{ + std::lock_guard lock(m_metadata_lock); + const u64 tmd_title_id = tmd.GetTitleId(); + + // If we're launching a disc game, we want to read the revision from + // the disc header instead of the TMD. They can differ. + // (IOS HLE ES calls us with a TMDReader rather than a volume when launching + // a disc game, because ES has no reason to be accessing the disc directly.) + if (platform == DiscIO::Platform::WiiWAD || + !Core::System::GetInstance().GetDVDInterface().UpdateRunningGameMetadata(tmd_title_id)) + { + // If not launching a disc game, just read everything from the TMD. + SetRunningGameMetadata(tmd.GetGameID(), tmd.GetGameTDBID(), tmd_title_id, tmd.GetTitleVersion(), + tmd.GetRegion(), 0); + } +} + +void SConfig::SetRunningGameMetadata(const std::string& game_id) +{ + std::lock_guard lock(m_metadata_lock); + SetRunningGameMetadata(game_id, "", 0, 0, DiscIO::Region::Unknown, 0); +} + +void SConfig::SetRunningGameMetadata(const std::string& game_id, const std::string& gametdb_id, + u64 title_id, u16 revision, DiscIO::Region region, + u32 simulated_memory_size) +{ + std::lock_guard lock(m_metadata_lock); + const bool was_changed = m_game_id != game_id || m_gametdb_id != gametdb_id || + m_title_id != title_id || m_revision != revision || + m_simulated_memory_size != simulated_memory_size; + m_game_id = game_id; + m_gametdb_id = gametdb_id; + m_title_id = title_id; + m_revision = revision; + m_simulated_memory_size = simulated_memory_size; + + if (game_id.length() == 6) + { + m_debugger_game_id = game_id; + } + else if (title_id != 0) + { + m_debugger_game_id = + fmt::format("{:08X}_{:08X}", static_cast(title_id >> 32), static_cast(title_id)); + } + else + { + m_debugger_game_id.clear(); + } + + if (!was_changed) + return; + + if (game_id == "00000000") + { + m_title_name.clear(); + m_title_description.clear(); + return; + } + + AchievementManager::GetInstance().CloseGame(); + + const Core::TitleDatabase title_database; + auto& system = Core::System::GetInstance(); + const DiscIO::Language language = GetLanguageAdjustedForRegion(system.IsWii(), region); + m_title_name = title_database.GetTitleName(m_gametdb_id, language); + m_title_description = title_database.Describe(m_gametdb_id, language); + NOTICE_LOG_FMT(CORE, "Active title: {}", m_title_description); + Host_TitleChanged(); + + const bool is_running_or_starting = Core::IsRunningOrStarting(system); + if (is_running_or_starting) + Core::UpdateTitle(system); + + Config::AddLayer(ConfigLoaders::GenerateGlobalGameConfigLoader(game_id, revision)); + Config::AddLayer(ConfigLoaders::GenerateLocalGameConfigLoader(game_id, revision)); + + if (is_running_or_starting) + DolphinAnalytics::Instance().ReportGameStart(); +} + +void SConfig::SetElfDolID(const std::string& game_id) +{ + std::lock_guard lock(m_metadata_lock); + m_game_id_elf_dol = game_id; +} + +void SConfig::OnESTitleChanged() +{ + auto& system = Core::System::GetInstance(); + Pad::LoadConfig(); + Keyboard::LoadConfig(); + if (system.IsWii() && !Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_ENABLED)) + { + Wiimote::LoadConfig(); + } + + ReloadTextures(system); +} + +void SConfig::OnTitleDirectlyBooted(const Core::CPUThreadGuard& guard) +{ + auto& system = guard.GetSystem(); + if (!Core::IsRunningOrStarting(system)) + return; + + auto& ppc_symbol_db = system.GetPPCSymbolDB(); + + if (ppc_symbol_db.LoadMapOnBoot(guard)) + Host_PPCSymbolsChanged(); + HLE::Reload(system); + + PatchEngine::Reload(system); + WC24PatchEngine::Reload(); + + // Note: Wii is handled by ES title change + if (!system.IsWii()) + { + ReloadTextures(system); + } +} + +void SConfig::ReloadTextures(Core::System& system) +{ + Pad::GenerateDynamicInputTextures(); + Keyboard::GenerateDynamicInputTextures(); + if (system.IsWii() && !Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_ENABLED)) + { + Wiimote::GenerateDynamicInputTextures(); + } + + HiresTexture::Update(); +} + +void SConfig::LoadDefaults() +{ + bBootToPause = false; + + auto& system = Core::System::GetInstance(); + system.SetIsWii(false); + system.SetIsTriforce(false); + + ResetRunningGameMetadata(); +} + +// Static method to make a simple game ID for elf/dol files +std::string SConfig::MakeGameID(std::string_view file_name) +{ + size_t lastdot = file_name.find_last_of("."); + if (lastdot == std::string::npos) + return "ID-" + std::string(file_name); + return "ID-" + std::string(file_name.substr(0, lastdot)); +} + +struct SetGameMetadata +{ + SetGameMetadata(SConfig* config_, Core::System& system_, DiscIO::Region* region_) + : config(config_), system(system_), region(region_) + { + } + bool operator()(const BootParameters::Disc& disc) const + { + *region = disc.volume->GetRegion(); + system.SetIsWii(disc.volume->GetVolumeType() == DiscIO::Platform::WiiDisc); + system.SetIsTriforce(disc.volume->GetVolumeType() == DiscIO::Platform::Triforce); + config->m_disc_booted_from_game_list = true; + config->SetRunningGameMetadata(*disc.volume, disc.volume->GetGamePartition()); + return true; + } + + bool operator()(const BootParameters::Executable& executable) const + { + if (!executable.reader->IsValid()) + return false; + + *region = DiscIO::Region::Unknown; + // A paired Starlet bootstrap plus a direct PPC probe is a Wii session even + // when the tiny probe has no HID4 instruction for the executable heuristic. + const bool paired_starlet_probe = Config::Get(Config::MAIN_WII_IOS_LLE) && + !Config::Get(Config::MAIN_WII_STARLET_BOOTSTRAP_ELF).empty(); + system.SetIsWii(executable.reader->IsWii() || paired_starlet_probe); + + // Strip the .elf/.dol file extension and directories before the name + SplitPath(executable.path, nullptr, &config->m_debugger_game_id, nullptr); + + // Set DOL/ELF game ID appropriately + std::string executable_path = executable.path; + constexpr char BACKSLASH = '\\'; + constexpr char FORWARDSLASH = '/'; + std::ranges::replace(executable_path, BACKSLASH, FORWARDSLASH); + const std::string made_game_id = SConfig::MakeGameID(PathToFileName(executable_path)); + config->SetRunningGameMetadata(made_game_id); + config->SetElfDolID(made_game_id); + + Host_TitleChanged(); + + return true; + } + + bool operator()(const DiscIO::VolumeWAD& wad) const + { + if (!wad.GetTMD().IsValid()) + { + PanicAlertFmtT("This WAD is not valid."); + return false; + } + if (!IOS::ES::IsChannel(wad.GetTMD().GetTitleId())) + { + PanicAlertFmtT("This WAD is not bootable."); + return false; + } + + const IOS::ES::TMDReader& tmd = wad.GetTMD(); + *region = tmd.GetRegion(); + system.SetIsWii(true); + config->SetRunningGameMetadata(tmd, DiscIO::Platform::WiiWAD); + + return true; + } + + bool operator()(const BootParameters::NANDTitle& nand_title) const + { + constexpr u64 SYSTEM_MENU_TITLE_ID = 0x0000000100000002; + if (Config::Get(Config::MAIN_WII_IOS_LLE)) + { + if (nand_title.id != SYSTEM_MENU_TITLE_ID) + { + PanicAlertFmtT("Direct NAND-title boot is unavailable with Wii IOS LLE. Select the Wii " + "System Menu to start the original NAND boot chain."); + return false; + } + + // The LLE NAND remains a raw, read-only dump with a copy-on-write overlay. Do not ask the + // HLE filesystem for a TMD here: boot2 and the original IOS will select and authenticate the + // System Menu themselves. + *region = DiscIO::Region::Unknown; + system.SetIsWii(true); + config->SetRunningGameMetadata("WIILLE"); + Host_TitleChanged(); + return true; + } + + IOS::HLE::Kernel ios; + const IOS::ES::TMDReader tmd = ios.GetESCore().FindInstalledTMD(nand_title.id); + if (!tmd.IsValid() || !IOS::ES::IsChannel(nand_title.id)) + { + PanicAlertFmtT("This title cannot be booted."); + return false; + } + + *region = tmd.GetRegion(); + system.SetIsWii(true); + config->SetRunningGameMetadata(tmd, DiscIO::Platform::WiiWAD); + + return true; + } + + bool operator()(const BootParameters::IPL& ipl) const + { + *region = ipl.region; + system.SetIsWii(false); + Host_TitleChanged(); + + return true; + } + + bool operator()(const BootParameters::DFF& dff) const + { + std::unique_ptr dff_file(FifoDataFile::Load(dff.dff_path, true)); + if (!dff_file) + return false; + + *region = DiscIO::Region::NTSC_U; + system.SetIsWii(dff_file->GetIsWii()); + + const std::string& game_id = dff_file->GetGameId(); + if (game_id == DEFAULT_GAME_ID) + { + Host_TitleChanged(); + } + else + { + config->SetRunningGameMetadata(game_id); + } + + return true; + } + +private: + SConfig* config; + Core::System& system; + DiscIO::Region* region; +}; + +bool SConfig::SetPathsAndGameMetadata(Core::System& system, const BootParameters& boot) +{ + system.SetIsMIOS(false); + m_disc_booted_from_game_list = false; + if (!std::visit(SetGameMetadata(this, system, &m_region), boot.parameters)) + return false; + + if (m_region == DiscIO::Region::Unknown) + m_region = Config::Get(Config::MAIN_FALLBACK_REGION); + + if (system.IsTriforce()) + m_region = DiscIO::Region::DEV; + + // Set up paths + const std::string region_dir = Config::GetDirectoryForRegion(Config::ToGameCubeRegion(m_region)); + m_strSRAM = File::GetUserPath(F_GCSRAM_IDX); + m_strBootROM = Config::GetBootROMPath(region_dir); + + return true; +} + +DiscIO::Language SConfig::GetCurrentLanguage(bool wii) const +{ + DiscIO::Language language; + if (wii) + language = static_cast(Config::Get(Config::SYSCONF_LANGUAGE)); + else + language = DiscIO::FromGameCubeLanguage(Config::Get(Config::MAIN_GC_LANGUAGE)); + + // Get rid of invalid values (probably doesn't matter, but might as well do it) + if (language > DiscIO::Language::Unknown || language < DiscIO::Language::Japanese) + language = DiscIO::Language::Unknown; + return language; +} + +DiscIO::Language SConfig::GetLanguageAdjustedForRegion(bool wii, DiscIO::Region region) const +{ + const DiscIO::Language language = GetCurrentLanguage(wii); + + if (!wii && region == DiscIO::Region::NTSC_K) + region = DiscIO::Region::NTSC_J; // NTSC-K only exists on Wii, so use a fallback + + if (!wii && region == DiscIO::Region::NTSC_J && language == DiscIO::Language::English) + return DiscIO::Language::Japanese; // English and Japanese both use the value 0 in GC SRAM + + if (!Config::Get(Config::MAIN_OVERRIDE_REGION_SETTINGS)) + { + if (region == DiscIO::Region::NTSC_J) + return DiscIO::Language::Japanese; + + if (region == DiscIO::Region::NTSC_U && language != DiscIO::Language::English && + (!wii || (language != DiscIO::Language::French && language != DiscIO::Language::Spanish))) + { + return DiscIO::Language::English; + } + + if (region == DiscIO::Region::PAL && + (language < DiscIO::Language::English || language > DiscIO::Language::Dutch)) + { + return DiscIO::Language::English; + } + + if (region == DiscIO::Region::NTSC_K) + return DiscIO::Language::Korean; + } + + return language; +} + +Common::IniFile SConfig::LoadDefaultGameIni() const +{ + return LoadDefaultGameIni(GetGameID(), m_revision); +} + +Common::IniFile SConfig::LoadLocalGameIni() const +{ + return LoadLocalGameIni(GetGameID(), m_revision); +} + +Common::IniFile SConfig::LoadGameIni() const +{ + return LoadGameIni(GetGameID(), m_revision); +} + +Common::IniFile SConfig::LoadDefaultGameIni(std::string_view id, std::optional revision) +{ + Common::IniFile game_ini; + for (const std::string& filename : ConfigLoaders::GetGameIniFilenames(id, revision)) + game_ini.Load(File::GetSysDirectory() + GAMESETTINGS_DIR DIR_SEP + filename, true); + return game_ini; +} + +Common::IniFile SConfig::LoadLocalGameIni(std::string_view id, std::optional revision) +{ + Common::IniFile game_ini; + for (const std::string& filename : ConfigLoaders::GetGameIniFilenames(id, revision)) + game_ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + filename, true); + return game_ini; +} + +Common::IniFile SConfig::LoadGameIni(std::string_view id, std::optional revision) +{ + Common::IniFile game_ini; + for (const std::string& filename : ConfigLoaders::GetGameIniFilenames(id, revision)) + game_ini.Load(File::GetSysDirectory() + GAMESETTINGS_DIR DIR_SEP + filename, true); + for (const std::string& filename : ConfigLoaders::GetGameIniFilenames(id, revision)) + game_ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + filename, true); + return game_ini; +} + +std::string SConfig::GetGameTDBImageRegionCode(bool wii, DiscIO::Region region) const +{ + switch (region) + { + case DiscIO::Region::NTSC_J: + { + // Taiwanese games share the Japanese region code however their title ID ends with 'W'. + // GameTDB differentiates the covers using the code "ZH". + if (m_game_id.size() >= 4 && m_game_id.at(3) == 'W') + return "ZH"; + + return "JA"; + } + case DiscIO::Region::NTSC_U: + return "US"; + case DiscIO::Region::NTSC_K: + return "KO"; + case DiscIO::Region::PAL: + { + const auto user_lang = GetCurrentLanguage(wii); + switch (user_lang) + { + case DiscIO::Language::German: + return "DE"; + case DiscIO::Language::French: + return "FR"; + case DiscIO::Language::Spanish: + return "ES"; + case DiscIO::Language::Italian: + return "IT"; + case DiscIO::Language::Dutch: + return "NL"; + case DiscIO::Language::English: + default: + return "EN"; + } + } + default: + return "EN"; + } +} diff --git a/Source/Core/Core/HW/HW.cpp b/Source/Core/Core/HW/HW.cpp index 1f157c74d9..ee091e9948 100644 --- a/Source/Core/Core/HW/HW.cpp +++ b/Source/Core/Core/HW/HW.cpp @@ -1,159 +1,159 @@ -// Copyright 2008 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "Core/HW/HW.h" - -#include -#include - -#include "Common/ChunkFile.h" -#include "Common/MsgHandler.h" - -#include "Core/Config/MainSettings.h" -#include "Core/Core.h" -#include "Core/CoreTiming.h" -#include "Core/HW/AddressSpace.h" -#include "Core/HW/AudioInterface.h" -#include "Core/HW/CPU.h" -#include "Core/HW/DSP.h" -#include "Core/HW/DVD/DVDInterface.h" -#include "Core/HW/EXI/EXI.h" -#include "Core/HW/GPFifo.h" -#include "Core/HW/HSP/HSP.h" -#include "Core/HW/Memmap.h" -#include "Core/HW/MemoryInterface.h" -#include "Core/HW/ProcessorInterface.h" -#include "Core/HW/SI/SI.h" -#include "Core/HW/SystemTimers.h" -#include "Core/HW/VideoInterface.h" -#include "Core/HW/WII_IPC.h" -#include "Core/IOS/IOS.h" -#include "Core/IOS/Starlet/Starlet.h" -#include "Core/State.h" -#include "Core/System.h" - -namespace HW -{ -void Init(Core::System& system, const Sram* override_sram) -{ - system.GetCoreTiming().Init(); - system.GetSystemTimers().PreInit(); - - State::Init(system); - - // Init the whole Hardware - system.GetAudioInterface().Init(); - system.GetVideoInterface().Init(); - system.GetSerialInterface().Init(); - system.GetProcessorInterface().Init(); - system.GetExpansionInterface().Init(override_sram); // Needs to be initialized before Memory - system.GetHSP().Init(); - system.GetMemory().Init(); // Needs to be initialized before AddressSpace - AddressSpace::Init(); - system.GetMemoryInterface().Init(); - system.GetDSP().Init(Config::Get(Config::MAIN_DSP_HLE)); - system.GetDVDInterface().Init(); - system.GetGPFifo().Init(); - system.GetCPU().Init(Config::Get(Config::MAIN_CPU_CORE)); - system.GetSystemTimers().Init(); - - if (system.IsWii()) - { - system.GetWiiIPC().Init(); - if (Config::Get(Config::MAIN_WII_IOS_LLE)) - { - auto starlet = std::make_unique(system); - std::string error; - if (starlet->Init(Config::Get(Config::MAIN_WII_LLE_DUMP_PATH), &error)) - { - system.SetStarlet(std::move(starlet)); - system.GetWiiIPC().HoldBroadwayForStarletBoot(); - } - else - { - PanicAlertFmtT("Could not start Wii IOS LLE: {0}", error); - Core::SetState(system, Core::State::Stopping); - return; - } - } - else - { - IOS::HLE::Init(system); // Depends on Memory - } - } - - system.GetMemory().InitMMIO(system); -} - -void Shutdown(Core::System& system) -{ - // IOS should always be shut down regardless of IsWii because it can be running in GC mode (MIOS). - if (system.GetStarlet()) - { - system.GetStarlet()->Shutdown(); - system.SetStarlet(nullptr); - } - if (system.GetIOS()) - IOS::HLE::Shutdown(system); // Depends on Memory - system.GetWiiIPC().Shutdown(); - - system.GetSystemTimers().Shutdown(); - system.GetCPU().Shutdown(); - system.GetDVDInterface().Shutdown(); - system.GetDSP().Shutdown(); - system.GetMemoryInterface().Shutdown(); - AddressSpace::Shutdown(); - system.GetMemory().Shutdown(); - system.GetHSP().Shutdown(); - system.GetExpansionInterface().Shutdown(); - system.GetSerialInterface().Shutdown(); - system.GetAudioInterface().Shutdown(); - - State::Shutdown(); - system.GetCoreTiming().Shutdown(); -} - -void DoState(Core::System& system, PointerWrap& p) -{ - system.GetMemory().DoState(p); - p.DoMarker("Memory"); - system.GetMemoryInterface().DoState(p); - p.DoMarker("MemoryInterface"); - system.GetVideoInterface().DoState(p); - p.DoMarker("VideoInterface"); - system.GetSerialInterface().DoState(p); - p.DoMarker("SerialInterface"); - system.GetProcessorInterface().DoState(p); - p.DoMarker("ProcessorInterface"); - system.GetDSP().DoState(p); - p.DoMarker("DSP"); - system.GetDVDInterface().DoState(p); - p.DoMarker("DVDInterface"); - system.GetGPFifo().DoState(p); - p.DoMarker("GPFifo"); - system.GetExpansionInterface().DoState(p); - p.DoMarker("ExpansionInterface"); - system.GetAudioInterface().DoState(p); - p.DoMarker("AudioInterface"); - system.GetHSP().DoState(p); - p.DoMarker("HSP"); - - if (system.IsWii()) - { - system.GetWiiIPC().DoState(p); - p.DoMarker("IOS"); - if (system.GetStarlet()) - { - system.GetStarlet()->DoState(p); - p.DoMarker("IOS::LLE"); - } - else if (system.GetIOS()) - { - system.GetIOS()->DoState(p); - p.DoMarker("IOS::HLE"); - } - } - - p.DoMarker("WIIHW"); -} -} // namespace HW +// Copyright 2008 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/HW/HW.h" + +#include +#include + +#include "Common/ChunkFile.h" +#include "Common/MsgHandler.h" + +#include "Core/Config/MainSettings.h" +#include "Core/Core.h" +#include "Core/CoreTiming.h" +#include "Core/HW/AddressSpace.h" +#include "Core/HW/AudioInterface.h" +#include "Core/HW/CPU.h" +#include "Core/HW/DSP.h" +#include "Core/HW/DVD/DVDInterface.h" +#include "Core/HW/EXI/EXI.h" +#include "Core/HW/GPFifo.h" +#include "Core/HW/HSP/HSP.h" +#include "Core/HW/Memmap.h" +#include "Core/HW/MemoryInterface.h" +#include "Core/HW/ProcessorInterface.h" +#include "Core/HW/SI/SI.h" +#include "Core/HW/SystemTimers.h" +#include "Core/HW/VideoInterface.h" +#include "Core/HW/WII_IPC.h" +#include "Core/IOS/IOS.h" +#include "Core/IOS/Starlet/Starlet.h" +#include "Core/State.h" +#include "Core/System.h" + +namespace HW +{ +void Init(Core::System& system, const Sram* override_sram) +{ + system.GetCoreTiming().Init(); + system.GetSystemTimers().PreInit(); + + State::Init(system); + + // Init the whole Hardware + system.GetAudioInterface().Init(); + system.GetVideoInterface().Init(); + system.GetSerialInterface().Init(); + system.GetProcessorInterface().Init(); + system.GetExpansionInterface().Init(override_sram); // Needs to be initialized before Memory + system.GetHSP().Init(); + system.GetMemory().Init(); // Needs to be initialized before AddressSpace + AddressSpace::Init(); + system.GetMemoryInterface().Init(); + system.GetDSP().Init(Config::Get(Config::MAIN_DSP_HLE)); + system.GetDVDInterface().Init(); + system.GetGPFifo().Init(); + system.GetCPU().Init(Config::Get(Config::MAIN_CPU_CORE)); + system.GetSystemTimers().Init(); + + if (system.IsWii()) + { + system.GetWiiIPC().Init(); + if (Config::Get(Config::MAIN_WII_IOS_LLE)) + { + auto starlet = IOS::LLE::Starlet::Create(system); + std::string error; + if (starlet->Init(Config::Get(Config::MAIN_WII_LLE_DUMP_PATH), &error)) + { + system.SetStarlet(std::move(starlet)); + system.GetWiiIPC().HoldBroadwayForStarletBoot(); + } + else + { + PanicAlertFmtT("Could not start Wii IOS LLE: {0}", error); + Core::SetState(system, Core::State::Stopping); + return; + } + } + else + { + IOS::HLE::Init(system); // Depends on Memory + } + } + + system.GetMemory().InitMMIO(system); +} + +void Shutdown(Core::System& system) +{ + // IOS should always be shut down regardless of IsWii because it can be running in GC mode (MIOS). + if (system.GetStarlet()) + { + system.GetStarlet()->Shutdown(); + system.SetStarlet(nullptr); + } + if (system.GetIOS()) + IOS::HLE::Shutdown(system); // Depends on Memory + system.GetWiiIPC().Shutdown(); + + system.GetSystemTimers().Shutdown(); + system.GetCPU().Shutdown(); + system.GetDVDInterface().Shutdown(); + system.GetDSP().Shutdown(); + system.GetMemoryInterface().Shutdown(); + AddressSpace::Shutdown(); + system.GetMemory().Shutdown(); + system.GetHSP().Shutdown(); + system.GetExpansionInterface().Shutdown(); + system.GetSerialInterface().Shutdown(); + system.GetAudioInterface().Shutdown(); + + State::Shutdown(); + system.GetCoreTiming().Shutdown(); +} + +void DoState(Core::System& system, PointerWrap& p) +{ + system.GetMemory().DoState(p); + p.DoMarker("Memory"); + system.GetMemoryInterface().DoState(p); + p.DoMarker("MemoryInterface"); + system.GetVideoInterface().DoState(p); + p.DoMarker("VideoInterface"); + system.GetSerialInterface().DoState(p); + p.DoMarker("SerialInterface"); + system.GetProcessorInterface().DoState(p); + p.DoMarker("ProcessorInterface"); + system.GetDSP().DoState(p); + p.DoMarker("DSP"); + system.GetDVDInterface().DoState(p); + p.DoMarker("DVDInterface"); + system.GetGPFifo().DoState(p); + p.DoMarker("GPFifo"); + system.GetExpansionInterface().DoState(p); + p.DoMarker("ExpansionInterface"); + system.GetAudioInterface().DoState(p); + p.DoMarker("AudioInterface"); + system.GetHSP().DoState(p); + p.DoMarker("HSP"); + + if (system.IsWii()) + { + system.GetWiiIPC().DoState(p); + p.DoMarker("IOS"); + if (system.GetStarlet()) + { + system.GetStarlet()->DoState(p); + p.DoMarker("IOS::LLE"); + } + else if (system.GetIOS()) + { + system.GetIOS()->DoState(p); + p.DoMarker("IOS::HLE"); + } + } + + p.DoMarker("WIIHW"); +} +} // namespace HW diff --git a/Source/Core/Core/IOS/Starlet/ARMCore.cpp b/Source/Core/Core/IOS/Starlet/ARMCore.cpp index 9c0298614b..06dba48cdb 100644 --- a/Source/Core/Core/IOS/Starlet/ARMCore.cpp +++ b/Source/Core/Core/IOS/Starlet/ARMCore.cpp @@ -1,2627 +1,2660 @@ -// Copyright 2026 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "Core/IOS/Starlet/ARMCore.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "Common/ChunkFile.h" -#include "Common/PerformanceTrace.h" -#if defined(_M_X86_64) -// ARMCore owns ARMJitX64 by value through unique_ptr construction in this translation unit, so a -// private JIT layout change must rebuild this file as well as the emitter. -#include "Core/IOS/Starlet/ARMJitX64.h" -#endif - -namespace IOS::LLE -{ -namespace -{ -constexpr u32 CP15_CONTROL_BIG_ENDIAN = 1U << 7; -constexpr u32 CP15_CONTROL_MMU = 1U << 0; -constexpr u32 STARLET_EXCEPTION_VECTOR_BASE = 0xffff0000; - -constexpr u32 ModeValue(ARMCore::Mode mode) -{ - return static_cast(mode); -} -} // namespace - -ARMCore::ARMCore(ARMBus& bus, bool profile_jit_memory) - : m_bus(bus), m_profile_jit_memory(profile_jit_memory) -{ - Reset(); -} - -ARMCore::~ARMCore() = default; - -void ARMCore::SetJitEnabled(bool enabled) -{ -#if defined(_M_X86_64) - if (enabled && !m_jit) - m_jit = std::make_unique(*this); - m_jit_enabled = enabled; -#else - m_jit_enabled = false; -#endif -} - -void ARMCore::Reset(u32 reset_vector) -{ - m_trace_generation = Common::PerformanceTrace::NewGeneration(); - m_registers.fill(0); - m_user_r8_r14.fill(0); - m_fiq_r8_r14.fill(0); - m_irq_r13_r14.fill(0); - m_svc_r13_r14.fill(0); - m_abt_r13_r14.fill(0); - m_und_r13_r14.fill(0); - m_spsr_fiq = 0; - m_spsr_irq = 0; - m_spsr_svc = 0; - m_spsr_abt = 0; - m_spsr_und = 0; - m_cp15 = {}; - InvalidateTLB(); - InvalidateInstructionCache(); - if (m_big_endian) - m_cp15.control |= CP15_CONTROL_BIG_ENDIAN; - - m_cpsr = ModeValue(Mode::Supervisor) | CPSR_I | CPSR_F; - m_registers[15] = reset_vector & ~3U; - m_irq_line = false; - m_fiq_line = false; - m_waiting_for_interrupt = false; - m_waiting_for_memory_poll = false; - m_yield_requested = false; - m_slice_fast_forward_requested = false; - m_memory_poll_address = 0; - m_pc_written = false; - m_instruction_address = m_registers[15]; - m_last_undefined_instruction = 0; - m_executed_instructions = 0; - m_memory_poll_entry_count = 0; - m_jit_fallback_instruction_count = 0; - m_jit_fallback_samples.clear(); - m_jit_fallback_interval_count = 0; - m_jit_fallback_interval_samples.clear(); - m_next_profile_instruction = PROFILE_SAMPLE_INTERVAL; - m_pc_samples.clear(); -} - -u32 ARMCore::GetRegister(size_t index) const -{ - assert(index < m_registers.size()); - return m_registers[index]; -} - -void ARMCore::SetRegister(size_t index, u32 value) -{ - assert(index < m_registers.size()); - if (index == 15) - { - m_registers[index] = value & ((m_cpsr & CPSR_T) ? ~1U : ~3U); - return; - } - m_registers[index] = value; -} - -bool ARMCore::IsValidMode(u32 mode) -{ - switch (static_cast(mode)) - { - case Mode::User: - case Mode::FIQ: - case Mode::IRQ: - case Mode::Supervisor: - case Mode::Abort: - case Mode::Undefined: - case Mode::System: - return true; - default: - return false; - } -} - -ARMCore::Mode ARMCore::GetMode() const -{ - return static_cast(m_cpsr & CPSR_MODE_MASK); -} - -void ARMCore::SetCPSR(u32 value) -{ - const u32 new_mode_value = value & CPSR_MODE_MASK; - if (!IsValidMode(new_mode_value)) - value = (value & ~CPSR_MODE_MASK) | ModeValue(GetMode()); - - const Mode new_mode = static_cast(value & CPSR_MODE_MASK); - if (new_mode != GetMode()) - SwitchMode(new_mode); - m_cpsr = value; -} - -void ARMCore::SetBigEndian(bool big_endian) -{ - m_big_endian = big_endian; - if (big_endian) - m_cp15.control |= CP15_CONTROL_BIG_ENDIAN; - else - m_cp15.control &= ~CP15_CONTROL_BIG_ENDIAN; - InvalidateTLB(); - InvalidateInstructionCache(); -} - -s32 ARMCore::SignExtend(u32 value, unsigned bits) -{ - const u32 sign = 1U << (bits - 1); - return static_cast((value ^ sign) - sign); -} - -ARMCore::AddResult ARMCore::AddWithCarry(u32 lhs, u32 rhs, bool carry) -{ - const u64 unsigned_sum = static_cast(lhs) + rhs + static_cast(carry); - const s64 signed_sum = static_cast(static_cast(lhs)) + - static_cast(static_cast(rhs)) + static_cast(carry); - return { - .value = static_cast(unsigned_sum), - .carry = (unsigned_sum >> 32) != 0, - .overflow = signed_sum > std::numeric_limits::max() || - signed_sum < std::numeric_limits::min(), - }; -} - -u32 ARMCore::ReadPhysical32(u32 address) const -{ - const u32 value = m_bus.Read32(address); - if (m_big_endian) - return value; - return (value >> 24) | ((value >> 8) & 0x0000ff00) | ((value << 8) & 0x00ff0000) | (value << 24); -} - -u32 ARMCore::TranslateVirtualAddress(u32 address) const -{ - if ((m_cp15.control & CP15_CONTROL_MMU) == 0) - return address; - - // ARM926 FCSE remaps the low 32 MiB through the process identifier before - // translation. - const u32 modified_address = - address < 0x02000000 ? address | (m_cp15.process_id & 0xfe000000) : address; - const u32 virtual_page = modified_address >> 10; - TLBEntry& entry = m_tlb[GetTLBIndex(virtual_page)]; - if (entry.generation == m_tlb_generation && entry.virtual_page == virtual_page) - return entry.physical_page | (modified_address & 0x3ff); - - const auto cache_translation = [&](u32 physical_address) { - entry.virtual_page = virtual_page; - entry.physical_page = physical_address & ~0x3ffU; - entry.generation = m_tlb_generation; - return physical_address; - }; - - return cache_translation(WalkPageTables(modified_address, nullptr, nullptr, nullptr, nullptr)); -} - -u32 ARMCore::TranslateVirtualAddressForJit(u32 address, const u8** first_descriptor, - u32* first_descriptor_value, - const u8** second_descriptor, - u32* second_descriptor_value) const -{ - *first_descriptor = nullptr; - *first_descriptor_value = 0; - *second_descriptor = nullptr; - *second_descriptor_value = 0; - if ((m_cp15.control & CP15_CONTROL_MMU) == 0) - return address; - - const u32 modified_address = - address < 0x02000000 ? address | (m_cp15.process_id & 0xfe000000) : address; - const u32 physical_address = - WalkPageTables(modified_address, first_descriptor, first_descriptor_value, second_descriptor, - second_descriptor_value); - - // Keep the interpreter and generated data-access path coherent with the translation established - // for this native code block. - const u32 virtual_page = modified_address >> 10; - TLBEntry& entry = m_tlb[GetTLBIndex(virtual_page)]; - entry.virtual_page = virtual_page; - entry.physical_page = physical_address & ~0x3ffU; - entry.generation = m_tlb_generation; - return physical_address; -} - -u32 ARMCore::WalkPageTables(u32 modified_address, const u8** first_descriptor, - u32* first_descriptor_value, const u8** second_descriptor, - u32* second_descriptor_value) const -{ - const auto read_descriptor = [&](u32 physical_address, const u8** host_pointer, u32* host_value) { - // Only native block validation needs a persistent descriptor pointer. Ordinary TLB - // misses need the bus value alone; do not resolve the same physical memory twice. - const u8* const pointer = (host_pointer || host_value) ? - m_bus.GetDirectMemoryPointer(physical_address, sizeof(u32)) : - nullptr; - if (host_pointer) - *host_pointer = pointer; - if (host_value) - { - *host_value = 0; - if (pointer) - std::memcpy(host_value, pointer, sizeof(u32)); - } - return ReadPhysical32(physical_address); - }; - - const u32 first_level_address = - (m_cp15.translation_table_base & 0xffffc000) | ((modified_address >> 18) & 0x3ffc); - const u32 first_level = - read_descriptor(first_level_address, first_descriptor, first_descriptor_value); - switch (first_level & 3) - { - case 1: // Coarse second-level table. - { - const u32 second_level_address = - (first_level & 0xfffffc00) | ((modified_address >> 10) & 0x3fc); - const u32 second_level = - read_descriptor(second_level_address, second_descriptor, second_descriptor_value); - switch (second_level & 3) - { - case 1: // 64 KiB large page. - return (second_level & 0xffff0000) | (modified_address & 0xffff); - case 2: - case 3: // 4 KiB small page; extended small pages share this mapping shape. - return (second_level & 0xfffff000) | (modified_address & 0xfff); - default: - return modified_address; - } - } - case 2: // 1 MiB section. - return (first_level & 0xfff00000) | (modified_address & 0x000fffff); - case 3: // Fine second-level table. - { - const u32 second_level_address = (first_level & 0xfffff000) | ((modified_address >> 8) & 0xffc); - const u32 second_level = - read_descriptor(second_level_address, second_descriptor, second_descriptor_value); - switch (second_level & 3) - { - case 1: - return (second_level & 0xffff0000) | (modified_address & 0xffff); - case 2: - return (second_level & 0xfffff000) | (modified_address & 0xfff); - case 3: // 1 KiB tiny page. - return (second_level & 0xfffffc00) | (modified_address & 0x3ff); - default: - return modified_address; - } - } - default: - // Fault entry. Abort delivery and access-permission checks are - // intentionally introduced after basic page-table translation; identity - // fallback keeps diagnostics observable meanwhile. Cache that provisional translation just - // like a mapped page: real software must invalidate the TLB after changing a page-table entry, - // and without this cache the JIT would side-exit forever on every identity access. - return modified_address; - } -} - -void ARMCore::InvalidateTLB() -{ - if (++m_tlb_generation == 0) - { - for (TLBEntry& entry : m_tlb) - entry.generation = 0; - m_tlb_generation = 1; - } -#if defined(_M_X86_64) - if (m_jit) - m_jit->InvalidateTranslationContext(); -#endif -} - -u32 ARMCore::FetchARMInstruction(u32 address) -{ - const u32 physical_address = TranslateVirtualAddress(address); - const u32 physical_word = physical_address & ~3U; - InstructionCacheEntry& entry = - m_instruction_cache[(physical_word >> 2) & (INSTRUCTION_CACHE_ENTRY_COUNT - 1)]; - if (entry.generation != m_instruction_cache_generation || entry.physical_word != physical_word) - { - entry.physical_word = physical_word; - entry.raw_value = m_bus.Read32(physical_word); - entry.generation = m_instruction_cache_generation; - } - - const u32 value = entry.raw_value; - if (m_big_endian) - return value; - return (value >> 24) | ((value >> 8) & 0x0000ff00) | ((value << 8) & 0x00ff0000) | (value << 24); -} - -u16 ARMCore::FetchThumbInstruction(u32 address) -{ - const u32 physical_address = TranslateVirtualAddress(address); - const u32 physical_word = physical_address & ~3U; - InstructionCacheEntry& entry = - m_instruction_cache[(physical_word >> 2) & (INSTRUCTION_CACHE_ENTRY_COUNT - 1)]; - if (entry.generation != m_instruction_cache_generation || entry.physical_word != physical_word) - { - entry.physical_word = physical_word; - entry.raw_value = m_bus.Read32(physical_word); - entry.generation = m_instruction_cache_generation; - } - - u16 value = static_cast((physical_address & 2) ? entry.raw_value : entry.raw_value >> 16); - if (!m_big_endian) - value = static_cast((value >> 8) | (value << 8)); - return value; -} - -void ARMCore::InvalidateInstructionCache() -{ - if (++m_instruction_cache_generation == 0) - { - for (InstructionCacheEntry& entry : m_instruction_cache) - entry.generation = 0; - m_instruction_cache_generation = 1; - } -#if defined(_M_X86_64) - if (m_jit) - m_jit->Clear(); -#endif -} - -u8 ARMCore::ReadByte(u32 address) const -{ - return m_bus.Read8(TranslateVirtualAddress(address)); -} - -void ARMCore::WriteByte(u32 address, u8 value) -{ - m_bus.Write8(TranslateVirtualAddress(address), value); -} - -u16 ARMCore::Read16(u32 address) const -{ - const u16 value = - (address & 1) == 0 ? - m_bus.Read16(TranslateVirtualAddress(address)) : - static_cast((static_cast(m_bus.Read8(TranslateVirtualAddress(address))) << 8) | - m_bus.Read8(TranslateVirtualAddress(address + 1))); - return m_big_endian ? value : static_cast((value >> 8) | (value << 8)); -} - -u32 ARMCore::Read32(u32 address) const -{ - const u32 value = - (address & 3) == 0 ? - m_bus.Read32(TranslateVirtualAddress(address)) : - (static_cast(m_bus.Read8(TranslateVirtualAddress(address))) << 24) | - (static_cast(m_bus.Read8(TranslateVirtualAddress(address + 1))) << 16) | - (static_cast(m_bus.Read8(TranslateVirtualAddress(address + 2))) << 8) | - m_bus.Read8(TranslateVirtualAddress(address + 3)); - if (m_big_endian) - return value; - return (value >> 24) | ((value >> 8) & 0x0000ff00) | ((value << 8) & 0x00ff0000) | (value << 24); -} - -void ARMCore::Write16(u32 address, u16 value) -{ - if (!m_big_endian) - value = static_cast((value >> 8) | (value << 8)); - if ((address & 1) == 0) - { - m_bus.Write16(TranslateVirtualAddress(address), value); - return; - } - m_bus.Write8(TranslateVirtualAddress(address), static_cast(value >> 8)); - m_bus.Write8(TranslateVirtualAddress(address + 1), static_cast(value)); -} - -void ARMCore::Write32(u32 address, u32 value) -{ - if (!m_big_endian) - { - value = - (value >> 24) | ((value >> 8) & 0x0000ff00) | ((value << 8) & 0x00ff0000) | (value << 24); - } - if ((address & 3) == 0) - { - m_bus.Write32(TranslateVirtualAddress(address), value); - return; - } - m_bus.Write8(TranslateVirtualAddress(address), static_cast(value >> 24)); - m_bus.Write8(TranslateVirtualAddress(address + 1), static_cast(value >> 16)); - m_bus.Write8(TranslateVirtualAddress(address + 2), static_cast(value >> 8)); - m_bus.Write8(TranslateVirtualAddress(address + 3), static_cast(value)); -} - -u32 ARMCore::ReadRegisterOperand(u32 index) const -{ - assert(index < m_registers.size()); - if (index != 15) - return m_registers[index]; - return m_instruction_address + ((m_cpsr & CPSR_T) ? 4 : 8); -} - -u32 ARMCore::ReadRegisterForStore(u32 index) const -{ - assert(index < m_registers.size()); - if (index != 15) - return m_registers[index]; - // ARM9 exposes an implementation-defined PC value for stores. ARM926 uses - // instruction + 12. - return m_instruction_address + ((m_cpsr & CPSR_T) ? 4 : 12); -} - -void ARMCore::WritePC(u32 value, bool exchange) -{ - if (exchange) - { - if (value & 1) - m_cpsr |= CPSR_T; - else - m_cpsr &= ~CPSR_T; - } - - m_registers[15] = value & ((m_cpsr & CPSR_T) ? ~1U : ~3U); - m_pc_written = true; -} - -void ARMCore::SetNZ(u32 value) -{ - m_cpsr &= ~(CPSR_N | CPSR_Z); - if (value & 0x80000000) - m_cpsr |= CPSR_N; - if (value == 0) - m_cpsr |= CPSR_Z; -} - -bool ARMCore::ConditionPassed(u32 condition) const -{ - const bool n = (m_cpsr & CPSR_N) != 0; - const bool z = (m_cpsr & CPSR_Z) != 0; - const bool c = (m_cpsr & CPSR_C) != 0; - const bool v = (m_cpsr & CPSR_V) != 0; - switch (condition) - { - case 0x0: - return z; - case 0x1: - return !z; - case 0x2: - return c; - case 0x3: - return !c; - case 0x4: - return n; - case 0x5: - return !n; - case 0x6: - return v; - case 0x7: - return !v; - case 0x8: - return c && !z; - case 0x9: - return !c || z; - case 0xa: - return n == v; - case 0xb: - return n != v; - case 0xc: - return !z && n == v; - case 0xd: - return z || n != v; - case 0xe: - return true; - default: - return false; - } -} - -ARMCore::ShiftResult ARMCore::ShiftImmediate(u32 value, u32 type, u32 amount, bool old_carry) const -{ - switch (type) - { - case 0: // LSL - if (amount == 0) - return {value, old_carry}; - return {value << amount, ((value >> (32 - amount)) & 1) != 0}; - case 1: // LSR, immediate zero means 32 - if (amount == 0) - return {0, (value >> 31) != 0}; - return {value >> amount, ((value >> (amount - 1)) & 1) != 0}; - case 2: // ASR, immediate zero means 32 - if (amount == 0) - return {(value & 0x80000000) ? 0xffffffffU : 0, (value >> 31) != 0}; - return {static_cast(static_cast(value) >> amount), - ((value >> (amount - 1)) & 1) != 0}; - case 3: // ROR, immediate zero is RRX - if (amount == 0) - return {(static_cast(old_carry) << 31) | (value >> 1), (value & 1) != 0}; - return {std::rotr(value, static_cast(amount)), ((value >> (amount - 1)) & 1) != 0}; - default: - return {value, old_carry}; - } -} - -ARMCore::ShiftResult ARMCore::ShiftRegister(u32 value, u32 type, u32 amount, bool old_carry) const -{ - amount &= 0xff; - if (amount == 0) - return {value, old_carry}; - - switch (type) - { - case 0: // LSL - if (amount < 32) - return {value << amount, ((value >> (32 - amount)) & 1) != 0}; - return {0, amount == 32 && (value & 1) != 0}; - case 1: // LSR - if (amount < 32) - return {value >> amount, ((value >> (amount - 1)) & 1) != 0}; - return {0, amount == 32 && (value >> 31) != 0}; - case 2: // ASR - if (amount < 32) - return {static_cast(static_cast(value) >> amount), - ((value >> (amount - 1)) & 1) != 0}; - return {(value & 0x80000000) ? 0xffffffffU : 0, (value >> 31) != 0}; - case 3: // ROR - { - const u32 rotate = amount & 31; - if (rotate == 0) - return {value, (value >> 31) != 0}; - return {std::rotr(value, static_cast(rotate)), ((value >> (rotate - 1)) & 1) != 0}; - } - default: - return {value, old_carry}; - } -} - -ARMCore::ShiftResult ARMCore::DecodeOperand2(u32 instruction) const -{ - const bool old_carry = (m_cpsr & CPSR_C) != 0; - if (instruction & (1U << 25)) - { - const u32 rotate = ((instruction >> 8) & 0xf) * 2; - const u32 value = std::rotr(instruction & 0xff, static_cast(rotate)); - return {value, rotate == 0 ? old_carry : (value >> 31) != 0}; - } - - const u32 rm_value = ReadRegisterOperand(instruction & 0xf); - const u32 type = (instruction >> 5) & 3; - if (instruction & (1U << 4)) - { - const u32 amount = ReadRegisterOperand((instruction >> 8) & 0xf) & 0xff; - return ShiftRegister(rm_value, type, amount, old_carry); - } - return ShiftImmediate(rm_value, type, (instruction >> 7) & 0x1f, old_carry); -} - -int ARMCore::Step() -{ - return StepInternal(true); -} - -bool ARMCore::HasUnmaskedInterrupt() const -{ - return (m_irq_line && (m_cpsr & CPSR_I) == 0) || (m_fiq_line && (m_cpsr & CPSR_F) == 0); -} - -bool ARMCore::TryEnterThumbMemoryPoll(u16 branch_instruction) -{ - // IOS uses a tiny Thumb idle loop which repeatedly loads a shared word, compares it with zero, - // and branches back while no scheduler work is pending. Replaying that side-effect-free loop at - // the full 243 MHz Starlet clock dominates the host CPU. Recognize the general instruction shape - // and fast-forward only physical RAM/SRAM polls; MMIO polling remains fully interpreted. - if ((branch_instruction & 0xff00) != 0xd000 || !m_pc_written || HasUnmaskedInterrupt()) - return false; - - const u32 loop_address = m_registers[15]; - if (loop_address + 4 != m_instruction_address) - return false; - - const u16 load = FetchThumbInstruction(loop_address); - const u16 compare = FetchThumbInstruction(loop_address + 2); - if ((load & 0xf800) != 0x6800 || (compare & 0xf800) != 0x2800 || (compare & 0xff) != 0) - { - return false; - } - - const u32 destination_register = load & 7; - if (static_cast((compare >> 8) & 7) != destination_register || - m_registers[destination_register] != 0) - return false; - - const u32 base_register = (load >> 3) & 7; - const u32 address = m_registers[base_register] + (((load >> 6) & 0x1f) << 2); - if ((address & 3) != 0) - return false; - const u32 physical_address = TranslateVirtualAddress(address); - if (!m_bus.IsIdlePollAddress(physical_address, sizeof(u32))) - return false; - - m_memory_poll_address = address; - m_waiting_for_memory_poll = true; - ++m_memory_poll_entry_count; - return true; -} - -bool ARMCore::TryEnterARMSliceStablePoll(u32 branch_instruction) -{ - // IOS's early timer delay has this ARM shape (the middle literal load initializes the base only - // on entry): LDR value,[base]; B compare; literal/base setup; CMP value,target; Bcc loop. The - // timer cannot advance again until the bus receives AdvanceCycles at the slice boundary, making - // every remaining iteration in this slice observationally redundant. - const u32 condition = branch_instruction >> 28; - if ((branch_instruction & 0x0e000000) != 0x0a000000 || condition >= 0xe || !m_pc_written || - HasUnmaskedInterrupt()) - { - return false; - } - - const u32 loop_address = m_registers[15]; - if (loop_address + 16 != m_instruction_address) - return false; - - const u32 load = FetchARMInstruction(loop_address); - const u32 skip = FetchARMInstruction(loop_address + 4); - const u32 compare = FetchARMInstruction(loop_address + 12); - if ((load & 0xfff00000) != 0xe5900000 || (skip & 0xff000000) != 0xea000000 || - (compare & 0xfff00ff0) != 0xe1500000) - { - return false; - } - - const s32 skip_offset = SignExtend((skip & 0x00ffffff) << 2, 26); - if (loop_address + 12 != loop_address + 12 + static_cast(skip_offset)) - return false; - - const u32 load_destination = (load >> 12) & 0xf; - if (((compare >> 16) & 0xf) != load_destination) - return false; - - const u32 base = ReadRegisterOperand((load >> 16) & 0xf); - const u32 poll_address = base + (load & 0xfff); - const u32 physical_address = TranslateVirtualAddress(poll_address); - if (!m_bus.IsSliceStablePollAddress(physical_address, sizeof(u32))) - return false; - - m_memory_poll_address = poll_address; - m_waiting_for_memory_poll = true; - ++m_memory_poll_entry_count; - return true; -} - -int ARMCore::StepInternal(bool advance_bus) -{ - // ARM926's wait-for-interrupt operation stops instruction issue while clocks - // used by timers and peripherals continue running. A pending IRQ/FIQ wakes - // the core even when the corresponding CPSR mask prevents immediate exception - // entry. - if (m_waiting_for_memory_poll) - { - const u32 physical_poll_address = TranslateVirtualAddress(m_memory_poll_address); - if (m_bus.IsSliceStablePollAddress(physical_poll_address, sizeof(u32))) - { - // Step() advances the device clock itself, so only RunCycles can collapse the rest of a - // scheduler slice. Resume normally here. - m_waiting_for_memory_poll = false; - } - else if (!HasUnmaskedInterrupt() && Read32(m_memory_poll_address) == 0) - { - if (advance_bus) - m_bus.AdvanceCycles(1); - return 1; - } - else - { - m_waiting_for_memory_poll = false; - } - } - if (m_waiting_for_interrupt && !m_irq_line && !m_fiq_line) - { - if (advance_bus) - m_bus.AdvanceCycles(1); - return 1; - } - if (m_irq_line || m_fiq_line) - m_waiting_for_interrupt = false; - - // Starlet boots from and vectors through the high SRAM window independently - // of the generic ARM926 control-register V bit. - constexpr u32 vector_base = STARLET_EXCEPTION_VECTOR_BASE; - if (m_fiq_line && !(m_cpsr & CPSR_F)) - { - EnterException(Mode::FIQ, vector_base + 0x1c, m_registers[15] + 4, true); - if (advance_bus) - m_bus.AdvanceCycles(3); - return 3; - } - if (m_irq_line && !(m_cpsr & CPSR_I)) - { - EnterException(Mode::IRQ, vector_base + 0x18, m_registers[15] + 4); - if (advance_bus) - m_bus.AdvanceCycles(3); - return 3; - } - - m_instruction_address = m_registers[15]; - m_pc_written = false; - if (m_cpsr & CPSR_T) - { - const u16 instruction = FetchThumbInstruction(m_instruction_address); - ExecuteThumb(instruction); - TryEnterThumbMemoryPoll(instruction); - } - else - { - const u32 instruction = FetchARMInstruction(m_instruction_address); - - // BLX immediate uses the otherwise-reserved condition value 0xf. - if ((instruction & 0xfe000000) == 0xfa000000) - { - const u32 h = (instruction >> 24) & 1; - const s32 offset = SignExtend((instruction & 0x00ffffff) << 2, 26) + static_cast(h << 1); - m_registers[14] = m_instruction_address + 4; - m_cpsr |= CPSR_T; - WritePC(m_instruction_address + 8 + static_cast(offset)); - } - else if (ConditionPassed(instruction >> 28)) - { - ExecuteARM(instruction); - } - TryEnterARMSliceStablePoll(instruction); - } - - if (!m_pc_written) - m_registers[15] = m_instruction_address + ((m_cpsr & CPSR_T) ? 2 : 4); - ++m_executed_instructions; - if (advance_bus) - m_bus.AdvanceCycles(1); - return 1; -} - -u64 ARMCore::Run(u64 instruction_count) -{ - u64 cycles = 0; - while (instruction_count-- != 0) - cycles += static_cast(Step()); - return cycles; -} - -u64 ARMCore::RunCycles(u64 cycle_budget) -{ - const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::ARM); - u64 cycles = 0; - while (cycles < cycle_budget && !m_yield_requested) - { - if (m_waiting_for_memory_poll) - { - const u32 physical_poll_address = TranslateVirtualAddress(m_memory_poll_address); - if (!HasUnmaskedInterrupt() && - m_bus.IsSliceStablePollAddress(physical_poll_address, sizeof(u32))) - { - // This MMIO value cannot change before AdvanceCycles below. Consume the unused slice and - // retry the guest loop against the newly advanced device state next time. - m_waiting_for_memory_poll = false; - cycles = cycle_budget; - break; - } - if (HasUnmaskedInterrupt() || Read32(m_memory_poll_address) != 0) - { - m_waiting_for_memory_poll = false; - } - else - { - cycles = cycle_budget; - break; - } - } - if (m_waiting_for_interrupt && !m_irq_line && !m_fiq_line) - { - cycles = cycle_budget; - break; - } -#if defined(_M_X86_64) - if (m_jit_enabled && m_jit && !HasUnmaskedInterrupt()) - { - const u32 executed = m_jit->Run(cycle_budget - cycles); - if (executed != 0) - { - cycles += executed; - SampleExecutionPC(); - if (m_slice_fast_forward_requested) - { - m_slice_fast_forward_requested = false; - cycles = cycle_budget; - break; - } - continue; - } - } -#endif - cycles += static_cast(StepInternal(false)); - SampleExecutionPC(); - if (m_slice_fast_forward_requested) - { - m_slice_fast_forward_requested = false; - cycles = cycle_budget; - break; - } - } - // Starlet interrupt lines are sampled by the scheduler at slice boundaries. - // Updating timers and peripherals once here is therefore architecturally - // equivalent for interrupt delivery and avoids running the complete device - // clock path after every interpreted instruction. - m_bus.AdvanceCycles(cycles); - m_yield_requested = false; - return cycles; -} - -void ARMCore::SampleExecutionPC() -{ - if (m_executed_instructions < m_next_profile_instruction) - return; - - const u32 key = m_registers[15] | ((m_cpsr & CPSR_T) != 0 ? 1U : 0U); - ++m_pc_samples[key]; - m_next_profile_instruction += PROFILE_SAMPLE_INTERVAL; -} - -std::vector ARMCore::GetHotPCSamples(size_t maximum_count) -{ - std::vector> sorted(m_pc_samples.begin(), m_pc_samples.end()); - std::ranges::sort(sorted, {}, [](const auto& entry) { return entry.second; }); - if (sorted.size() > maximum_count) - sorted.resize(maximum_count); - - std::vector result; - result.reserve(sorted.size()); - for (auto it = sorted.rbegin(); it != sorted.rend(); ++it) - { - const bool thumb = (it->first & 1) != 0; - const u32 address = it->first & ~1U; - result.push_back( - {.address = address, - .instruction = thumb ? FetchThumbInstruction(address) : FetchARMInstruction(address), - .samples = it->second, - .thumb = thumb}); - } - return result; -} - -u64 ARMCore::GetJitExecutedInstructions() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetExecutedInstructions() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitNativeExecutedInstructions() const -{ -#if defined(_M_X86_64) - // Kept out of line so changes to the x64 JIT's diagnostic layout rebuild this translation unit. - return m_jit ? m_jit->GetNativeExecutedInstructions() : 0; -#else - return 0; -#endif -} - -size_t ARMCore::GetJitCompiledBlockCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetCompiledBlockCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitLifetimeCompiledBlockCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetLifetimeCompiledBlockCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitBlockLookupCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetBlockLookupCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitBlockCacheHitCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetBlockCacheHitCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitDispatchSlowCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetDispatchSlowCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitDispatchStaleGenerationCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetDispatchStaleGenerationCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitDispatchPhysicalAliasCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetDispatchPhysicalAliasCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitDispatchCollisionCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetDispatchCollisionCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitDispatchKeyMissCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetDispatchKeyMissCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitDispatchPageMissCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetDispatchPageMissCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitDispatchEmptyEntryCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetDispatchEmptyEntryCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitDispatchGeneratedKeyMismatchCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetDispatchGeneratedKeyMismatchCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitDispatchGeneratedSetMismatchCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetDispatchGeneratedSetMismatchCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitDispatchKeyMissWithExactMatchCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetDispatchKeyMissWithExactMatchCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitDispatchKeyMissWithPhysicalAliasCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetDispatchKeyMissWithPhysicalAliasCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitDispatchKeyMissWithSamePhysicalPageCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetDispatchKeyMissWithSamePhysicalPageCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitDispatchKeyMissWithEmptySlotCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetDispatchKeyMissWithEmptySlotCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitDispatchKeyMissWithFullSetCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetDispatchKeyMissWithFullSetCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitDispatchKeyMissWithStaleTranslationCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetDispatchKeyMissWithStaleTranslationCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitFastEntryEmptyInsertCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetFastEntryEmptyInsertCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitFastEntryReplacementCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetFastEntryReplacementCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitInstructionCacheClearCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetInstructionCacheClearCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitCodeSpaceClearCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetCodeSpaceClearCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitDiscardedBlockCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetDiscardedBlockCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitAddressTranslationCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetAddressTranslationCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitSlowReadCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetSlowReadCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitSlowWriteCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetSlowWriteCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitSlowRAMAccessCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetSlowRAMAccessCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitSlowSRAMAccessCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetSlowSRAMAccessCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitSlowMMIOAccessCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetSlowMMIOAccessCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitSlowOtherAccessCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetSlowOtherAccessCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitSlowSRAMLowAccessCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetSlowSRAMLowAccessCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitSlowSRAMHighAccessCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetSlowSRAMHighAccessCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitSlowSRAMReadAccessCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetSlowSRAMReadAccessCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitSlowSRAMWriteAccessCount() const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetSlowSRAMWriteAccessCount() : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitSlowSRAMPageAccessCount(size_t page) const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetSlowSRAMPageAccessCount(page) : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitSlowSRAMPageReadAccessCount(size_t page) const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetSlowSRAMPageReadAccessCount(page) : 0; -#else - return 0; -#endif -} - -u64 ARMCore::GetJitSlowSRAMPageWriteAccessCount(size_t page) const -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->GetSlowSRAMPageWriteAccessCount(page) : 0; -#else - return 0; -#endif -} - -std::vector> ARMCore::TakeHotJitSlowMemorySamples(size_t maximum_count) -{ -#if defined(_M_X86_64) - return m_jit ? m_jit->TakeHotSlowMemorySamples(maximum_count) : - std::vector>{}; -#else - return {}; -#endif -} - -void ARMCore::RecordJitFallback(u32 address, bool thumb) -{ - ++m_jit_fallback_instruction_count; - ++m_jit_fallback_interval_count; - // Sampling keeps profiling effectively free even when hundreds of millions of instructions - // use an interpreter side exit. - if ((m_jit_fallback_instruction_count & 0xfff) == 0) - { - ++m_jit_fallback_samples[address | (thumb ? 1U : 0U)]; - ++m_jit_fallback_interval_samples[address | (thumb ? 1U : 0U)]; - } -} - -std::vector ARMCore::GetHotJitFallbackSamples(size_t maximum_count) -{ - std::vector> sorted(m_jit_fallback_samples.begin(), - m_jit_fallback_samples.end()); - std::ranges::sort(sorted, {}, [](const auto& entry) { return entry.second; }); - if (sorted.size() > maximum_count) - sorted.erase(sorted.begin(), sorted.end() - maximum_count); - - std::vector result; - result.reserve(sorted.size()); - for (auto it = sorted.rbegin(); it != sorted.rend(); ++it) - { - const bool thumb = (it->first & 1) != 0; - const u32 address = it->first & ~1U; - result.push_back( - {.address = address, - .instruction = thumb ? FetchThumbInstruction(address) : FetchARMInstruction(address), - .samples = it->second, - .thumb = thumb}); - } - return result; -} - -ARMCore::HotPCSample ARMCore::GetCurrentPCSample() -{ - const bool thumb = (m_cpsr & CPSR_T) != 0; - return GetInstructionSample(m_registers[15], thumb); -} - -ARMCore::HotPCSample ARMCore::GetInstructionSample(u32 address, bool thumb) -{ - return {.address = address, - .instruction = thumb ? FetchThumbInstruction(address) : FetchARMInstruction(address), - .samples = 1, - .thumb = thumb}; -} - -u64 ARMCore::TakeJitFallbackIntervalCount() -{ - return std::exchange(m_jit_fallback_interval_count, 0); -} - -std::vector ARMCore::TakeHotJitFallbackIntervalSamples(size_t maximum_count) -{ - std::vector> sorted(m_jit_fallback_interval_samples.begin(), - m_jit_fallback_interval_samples.end()); - m_jit_fallback_interval_samples.clear(); - std::ranges::sort(sorted, {}, [](const auto& entry) { return entry.second; }); - if (sorted.size() > maximum_count) - sorted.erase(sorted.begin(), sorted.end() - maximum_count); - - std::vector result; - result.reserve(sorted.size()); - for (auto it = sorted.rbegin(); it != sorted.rend(); ++it) - { - const bool thumb = (it->first & 1) != 0; - const u32 address = it->first & ~1U; - result.push_back( - {.address = address, - .instruction = thumb ? FetchThumbInstruction(address) : FetchARMInstruction(address), - .samples = it->second, - .thumb = thumb}); - } - return result; -} - -void ARMCore::DoState(PointerWrap& p) -{ - if (p.IsReadMode()) - m_trace_generation = Common::PerformanceTrace::NewGeneration(); - p.DoArray(m_registers); - p.Do(m_cpsr); - p.DoArray(m_user_r8_r14); - p.DoArray(m_fiq_r8_r14); - p.DoArray(m_irq_r13_r14); - p.DoArray(m_svc_r13_r14); - p.DoArray(m_abt_r13_r14); - p.DoArray(m_und_r13_r14); - p.Do(m_spsr_fiq); - p.Do(m_spsr_irq); - p.Do(m_spsr_svc); - p.Do(m_spsr_abt); - p.Do(m_spsr_und); - p.Do(m_cp15); - p.Do(m_big_endian); - p.Do(m_irq_line); - p.Do(m_fiq_line); - p.Do(m_waiting_for_interrupt); - p.Do(m_waiting_for_memory_poll); - p.Do(m_yield_requested); - p.Do(m_slice_fast_forward_requested); - p.Do(m_memory_poll_address); - p.Do(m_pc_written); - p.Do(m_instruction_address); - p.Do(m_last_undefined_instruction); - p.Do(m_executed_instructions); - // The cache is derived entirely from architectural CP15 state and guest page - // tables. - InvalidateTLB(); - InvalidateInstructionCache(); -} - -void ARMCore::ExecuteARM(u32 instruction) -{ - // Branch/exchange and count-leading-zero are ARMv5 additions encoded in the - // data-processing space, so they must be recognized before the generic - // decoder. - if ((instruction & 0x0ffffff0) == 0x012fff10) // BX - { - WritePC(ReadRegisterOperand(instruction & 0xf), true); - return; - } - if ((instruction & 0x0ffffff0) == 0x012fff30) // BLX register - { - const u32 target = ReadRegisterOperand(instruction & 0xf); - m_registers[14] = m_instruction_address + 4; - WritePC(target, true); - return; - } - if ((instruction & 0x0fff0ff0) == 0x016f0f10) // CLZ - { - const u32 rd = (instruction >> 12) & 0xf; - const u32 value = ReadRegisterOperand(instruction & 0xf); - m_registers[rd] = value == 0 ? 32 : std::countl_zero(value); - return; - } - - if ((instruction & 0x0f8000f0) == 0x00800090) - { - ExecuteMultiplyLong(instruction); - return; - } - if ((instruction & 0x0fc000f0) == 0x00000090) - { - ExecuteMultiply(instruction); - return; - } - - // SWP/SWPB. - if ((instruction & 0x0fb00ff0) == 0x01000090) - { - const bool byte = (instruction & (1U << 22)) != 0; - const u32 rn = (instruction >> 16) & 0xf; - const u32 rd = (instruction >> 12) & 0xf; - const u32 rm = instruction & 0xf; - const u32 address = ReadRegisterOperand(rn); - if (byte) - { - const u8 old = ReadByte(address); - WriteByte(address, static_cast(ReadRegisterOperand(rm))); - m_registers[rd] = old; - } - else - { - const u32 old = Read32(address & ~3U); - Write32(address & ~3U, ReadRegisterOperand(rm)); - m_registers[rd] = old; - } - return; - } - - const bool is_mrs = (instruction & 0x0fbf0fff) == 0x010f0000; - const bool is_msr_register = (instruction & 0x0fb0fff0) == 0x0120f000; - const bool is_msr_immediate = (instruction & 0x0fb0f000) == 0x0320f000; - if (is_mrs || is_msr_register || is_msr_immediate) - { - ExecutePSRTransfer(instruction); - return; - } - - if ((instruction & 0x0e000090) == 0x00000090) - { - ExecuteHalfwordDataTransfer(instruction); - return; - } - - switch ((instruction >> 25) & 7) - { - case 0: - case 1: - ExecuteDataProcessing(instruction); - return; - case 2: - case 3: - ExecuteSingleDataTransfer(instruction); - return; - case 4: - ExecuteBlockDataTransfer(instruction); - return; - case 5: - ExecuteBranch(instruction); - return; - case 7: - if ((instruction & 0x0f000000) == 0x0f000000) - { - EnterException(Mode::Supervisor, STARLET_EXCEPTION_VECTOR_BASE + 0x08, - m_instruction_address + 4); - return; - } - if (instruction & (1U << 4)) - { - ExecuteCoprocessor(instruction); - return; - } - break; - default: - break; - } - - UndefinedInstruction(instruction); -} - -void ARMCore::ExecuteThumb(u16 instruction) -{ - const auto set_logical_flags = [this](u32 value, bool carry, bool update_carry) { - const bool old_overflow = (m_cpsr & CPSR_V) != 0; - SetNZ(value); - if (update_carry) - { - m_cpsr &= ~CPSR_C; - if (carry) - m_cpsr |= CPSR_C; - } - if (old_overflow) - m_cpsr |= CPSR_V; - }; - const auto set_arithmetic_flags = [this](const AddResult& result) { - SetNZ(result.value); - m_cpsr &= ~(CPSR_C | CPSR_V); - if (result.carry) - m_cpsr |= CPSR_C; - if (result.overflow) - m_cpsr |= CPSR_V; - }; - - // Move shifted register and add/subtract. - if ((instruction & 0xe000) == 0x0000) - { - if ((instruction & 0x1800) == 0x1800) - { - const bool immediate = (instruction & 0x0400) != 0; - const bool subtract = (instruction & 0x0200) != 0; - const u32 operand = - immediate ? ((instruction >> 6) & 7) : m_registers[(instruction >> 6) & 7]; - const u32 rs = (instruction >> 3) & 7; - const u32 rd = instruction & 7; - const AddResult result = subtract ? AddWithCarry(m_registers[rs], ~operand, true) : - AddWithCarry(m_registers[rs], operand, false); - m_registers[rd] = result.value; - set_arithmetic_flags(result); - return; - } - - const u32 type = (instruction >> 11) & 3; - const u32 amount = (instruction >> 6) & 0x1f; - const u32 rs = (instruction >> 3) & 7; - const u32 rd = instruction & 7; - const ShiftResult result = - ShiftImmediate(m_registers[rs], type, amount, (m_cpsr & CPSR_C) != 0); - m_registers[rd] = result.value; - set_logical_flags(result.value, result.carry, true); - return; - } - - // Move/compare/add/subtract immediate. - if ((instruction & 0xe000) == 0x2000) - { - const u32 opcode = (instruction >> 11) & 3; - const u32 rd = (instruction >> 8) & 7; - const u32 immediate = instruction & 0xff; - if (opcode == 0) // MOV - { - m_registers[rd] = immediate; - set_logical_flags(immediate, false, false); - } - else - { - const bool subtract = opcode == 1 || opcode == 3; - const u32 lhs = m_registers[rd]; - const AddResult result = - subtract ? AddWithCarry(lhs, ~immediate, true) : AddWithCarry(lhs, immediate, false); - if (opcode != 1) // CMP does not write. - m_registers[rd] = result.value; - set_arithmetic_flags(result); - } - return; - } - - // ALU operations. - if ((instruction & 0xfc00) == 0x4000) - { - const u32 opcode = (instruction >> 6) & 0xf; - const u32 rs = (instruction >> 3) & 7; - const u32 rd = instruction & 7; - const u32 lhs = m_registers[rd]; - const u32 rhs = m_registers[rs]; - u32 result = 0; - bool write_result = true; - bool arithmetic = false; - bool update_carry = false; - bool carry = false; - AddResult add{}; - switch (opcode) - { - case 0x0: // AND - result = lhs & rhs; - break; - case 0x1: // EOR - result = lhs ^ rhs; - break; - case 0x2: // LSL - case 0x3: // LSR - case 0x4: // ASR - case 0x7: // ROR - { - const u32 type = opcode == 0x7 ? 3 : opcode - 2; - const ShiftResult shifted = ShiftRegister(lhs, type, rhs & 0xff, (m_cpsr & CPSR_C) != 0); - result = shifted.value; - carry = shifted.carry; - update_carry = true; - break; - } - case 0x5: // ADC - add = AddWithCarry(lhs, rhs, (m_cpsr & CPSR_C) != 0); - result = add.value; - arithmetic = true; - break; - case 0x6: // SBC - add = AddWithCarry(lhs, ~rhs, (m_cpsr & CPSR_C) != 0); - result = add.value; - arithmetic = true; - break; - case 0x8: // TST - result = lhs & rhs; - write_result = false; - break; - case 0x9: // NEG - add = AddWithCarry(0, ~rhs, true); - result = add.value; - arithmetic = true; - break; - case 0xa: // CMP - add = AddWithCarry(lhs, ~rhs, true); - result = add.value; - arithmetic = true; - write_result = false; - break; - case 0xb: // CMN - add = AddWithCarry(lhs, rhs, false); - result = add.value; - arithmetic = true; - write_result = false; - break; - case 0xc: // ORR - result = lhs | rhs; - break; - case 0xd: // MUL - result = lhs * rhs; - break; - case 0xe: // BIC - result = lhs & ~rhs; - break; - case 0xf: // MVN - result = ~rhs; - break; - } - - if (write_result) - m_registers[rd] = result; - if (arithmetic) - set_arithmetic_flags(add); - else - set_logical_flags(result, carry, update_carry); - return; - } - - // High-register operations and BX/BLX. - if ((instruction & 0xfc00) == 0x4400) - { - const u32 opcode = (instruction >> 8) & 3; - const u32 rd = (instruction & 7) | ((instruction >> 4) & 8); - const u32 rs = ((instruction >> 3) & 7) | ((instruction >> 3) & 8); - const u32 rhs = ReadRegisterOperand(rs); - if (opcode == 0) // ADD - { - const u32 result = ReadRegisterOperand(rd) + rhs; - if (rd == 15) - WritePC(result); - else - m_registers[rd] = result; - } - else if (opcode == 1) // CMP - { - set_arithmetic_flags(AddWithCarry(ReadRegisterOperand(rd), ~rhs, true)); - } - else if (opcode == 2) // MOV - { - if (rd == 15) - WritePC(rhs); - else - m_registers[rd] = rhs; - } - else // BX/BLX - { - if (instruction & 0x0080) - m_registers[14] = (m_instruction_address + 2) | 1; - WritePC(rhs, true); - } - return; - } - - // PC-relative load. - if ((instruction & 0xf800) == 0x4800) - { - const u32 rd = (instruction >> 8) & 7; - const u32 address = ((m_instruction_address + 4) & ~3U) + ((instruction & 0xff) << 2); - m_registers[rd] = Read32(address); - return; - } - - // Register-offset loads and stores. - if ((instruction & 0xf000) == 0x5000) - { - const u32 operation = (instruction >> 9) & 7; - const u32 ro = (instruction >> 6) & 7; - const u32 rb = (instruction >> 3) & 7; - const u32 rd = instruction & 7; - const u32 address = m_registers[rb] + m_registers[ro]; - switch (operation) - { - case 0: // STR - Write32(address & ~3U, m_registers[rd]); - break; - case 1: // STRH - Write16(address, static_cast(m_registers[rd])); - break; - case 2: // STRB - WriteByte(address, static_cast(m_registers[rd])); - break; - case 3: // LDRSB - m_registers[rd] = static_cast(static_cast(static_cast(ReadByte(address)))); - break; - case 4: // LDR - m_registers[rd] = std::rotr(Read32(address & ~3U), static_cast((address & 3) * 8)); - break; - case 5: // LDRH - m_registers[rd] = Read16(address); - break; - case 6: // LDRB - m_registers[rd] = ReadByte(address); - break; - case 7: // LDRSH - m_registers[rd] = static_cast(static_cast(static_cast(Read16(address)))); - break; - } - return; - } - - // Immediate-offset word/byte loads and stores. - if ((instruction & 0xe000) == 0x6000) - { - const bool byte = (instruction & 0x1000) != 0; - const bool load = (instruction & 0x0800) != 0; - const u32 offset = ((instruction >> 6) & 0x1f) << (byte ? 0 : 2); - const u32 rb = (instruction >> 3) & 7; - const u32 rd = instruction & 7; - const u32 address = m_registers[rb] + offset; - if (load) - { - m_registers[rd] = byte ? ReadByte(address) : Read32(address); - } - else if (byte) - { - WriteByte(address, static_cast(m_registers[rd])); - } - else - { - Write32(address, m_registers[rd]); - } - return; - } - - // Immediate-offset halfword loads and stores. - if ((instruction & 0xf000) == 0x8000) - { - const bool load = (instruction & 0x0800) != 0; - const u32 address = m_registers[(instruction >> 3) & 7] + (((instruction >> 6) & 0x1f) << 1); - const u32 rd = instruction & 7; - if (load) - m_registers[rd] = Read16(address); - else - Write16(address, static_cast(m_registers[rd])); - return; - } - - // SP-relative loads and stores. - if ((instruction & 0xf000) == 0x9000) - { - const bool load = (instruction & 0x0800) != 0; - const u32 rd = (instruction >> 8) & 7; - const u32 address = m_registers[13] + ((instruction & 0xff) << 2); - if (load) - m_registers[rd] = Read32(address); - else - Write32(address, m_registers[rd]); - return; - } - - // Load address relative to PC or SP. - if ((instruction & 0xf000) == 0xa000) - { - const bool use_sp = (instruction & 0x0800) != 0; - const u32 rd = (instruction >> 8) & 7; - const u32 base = use_sp ? m_registers[13] : ((m_instruction_address + 4) & ~3U); - m_registers[rd] = base + ((instruction & 0xff) << 2); - return; - } - - // Add/subtract immediate to SP. - if ((instruction & 0xff00) == 0xb000) - { - const u32 offset = (instruction & 0x7f) << 2; - if (instruction & 0x0080) - m_registers[13] -= offset; - else - m_registers[13] += offset; - return; - } - - // PUSH/POP. - if ((instruction & 0xf600) == 0xb400) - { - const bool pop = (instruction & 0x0800) != 0; - const bool extra = (instruction & 0x0100) != 0; - const u32 register_list = instruction & 0xff; - const u32 count = std::popcount(register_list) + static_cast(extra); - if (count == 0) - { - UndefinedInstruction(instruction); - return; - } - - if (!pop) - { - u32 address = m_registers[13] - count * 4; - m_registers[13] = address; - for (u32 reg = 0; reg < 8; ++reg) - { - if (register_list & (1U << reg)) - { - Write32(address, m_registers[reg]); - address += 4; - } - } - if (extra) - Write32(address, m_registers[14]); - } - else - { - u32 address = m_registers[13]; - for (u32 reg = 0; reg < 8; ++reg) - { - if (register_list & (1U << reg)) - { - m_registers[reg] = Read32(address); - address += 4; - } - } - if (extra) - { - const u32 pc = Read32(address); - address += 4; - WritePC(pc, true); - } - m_registers[13] = address; - } - return; - } - - if ((instruction & 0xff00) == 0xbe00) // BKPT - { - UndefinedInstruction(instruction); - return; - } - - // Multiple load/store. - if ((instruction & 0xf000) == 0xc000) - { - const bool load = (instruction & 0x0800) != 0; - const u32 rb = (instruction >> 8) & 7; - const u32 register_list = instruction & 0xff; - if (register_list == 0) - { - UndefinedInstruction(instruction); - return; - } - u32 address = m_registers[rb]; - for (u32 reg = 0; reg < 8; ++reg) - { - if (!(register_list & (1U << reg))) - continue; - if (load) - m_registers[reg] = Read32(address); - else - Write32(address, m_registers[reg]); - address += 4; - } - if (!(load && (register_list & (1U << rb)))) - m_registers[rb] = address; - return; - } - - // Conditional branch and SWI. - if ((instruction & 0xf000) == 0xd000) - { - const u32 condition = (instruction >> 8) & 0xf; - if (condition == 0xf) - { - EnterException(Mode::Supervisor, STARLET_EXCEPTION_VECTOR_BASE + 0x08, - m_instruction_address + 2); - } - else if (condition == 0xe) - { - UndefinedInstruction(instruction); - } - else if (ConditionPassed(condition)) - { - const s32 offset = SignExtend(instruction & 0xff, 8) * 2; - WritePC(m_instruction_address + 4 + static_cast(offset)); - } - return; - } - - // Unconditional branch. - if ((instruction & 0xf800) == 0xe000) - { - const s32 offset = SignExtend(instruction & 0x7ff, 11) * 2; - WritePC(m_instruction_address + 4 + static_cast(offset)); - return; - } - - // BL/BLX first and second half. - if ((instruction & 0xf800) == 0xf000) - { - const s32 high_offset = SignExtend(instruction & 0x7ff, 11) * 4096; - m_registers[14] = m_instruction_address + 4 + static_cast(high_offset); - return; - } - if ((instruction & 0xf800) == 0xf800 || (instruction & 0xf800) == 0xe800) - { - const bool exchange = (instruction & 0xf800) == 0xe800; - const u32 target = m_registers[14] + ((instruction & 0x7ff) << 1); - m_registers[14] = (m_instruction_address + 2) | 1; - if (exchange) - m_cpsr &= ~CPSR_T; - WritePC(target); - return; - } - - UndefinedInstruction(instruction); -} - -void ARMCore::ExecuteDataProcessing(u32 instruction) -{ - const u32 opcode = (instruction >> 21) & 0xf; - const bool set_flags = (instruction & (1U << 20)) != 0; - const u32 rn = (instruction >> 16) & 0xf; - const u32 rd = (instruction >> 12) & 0xf; - const u32 lhs = ReadRegisterOperand(rn); - const ShiftResult operand2 = DecodeOperand2(instruction); - - u32 result = 0; - bool write_result = true; - bool arithmetic = false; - AddResult add{}; - switch (opcode) - { - case 0x0: // AND - result = lhs & operand2.value; - break; - case 0x1: // EOR - result = lhs ^ operand2.value; - break; - case 0x2: // SUB - add = AddWithCarry(lhs, ~operand2.value, true); - result = add.value; - arithmetic = true; - break; - case 0x3: // RSB - add = AddWithCarry(operand2.value, ~lhs, true); - result = add.value; - arithmetic = true; - break; - case 0x4: // ADD - add = AddWithCarry(lhs, operand2.value, false); - result = add.value; - arithmetic = true; - break; - case 0x5: // ADC - add = AddWithCarry(lhs, operand2.value, (m_cpsr & CPSR_C) != 0); - result = add.value; - arithmetic = true; - break; - case 0x6: // SBC - add = AddWithCarry(lhs, ~operand2.value, (m_cpsr & CPSR_C) != 0); - result = add.value; - arithmetic = true; - break; - case 0x7: // RSC - add = AddWithCarry(operand2.value, ~lhs, (m_cpsr & CPSR_C) != 0); - result = add.value; - arithmetic = true; - break; - case 0x8: // TST - result = lhs & operand2.value; - write_result = false; - break; - case 0x9: // TEQ - result = lhs ^ operand2.value; - write_result = false; - break; - case 0xa: // CMP - add = AddWithCarry(lhs, ~operand2.value, true); - result = add.value; - arithmetic = true; - write_result = false; - break; - case 0xb: // CMN - add = AddWithCarry(lhs, operand2.value, false); - result = add.value; - arithmetic = true; - write_result = false; - break; - case 0xc: // ORR - result = lhs | operand2.value; - break; - case 0xd: // MOV - result = operand2.value; - break; - case 0xe: // BIC - result = lhs & ~operand2.value; - break; - case 0xf: // MVN - result = ~operand2.value; - break; - } - - if (set_flags || !write_result) - { - const bool old_overflow = (m_cpsr & CPSR_V) != 0; - SetNZ(result); - m_cpsr &= ~(CPSR_C | CPSR_V); - if (arithmetic) - { - if (add.carry) - m_cpsr |= CPSR_C; - if (add.overflow) - m_cpsr |= CPSR_V; - } - else - { - if (operand2.carry) - m_cpsr |= CPSR_C; - // Logical operations leave V unchanged. - if (old_overflow) - m_cpsr |= CPSR_V; - } - } - - if (!write_result) - return; - if (rd == 15) - { - if (set_flags) - RestoreCPSRFromSPSR(); - WritePC(result); - } - else - { - m_registers[rd] = result; - } -} - -void ARMCore::ExecuteSingleDataTransfer(u32 instruction) -{ - const bool register_offset = (instruction & (1U << 25)) != 0; - const bool preindex = (instruction & (1U << 24)) != 0; - const bool add_offset = (instruction & (1U << 23)) != 0; - const bool byte = (instruction & (1U << 22)) != 0; - const bool writeback = (instruction & (1U << 21)) != 0; - const bool load = (instruction & (1U << 20)) != 0; - const u32 rn = (instruction >> 16) & 0xf; - const u32 rd = (instruction >> 12) & 0xf; - - u32 offset; - if (register_offset) - { - if (instruction & (1U << 4)) - { - UndefinedInstruction(instruction); - return; - } - const u32 rm = ReadRegisterOperand(instruction & 0xf); - offset = ShiftImmediate(rm, (instruction >> 5) & 3, (instruction >> 7) & 0x1f, - (m_cpsr & CPSR_C) != 0) - .value; - } - else - { - offset = instruction & 0xfff; - } - - const u32 base = ReadRegisterOperand(rn); - const u32 indexed = add_offset ? base + offset : base - offset; - const u32 address = preindex ? indexed : base; - if (load) - { - u32 value; - if (byte) - { - value = ReadByte(address); - } - else - { - const u32 aligned_value = Read32(address & ~3U); - value = std::rotr(aligned_value, static_cast((address & 3) * 8)); - } - - if (rd == 15) - WritePC(value, true); - else - m_registers[rd] = value; - } - else - { - const u32 value = ReadRegisterForStore(rd); - if (byte) - WriteByte(address, static_cast(value)); - else - Write32(address & ~3U, value); - } - - if ((!preindex || writeback) && !(load && rn == rd)) - m_registers[rn] = indexed; -} - -void ARMCore::ExecuteHalfwordDataTransfer(u32 instruction) -{ - const bool preindex = (instruction & (1U << 24)) != 0; - const bool add_offset = (instruction & (1U << 23)) != 0; - const bool immediate = (instruction & (1U << 22)) != 0; - const bool writeback = (instruction & (1U << 21)) != 0; - const bool load = (instruction & (1U << 20)) != 0; - const u32 rn = (instruction >> 16) & 0xf; - const u32 rd = (instruction >> 12) & 0xf; - const u32 type = (instruction >> 5) & 3; - const u32 offset = immediate ? (((instruction >> 4) & 0xf0) | (instruction & 0xf)) : - ReadRegisterOperand(instruction & 0xf); - const u32 base = ReadRegisterOperand(rn); - const u32 indexed = add_offset ? base + offset : base - offset; - const u32 address = preindex ? indexed : base; - - if (load) - { - u32 value; - switch (type) - { - case 1: // LDRH - value = Read16(address); - break; - case 2: // LDRSB - value = static_cast(static_cast(static_cast(ReadByte(address)))); - break; - case 3: // LDRSH - value = static_cast(static_cast(static_cast(Read16(address)))); - break; - default: - UndefinedInstruction(instruction); - return; - } - if (rd == 15) - WritePC(value); - else - m_registers[rd] = value; - } - else - { - if (type != 1) - { - // ARMv5 also uses this space for LDRD/STRD. They are deliberately - // rejected until their alignment and abort behavior is implemented. - UndefinedInstruction(instruction); - return; - } - Write16(address, static_cast(ReadRegisterForStore(rd))); - } - - if ((!preindex || writeback) && !(load && rn == rd)) - m_registers[rn] = indexed; -} - -void ARMCore::ExecuteBlockDataTransfer(u32 instruction) -{ - const bool preindex = (instruction & (1U << 24)) != 0; - const bool increment = (instruction & (1U << 23)) != 0; - const bool psr_or_user = (instruction & (1U << 22)) != 0; - const bool writeback = (instruction & (1U << 21)) != 0; - const bool load = (instruction & (1U << 20)) != 0; - const u32 rn = (instruction >> 16) & 0xf; - const u32 register_list = instruction & 0xffff; - if (register_list == 0) - { - UndefinedInstruction(instruction); - return; - } - - const u32 count = std::popcount(register_list); - const u32 base = ReadRegisterOperand(rn); - const bool user_bank_transfer = psr_or_user && !(load && (register_list & (1U << 15)) != 0); - const auto read_user_register = [this](u32 reg) { - if (GetMode() == Mode::User || GetMode() == Mode::System || reg < 8 || - (reg < 13 && GetMode() != Mode::FIQ)) - return ReadRegisterForStore(reg); - if (reg < 15) - return m_user_r8_r14[reg - 8]; - return ReadRegisterForStore(reg); - }; - const auto write_user_register = [this](u32 reg, u32 value) { - if (GetMode() == Mode::User || GetMode() == Mode::System || reg < 8 || - (reg < 13 && GetMode() != Mode::FIQ)) - m_registers[reg] = value; - else if (reg < 15) - m_user_r8_r14[reg - 8] = value; - }; - u32 address; - if (increment) - address = preindex ? base + 4 : base; - else - address = preindex ? base - count * 4 : base - (count - 1) * 4; - - u32 loaded_pc = 0; - bool pc_loaded = false; - for (u32 reg = 0; reg < 16; ++reg) - { - if (!(register_list & (1U << reg))) - continue; - if (load) - { - const u32 value = Read32(address); - if (reg == 15) - { - loaded_pc = value; - pc_loaded = true; - } - else - { - if (user_bank_transfer) - write_user_register(reg, value); - else - m_registers[reg] = value; - } - } - else - { - Write32(address, user_bank_transfer ? read_user_register(reg) : ReadRegisterForStore(reg)); - } - address += 4; - } - - if (writeback && !(load && (register_list & (1U << rn)))) - m_registers[rn] = increment ? base + count * 4 : base - count * 4; - - if (pc_loaded) - { - if (psr_or_user) - RestoreCPSRFromSPSR(); - WritePC(loaded_pc, true); - } -} - -void ARMCore::ExecuteMultiply(u32 instruction) -{ - const bool accumulate = (instruction & (1U << 21)) != 0; - const bool set_flags = (instruction & (1U << 20)) != 0; - const u32 rd = (instruction >> 16) & 0xf; - const u32 rn = (instruction >> 12) & 0xf; - const u32 rs = (instruction >> 8) & 0xf; - const u32 rm = instruction & 0xf; - u32 result = ReadRegisterOperand(rm) * ReadRegisterOperand(rs); - if (accumulate) - result += ReadRegisterOperand(rn); - m_registers[rd] = result; - if (set_flags) - SetNZ(result); -} - -void ARMCore::ExecuteMultiplyLong(u32 instruction) -{ - const bool signed_multiply = (instruction & (1U << 22)) != 0; - const bool accumulate = (instruction & (1U << 21)) != 0; - const bool set_flags = (instruction & (1U << 20)) != 0; - const u32 rd_hi = (instruction >> 16) & 0xf; - const u32 rd_lo = (instruction >> 12) & 0xf; - const u32 rs = (instruction >> 8) & 0xf; - const u32 rm = instruction & 0xf; - - u64 result; - if (signed_multiply) - { - result = static_cast(static_cast(static_cast(ReadRegisterOperand(rm))) * - static_cast(static_cast(ReadRegisterOperand(rs)))); - } - else - { - result = static_cast(ReadRegisterOperand(rm)) * ReadRegisterOperand(rs); - } - if (accumulate) - result += (static_cast(m_registers[rd_hi]) << 32) | m_registers[rd_lo]; - m_registers[rd_lo] = static_cast(result); - m_registers[rd_hi] = static_cast(result >> 32); - if (set_flags) - { - m_cpsr &= ~(CPSR_N | CPSR_Z); - if (result & (1ULL << 63)) - m_cpsr |= CPSR_N; - if (result == 0) - m_cpsr |= CPSR_Z; - } -} - -void ARMCore::ExecuteBranch(u32 instruction) -{ - const bool link = (instruction & (1U << 24)) != 0; - const s32 offset = SignExtend((instruction & 0x00ffffff) << 2, 26); - if (link) - m_registers[14] = m_instruction_address + 4; - WritePC(m_instruction_address + 8 + static_cast(offset)); -} - -void ARMCore::ExecuteCoprocessor(u32 instruction) -{ - const bool load = (instruction & (1U << 20)) != 0; - const u32 opcode1 = (instruction >> 21) & 7; - const u32 crn = (instruction >> 16) & 0xf; - const u32 rd = (instruction >> 12) & 0xf; - const u32 coprocessor = (instruction >> 8) & 0xf; - const u32 opcode2 = (instruction >> 5) & 7; - const u32 crm = instruction & 0xf; - if (coprocessor != 15) - { - UndefinedInstruction(instruction); - return; - } - - if (load) - { - const u32 value = ReadCP15(opcode1, crn, crm, opcode2); - if (rd == 15) - m_cpsr = (m_cpsr & 0x0fffffff) | (value & 0xf0000000); - else - m_registers[rd] = value; - } - else - { - WriteCP15(opcode1, crn, crm, opcode2, ReadRegisterOperand(rd)); - } -} - -void ARMCore::ExecutePSRTransfer(u32 instruction) -{ - if ((instruction & 0x0fbf0fff) == 0x010f0000) // MRS - { - const bool spsr = (instruction & (1U << 22)) != 0; - const u32 rd = (instruction >> 12) & 0xf; - const u32* source = spsr ? GetSPSR(GetMode()) : &m_cpsr; - m_registers[rd] = source ? *source : m_cpsr; - return; - } - - const bool spsr = (instruction & (1U << 22)) != 0; - const u32 field_mask = (instruction >> 16) & 0xf; - u32 value; - if (instruction & (1U << 25)) - { - const u32 rotate = ((instruction >> 8) & 0xf) * 2; - value = std::rotr(instruction & 0xff, static_cast(rotate)); - } - else - { - value = ReadRegisterOperand(instruction & 0xf); - } - WritePSR(spsr, field_mask, value); -} - -void ARMCore::EnterException(Mode mode, u32 vector, u32 return_address, bool disable_fiq) -{ - const u32 old_cpsr = m_cpsr; - SwitchMode(mode); - if (u32* spsr = GetSPSR(mode)) - *spsr = old_cpsr; - m_cpsr = (old_cpsr & ~(CPSR_MODE_MASK | CPSR_T)) | ModeValue(mode) | CPSR_I; - if (disable_fiq) - m_cpsr |= CPSR_F; - m_registers[14] = return_address; - m_registers[15] = vector & ~3U; - m_pc_written = true; -} - -void ARMCore::UndefinedInstruction(u32 instruction) -{ - m_last_undefined_instruction = instruction; - const u32 return_address = m_instruction_address + ((m_cpsr & CPSR_T) ? 2 : 4); - EnterException(Mode::Undefined, STARLET_EXCEPTION_VECTOR_BASE + 0x04, return_address); -} - -void ARMCore::SaveBankedRegisters(Mode mode) -{ - if (mode == Mode::FIQ) - { - for (size_t i = 0; i < 7; ++i) - m_fiq_r8_r14[i] = m_registers[8 + i]; - return; - } - - for (size_t i = 0; i < 5; ++i) - m_user_r8_r14[i] = m_registers[8 + i]; - std::array* bank = nullptr; - switch (mode) - { - case Mode::User: - case Mode::System: - m_user_r8_r14[5] = m_registers[13]; - m_user_r8_r14[6] = m_registers[14]; - return; - case Mode::IRQ: - bank = &m_irq_r13_r14; - break; - case Mode::Supervisor: - bank = &m_svc_r13_r14; - break; - case Mode::Abort: - bank = &m_abt_r13_r14; - break; - case Mode::Undefined: - bank = &m_und_r13_r14; - break; - default: - return; - } - (*bank)[0] = m_registers[13]; - (*bank)[1] = m_registers[14]; -} - -void ARMCore::LoadBankedRegisters(Mode mode) -{ - if (mode == Mode::FIQ) - { - for (size_t i = 0; i < 7; ++i) - m_registers[8 + i] = m_fiq_r8_r14[i]; - return; - } - - for (size_t i = 0; i < 5; ++i) - m_registers[8 + i] = m_user_r8_r14[i]; - const std::array* bank = nullptr; - switch (mode) - { - case Mode::User: - case Mode::System: - m_registers[13] = m_user_r8_r14[5]; - m_registers[14] = m_user_r8_r14[6]; - return; - case Mode::IRQ: - bank = &m_irq_r13_r14; - break; - case Mode::Supervisor: - bank = &m_svc_r13_r14; - break; - case Mode::Abort: - bank = &m_abt_r13_r14; - break; - case Mode::Undefined: - bank = &m_und_r13_r14; - break; - default: - return; - } - m_registers[13] = (*bank)[0]; - m_registers[14] = (*bank)[1]; -} - -void ARMCore::SwitchMode(Mode mode) -{ - const Mode old_mode = GetMode(); - if (old_mode == mode) - return; - SaveBankedRegisters(old_mode); - m_cpsr = (m_cpsr & ~CPSR_MODE_MASK) | ModeValue(mode); - LoadBankedRegisters(mode); -} - -u32* ARMCore::GetSPSR(Mode mode) -{ - switch (mode) - { - case Mode::FIQ: - return &m_spsr_fiq; - case Mode::IRQ: - return &m_spsr_irq; - case Mode::Supervisor: - return &m_spsr_svc; - case Mode::Abort: - return &m_spsr_abt; - case Mode::Undefined: - return &m_spsr_und; - default: - return nullptr; - } -} - -const u32* ARMCore::GetSPSR(Mode mode) const -{ - return const_cast(this)->GetSPSR(mode); -} - -void ARMCore::RestoreCPSRFromSPSR() -{ - if (const u32* spsr = GetSPSR(GetMode())) - SetCPSR(*spsr); -} - -void ARMCore::WritePSR(bool spsr, u32 field_mask, u32 value) -{ - u32 mask = 0; - if (field_mask & 1) - mask |= 0x000000ff; - if (field_mask & 2) - mask |= 0x0000ff00; - if (field_mask & 4) - mask |= 0x00ff0000; - if (field_mask & 8) - mask |= 0xff000000; - - if (spsr) - { - if (u32* destination = GetSPSR(GetMode())) - *destination = (*destination & ~mask) | (value & mask); - return; - } - - if (GetMode() == Mode::User) - mask &= 0xff000000; - SetCPSR((m_cpsr & ~mask) | (value & mask)); -} - -u32 ARMCore::ReadCP15(u32 opcode1, u32 crn, u32 crm, u32 opcode2) const -{ - if (opcode1 == 0 && crn == 0 && crm == 0 && opcode2 == 0) - return 0x41069265; // ARM926EJ-S main ID register. - if (opcode1 == 0 && crn == 7 && (crm == 10 || crm == 14) && opcode2 == 3) - { - // ARM926 test-and-clean operations return the D-cache dirty state in - // bit 30. Caches are not modeled, so memory is already coherent and the - // cache must always report clean. - return 1U << 30; - } - if (opcode1 != 0) - return 0; - switch (crn) - { - case 1: - return m_cp15.control; - case 2: - return m_cp15.translation_table_base; - case 3: - return m_cp15.domain_access_control; - case 5: - return m_cp15.fault_status; - case 6: - return m_cp15.fault_address; - case 13: - return m_cp15.process_id; - default: - return 0; - } -} - -void ARMCore::WriteCP15(u32 opcode1, u32 crn, u32 crm, u32 opcode2, u32 value) -{ - if (opcode1 != 0) - return; - - // ARM926EJ-S wait for interrupt: MCR p15, 0, Rd, c7, c0, 4. If an interrupt - // is already asserted the instruction completes without entering the - // low-power state. - if (crn == 7 && crm == 0 && opcode2 == 4) - { - m_waiting_for_interrupt = !m_irq_line && !m_fiq_line; - return; - } - - switch (crn) - { - case 1: - m_cp15.control = value; - m_big_endian = (value & CP15_CONTROL_BIG_ENDIAN) != 0; - InvalidateTLB(); - InvalidateInstructionCache(); - break; - case 2: - m_cp15.translation_table_base = value; - break; - case 3: - m_cp15.domain_access_control = value; - break; - case 5: - m_cp15.fault_status = value; - break; - case 6: - m_cp15.fault_address = value; - break; - case 7: - // ARM926 separates I-cache and D-cache maintenance. In particular c7,c10,4 is Drain Write - // Buffer, an ordering operation used heavily by IOS; invalidating translated code for it is - // both architecturally wrong and catastrophically expensive. Only operations which include - // the instruction cache make previously modified instructions visible. - if (crm == 5 || crm == 7) - InvalidateInstructionCache(); - break; - case 8: // TLB maintenance. - InvalidateTLB(); - break; - case 13: - // ARM926 FCSE is specifically designed to switch low-address process spaces without flushing - // either cache or TLB: their tags contain the modified virtual address (MVA), which already - // includes PID[31:25]. IOS writes c13 at virtually every process switch. Retaining those MVA - // translations is both the architectural behavior and essential to avoiding a page-table walk - // storm. Explicit c8 maintenance above remains the sole guest-visible TLB invalidation path. - m_cp15.process_id = value; - break; - default: - break; - } -} -} // namespace IOS::LLE +// Copyright 2026 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/IOS/Starlet/ARMCore.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "Common/ChunkFile.h" +#include "Common/PerformanceTrace.h" +#if defined(_M_X86_64) +// ARMCore owns ARMJitX64 by value through unique_ptr construction in this translation unit, so a +// private JIT layout change must rebuild this file as well as the emitter. +#include "Core/IOS/Starlet/ARMJitX64.h" +#endif + +namespace IOS::LLE +{ +namespace +{ +constexpr u32 CP15_CONTROL_BIG_ENDIAN = 1U << 7; +constexpr u32 CP15_CONTROL_MMU = 1U << 0; +constexpr u32 STARLET_EXCEPTION_VECTOR_BASE = 0xffff0000; + +constexpr u32 ModeValue(ARMCore::Mode mode) +{ + return static_cast(mode); +} +} // namespace + +ARMCore::ARMCore(ARMBus& bus, bool profile_jit_memory) + : m_bus(bus), m_profile_jit_memory(profile_jit_memory) +{ + Reset(); +} + +ARMCore::~ARMCore() = default; + +void ARMCore::SetJitEnabled(bool enabled) +{ +#if defined(_M_X86_64) + if (enabled && !m_jit) + m_jit = std::make_unique(*this); + m_jit_enabled = enabled; +#else + m_jit_enabled = false; +#endif +} + +void ARMCore::Reset(u32 reset_vector) +{ + m_trace_generation = Common::PerformanceTrace::NewGeneration(); + m_registers.fill(0); + m_user_r8_r14.fill(0); + m_fiq_r8_r14.fill(0); + m_irq_r13_r14.fill(0); + m_svc_r13_r14.fill(0); + m_abt_r13_r14.fill(0); + m_und_r13_r14.fill(0); + m_spsr_fiq = 0; + m_spsr_irq = 0; + m_spsr_svc = 0; + m_spsr_abt = 0; + m_spsr_und = 0; + m_cp15 = {}; + InvalidateTLB(); + InvalidateInstructionCache(); + if (m_big_endian) + m_cp15.control |= CP15_CONTROL_BIG_ENDIAN; + + m_cpsr = ModeValue(Mode::Supervisor) | CPSR_I | CPSR_F; + m_registers[15] = reset_vector & ~3U; + m_irq_line = false; + m_fiq_line = false; + m_waiting_for_interrupt = false; + m_waiting_for_memory_poll = false; + m_yield_requested = false; + m_slice_fast_forward_requested = false; + m_memory_poll_address = 0; + m_pc_written = false; + m_instruction_address = m_registers[15]; + m_last_undefined_instruction = 0; + m_executed_instructions = 0; + m_memory_poll_entry_count = 0; + m_jit_fallback_instruction_count = 0; + m_jit_fallback_samples.clear(); + m_jit_fallback_interval_count = 0; + m_jit_fallback_interval_samples.clear(); + m_next_profile_instruction = PROFILE_SAMPLE_INTERVAL; + m_pc_samples.clear(); +} + +u32 ARMCore::GetRegister(size_t index) const +{ + assert(index < m_registers.size()); + return m_registers[index]; +} + +void ARMCore::SetRegister(size_t index, u32 value) +{ + assert(index < m_registers.size()); + if (index == 15) + { + m_registers[index] = value & ((m_cpsr & CPSR_T) ? ~1U : ~3U); + return; + } + m_registers[index] = value; +} + +bool ARMCore::IsValidMode(u32 mode) +{ + switch (static_cast(mode)) + { + case Mode::User: + case Mode::FIQ: + case Mode::IRQ: + case Mode::Supervisor: + case Mode::Abort: + case Mode::Undefined: + case Mode::System: + return true; + default: + return false; + } +} + +ARMCore::Mode ARMCore::GetMode() const +{ + return static_cast(m_cpsr & CPSR_MODE_MASK); +} + +void ARMCore::SetCPSR(u32 value) +{ + const u32 new_mode_value = value & CPSR_MODE_MASK; + if (!IsValidMode(new_mode_value)) + value = (value & ~CPSR_MODE_MASK) | ModeValue(GetMode()); + + const Mode new_mode = static_cast(value & CPSR_MODE_MASK); + if (new_mode != GetMode()) + SwitchMode(new_mode); + m_cpsr = value; +} + +void ARMCore::SetBigEndian(bool big_endian) +{ + m_big_endian = big_endian; + if (big_endian) + m_cp15.control |= CP15_CONTROL_BIG_ENDIAN; + else + m_cp15.control &= ~CP15_CONTROL_BIG_ENDIAN; + InvalidateTLB(); + InvalidateInstructionCache(); +} + +s32 ARMCore::SignExtend(u32 value, unsigned bits) +{ + const u32 sign = 1U << (bits - 1); + return static_cast((value ^ sign) - sign); +} + +ARMCore::AddResult ARMCore::AddWithCarry(u32 lhs, u32 rhs, bool carry) +{ + const u64 unsigned_sum = static_cast(lhs) + rhs + static_cast(carry); + const s64 signed_sum = static_cast(static_cast(lhs)) + + static_cast(static_cast(rhs)) + static_cast(carry); + return { + .value = static_cast(unsigned_sum), + .carry = (unsigned_sum >> 32) != 0, + .overflow = signed_sum > std::numeric_limits::max() || + signed_sum < std::numeric_limits::min(), + }; +} + +u32 ARMCore::ReadPhysical32(u32 address) const +{ + const u32 value = m_bus.Read32(address); + if (m_big_endian) + return value; + return (value >> 24) | ((value >> 8) & 0x0000ff00) | ((value << 8) & 0x00ff0000) | (value << 24); +} + +u32 ARMCore::TranslateVirtualAddress(u32 address) const +{ + if ((m_cp15.control & CP15_CONTROL_MMU) == 0) + return address; + + // ARM926 FCSE remaps the low 32 MiB through the process identifier before + // translation. + const u32 modified_address = + address < 0x02000000 ? address | (m_cp15.process_id & 0xfe000000) : address; + const u32 virtual_page = modified_address >> 10; + TLBEntry& entry = m_tlb[GetTLBIndex(virtual_page)]; + if (entry.generation == m_tlb_generation && entry.virtual_page == virtual_page) + return entry.physical_page | (modified_address & 0x3ff); + + const auto cache_translation = [&](u32 physical_address) { + entry.virtual_page = virtual_page; + entry.physical_page = physical_address & ~0x3ffU; + entry.generation = m_tlb_generation; + return physical_address; + }; + + return cache_translation(WalkPageTables(modified_address, nullptr, nullptr, nullptr, nullptr)); +} + +u32 ARMCore::TranslateVirtualAddressForJit(u32 address, const u8** first_descriptor, + u32* first_descriptor_value, + const u8** second_descriptor, + u32* second_descriptor_value) const +{ + *first_descriptor = nullptr; + *first_descriptor_value = 0; + *second_descriptor = nullptr; + *second_descriptor_value = 0; + if ((m_cp15.control & CP15_CONTROL_MMU) == 0) + return address; + + const u32 modified_address = + address < 0x02000000 ? address | (m_cp15.process_id & 0xfe000000) : address; + const u32 physical_address = + WalkPageTables(modified_address, first_descriptor, first_descriptor_value, second_descriptor, + second_descriptor_value); + + // Keep the interpreter and generated data-access path coherent with the translation established + // for this native code block. + const u32 virtual_page = modified_address >> 10; + TLBEntry& entry = m_tlb[GetTLBIndex(virtual_page)]; + entry.virtual_page = virtual_page; + entry.physical_page = physical_address & ~0x3ffU; + entry.generation = m_tlb_generation; + return physical_address; +} + +u32 ARMCore::WalkPageTables(u32 modified_address, const u8** first_descriptor, + u32* first_descriptor_value, const u8** second_descriptor, + u32* second_descriptor_value) const +{ + const auto read_descriptor = [&](u32 physical_address, const u8** host_pointer, u32* host_value) { + // Only native block validation needs a persistent descriptor pointer. Ordinary TLB + // misses need the bus value alone; do not resolve the same physical memory twice. + const u8* const pointer = (host_pointer || host_value) ? + m_bus.GetDirectMemoryPointer(physical_address, sizeof(u32)) : + nullptr; + if (host_pointer) + *host_pointer = pointer; + if (host_value) + { + *host_value = 0; + if (pointer) + std::memcpy(host_value, pointer, sizeof(u32)); + } + return ReadPhysical32(physical_address); + }; + + const u32 first_level_address = + (m_cp15.translation_table_base & 0xffffc000) | ((modified_address >> 18) & 0x3ffc); + const u32 first_level = + read_descriptor(first_level_address, first_descriptor, first_descriptor_value); + switch (first_level & 3) + { + case 1: // Coarse second-level table. + { + const u32 second_level_address = + (first_level & 0xfffffc00) | ((modified_address >> 10) & 0x3fc); + const u32 second_level = + read_descriptor(second_level_address, second_descriptor, second_descriptor_value); + switch (second_level & 3) + { + case 1: // 64 KiB large page. + return (second_level & 0xffff0000) | (modified_address & 0xffff); + case 2: + case 3: // 4 KiB small page; extended small pages share this mapping shape. + return (second_level & 0xfffff000) | (modified_address & 0xfff); + default: + return modified_address; + } + } + case 2: // 1 MiB section. + return (first_level & 0xfff00000) | (modified_address & 0x000fffff); + case 3: // Fine second-level table. + { + const u32 second_level_address = (first_level & 0xfffff000) | ((modified_address >> 8) & 0xffc); + const u32 second_level = + read_descriptor(second_level_address, second_descriptor, second_descriptor_value); + switch (second_level & 3) + { + case 1: + return (second_level & 0xffff0000) | (modified_address & 0xffff); + case 2: + return (second_level & 0xfffff000) | (modified_address & 0xfff); + case 3: // 1 KiB tiny page. + return (second_level & 0xfffffc00) | (modified_address & 0x3ff); + default: + return modified_address; + } + } + default: + // Fault entry. Abort delivery and access-permission checks are + // intentionally introduced after basic page-table translation; identity + // fallback keeps diagnostics observable meanwhile. Cache that provisional translation just + // like a mapped page: real software must invalidate the TLB after changing a page-table entry, + // and without this cache the JIT would side-exit forever on every identity access. + return modified_address; + } +} + +void ARMCore::InvalidateTLB() +{ + if (++m_tlb_generation == 0) + { + for (TLBEntry& entry : m_tlb) + entry.generation = 0; + m_tlb_generation = 1; + } +#if defined(_M_X86_64) + if (m_jit) + m_jit->InvalidateTranslationContext(); +#endif +} + +u32 ARMCore::FetchARMInstruction(u32 address) +{ + const u32 physical_address = TranslateVirtualAddress(address); + const u32 physical_word = physical_address & ~3U; + InstructionCacheEntry& entry = + m_instruction_cache[(physical_word >> 2) & (INSTRUCTION_CACHE_ENTRY_COUNT - 1)]; + if (entry.generation != m_instruction_cache_generation || entry.physical_word != physical_word) + { + entry.physical_word = physical_word; + entry.raw_value = m_bus.Read32(physical_word); + entry.generation = m_instruction_cache_generation; + } + + const u32 value = entry.raw_value; + if (m_big_endian) + return value; + return (value >> 24) | ((value >> 8) & 0x0000ff00) | ((value << 8) & 0x00ff0000) | (value << 24); +} + +u16 ARMCore::FetchThumbInstruction(u32 address) +{ + const u32 physical_address = TranslateVirtualAddress(address); + const u32 physical_word = physical_address & ~3U; + InstructionCacheEntry& entry = + m_instruction_cache[(physical_word >> 2) & (INSTRUCTION_CACHE_ENTRY_COUNT - 1)]; + if (entry.generation != m_instruction_cache_generation || entry.physical_word != physical_word) + { + entry.physical_word = physical_word; + entry.raw_value = m_bus.Read32(physical_word); + entry.generation = m_instruction_cache_generation; + } + + u16 value = static_cast((physical_address & 2) ? entry.raw_value : entry.raw_value >> 16); + if (!m_big_endian) + value = static_cast((value >> 8) | (value << 8)); + return value; +} + +void ARMCore::InvalidateInstructionCache() +{ + if (++m_instruction_cache_generation == 0) + { + for (InstructionCacheEntry& entry : m_instruction_cache) + entry.generation = 0; + m_instruction_cache_generation = 1; + } +#if defined(_M_X86_64) + if (m_jit) + m_jit->Clear(); +#endif +} + +u8 ARMCore::ReadByte(u32 address) const +{ + return m_bus.Read8(TranslateVirtualAddress(address)); +} + +void ARMCore::WriteByte(u32 address, u8 value) +{ + m_bus.Write8(TranslateVirtualAddress(address), value); +} + +u16 ARMCore::Read16(u32 address) const +{ + const u16 value = + (address & 1) == 0 ? + m_bus.Read16(TranslateVirtualAddress(address)) : + static_cast((static_cast(m_bus.Read8(TranslateVirtualAddress(address))) << 8) | + m_bus.Read8(TranslateVirtualAddress(address + 1))); + return m_big_endian ? value : static_cast((value >> 8) | (value << 8)); +} + +u32 ARMCore::Read32(u32 address) const +{ + const u32 value = + (address & 3) == 0 ? + m_bus.Read32(TranslateVirtualAddress(address)) : + (static_cast(m_bus.Read8(TranslateVirtualAddress(address))) << 24) | + (static_cast(m_bus.Read8(TranslateVirtualAddress(address + 1))) << 16) | + (static_cast(m_bus.Read8(TranslateVirtualAddress(address + 2))) << 8) | + m_bus.Read8(TranslateVirtualAddress(address + 3)); + if (m_big_endian) + return value; + return (value >> 24) | ((value >> 8) & 0x0000ff00) | ((value << 8) & 0x00ff0000) | (value << 24); +} + +void ARMCore::Write16(u32 address, u16 value) +{ + if (!m_big_endian) + value = static_cast((value >> 8) | (value << 8)); + if ((address & 1) == 0) + { + m_bus.Write16(TranslateVirtualAddress(address), value); + return; + } + m_bus.Write8(TranslateVirtualAddress(address), static_cast(value >> 8)); + m_bus.Write8(TranslateVirtualAddress(address + 1), static_cast(value)); +} + +void ARMCore::Write32(u32 address, u32 value) +{ + if (!m_big_endian) + { + value = + (value >> 24) | ((value >> 8) & 0x0000ff00) | ((value << 8) & 0x00ff0000) | (value << 24); + } + if ((address & 3) == 0) + { + m_bus.Write32(TranslateVirtualAddress(address), value); + return; + } + m_bus.Write8(TranslateVirtualAddress(address), static_cast(value >> 24)); + m_bus.Write8(TranslateVirtualAddress(address + 1), static_cast(value >> 16)); + m_bus.Write8(TranslateVirtualAddress(address + 2), static_cast(value >> 8)); + m_bus.Write8(TranslateVirtualAddress(address + 3), static_cast(value)); +} + +u32 ARMCore::ReadRegisterOperand(u32 index) const +{ + assert(index < m_registers.size()); + if (index != 15) + return m_registers[index]; + return m_instruction_address + ((m_cpsr & CPSR_T) ? 4 : 8); +} + +u32 ARMCore::ReadRegisterForStore(u32 index) const +{ + assert(index < m_registers.size()); + if (index != 15) + return m_registers[index]; + // ARM9 exposes an implementation-defined PC value for stores. ARM926 uses + // instruction + 12. + return m_instruction_address + ((m_cpsr & CPSR_T) ? 4 : 12); +} + +void ARMCore::WritePC(u32 value, bool exchange) +{ + if (exchange) + { + if (value & 1) + m_cpsr |= CPSR_T; + else + m_cpsr &= ~CPSR_T; + } + + m_registers[15] = value & ((m_cpsr & CPSR_T) ? ~1U : ~3U); + m_pc_written = true; +} + +void ARMCore::SetNZ(u32 value) +{ + m_cpsr &= ~(CPSR_N | CPSR_Z); + if (value & 0x80000000) + m_cpsr |= CPSR_N; + if (value == 0) + m_cpsr |= CPSR_Z; +} + +bool ARMCore::ConditionPassed(u32 condition) const +{ + const bool n = (m_cpsr & CPSR_N) != 0; + const bool z = (m_cpsr & CPSR_Z) != 0; + const bool c = (m_cpsr & CPSR_C) != 0; + const bool v = (m_cpsr & CPSR_V) != 0; + switch (condition) + { + case 0x0: + return z; + case 0x1: + return !z; + case 0x2: + return c; + case 0x3: + return !c; + case 0x4: + return n; + case 0x5: + return !n; + case 0x6: + return v; + case 0x7: + return !v; + case 0x8: + return c && !z; + case 0x9: + return !c || z; + case 0xa: + return n == v; + case 0xb: + return n != v; + case 0xc: + return !z && n == v; + case 0xd: + return z || n != v; + case 0xe: + return true; + default: + return false; + } +} + +ARMCore::ShiftResult ARMCore::ShiftImmediate(u32 value, u32 type, u32 amount, bool old_carry) const +{ + switch (type) + { + case 0: // LSL + if (amount == 0) + return {value, old_carry}; + return {value << amount, ((value >> (32 - amount)) & 1) != 0}; + case 1: // LSR, immediate zero means 32 + if (amount == 0) + return {0, (value >> 31) != 0}; + return {value >> amount, ((value >> (amount - 1)) & 1) != 0}; + case 2: // ASR, immediate zero means 32 + if (amount == 0) + return {(value & 0x80000000) ? 0xffffffffU : 0, (value >> 31) != 0}; + return {static_cast(static_cast(value) >> amount), + ((value >> (amount - 1)) & 1) != 0}; + case 3: // ROR, immediate zero is RRX + if (amount == 0) + return {(static_cast(old_carry) << 31) | (value >> 1), (value & 1) != 0}; + return {std::rotr(value, static_cast(amount)), ((value >> (amount - 1)) & 1) != 0}; + default: + return {value, old_carry}; + } +} + +ARMCore::ShiftResult ARMCore::ShiftRegister(u32 value, u32 type, u32 amount, bool old_carry) const +{ + amount &= 0xff; + if (amount == 0) + return {value, old_carry}; + + switch (type) + { + case 0: // LSL + if (amount < 32) + return {value << amount, ((value >> (32 - amount)) & 1) != 0}; + return {0, amount == 32 && (value & 1) != 0}; + case 1: // LSR + if (amount < 32) + return {value >> amount, ((value >> (amount - 1)) & 1) != 0}; + return {0, amount == 32 && (value >> 31) != 0}; + case 2: // ASR + if (amount < 32) + return {static_cast(static_cast(value) >> amount), + ((value >> (amount - 1)) & 1) != 0}; + return {(value & 0x80000000) ? 0xffffffffU : 0, (value >> 31) != 0}; + case 3: // ROR + { + const u32 rotate = amount & 31; + if (rotate == 0) + return {value, (value >> 31) != 0}; + return {std::rotr(value, static_cast(rotate)), ((value >> (rotate - 1)) & 1) != 0}; + } + default: + return {value, old_carry}; + } +} + +ARMCore::ShiftResult ARMCore::DecodeOperand2(u32 instruction) const +{ + const bool old_carry = (m_cpsr & CPSR_C) != 0; + if (instruction & (1U << 25)) + { + const u32 rotate = ((instruction >> 8) & 0xf) * 2; + const u32 value = std::rotr(instruction & 0xff, static_cast(rotate)); + return {value, rotate == 0 ? old_carry : (value >> 31) != 0}; + } + + const u32 rm_value = ReadRegisterOperand(instruction & 0xf); + const u32 type = (instruction >> 5) & 3; + if (instruction & (1U << 4)) + { + const u32 amount = ReadRegisterOperand((instruction >> 8) & 0xf) & 0xff; + return ShiftRegister(rm_value, type, amount, old_carry); + } + return ShiftImmediate(rm_value, type, (instruction >> 7) & 0x1f, old_carry); +} + +int ARMCore::Step() +{ + return StepInternal(true); +} + +bool ARMCore::HasUnmaskedInterrupt() const +{ + return (m_irq_line && (m_cpsr & CPSR_I) == 0) || (m_fiq_line && (m_cpsr & CPSR_F) == 0); +} + +bool ARMCore::TryEnterThumbMemoryPoll(u16 branch_instruction) +{ + // IOS uses a tiny Thumb idle loop which repeatedly loads a shared word, compares it with zero, + // and branches back while no scheduler work is pending. Replaying that side-effect-free loop at + // the full 243 MHz Starlet clock dominates the host CPU. Recognize the general instruction shape + // and fast-forward only physical RAM/SRAM polls; MMIO polling remains fully interpreted. + if ((branch_instruction & 0xff00) != 0xd000 || !m_pc_written || HasUnmaskedInterrupt()) + return false; + + const u32 loop_address = m_registers[15]; + if (loop_address + 4 != m_instruction_address) + return false; + + const u16 load = FetchThumbInstruction(loop_address); + const u16 compare = FetchThumbInstruction(loop_address + 2); + if ((load & 0xf800) != 0x6800 || (compare & 0xf800) != 0x2800 || (compare & 0xff) != 0) + { + return false; + } + + const u32 destination_register = load & 7; + if (static_cast((compare >> 8) & 7) != destination_register || + m_registers[destination_register] != 0) + return false; + + const u32 base_register = (load >> 3) & 7; + const u32 address = m_registers[base_register] + (((load >> 6) & 0x1f) << 2); + if ((address & 3) != 0) + return false; + const u32 physical_address = TranslateVirtualAddress(address); + if (!m_bus.IsIdlePollAddress(physical_address, sizeof(u32))) + return false; + + m_memory_poll_address = address; + m_waiting_for_memory_poll = true; + ++m_memory_poll_entry_count; + return true; +} + +bool ARMCore::TryEnterARMSliceStablePoll(u32 branch_instruction) +{ + // IOS's early timer delay has this ARM shape (the middle literal load initializes the base only + // on entry): LDR value,[base]; B compare; literal/base setup; CMP value,target; Bcc loop. The + // timer cannot advance again until the bus receives AdvanceCycles at the slice boundary, making + // every remaining iteration in this slice observationally redundant. + const u32 condition = branch_instruction >> 28; + if ((branch_instruction & 0x0e000000) != 0x0a000000 || condition >= 0xe || !m_pc_written || + HasUnmaskedInterrupt()) + { + return false; + } + + const u32 loop_address = m_registers[15]; + if (loop_address + 16 != m_instruction_address) + return false; + + const u32 load = FetchARMInstruction(loop_address); + const u32 skip = FetchARMInstruction(loop_address + 4); + const u32 compare = FetchARMInstruction(loop_address + 12); + if ((load & 0xfff00000) != 0xe5900000 || (skip & 0xff000000) != 0xea000000 || + (compare & 0xfff00ff0) != 0xe1500000) + { + return false; + } + + const s32 skip_offset = SignExtend((skip & 0x00ffffff) << 2, 26); + if (loop_address + 12 != loop_address + 12 + static_cast(skip_offset)) + return false; + + const u32 load_destination = (load >> 12) & 0xf; + if (((compare >> 16) & 0xf) != load_destination) + return false; + + const u32 base = ReadRegisterOperand((load >> 16) & 0xf); + const u32 poll_address = base + (load & 0xfff); + const u32 physical_address = TranslateVirtualAddress(poll_address); + if (!m_bus.IsSliceStablePollAddress(physical_address, sizeof(u32))) + return false; + + m_memory_poll_address = poll_address; + m_waiting_for_memory_poll = true; + ++m_memory_poll_entry_count; + return true; +} + +int ARMCore::StepInternal(bool advance_bus) +{ + // ARM926's wait-for-interrupt operation stops instruction issue while clocks + // used by timers and peripherals continue running. A pending IRQ/FIQ wakes + // the core even when the corresponding CPSR mask prevents immediate exception + // entry. + if (m_waiting_for_memory_poll) + { + const u32 physical_poll_address = TranslateVirtualAddress(m_memory_poll_address); + if (m_bus.IsSliceStablePollAddress(physical_poll_address, sizeof(u32))) + { + // Step() advances the device clock itself, so only RunCycles can collapse the rest of a + // scheduler slice. Resume normally here. + m_waiting_for_memory_poll = false; + } + else if (!HasUnmaskedInterrupt() && Read32(m_memory_poll_address) == 0) + { + if (advance_bus) + m_bus.AdvanceCycles(1); + return 1; + } + else + { + m_waiting_for_memory_poll = false; + } + } + if (m_waiting_for_interrupt && !m_irq_line && !m_fiq_line) + { + if (advance_bus) + m_bus.AdvanceCycles(1); + return 1; + } + if (m_irq_line || m_fiq_line) + m_waiting_for_interrupt = false; + + // Starlet boots from and vectors through the high SRAM window independently + // of the generic ARM926 control-register V bit. + constexpr u32 vector_base = STARLET_EXCEPTION_VECTOR_BASE; + if (m_fiq_line && !(m_cpsr & CPSR_F)) + { + EnterException(Mode::FIQ, vector_base + 0x1c, m_registers[15] + 4, true); + if (advance_bus) + m_bus.AdvanceCycles(3); + return 3; + } + if (m_irq_line && !(m_cpsr & CPSR_I)) + { + EnterException(Mode::IRQ, vector_base + 0x18, m_registers[15] + 4); + if (advance_bus) + m_bus.AdvanceCycles(3); + return 3; + } + + m_instruction_address = m_registers[15]; + m_pc_written = false; + if (m_cpsr & CPSR_T) + { + const u16 instruction = FetchThumbInstruction(m_instruction_address); + ExecuteThumb(instruction); + TryEnterThumbMemoryPoll(instruction); + } + else + { + const u32 instruction = FetchARMInstruction(m_instruction_address); + + // BLX immediate uses the otherwise-reserved condition value 0xf. + if ((instruction & 0xfe000000) == 0xfa000000) + { + const u32 h = (instruction >> 24) & 1; + const s32 offset = SignExtend((instruction & 0x00ffffff) << 2, 26) + static_cast(h << 1); + m_registers[14] = m_instruction_address + 4; + m_cpsr |= CPSR_T; + WritePC(m_instruction_address + 8 + static_cast(offset)); + } + else if (ConditionPassed(instruction >> 28)) + { + ExecuteARM(instruction); + } + TryEnterARMSliceStablePoll(instruction); + } + + if (!m_pc_written) + m_registers[15] = m_instruction_address + ((m_cpsr & CPSR_T) ? 2 : 4); + ++m_executed_instructions; + if (advance_bus) + m_bus.AdvanceCycles(1); + return 1; +} + +u64 ARMCore::Run(u64 instruction_count) +{ + u64 cycles = 0; + while (instruction_count-- != 0) + cycles += static_cast(Step()); + return cycles; +} + +u64 ARMCore::RunCycles(u64 cycle_budget) +{ + const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::ARM); + u64 cycles = 0; + while (cycles < cycle_budget && !m_yield_requested) + { + if (m_waiting_for_memory_poll) + { + const u32 physical_poll_address = TranslateVirtualAddress(m_memory_poll_address); + if (!HasUnmaskedInterrupt() && + m_bus.IsSliceStablePollAddress(physical_poll_address, sizeof(u32))) + { + // This MMIO value cannot change before AdvanceCycles below. Consume the unused slice and + // retry the guest loop against the newly advanced device state next time. + m_waiting_for_memory_poll = false; + cycles = cycle_budget; + break; + } + if (HasUnmaskedInterrupt() || Read32(m_memory_poll_address) != 0) + { + m_waiting_for_memory_poll = false; + } + else + { + cycles = cycle_budget; + break; + } + } + if (m_waiting_for_interrupt && !m_irq_line && !m_fiq_line) + { + cycles = cycle_budget; + break; + } +#if defined(_M_X86_64) + if (m_jit_enabled && m_jit && !HasUnmaskedInterrupt()) + { + const u32 executed = m_jit->Run(cycle_budget - cycles); + if (executed != 0) + { + cycles += executed; + SampleExecutionPC(); + if (m_slice_fast_forward_requested) + { + m_slice_fast_forward_requested = false; + cycles = cycle_budget; + break; + } + continue; + } + } +#endif + cycles += static_cast(StepInternal(false)); + SampleExecutionPC(); + if (m_slice_fast_forward_requested) + { + m_slice_fast_forward_requested = false; + cycles = cycle_budget; + break; + } + } + // Starlet interrupt lines are sampled by the scheduler at slice boundaries. + // Updating timers and peripherals once here is therefore architecturally + // equivalent for interrupt delivery and avoids running the complete device + // clock path after every interpreted instruction. + m_bus.AdvanceCycles(cycles); + m_yield_requested = false; + return cycles; +} + +void ARMCore::SampleExecutionPC() +{ + if (m_executed_instructions < m_next_profile_instruction) + return; + + const u32 key = m_registers[15] | ((m_cpsr & CPSR_T) != 0 ? 1U : 0U); + ++m_pc_samples[key]; + m_next_profile_instruction += PROFILE_SAMPLE_INTERVAL; +} + +std::vector ARMCore::GetHotPCSamples(size_t maximum_count) +{ + std::vector> sorted(m_pc_samples.begin(), m_pc_samples.end()); + std::ranges::sort(sorted, {}, [](const auto& entry) { return entry.second; }); + if (sorted.size() > maximum_count) + sorted.resize(maximum_count); + + std::vector result; + result.reserve(sorted.size()); + for (auto it = sorted.rbegin(); it != sorted.rend(); ++it) + { + const bool thumb = (it->first & 1) != 0; + const u32 address = it->first & ~1U; + result.push_back( + {.address = address, + .instruction = thumb ? FetchThumbInstruction(address) : FetchARMInstruction(address), + .samples = it->second, + .thumb = thumb}); + } + return result; +} + +u64 ARMCore::GetJitExecutedInstructions() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetExecutedInstructions() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitNativeExecutedInstructions() const +{ +#if defined(_M_X86_64) + // Kept out of line so changes to the x64 JIT's diagnostic layout rebuild this translation unit. + return m_jit ? m_jit->GetNativeExecutedInstructions() : 0; +#else + return 0; +#endif +} + +size_t ARMCore::GetJitCompiledBlockCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetCompiledBlockCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitLifetimeCompiledBlockCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetLifetimeCompiledBlockCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitBlockLookupCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetBlockLookupCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitBlockCacheHitCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetBlockCacheHitCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitDispatchSlowCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetDispatchSlowCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitDispatchStaleGenerationCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetDispatchStaleGenerationCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitDispatchPhysicalAliasCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetDispatchPhysicalAliasCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitDispatchCollisionCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetDispatchCollisionCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitDispatchKeyMissCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetDispatchKeyMissCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitDispatchPageMissCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetDispatchPageMissCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitDispatchEmptyEntryCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetDispatchEmptyEntryCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitDispatchGeneratedKeyMismatchCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetDispatchGeneratedKeyMismatchCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitDispatchGeneratedSetMismatchCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetDispatchGeneratedSetMismatchCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitDispatchKeyMissWithExactMatchCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetDispatchKeyMissWithExactMatchCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitDispatchKeyMissWithPhysicalAliasCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetDispatchKeyMissWithPhysicalAliasCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitDispatchKeyMissWithSamePhysicalPageCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetDispatchKeyMissWithSamePhysicalPageCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitDispatchKeyMissWithEmptySlotCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetDispatchKeyMissWithEmptySlotCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitDispatchKeyMissWithFullSetCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetDispatchKeyMissWithFullSetCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitDispatchKeyMissWithStaleTranslationCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetDispatchKeyMissWithStaleTranslationCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitFastEntryEmptyInsertCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetFastEntryEmptyInsertCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitFastEntryReplacementCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetFastEntryReplacementCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitInstructionCacheClearCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetInstructionCacheClearCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitCodeSpaceClearCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetCodeSpaceClearCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitDiscardedBlockCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetDiscardedBlockCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitAddressTranslationCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetAddressTranslationCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitSlowReadCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetSlowReadCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitSlowWriteCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetSlowWriteCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitSlowRAMAccessCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetSlowRAMAccessCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitSlowSRAMAccessCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetSlowSRAMAccessCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitSlowMMIOAccessCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetSlowMMIOAccessCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitSlowOtherAccessCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetSlowOtherAccessCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitSlowSRAMLowAccessCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetSlowSRAMLowAccessCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitSlowSRAMHighAccessCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetSlowSRAMHighAccessCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitSlowSRAMReadAccessCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetSlowSRAMReadAccessCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitSlowSRAMWriteAccessCount() const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetSlowSRAMWriteAccessCount() : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitSlowSRAMPageAccessCount(size_t page) const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetSlowSRAMPageAccessCount(page) : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitSlowSRAMPageReadAccessCount(size_t page) const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetSlowSRAMPageReadAccessCount(page) : 0; +#else + return 0; +#endif +} + +u64 ARMCore::GetJitSlowSRAMPageWriteAccessCount(size_t page) const +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->GetSlowSRAMPageWriteAccessCount(page) : 0; +#else + return 0; +#endif +} + +std::vector> ARMCore::TakeHotJitSlowMemorySamples(size_t maximum_count) +{ +#if defined(_M_X86_64) + return m_jit ? m_jit->TakeHotSlowMemorySamples(maximum_count) : + std::vector>{}; +#else + return {}; +#endif +} + +void ARMCore::RecordJitFallback(u32 address, bool thumb) +{ + ++m_jit_fallback_instruction_count; + ++m_jit_fallback_interval_count; + // Sampling keeps profiling effectively free even when hundreds of millions of instructions + // use an interpreter side exit. + if ((m_jit_fallback_instruction_count & 0xfff) == 0) + { + ++m_jit_fallback_samples[address | (thumb ? 1U : 0U)]; + ++m_jit_fallback_interval_samples[address | (thumb ? 1U : 0U)]; + } +} + +std::vector ARMCore::GetHotJitFallbackSamples(size_t maximum_count) +{ + std::vector> sorted(m_jit_fallback_samples.begin(), + m_jit_fallback_samples.end()); + std::ranges::sort(sorted, {}, [](const auto& entry) { return entry.second; }); + if (sorted.size() > maximum_count) + sorted.erase(sorted.begin(), sorted.end() - maximum_count); + + std::vector result; + result.reserve(sorted.size()); + for (auto it = sorted.rbegin(); it != sorted.rend(); ++it) + { + const bool thumb = (it->first & 1) != 0; + const u32 address = it->first & ~1U; + result.push_back( + {.address = address, + .instruction = thumb ? FetchThumbInstruction(address) : FetchARMInstruction(address), + .samples = it->second, + .thumb = thumb}); + } + return result; +} + +ARMCore::HotPCSample ARMCore::GetCurrentPCSample() +{ + const bool thumb = (m_cpsr & CPSR_T) != 0; + return GetInstructionSample(m_registers[15], thumb); +} + +ARMCore::HotPCSample ARMCore::GetInstructionSample(u32 address, bool thumb) +{ + return {.address = address, + .instruction = thumb ? FetchThumbInstruction(address) : FetchARMInstruction(address), + .samples = 1, + .thumb = thumb}; +} + +u64 ARMCore::TakeJitFallbackIntervalCount() +{ + return std::exchange(m_jit_fallback_interval_count, 0); +} + +std::vector ARMCore::TakeHotJitFallbackIntervalSamples(size_t maximum_count) +{ + std::vector> sorted(m_jit_fallback_interval_samples.begin(), + m_jit_fallback_interval_samples.end()); + m_jit_fallback_interval_samples.clear(); + std::ranges::sort(sorted, {}, [](const auto& entry) { return entry.second; }); + if (sorted.size() > maximum_count) + sorted.erase(sorted.begin(), sorted.end() - maximum_count); + + std::vector result; + result.reserve(sorted.size()); + for (auto it = sorted.rbegin(); it != sorted.rend(); ++it) + { + const bool thumb = (it->first & 1) != 0; + const u32 address = it->first & ~1U; + result.push_back( + {.address = address, + .instruction = thumb ? FetchThumbInstruction(address) : FetchARMInstruction(address), + .samples = it->second, + .thumb = thumb}); + } + return result; +} + +void ARMCore::DoState(PointerWrap& p) +{ + if (p.IsReadMode()) + m_trace_generation = Common::PerformanceTrace::NewGeneration(); + p.DoArray(m_registers); + p.Do(m_cpsr); + p.DoArray(m_user_r8_r14); + p.DoArray(m_fiq_r8_r14); + p.DoArray(m_irq_r13_r14); + p.DoArray(m_svc_r13_r14); + p.DoArray(m_abt_r13_r14); + p.DoArray(m_und_r13_r14); + p.Do(m_spsr_fiq); + p.Do(m_spsr_irq); + p.Do(m_spsr_svc); + p.Do(m_spsr_abt); + p.Do(m_spsr_und); + p.Do(m_cp15); + p.Do(m_big_endian); + p.Do(m_irq_line); + p.Do(m_fiq_line); + p.Do(m_waiting_for_interrupt); + p.Do(m_waiting_for_memory_poll); + p.Do(m_yield_requested); + p.Do(m_slice_fast_forward_requested); + p.Do(m_memory_poll_address); + p.Do(m_pc_written); + p.Do(m_instruction_address); + p.Do(m_last_undefined_instruction); + p.Do(m_executed_instructions); + // The cache is derived entirely from architectural CP15 state and guest page + // tables. + InvalidateTLB(); + InvalidateInstructionCache(); +} + +void ARMCore::ExecuteARM(u32 instruction) +{ + // Branch/exchange and count-leading-zero are ARMv5 additions encoded in the + // data-processing space, so they must be recognized before the generic + // decoder. + if ((instruction & 0x0ffffff0) == 0x012fff10) // BX + { + WritePC(ReadRegisterOperand(instruction & 0xf), true); + return; + } + if ((instruction & 0x0ffffff0) == 0x012fff30) // BLX register + { + const u32 target = ReadRegisterOperand(instruction & 0xf); + m_registers[14] = m_instruction_address + 4; + WritePC(target, true); + return; + } + if ((instruction & 0x0fff0ff0) == 0x016f0f10) // CLZ + { + const u32 rd = (instruction >> 12) & 0xf; + const u32 value = ReadRegisterOperand(instruction & 0xf); + m_registers[rd] = value == 0 ? 32 : std::countl_zero(value); + return; + } + + if ((instruction & 0x0f8000f0) == 0x00800090) + { + ExecuteMultiplyLong(instruction); + return; + } + if ((instruction & 0x0fc000f0) == 0x00000090) + { + ExecuteMultiply(instruction); + return; + } + + // SWP/SWPB. + if ((instruction & 0x0fb00ff0) == 0x01000090) + { + const bool byte = (instruction & (1U << 22)) != 0; + const u32 rn = (instruction >> 16) & 0xf; + const u32 rd = (instruction >> 12) & 0xf; + const u32 rm = instruction & 0xf; + const u32 address = ReadRegisterOperand(rn); + if (byte) + { + const u8 old = ReadByte(address); + WriteByte(address, static_cast(ReadRegisterOperand(rm))); + m_registers[rd] = old; + } + else + { + const u32 old = Read32(address & ~3U); + Write32(address & ~3U, ReadRegisterOperand(rm)); + m_registers[rd] = old; + } + return; + } + + const bool is_mrs = (instruction & 0x0fbf0fff) == 0x010f0000; + const bool is_msr_register = (instruction & 0x0fb0fff0) == 0x0120f000; + const bool is_msr_immediate = (instruction & 0x0fb0f000) == 0x0320f000; + if (is_mrs || is_msr_register || is_msr_immediate) + { + ExecutePSRTransfer(instruction); + return; + } + + if ((instruction & 0x0e000090) == 0x00000090) + { + ExecuteHalfwordDataTransfer(instruction); + return; + } + + switch ((instruction >> 25) & 7) + { + case 0: + case 1: + ExecuteDataProcessing(instruction); + return; + case 2: + case 3: + ExecuteSingleDataTransfer(instruction); + return; + case 4: + ExecuteBlockDataTransfer(instruction); + return; + case 5: + ExecuteBranch(instruction); + return; + case 7: + if ((instruction & 0x0f000000) == 0x0f000000) + { + EnterException(Mode::Supervisor, STARLET_EXCEPTION_VECTOR_BASE + 0x08, + m_instruction_address + 4); + return; + } + if (instruction & (1U << 4)) + { + ExecuteCoprocessor(instruction); + return; + } + break; + default: + break; + } + + UndefinedInstruction(instruction); +} + +void ARMCore::ExecuteThumb(u16 instruction) +{ + const auto set_logical_flags = [this](u32 value, bool carry, bool update_carry) { + const bool old_overflow = (m_cpsr & CPSR_V) != 0; + SetNZ(value); + if (update_carry) + { + m_cpsr &= ~CPSR_C; + if (carry) + m_cpsr |= CPSR_C; + } + if (old_overflow) + m_cpsr |= CPSR_V; + }; + const auto set_arithmetic_flags = [this](const AddResult& result) { + SetNZ(result.value); + m_cpsr &= ~(CPSR_C | CPSR_V); + if (result.carry) + m_cpsr |= CPSR_C; + if (result.overflow) + m_cpsr |= CPSR_V; + }; + + // Move shifted register and add/subtract. + if ((instruction & 0xe000) == 0x0000) + { + if ((instruction & 0x1800) == 0x1800) + { + const bool immediate = (instruction & 0x0400) != 0; + const bool subtract = (instruction & 0x0200) != 0; + const u32 operand = + immediate ? ((instruction >> 6) & 7) : m_registers[(instruction >> 6) & 7]; + const u32 rs = (instruction >> 3) & 7; + const u32 rd = instruction & 7; + const AddResult result = subtract ? AddWithCarry(m_registers[rs], ~operand, true) : + AddWithCarry(m_registers[rs], operand, false); + m_registers[rd] = result.value; + set_arithmetic_flags(result); + return; + } + + const u32 type = (instruction >> 11) & 3; + const u32 amount = (instruction >> 6) & 0x1f; + const u32 rs = (instruction >> 3) & 7; + const u32 rd = instruction & 7; + const ShiftResult result = + ShiftImmediate(m_registers[rs], type, amount, (m_cpsr & CPSR_C) != 0); + m_registers[rd] = result.value; + set_logical_flags(result.value, result.carry, true); + return; + } + + // Move/compare/add/subtract immediate. + if ((instruction & 0xe000) == 0x2000) + { + const u32 opcode = (instruction >> 11) & 3; + const u32 rd = (instruction >> 8) & 7; + const u32 immediate = instruction & 0xff; + if (opcode == 0) // MOV + { + m_registers[rd] = immediate; + set_logical_flags(immediate, false, false); + } + else + { + const bool subtract = opcode == 1 || opcode == 3; + const u32 lhs = m_registers[rd]; + const AddResult result = + subtract ? AddWithCarry(lhs, ~immediate, true) : AddWithCarry(lhs, immediate, false); + if (opcode != 1) // CMP does not write. + m_registers[rd] = result.value; + set_arithmetic_flags(result); + } + return; + } + + // ALU operations. + if ((instruction & 0xfc00) == 0x4000) + { + const u32 opcode = (instruction >> 6) & 0xf; + const u32 rs = (instruction >> 3) & 7; + const u32 rd = instruction & 7; + const u32 lhs = m_registers[rd]; + const u32 rhs = m_registers[rs]; + u32 result = 0; + bool write_result = true; + bool arithmetic = false; + bool update_carry = false; + bool carry = false; + AddResult add{}; + switch (opcode) + { + case 0x0: // AND + result = lhs & rhs; + break; + case 0x1: // EOR + result = lhs ^ rhs; + break; + case 0x2: // LSL + case 0x3: // LSR + case 0x4: // ASR + case 0x7: // ROR + { + const u32 type = opcode == 0x7 ? 3 : opcode - 2; + const ShiftResult shifted = ShiftRegister(lhs, type, rhs & 0xff, (m_cpsr & CPSR_C) != 0); + result = shifted.value; + carry = shifted.carry; + update_carry = true; + break; + } + case 0x5: // ADC + add = AddWithCarry(lhs, rhs, (m_cpsr & CPSR_C) != 0); + result = add.value; + arithmetic = true; + break; + case 0x6: // SBC + add = AddWithCarry(lhs, ~rhs, (m_cpsr & CPSR_C) != 0); + result = add.value; + arithmetic = true; + break; + case 0x8: // TST + result = lhs & rhs; + write_result = false; + break; + case 0x9: // NEG + add = AddWithCarry(0, ~rhs, true); + result = add.value; + arithmetic = true; + break; + case 0xa: // CMP + add = AddWithCarry(lhs, ~rhs, true); + result = add.value; + arithmetic = true; + write_result = false; + break; + case 0xb: // CMN + add = AddWithCarry(lhs, rhs, false); + result = add.value; + arithmetic = true; + write_result = false; + break; + case 0xc: // ORR + result = lhs | rhs; + break; + case 0xd: // MUL + result = lhs * rhs; + break; + case 0xe: // BIC + result = lhs & ~rhs; + break; + case 0xf: // MVN + result = ~rhs; + break; + } + + if (write_result) + m_registers[rd] = result; + if (arithmetic) + set_arithmetic_flags(add); + else + set_logical_flags(result, carry, update_carry); + return; + } + + // High-register operations and BX/BLX. + if ((instruction & 0xfc00) == 0x4400) + { + const u32 opcode = (instruction >> 8) & 3; + const u32 rd = (instruction & 7) | ((instruction >> 4) & 8); + const u32 rs = ((instruction >> 3) & 7) | ((instruction >> 3) & 8); + const u32 rhs = ReadRegisterOperand(rs); + if (opcode == 0) // ADD + { + const u32 result = ReadRegisterOperand(rd) + rhs; + if (rd == 15) + WritePC(result); + else + m_registers[rd] = result; + } + else if (opcode == 1) // CMP + { + set_arithmetic_flags(AddWithCarry(ReadRegisterOperand(rd), ~rhs, true)); + } + else if (opcode == 2) // MOV + { + if (rd == 15) + WritePC(rhs); + else + m_registers[rd] = rhs; + } + else // BX/BLX + { + if (instruction & 0x0080) + m_registers[14] = (m_instruction_address + 2) | 1; + WritePC(rhs, true); + } + return; + } + + // PC-relative load. + if ((instruction & 0xf800) == 0x4800) + { + const u32 rd = (instruction >> 8) & 7; + const u32 address = ((m_instruction_address + 4) & ~3U) + ((instruction & 0xff) << 2); + m_registers[rd] = Read32(address); + return; + } + + // Register-offset loads and stores. + if ((instruction & 0xf000) == 0x5000) + { + const u32 operation = (instruction >> 9) & 7; + const u32 ro = (instruction >> 6) & 7; + const u32 rb = (instruction >> 3) & 7; + const u32 rd = instruction & 7; + const u32 address = m_registers[rb] + m_registers[ro]; + switch (operation) + { + case 0: // STR + Write32(address & ~3U, m_registers[rd]); + break; + case 1: // STRH + Write16(address, static_cast(m_registers[rd])); + break; + case 2: // STRB + WriteByte(address, static_cast(m_registers[rd])); + break; + case 3: // LDRSB + m_registers[rd] = static_cast(static_cast(static_cast(ReadByte(address)))); + break; + case 4: // LDR + m_registers[rd] = std::rotr(Read32(address & ~3U), static_cast((address & 3) * 8)); + break; + case 5: // LDRH + m_registers[rd] = Read16(address); + break; + case 6: // LDRB + m_registers[rd] = ReadByte(address); + break; + case 7: // LDRSH + m_registers[rd] = static_cast(static_cast(static_cast(Read16(address)))); + break; + } + return; + } + + // Immediate-offset word/byte loads and stores. + if ((instruction & 0xe000) == 0x6000) + { + const bool byte = (instruction & 0x1000) != 0; + const bool load = (instruction & 0x0800) != 0; + const u32 offset = ((instruction >> 6) & 0x1f) << (byte ? 0 : 2); + const u32 rb = (instruction >> 3) & 7; + const u32 rd = instruction & 7; + const u32 address = m_registers[rb] + offset; + if (load) + { + m_registers[rd] = byte ? ReadByte(address) : Read32(address); + } + else if (byte) + { + WriteByte(address, static_cast(m_registers[rd])); + } + else + { + Write32(address, m_registers[rd]); + } + return; + } + + // Immediate-offset halfword loads and stores. + if ((instruction & 0xf000) == 0x8000) + { + const bool load = (instruction & 0x0800) != 0; + const u32 address = m_registers[(instruction >> 3) & 7] + (((instruction >> 6) & 0x1f) << 1); + const u32 rd = instruction & 7; + if (load) + m_registers[rd] = Read16(address); + else + Write16(address, static_cast(m_registers[rd])); + return; + } + + // SP-relative loads and stores. + if ((instruction & 0xf000) == 0x9000) + { + const bool load = (instruction & 0x0800) != 0; + const u32 rd = (instruction >> 8) & 7; + const u32 address = m_registers[13] + ((instruction & 0xff) << 2); + if (load) + m_registers[rd] = Read32(address); + else + Write32(address, m_registers[rd]); + return; + } + + // Load address relative to PC or SP. + if ((instruction & 0xf000) == 0xa000) + { + const bool use_sp = (instruction & 0x0800) != 0; + const u32 rd = (instruction >> 8) & 7; + const u32 base = use_sp ? m_registers[13] : ((m_instruction_address + 4) & ~3U); + m_registers[rd] = base + ((instruction & 0xff) << 2); + return; + } + + // Add/subtract immediate to SP. + if ((instruction & 0xff00) == 0xb000) + { + const u32 offset = (instruction & 0x7f) << 2; + if (instruction & 0x0080) + m_registers[13] -= offset; + else + m_registers[13] += offset; + return; + } + + // PUSH/POP. + if ((instruction & 0xf600) == 0xb400) + { + const bool pop = (instruction & 0x0800) != 0; + const bool extra = (instruction & 0x0100) != 0; + const u32 register_list = instruction & 0xff; + const u32 count = std::popcount(register_list) + static_cast(extra); + if (count == 0) + { + UndefinedInstruction(instruction); + return; + } + + if (!pop) + { + u32 address = m_registers[13] - count * 4; + m_registers[13] = address; + for (u32 reg = 0; reg < 8; ++reg) + { + if (register_list & (1U << reg)) + { + Write32(address, m_registers[reg]); + address += 4; + } + } + if (extra) + Write32(address, m_registers[14]); + } + else + { + u32 address = m_registers[13]; + for (u32 reg = 0; reg < 8; ++reg) + { + if (register_list & (1U << reg)) + { + m_registers[reg] = Read32(address); + address += 4; + } + } + if (extra) + { + const u32 pc = Read32(address); + address += 4; + WritePC(pc, true); + } + m_registers[13] = address; + } + return; + } + + if ((instruction & 0xff00) == 0xbe00) // BKPT + { + UndefinedInstruction(instruction); + return; + } + + // Multiple load/store. + if ((instruction & 0xf000) == 0xc000) + { + const bool load = (instruction & 0x0800) != 0; + const u32 rb = (instruction >> 8) & 7; + const u32 register_list = instruction & 0xff; + if (register_list == 0) + { + UndefinedInstruction(instruction); + return; + } + u32 address = m_registers[rb]; + for (u32 reg = 0; reg < 8; ++reg) + { + if (!(register_list & (1U << reg))) + continue; + if (load) + m_registers[reg] = Read32(address); + else + Write32(address, m_registers[reg]); + address += 4; + } + if (!(load && (register_list & (1U << rb)))) + m_registers[rb] = address; + return; + } + + // Conditional branch and SWI. + if ((instruction & 0xf000) == 0xd000) + { + const u32 condition = (instruction >> 8) & 0xf; + if (condition == 0xf) + { + EnterException(Mode::Supervisor, STARLET_EXCEPTION_VECTOR_BASE + 0x08, + m_instruction_address + 2); + } + else if (condition == 0xe) + { + UndefinedInstruction(instruction); + } + else if (ConditionPassed(condition)) + { + const s32 offset = SignExtend(instruction & 0xff, 8) * 2; + WritePC(m_instruction_address + 4 + static_cast(offset)); + } + return; + } + + // Unconditional branch. + if ((instruction & 0xf800) == 0xe000) + { + const s32 offset = SignExtend(instruction & 0x7ff, 11) * 2; + WritePC(m_instruction_address + 4 + static_cast(offset)); + return; + } + + // BL/BLX first and second half. + if ((instruction & 0xf800) == 0xf000) + { + const s32 high_offset = SignExtend(instruction & 0x7ff, 11) * 4096; + m_registers[14] = m_instruction_address + 4 + static_cast(high_offset); + return; + } + if ((instruction & 0xf800) == 0xf800 || (instruction & 0xf800) == 0xe800) + { + const bool exchange = (instruction & 0xf800) == 0xe800; + const u32 target = m_registers[14] + ((instruction & 0x7ff) << 1); + m_registers[14] = (m_instruction_address + 2) | 1; + if (exchange) + m_cpsr &= ~CPSR_T; + WritePC(target); + return; + } + + UndefinedInstruction(instruction); +} + +void ARMCore::ExecuteDataProcessing(u32 instruction) +{ + const u32 opcode = (instruction >> 21) & 0xf; + const bool set_flags = (instruction & (1U << 20)) != 0; + const u32 rn = (instruction >> 16) & 0xf; + const u32 rd = (instruction >> 12) & 0xf; + const u32 lhs = ReadRegisterOperand(rn); + const ShiftResult operand2 = DecodeOperand2(instruction); + + u32 result = 0; + bool write_result = true; + bool arithmetic = false; + AddResult add{}; + switch (opcode) + { + case 0x0: // AND + result = lhs & operand2.value; + break; + case 0x1: // EOR + result = lhs ^ operand2.value; + break; + case 0x2: // SUB + add = AddWithCarry(lhs, ~operand2.value, true); + result = add.value; + arithmetic = true; + break; + case 0x3: // RSB + add = AddWithCarry(operand2.value, ~lhs, true); + result = add.value; + arithmetic = true; + break; + case 0x4: // ADD + add = AddWithCarry(lhs, operand2.value, false); + result = add.value; + arithmetic = true; + break; + case 0x5: // ADC + add = AddWithCarry(lhs, operand2.value, (m_cpsr & CPSR_C) != 0); + result = add.value; + arithmetic = true; + break; + case 0x6: // SBC + add = AddWithCarry(lhs, ~operand2.value, (m_cpsr & CPSR_C) != 0); + result = add.value; + arithmetic = true; + break; + case 0x7: // RSC + add = AddWithCarry(operand2.value, ~lhs, (m_cpsr & CPSR_C) != 0); + result = add.value; + arithmetic = true; + break; + case 0x8: // TST + result = lhs & operand2.value; + write_result = false; + break; + case 0x9: // TEQ + result = lhs ^ operand2.value; + write_result = false; + break; + case 0xa: // CMP + add = AddWithCarry(lhs, ~operand2.value, true); + result = add.value; + arithmetic = true; + write_result = false; + break; + case 0xb: // CMN + add = AddWithCarry(lhs, operand2.value, false); + result = add.value; + arithmetic = true; + write_result = false; + break; + case 0xc: // ORR + result = lhs | operand2.value; + break; + case 0xd: // MOV + result = operand2.value; + break; + case 0xe: // BIC + result = lhs & ~operand2.value; + break; + case 0xf: // MVN + result = ~operand2.value; + break; + } + + if (set_flags || !write_result) + { + const bool old_overflow = (m_cpsr & CPSR_V) != 0; + SetNZ(result); + m_cpsr &= ~(CPSR_C | CPSR_V); + if (arithmetic) + { + if (add.carry) + m_cpsr |= CPSR_C; + if (add.overflow) + m_cpsr |= CPSR_V; + } + else + { + if (operand2.carry) + m_cpsr |= CPSR_C; + // Logical operations leave V unchanged. + if (old_overflow) + m_cpsr |= CPSR_V; + } + } + + if (!write_result) + return; + if (rd == 15) + { + if (set_flags) + RestoreCPSRFromSPSR(); + WritePC(result); + } + else + { + m_registers[rd] = result; + } +} + +void ARMCore::ExecuteSingleDataTransfer(u32 instruction) +{ + const bool register_offset = (instruction & (1U << 25)) != 0; + const bool preindex = (instruction & (1U << 24)) != 0; + const bool add_offset = (instruction & (1U << 23)) != 0; + const bool byte = (instruction & (1U << 22)) != 0; + const bool writeback = (instruction & (1U << 21)) != 0; + const bool load = (instruction & (1U << 20)) != 0; + const u32 rn = (instruction >> 16) & 0xf; + const u32 rd = (instruction >> 12) & 0xf; + + u32 offset; + if (register_offset) + { + if (instruction & (1U << 4)) + { + UndefinedInstruction(instruction); + return; + } + const u32 rm = ReadRegisterOperand(instruction & 0xf); + offset = ShiftImmediate(rm, (instruction >> 5) & 3, (instruction >> 7) & 0x1f, + (m_cpsr & CPSR_C) != 0) + .value; + } + else + { + offset = instruction & 0xfff; + } + + const u32 base = ReadRegisterOperand(rn); + const u32 indexed = add_offset ? base + offset : base - offset; + const u32 address = preindex ? indexed : base; + if (load) + { + u32 value; + if (byte) + { + value = ReadByte(address); + } + else + { + const u32 aligned_value = Read32(address & ~3U); + value = std::rotr(aligned_value, static_cast((address & 3) * 8)); + } + + if (rd == 15) + WritePC(value, true); + else + m_registers[rd] = value; + } + else + { + const u32 value = ReadRegisterForStore(rd); + if (byte) + WriteByte(address, static_cast(value)); + else + Write32(address & ~3U, value); + } + + if ((!preindex || writeback) && !(load && rn == rd)) + m_registers[rn] = indexed; +} + +void ARMCore::ExecuteHalfwordDataTransfer(u32 instruction) +{ + const bool preindex = (instruction & (1U << 24)) != 0; + const bool add_offset = (instruction & (1U << 23)) != 0; + const bool immediate = (instruction & (1U << 22)) != 0; + const bool writeback = (instruction & (1U << 21)) != 0; + const bool load = (instruction & (1U << 20)) != 0; + const u32 rn = (instruction >> 16) & 0xf; + const u32 rd = (instruction >> 12) & 0xf; + const u32 type = (instruction >> 5) & 3; + const u32 offset = immediate ? (((instruction >> 4) & 0xf0) | (instruction & 0xf)) : + ReadRegisterOperand(instruction & 0xf); + const u32 base = ReadRegisterOperand(rn); + const u32 indexed = add_offset ? base + offset : base - offset; + const u32 address = preindex ? indexed : base; + + // ARMv5TE encodes LDRD/STRD in the extra load/store space with L clear. + // The even register names the first word; its odd successor names the + // second. ARM926 requires an 8-byte-aligned effective address. + if (!load && (type == 2 || type == 3)) + { + const bool update_base = !preindex || writeback; + if ((rd & 1) != 0 || rd >= 14 || (update_base && (rn == rd || rn == rd + 1))) + { + UndefinedInstruction(instruction); + return; + } + if ((address & 7) != 0) + { + m_cp15.fault_status = 1; // alignment fault + m_cp15.fault_address = address; + EnterException(Mode::Abort, STARLET_EXCEPTION_VECTOR_BASE + 0x10, m_instruction_address + 8); + return; + } + if (type == 2) // LDRD + { + const u32 first = Read32(address); + const u32 second = Read32(address + 4); + m_registers[rd] = first; + m_registers[rd + 1] = second; + } + else // STRD + { + Write32(address, ReadRegisterForStore(rd)); + Write32(address + 4, ReadRegisterForStore(rd + 1)); + } + if (update_base) + m_registers[rn] = indexed; + return; + } + + if (load) + { + u32 value; + switch (type) + { + case 1: // LDRH + value = Read16(address); + break; + case 2: // LDRSB + value = static_cast(static_cast(static_cast(ReadByte(address)))); + break; + case 3: // LDRSH + value = static_cast(static_cast(static_cast(Read16(address)))); + break; + default: + UndefinedInstruction(instruction); + return; + } + if (rd == 15) + WritePC(value); + else + m_registers[rd] = value; + } + else + { + if (type != 1) + { + UndefinedInstruction(instruction); + return; + } + Write16(address, static_cast(ReadRegisterForStore(rd))); + } + + if ((!preindex || writeback) && !(load && rn == rd)) + m_registers[rn] = indexed; +} + +void ARMCore::ExecuteBlockDataTransfer(u32 instruction) +{ + const bool preindex = (instruction & (1U << 24)) != 0; + const bool increment = (instruction & (1U << 23)) != 0; + const bool psr_or_user = (instruction & (1U << 22)) != 0; + const bool writeback = (instruction & (1U << 21)) != 0; + const bool load = (instruction & (1U << 20)) != 0; + const u32 rn = (instruction >> 16) & 0xf; + const u32 register_list = instruction & 0xffff; + if (register_list == 0) + { + UndefinedInstruction(instruction); + return; + } + + const u32 count = std::popcount(register_list); + const u32 base = ReadRegisterOperand(rn); + const bool user_bank_transfer = psr_or_user && !(load && (register_list & (1U << 15)) != 0); + const auto read_user_register = [this](u32 reg) { + if (GetMode() == Mode::User || GetMode() == Mode::System || reg < 8 || + (reg < 13 && GetMode() != Mode::FIQ)) + return ReadRegisterForStore(reg); + if (reg < 15) + return m_user_r8_r14[reg - 8]; + return ReadRegisterForStore(reg); + }; + const auto write_user_register = [this](u32 reg, u32 value) { + if (GetMode() == Mode::User || GetMode() == Mode::System || reg < 8 || + (reg < 13 && GetMode() != Mode::FIQ)) + m_registers[reg] = value; + else if (reg < 15) + m_user_r8_r14[reg - 8] = value; + }; + u32 address; + if (increment) + address = preindex ? base + 4 : base; + else + address = preindex ? base - count * 4 : base - (count - 1) * 4; + + u32 loaded_pc = 0; + bool pc_loaded = false; + for (u32 reg = 0; reg < 16; ++reg) + { + if (!(register_list & (1U << reg))) + continue; + if (load) + { + const u32 value = Read32(address); + if (reg == 15) + { + loaded_pc = value; + pc_loaded = true; + } + else + { + if (user_bank_transfer) + write_user_register(reg, value); + else + m_registers[reg] = value; + } + } + else + { + Write32(address, user_bank_transfer ? read_user_register(reg) : ReadRegisterForStore(reg)); + } + address += 4; + } + + if (writeback && !(load && (register_list & (1U << rn)))) + m_registers[rn] = increment ? base + count * 4 : base - count * 4; + + if (pc_loaded) + { + if (psr_or_user) + RestoreCPSRFromSPSR(); + WritePC(loaded_pc, true); + } +} + +void ARMCore::ExecuteMultiply(u32 instruction) +{ + const bool accumulate = (instruction & (1U << 21)) != 0; + const bool set_flags = (instruction & (1U << 20)) != 0; + const u32 rd = (instruction >> 16) & 0xf; + const u32 rn = (instruction >> 12) & 0xf; + const u32 rs = (instruction >> 8) & 0xf; + const u32 rm = instruction & 0xf; + u32 result = ReadRegisterOperand(rm) * ReadRegisterOperand(rs); + if (accumulate) + result += ReadRegisterOperand(rn); + m_registers[rd] = result; + if (set_flags) + SetNZ(result); +} + +void ARMCore::ExecuteMultiplyLong(u32 instruction) +{ + const bool signed_multiply = (instruction & (1U << 22)) != 0; + const bool accumulate = (instruction & (1U << 21)) != 0; + const bool set_flags = (instruction & (1U << 20)) != 0; + const u32 rd_hi = (instruction >> 16) & 0xf; + const u32 rd_lo = (instruction >> 12) & 0xf; + const u32 rs = (instruction >> 8) & 0xf; + const u32 rm = instruction & 0xf; + + u64 result; + if (signed_multiply) + { + result = static_cast(static_cast(static_cast(ReadRegisterOperand(rm))) * + static_cast(static_cast(ReadRegisterOperand(rs)))); + } + else + { + result = static_cast(ReadRegisterOperand(rm)) * ReadRegisterOperand(rs); + } + if (accumulate) + result += (static_cast(m_registers[rd_hi]) << 32) | m_registers[rd_lo]; + m_registers[rd_lo] = static_cast(result); + m_registers[rd_hi] = static_cast(result >> 32); + if (set_flags) + { + m_cpsr &= ~(CPSR_N | CPSR_Z); + if (result & (1ULL << 63)) + m_cpsr |= CPSR_N; + if (result == 0) + m_cpsr |= CPSR_Z; + } +} + +void ARMCore::ExecuteBranch(u32 instruction) +{ + const bool link = (instruction & (1U << 24)) != 0; + const s32 offset = SignExtend((instruction & 0x00ffffff) << 2, 26); + if (link) + m_registers[14] = m_instruction_address + 4; + WritePC(m_instruction_address + 8 + static_cast(offset)); +} + +void ARMCore::ExecuteCoprocessor(u32 instruction) +{ + const bool load = (instruction & (1U << 20)) != 0; + const u32 opcode1 = (instruction >> 21) & 7; + const u32 crn = (instruction >> 16) & 0xf; + const u32 rd = (instruction >> 12) & 0xf; + const u32 coprocessor = (instruction >> 8) & 0xf; + const u32 opcode2 = (instruction >> 5) & 7; + const u32 crm = instruction & 0xf; + if (coprocessor != 15) + { + UndefinedInstruction(instruction); + return; + } + + if (load) + { + const u32 value = ReadCP15(opcode1, crn, crm, opcode2); + if (rd == 15) + m_cpsr = (m_cpsr & 0x0fffffff) | (value & 0xf0000000); + else + m_registers[rd] = value; + } + else + { + WriteCP15(opcode1, crn, crm, opcode2, ReadRegisterOperand(rd)); + } +} + +void ARMCore::ExecutePSRTransfer(u32 instruction) +{ + if ((instruction & 0x0fbf0fff) == 0x010f0000) // MRS + { + const bool spsr = (instruction & (1U << 22)) != 0; + const u32 rd = (instruction >> 12) & 0xf; + const u32* source = spsr ? GetSPSR(GetMode()) : &m_cpsr; + m_registers[rd] = source ? *source : m_cpsr; + return; + } + + const bool spsr = (instruction & (1U << 22)) != 0; + const u32 field_mask = (instruction >> 16) & 0xf; + u32 value; + if (instruction & (1U << 25)) + { + const u32 rotate = ((instruction >> 8) & 0xf) * 2; + value = std::rotr(instruction & 0xff, static_cast(rotate)); + } + else + { + value = ReadRegisterOperand(instruction & 0xf); + } + WritePSR(spsr, field_mask, value); +} + +void ARMCore::EnterException(Mode mode, u32 vector, u32 return_address, bool disable_fiq) +{ + const u32 old_cpsr = m_cpsr; + SwitchMode(mode); + if (u32* spsr = GetSPSR(mode)) + *spsr = old_cpsr; + m_cpsr = (old_cpsr & ~(CPSR_MODE_MASK | CPSR_T)) | ModeValue(mode) | CPSR_I; + if (disable_fiq) + m_cpsr |= CPSR_F; + m_registers[14] = return_address; + m_registers[15] = vector & ~3U; + m_pc_written = true; +} + +void ARMCore::UndefinedInstruction(u32 instruction) +{ + m_last_undefined_instruction = instruction; + const u32 return_address = m_instruction_address + ((m_cpsr & CPSR_T) ? 2 : 4); + EnterException(Mode::Undefined, STARLET_EXCEPTION_VECTOR_BASE + 0x04, return_address); +} + +void ARMCore::SaveBankedRegisters(Mode mode) +{ + if (mode == Mode::FIQ) + { + for (size_t i = 0; i < 7; ++i) + m_fiq_r8_r14[i] = m_registers[8 + i]; + return; + } + + for (size_t i = 0; i < 5; ++i) + m_user_r8_r14[i] = m_registers[8 + i]; + std::array* bank = nullptr; + switch (mode) + { + case Mode::User: + case Mode::System: + m_user_r8_r14[5] = m_registers[13]; + m_user_r8_r14[6] = m_registers[14]; + return; + case Mode::IRQ: + bank = &m_irq_r13_r14; + break; + case Mode::Supervisor: + bank = &m_svc_r13_r14; + break; + case Mode::Abort: + bank = &m_abt_r13_r14; + break; + case Mode::Undefined: + bank = &m_und_r13_r14; + break; + default: + return; + } + (*bank)[0] = m_registers[13]; + (*bank)[1] = m_registers[14]; +} + +void ARMCore::LoadBankedRegisters(Mode mode) +{ + if (mode == Mode::FIQ) + { + for (size_t i = 0; i < 7; ++i) + m_registers[8 + i] = m_fiq_r8_r14[i]; + return; + } + + for (size_t i = 0; i < 5; ++i) + m_registers[8 + i] = m_user_r8_r14[i]; + const std::array* bank = nullptr; + switch (mode) + { + case Mode::User: + case Mode::System: + m_registers[13] = m_user_r8_r14[5]; + m_registers[14] = m_user_r8_r14[6]; + return; + case Mode::IRQ: + bank = &m_irq_r13_r14; + break; + case Mode::Supervisor: + bank = &m_svc_r13_r14; + break; + case Mode::Abort: + bank = &m_abt_r13_r14; + break; + case Mode::Undefined: + bank = &m_und_r13_r14; + break; + default: + return; + } + m_registers[13] = (*bank)[0]; + m_registers[14] = (*bank)[1]; +} + +void ARMCore::SwitchMode(Mode mode) +{ + const Mode old_mode = GetMode(); + if (old_mode == mode) + return; + SaveBankedRegisters(old_mode); + m_cpsr = (m_cpsr & ~CPSR_MODE_MASK) | ModeValue(mode); + LoadBankedRegisters(mode); +} + +u32* ARMCore::GetSPSR(Mode mode) +{ + switch (mode) + { + case Mode::FIQ: + return &m_spsr_fiq; + case Mode::IRQ: + return &m_spsr_irq; + case Mode::Supervisor: + return &m_spsr_svc; + case Mode::Abort: + return &m_spsr_abt; + case Mode::Undefined: + return &m_spsr_und; + default: + return nullptr; + } +} + +const u32* ARMCore::GetSPSR(Mode mode) const +{ + return const_cast(this)->GetSPSR(mode); +} + +void ARMCore::RestoreCPSRFromSPSR() +{ + if (const u32* spsr = GetSPSR(GetMode())) + SetCPSR(*spsr); +} + +void ARMCore::WritePSR(bool spsr, u32 field_mask, u32 value) +{ + u32 mask = 0; + if (field_mask & 1) + mask |= 0x000000ff; + if (field_mask & 2) + mask |= 0x0000ff00; + if (field_mask & 4) + mask |= 0x00ff0000; + if (field_mask & 8) + mask |= 0xff000000; + + if (spsr) + { + if (u32* destination = GetSPSR(GetMode())) + *destination = (*destination & ~mask) | (value & mask); + return; + } + + if (GetMode() == Mode::User) + mask &= 0xff000000; + SetCPSR((m_cpsr & ~mask) | (value & mask)); +} + +u32 ARMCore::ReadCP15(u32 opcode1, u32 crn, u32 crm, u32 opcode2) const +{ + if (opcode1 == 0 && crn == 0 && crm == 0 && opcode2 == 0) + return 0x41069265; // ARM926EJ-S main ID register. + if (opcode1 == 0 && crn == 7 && (crm == 10 || crm == 14) && opcode2 == 3) + { + // ARM926 test-and-clean operations return the D-cache dirty state in + // bit 30. Caches are not modeled, so memory is already coherent and the + // cache must always report clean. + return 1U << 30; + } + if (opcode1 != 0) + return 0; + switch (crn) + { + case 1: + return m_cp15.control; + case 2: + return m_cp15.translation_table_base; + case 3: + return m_cp15.domain_access_control; + case 5: + return m_cp15.fault_status; + case 6: + return m_cp15.fault_address; + case 13: + return m_cp15.process_id; + default: + return 0; + } +} + +void ARMCore::WriteCP15(u32 opcode1, u32 crn, u32 crm, u32 opcode2, u32 value) +{ + if (opcode1 != 0) + return; + + // ARM926EJ-S wait for interrupt: MCR p15, 0, Rd, c7, c0, 4. If an interrupt + // is already asserted the instruction completes without entering the + // low-power state. + if (crn == 7 && crm == 0 && opcode2 == 4) + { + m_waiting_for_interrupt = !m_irq_line && !m_fiq_line; + return; + } + + switch (crn) + { + case 1: + m_cp15.control = value; + m_big_endian = (value & CP15_CONTROL_BIG_ENDIAN) != 0; + InvalidateTLB(); + InvalidateInstructionCache(); + break; + case 2: + m_cp15.translation_table_base = value; + break; + case 3: + m_cp15.domain_access_control = value; + break; + case 5: + m_cp15.fault_status = value; + break; + case 6: + m_cp15.fault_address = value; + break; + case 7: + // ARM926 separates I-cache and D-cache maintenance. In particular c7,c10,4 is Drain Write + // Buffer, an ordering operation used heavily by IOS; invalidating translated code for it is + // both architecturally wrong and catastrophically expensive. Only operations which include + // the instruction cache make previously modified instructions visible. + if (crm == 5 || crm == 7) + InvalidateInstructionCache(); + break; + case 8: // TLB maintenance. + InvalidateTLB(); + break; + case 13: + // ARM926 FCSE is specifically designed to switch low-address process spaces without flushing + // either cache or TLB: their tags contain the modified virtual address (MVA), which already + // includes PID[31:25]. IOS writes c13 at virtually every process switch. Retaining those MVA + // translations is both the architectural behavior and essential to avoiding a page-table walk + // storm. Explicit c8 maintenance above remains the sole guest-visible TLB invalidation path. + m_cp15.process_id = value; + break; + default: + break; + } +} +} // namespace IOS::LLE diff --git a/Source/Core/Core/IOS/Starlet/BootstrapELF.cpp b/Source/Core/Core/IOS/Starlet/BootstrapELF.cpp new file mode 100644 index 0000000000..34275212aa --- /dev/null +++ b/Source/Core/Core/IOS/Starlet/BootstrapELF.cpp @@ -0,0 +1,222 @@ +// Copyright 2026 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/IOS/Starlet/BootstrapELF.h" + +#include +#include +#include +#include + +#include "Core/IOS/Starlet/ARMCore.h" + +namespace IOS::LLE +{ +namespace +{ +constexpr u32 TEXT_BASE = 0xffff0000; +constexpr u64 TEXT_END = 0x1'0000'0000ULL; +constexpr u32 DATA_BASE = 0xfffe0000; +constexpr u32 DATA_END = 0xfffe4000; +// Revolution IOS Phase 06H: MEM2, for the optional disposable-image text segment. +constexpr u32 MEM2_BASE = 0x10000000; +constexpr u64 MEM2_END = 0x14000000ULL; +constexpr u32 TRACE_SIZE = 1032; +constexpr u32 ELF_HEADER_SIZE = 52; +constexpr u32 PROGRAM_HEADER_SIZE = 32; +constexpr u32 SECTION_HEADER_SIZE = 40; +constexpr u32 SYMBOL_SIZE = 16; + +struct Segment +{ + u32 offset = 0; + u32 address = 0; + u32 file_size = 0; + u32 memory_size = 0; +}; + +bool Fits(std::span image, u64 offset, u64 size) +{ + return offset <= image.size() && size <= image.size() - offset; +} + +u16 Read16(std::span image, size_t offset) +{ + return static_cast((static_cast(image[offset]) << 8) | image[offset + 1]); +} + +u32 Read32(std::span image, size_t offset) +{ + return (static_cast(image[offset]) << 24) | (static_cast(image[offset + 1]) << 16) | + (static_cast(image[offset + 2]) << 8) | image[offset + 3]; +} + +bool Fail(std::string* error, std::string_view reason) +{ + if (error) + *error = reason; + return false; +} + +std::optional SymbolName(std::span image, u32 strings_offset, + u32 strings_size, u32 name_offset) +{ + if (name_offset >= strings_size) + return std::nullopt; + const size_t start = static_cast(strings_offset) + name_offset; + const auto first = image.begin() + start; + const auto last = image.begin() + strings_offset + strings_size; + const auto end = std::find(first, last, 0); + if (end == last) + return std::nullopt; + return std::string_view(reinterpret_cast(&*first), static_cast(end - first)); +} +} // namespace + +bool LoadBootstrapELF(std::span image, ARMBus& bus, BootstrapELFInfo* info, + std::string* error, bool allow_mem2_text) +{ + if (!info) + return Fail(error, "missing bootstrap ELF result"); + if (!Fits(image, 0, ELF_HEADER_SIZE) || image[0] != 0x7f || image[1] != 'E' || image[2] != 'L' || + image[3] != 'F' || image[4] != 1 || image[5] != 2 || image[6] != 1 || + Read16(image, 16) != 2 || Read16(image, 18) != 40 || Read32(image, 20) != 1 || + Read16(image, 40) != ELF_HEADER_SIZE) + { + return Fail(error, "expected ELF32 big-endian ARM executable"); + } + + const u32 entry = Read32(image, 24); + const u32 program_offset = Read32(image, 28); + const u32 section_offset = Read32(image, 32); + const u16 program_size = Read16(image, 42); + const u16 program_count = Read16(image, 44); + const u16 section_size = Read16(image, 46); + const u16 section_count = Read16(image, 48); + const bool program_count_valid = program_count == 2 || (allow_mem2_text && program_count == 3); + if (entry != TEXT_BASE || program_size != PROGRAM_HEADER_SIZE || !program_count_valid || + section_size != SECTION_HEADER_SIZE || section_count == 0 || + !Fits(image, program_offset, static_cast(program_size) * program_count) || + !Fits(image, section_offset, static_cast(section_size) * section_count)) + { + return Fail(error, "bootstrap ELF has an unsupported entry or header table"); + } + + std::array storage; + const std::span segments(storage.data(), program_count); + u32 text_count = 0; + u32 data_count = 0; + u32 mem2_count = 0; + for (u32 index = 0; index < segments.size(); ++index) + { + const size_t offset = program_offset + index * PROGRAM_HEADER_SIZE; + const u32 file_offset = Read32(image, offset + 4); + const u32 address = Read32(image, offset + 8); + const u32 physical_address = Read32(image, offset + 12); + const u32 file_size = Read32(image, offset + 16); + const u32 memory_size = Read32(image, offset + 20); + const u32 flags = Read32(image, offset + 24); + const bool text = + address == TEXT_BASE && flags == 5 && static_cast(address) + memory_size <= TEXT_END; + const bool data = + address == DATA_BASE && flags == 6 && static_cast(address) + memory_size <= DATA_END; + const bool mem2 = allow_mem2_text && flags == 5 && address >= MEM2_BASE && + static_cast(address) + memory_size <= MEM2_END; + if (Read32(image, offset) != 1 || address != physical_address || file_size == 0 || + memory_size < file_size || !Fits(image, file_offset, file_size) || + (!text && !data && !mem2)) + { + return Fail(error, "bootstrap ELF has an invalid SRAM load segment"); + } + text_count += text; + data_count += data; + mem2_count += mem2; + segments[index] = {file_offset, address, file_size, memory_size}; + } + if (text_count != 1 || data_count != 1 || mem2_count != program_count - 2u) + return Fail(error, "bootstrap ELF must have one text and one data segment"); + + BootstrapELFInfo parsed{entry}; + bool found_trace = false; + bool found_fault_code = false; + bool found_fault_lr = false; + bool found_fault_spsr = false; + for (u32 index = 0; index < section_count; ++index) + { + const size_t offset = section_offset + index * SECTION_HEADER_SIZE; + if (Read32(image, offset + 4) != 2) + continue; + const u32 symbols_offset = Read32(image, offset + 16); + const u32 symbols_size = Read32(image, offset + 20); + const u32 strings_index = Read32(image, offset + 24); + const u32 entry_size = Read32(image, offset + 36); + if (entry_size != SYMBOL_SIZE || symbols_size % SYMBOL_SIZE != 0 || + !Fits(image, symbols_offset, symbols_size) || strings_index >= section_count) + { + return Fail(error, "bootstrap ELF has an invalid symbol table"); + } + const size_t strings_header = section_offset + strings_index * SECTION_HEADER_SIZE; + const u32 strings_offset = Read32(image, strings_header + 16); + const u32 strings_size = Read32(image, strings_header + 20); + if (Read32(image, strings_header + 4) != 3 || !Fits(image, strings_offset, strings_size)) + { + return Fail(error, "bootstrap ELF has an invalid symbol string table"); + } + for (u32 symbol = 0; symbol < symbols_size / SYMBOL_SIZE; ++symbol) + { + const size_t symbol_offset = symbols_offset + symbol * SYMBOL_SIZE; + const auto name = + SymbolName(image, strings_offset, strings_size, Read32(image, symbol_offset)); + if (!name) + return Fail(error, "bootstrap ELF has an invalid symbol name"); + const u32 address = Read32(image, symbol_offset + 4); + if (*name == "REV_TRACE") + { + if (Read32(image, symbol_offset + 8) != TRACE_SIZE) + return Fail(error, "bootstrap ELF trace layout does not match the monitor"); + parsed.trace = address; + found_trace = true; + } + else if (*name == "__fault_code") + { + parsed.fault_code = address; + found_fault_code = true; + } + else if (*name == "__fault_lr") + { + parsed.fault_lr = address; + found_fault_lr = true; + } + else if (*name == "__fault_spsr") + { + parsed.fault_spsr = address; + found_fault_spsr = true; + } + } + } + const auto data_segment = + std::find_if(segments.begin(), segments.end(), + [](const Segment& segment) { return segment.address == DATA_BASE; }); + const auto in_data = [data_segment](u32 address, u32 size) { + return address >= data_segment->address && + static_cast(address) + size <= + static_cast(data_segment->address) + data_segment->memory_size; + }; + if (!found_trace || !found_fault_code || !found_fault_lr || !found_fault_spsr || + !in_data(parsed.trace, TRACE_SIZE) || !in_data(parsed.fault_code, 4) || + !in_data(parsed.fault_lr, 4) || !in_data(parsed.fault_spsr, 4)) + { + return Fail(error, "bootstrap ELF is missing the trace or fault symbols"); + } + + for (const Segment& segment : segments) + { + for (u32 index = 0; index < segment.file_size; ++index) + bus.Write8(segment.address + index, image[segment.offset + index]); + for (u32 index = segment.file_size; index < segment.memory_size; ++index) + bus.Write8(segment.address + index, 0); + } + *info = parsed; + return true; +} +} // namespace IOS::LLE diff --git a/Source/Core/Core/IOS/Starlet/BootstrapELF.h b/Source/Core/Core/IOS/Starlet/BootstrapELF.h new file mode 100644 index 0000000000..015ce1f598 --- /dev/null +++ b/Source/Core/Core/IOS/Starlet/BootstrapELF.h @@ -0,0 +1,31 @@ +// Copyright 2026 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "Common/CommonTypes.h" + +namespace IOS::LLE +{ +class ARMBus; + +struct BootstrapELFInfo +{ + u32 entry = 0; + u32 trace = 0; + u32 fault_code = 0; + u32 fault_lr = 0; + u32 fault_spsr = 0; +}; + +// Load the Phase 01 ELF into a bus already configured with the high SRAM mapping. +// All file and address validation completes before the first bus write. +// Revolution IOS Phase 06H: `allow_mem2_text` (set only with the emulator-only disposable +// writable NAND) additionally accepts one read/execute segment wholly inside MEM2. The SRAM +// text and data segments stay mandatory; without the flag the rules are unchanged. +bool LoadBootstrapELF(std::span image, ARMBus& bus, BootstrapELFInfo* info, + std::string* error, bool allow_mem2_text = false); +} // namespace IOS::LLE diff --git a/Source/Core/Core/IOS/Starlet/Starlet.cpp b/Source/Core/Core/IOS/Starlet/Starlet.cpp index 33f4630021..a7ea85f84f 100644 --- a/Source/Core/Core/IOS/Starlet/Starlet.cpp +++ b/Source/Core/Core/IOS/Starlet/Starlet.cpp @@ -4,18 +4,24 @@ #include "Core/IOS/Starlet/Starlet.h" #include +#include #include +#include +#include #include "Common/ChunkFile.h" #include "Common/GekkoDisassembler.h" +#include "Common/IOFile.h" #include "Common/Logging/Log.h" #include "Common/PerformanceTrace.h" #include "Common/Timer.h" #include "Core/Config/MainSettings.h" #include "Core/CoreTiming.h" +#include "Core/HW/Memmap.h" #include "Core/HW/ProcessorInterface.h" #include "Core/HW/WII_IPC.h" #include "Core/IOS/Starlet/ARMCore.h" +#include "Core/IOS/Starlet/BootstrapELF.h" #include "Core/IOS/Starlet/StarletMemory.h" #include "Core/PowerPC/MMU.h" #include "Core/PowerPC/PowerPC.h" @@ -23,6 +29,11 @@ namespace IOS::LLE { +std::unique_ptr Starlet::Create(Core::System& system) +{ + return std::make_unique(system); +} + Starlet::Starlet(Core::System& system) : m_system(system) { } @@ -37,11 +48,87 @@ bool Starlet::Init(const std::string& dump_directory, std::string* error) // Allocate the concrete bus here; its device-state members are part of the // allocation size and must stay in lockstep with StarletMemory's definition. m_memory = std::make_unique(m_system); - if (!m_memory->Init(dump_directory, error)) + const std::string bootstrap_path = Config::Get(Config::MAIN_WII_STARLET_BOOTSTRAP_ELF); + if (bootstrap_path.empty() && !m_memory->Init(dump_directory, error)) { m_memory.reset(); return false; } + if (!bootstrap_path.empty()) + { + File::IOFile file(bootstrap_path, "rb"); + const u64 size = file ? file.GetSize() : 0; + if (size == 0 || size > 16 * 1024 * 1024) + { + if (error) + *error = "WiiStarletBootstrapELF must name a readable ELF of at most 16 MiB"; + m_memory.reset(); + return false; + } + std::vector image(static_cast(size)); + if (!file.ReadBytes(image.data(), image.size())) + { + if (error) + *error = "could not read WiiStarletBootstrapELF"; + m_memory.reset(); + return false; + } + m_memory->PrepareBootstrap(); + // Revolution IOS Phase 06H, emulator only: a disposable writable NAND, and with it the + // MEM2 text segment of the disposable-image kernel. Nothing else accepts either. + const char* disposable_source = std::getenv("REV_BOOTSTRAP_NAND_DISPOSABLE"); + const bool disposable = disposable_source && *disposable_source; + BootstrapELFInfo info; + if (!LoadBootstrapELF(image, *m_memory, &info, error, disposable)) + { + m_memory.reset(); + return false; + } + m_bootstrap = info; + INFO_LOG_FMT(IOS, "Starlet bootstrap ELF loaded: {} entry={:#010x} trace={:#010x}", + bootstrap_path, info.entry, info.trace); + if (disposable) + { + const char* read_only_path = std::getenv("REV_BOOTSTRAP_NAND"); + const char* copy_path = std::getenv("REV_BOOTSTRAP_NAND_COPY"); + if ((read_only_path && *read_only_path) || !copy_path || !*copy_path) + { + if (error) + *error = "REV_BOOTSTRAP_NAND_DISPOSABLE needs REV_BOOTSTRAP_NAND_COPY and excludes " + "REV_BOOTSTRAP_NAND"; + m_memory.reset(); + return false; + } + if (!m_memory->AttachDisposableNAND(disposable_source, copy_path, error)) + { + m_memory.reset(); + return false; + } + WARN_LOG_FMT(IOS, "Starlet bootstrap NAND DISPOSABLE: source {} (read-only), copy {}", + disposable_source, copy_path); + } + // Revolution IOS Phase 04B: optionally expose a raw NAND image, strictly read-only, so a + // bootstrap ELF can validate its NAND driver. Program and erase always fail in this mode. + if (const char* nand_path = std::getenv("REV_BOOTSTRAP_NAND"); nand_path && *nand_path) + { + if (!m_memory->AttachReadOnlyNAND(nand_path, error)) + { + m_memory.reset(); + return false; + } + INFO_LOG_FMT(IOS, "Starlet bootstrap NAND attached read-only: {}", nand_path); + } + // Revolution IOS Phase 04C: optional keys.bin so OTP reads match the attached dump. + if (const char* keys_path = std::getenv("REV_BOOTSTRAP_KEYS"); keys_path && *keys_path) + { + if (!m_memory->AttachKeys(keys_path, error)) + { + m_memory.reset(); + return false; + } + INFO_LOG_FMT(IOS, "Starlet bootstrap keys attached"); + } + } // ARMCore owns generation-tagged software TLB and instruction caches, so // construct it only after the complete physical bus exists. m_core = std::make_unique(*m_memory, @@ -51,7 +138,9 @@ bool Starlet::Init(const std::string& dump_directory, std::string* error) #else m_core->SetJitEnabled(false); #endif - m_core->Reset(StarletMemory::BOOT_ROM_BASE); + m_core->Reset(m_bootstrap ? m_bootstrap->entry : StarletMemory::BOOT_ROM_BASE); + if (m_bootstrap) + m_core->SetBigEndian(true); m_next_jit_diagnostic_instruction = 25'000'000; m_next_realtime_diagnostic_ms = Common::Timer::NowMs() + 1000; m_realtime_diagnostic_slice_count = 0; @@ -101,6 +190,7 @@ void Starlet::Shutdown() m_run_event = nullptr; m_core.reset(); m_memory.reset(); + m_bootstrap.reset(); } void Starlet::DoState(PointerWrap& p) @@ -110,6 +200,14 @@ void Starlet::DoState(PointerWrap& p) return; m_memory->DoState(p); m_core->DoState(p); + if (p.IsReadMode()) + { + m_bootstrap_trace_length = 0; + m_last_probe_status = 0; + m_bootstrap_line_length = 0; + m_bootstrap_outcome_logged = false; + m_bootstrap_fault_logged = false; + } } u32 Starlet::GetPC() const @@ -201,6 +299,41 @@ void Starlet::RunSlice(s64 cycles_late) ARM_IDLE_SLICE_CYCLES : (ipc_handshake_active ? ARM_IPC_SLICE_CYCLES : ARM_ACTIVE_SLICE_CYCLES); m_core->RunCycles(arm_cycles); + if (m_bootstrap && !m_bootstrap_outcome_logged) + TraceBootstrap(); + // Observe the PPC test program's result word. This is diagnostic only: all + // request and response traffic still passes through shared RAM and IPC MMIO. + if (m_bootstrap) + { + constexpr u32 PPC_PROBE_STATUS_ADDRESS = 0x00003c00; + const u32 status = m_system.GetMemory().Read_U32(PPC_PROBE_STATUS_ADDRESS); + if ((status & 0xffff0000) == 0x52500000 && status != m_last_probe_status) + { + m_last_probe_status = status; + INFO_LOG_FMT(IOS, "Broadway ios-probe status={:#010x}", status); + if (status == 0x52500002 || status == 0x52500003 || status == 0x52500005 || + (status & 0xfffff000) == 0x5250e000) + { + constexpr u32 PPC_PROBE_COUNTERS_ADDRESS = 0x00003c20; + INFO_LOG_FMT(IOS, + "Broadway ios-probe counters sent={} received={} invalid={} duplicate={} " + "timeout={}", + m_system.GetMemory().Read_U32(PPC_PROBE_COUNTERS_ADDRESS), + m_system.GetMemory().Read_U32(PPC_PROBE_COUNTERS_ADDRESS + 4), + m_system.GetMemory().Read_U32(PPC_PROBE_COUNTERS_ADDRESS + 8), + m_system.GetMemory().Read_U32(PPC_PROBE_COUNTERS_ADDRESS + 12), + m_system.GetMemory().Read_U32(PPC_PROBE_COUNTERS_ADDRESS + 16)); + // 0x52500005 / 0x5250e2xx: Revolution IOS Phase 04D /dev/fs cases. + if (status == 0x52500003 || status == 0x52500005 || + (status & 0xffffff00) == 0x5250e100 || (status & 0xffffff00) == 0x5250e200) + { + INFO_LOG_FMT(IOS, "Broadway ios-probe checks={} failures={}", + m_system.GetMemory().Read_U32(PPC_PROBE_COUNTERS_ADDRESS + 20), + m_system.GetMemory().Read_U32(PPC_PROBE_COUNTERS_ADDRESS + 24)); + } + } + } + } // Keep host-clock reads and diagnostic formatting off the normal scheduler path. const bool check_realtime_diagnostic = @@ -415,4 +548,57 @@ void Starlet::RunSlice(s64 cycles_late) const s64 next = std::max(1, static_cast(broadway_cycles) - cycles_late); m_system.GetCoreTiming().ScheduleEvent(next, m_run_event); } + +void Starlet::TraceBootstrap() +{ + const BootstrapELFInfo& bootstrap = *m_bootstrap; + const u32 fault_code = m_memory->Read32(bootstrap.fault_code); + if (fault_code != 0 && !m_bootstrap_fault_logged) + { + ERROR_LOG_FMT(IOS, + "Starlet bootstrap exception {}: LR={:#010x} SPSR={:#010x} PC={:#010x}", + fault_code, m_memory->Read32(bootstrap.fault_lr), + m_memory->Read32(bootstrap.fault_spsr), m_core->GetRegister(15)); + m_bootstrap_fault_logged = true; + m_bootstrap_outcome_logged = true; + return; + } + + if (m_memory->Read32(bootstrap.trace) != 0x52455631) + return; + const u32 length = m_memory->Read32(bootstrap.trace + 4); + if (length > 1024) + { + ERROR_LOG_FMT(IOS, "Starlet bootstrap trace length is invalid: {}", length); + m_bootstrap_outcome_logged = true; + return; + } + if (length < m_bootstrap_trace_length) + { + m_bootstrap_trace_length = 0; + m_bootstrap_line_length = 0; + } + for (u32 index = m_bootstrap_trace_length; index < length; ++index) + { + const char character = static_cast(m_memory->Read8(bootstrap.trace + 8 + index)); + if (character == '\n') + { + const std::string_view line(m_bootstrap_line.data(), m_bootstrap_line_length); + INFO_LOG_FMT(IOS, "Starlet bootstrap: {}", line); + if (line == "REV:BOOT:IDLE" || line.starts_with("REV:PANIC") || + line.starts_with("REV:FATAL")) + { + INFO_LOG_FMT(IOS, "Starlet bootstrap stopped at PC={:#010x} after {} instructions", + m_core->GetRegister(15), m_core->GetExecutedInstructions()); + m_bootstrap_outcome_logged = true; + } + m_bootstrap_line_length = 0; + } + else if (m_bootstrap_line_length < m_bootstrap_line.size()) + { + m_bootstrap_line[m_bootstrap_line_length++] = character; + } + } + m_bootstrap_trace_length = length; +} } // namespace IOS::LLE diff --git a/Source/Core/Core/IOS/Starlet/Starlet.h b/Source/Core/Core/IOS/Starlet/Starlet.h index 289606ca3c..c7e8cbbdf3 100644 --- a/Source/Core/Core/IOS/Starlet/Starlet.h +++ b/Source/Core/Core/IOS/Starlet/Starlet.h @@ -3,11 +3,13 @@ #pragma once +#include #include #include #include #include "Common/CommonTypes.h" +#include "Core/IOS/Starlet/BootstrapELF.h" class PointerWrap; @@ -32,6 +34,8 @@ class StarletMemory; class Starlet final { public: + // Allocate in Starlet.cpp so callers never depend on this class's size. + static std::unique_ptr Create(Core::System& system); explicit Starlet(Core::System& system); ~Starlet(); @@ -77,10 +81,18 @@ private: static void RunCallback(Core::System& system, u64 userdata, s64 cycles_late); void RunSlice(s64 cycles_late); + void TraceBootstrap(); Core::System& m_system; std::unique_ptr m_memory; std::unique_ptr m_core; + std::optional m_bootstrap; + std::array m_bootstrap_line{}; + u32 m_bootstrap_line_length = 0; + u32 m_bootstrap_trace_length = 0; + u32 m_last_probe_status = 0; + bool m_bootstrap_outcome_logged = false; + bool m_bootstrap_fault_logged = false; CoreTiming::EventType* m_run_event = nullptr; u64 m_next_jit_diagnostic_instruction = 25'000'000; u64 m_next_realtime_diagnostic_ms = 0; diff --git a/Source/Core/Core/IOS/Starlet/StarletMemory.cpp b/Source/Core/Core/IOS/Starlet/StarletMemory.cpp index 9a36d9dc4a..d25600e6f1 100644 --- a/Source/Core/Core/IOS/Starlet/StarletMemory.cpp +++ b/Source/Core/Core/IOS/Starlet/StarletMemory.cpp @@ -726,6 +726,85 @@ void StarletMemory::Reset() InitializeWiimotes(); } +void StarletMemory::PrepareBootstrap() +{ + m_initialized = false; + Reset(); + m_boot0_mapped = false; + m_sram_split_mode = true; + WriteRegister(HW_BOOT0, BOOT0_DISABLE); + WriteRegister(HW_SRNPROT, SRNPROT_SRAM_SPLIT_MODE); +} + +bool StarletMemory::AttachReadOnlyNAND(const std::string& path, std::string* error) +{ + m_nand_journal.reset(); + m_nand_overlay.clear(); + m_nand = File::IOFile(path, "rb", File::SharedAccess::Read); + const u64 nand_size = m_nand ? m_nand.GetSize() : 0; + if (nand_size != NAND_IMAGE_SIZE && nand_size != BOOTMII_NAND_IMAGE_SIZE) + { + m_nand.Close(); + if (error) + *error = "the bootstrap NAND must be a 0x21000000-byte raw NAND image or a " + "0x21000400-byte BootMii dump"; + return false; + } + m_nand_read_only = true; + return true; +} + +bool StarletMemory::AttachDisposableNAND(const std::string& source, const std::string& copy, + std::string* error) +{ + m_nand_disposable_copy.Close(); + if (copy.empty() || File::Exists(copy)) + { + if (error) + *error = "the disposable NAND copy must name a file that does not exist yet"; + return false; + } + if (!AttachReadOnlyNAND(source, error)) + return false; + // "x": creation fails if the file appeared since the check above. + File::IOFile output(copy, "wbx"); + std::array buffer{}; + const u64 size = m_nand.GetSize(); + bool copied = output.IsOpen() && m_nand.Seek(0, File::SeekOrigin::Begin); + for (u64 offset = 0; copied && offset < size; offset += buffer.size()) + { + const size_t count = static_cast(std::min(buffer.size(), size - offset)); + copied = m_nand.ReadArray(buffer.data(), count) && output.WriteArray(buffer.data(), count); + } + copied = copied && output.Flush(); + output.Close(); + if (copied) + m_nand_disposable_copy = File::IOFile(copy, "r+b"); + if (!copied || !m_nand_disposable_copy || m_nand_disposable_copy.GetSize() != size) + { + m_nand_disposable_copy.Close(); + m_nand.Close(); + if (error) + *error = "could not create the disposable NAND copy"; + return false; + } + m_nand_read_only = false; + return true; +} + +bool StarletMemory::AttachKeys(const std::string& path, std::string* error) +{ + File::IOFile keys_file(path, "rb"); + if (!keys_file || keys_file.GetSize() != m_keys.size() || !keys_file.ReadArray(&m_keys)) + { + m_keys.fill(0); + if (error) + *error = "the bootstrap keys must be a 1024-byte BootMii keys.bin"; + return false; + } + return true; +} + void StarletMemory::DoState(PointerWrap& p) { const std::lock_guard wifi_sdio_lock(m_wifi_sdio_register_mutex); @@ -2020,7 +2099,11 @@ void StarletMemory::ExecuteOHCI1HCICommand(const std::vector& command) case HCI_CMD_READ_STORED_LINK_KEY: { const bool read_all = parameter_size >= sizeof(hci_read_stored_link_key_cp) && command[9] != 0; - const u16 key_count = read_all ? static_cast(m_wiimotes.size()) : 0; + // Bootstrap mode resets these slots without constructing Wiimote devices. + // Only initialized devices have identities and keys to return. + const u16 key_count = read_all ? static_cast(std::ranges::count_if( + m_wiimotes, [](const auto& wiimote) { return !!wiimote; })) : + 0; append_le16(&reply, 255); append_le16(&reply, key_count); if (key_count != 0) @@ -2028,6 +2111,8 @@ void StarletMemory::ExecuteOHCI1HCICommand(const std::vector& command) std::vector parameters = {static_cast(key_count)}; for (const auto& wiimote : m_wiimotes) { + if (!wiimote) + continue; parameters.insert(parameters.end(), wiimote->GetBD().begin(), wiimote->GetBD().end()); parameters.insert(parameters.end(), wiimote->GetLinkKey().begin(), wiimote->GetLinkKey().end()); @@ -4154,6 +4239,21 @@ bool StarletMemory::CommitNANDPages(const NANDJournal::Pages& pages) const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::NAND); if (m_nand_journal && !m_nand_journal->Append(pages)) return false; + // Phase 06H: mirror into the disposable copy first; a failure leaves the array unchanged. + if (m_nand_disposable_copy) + { + for (const auto& [page, data] : pages) + { + if (!m_nand_disposable_copy.Seek(static_cast(page) * NAND_RAW_PAGE_SIZE, + File::SeekOrigin::Begin) || + !m_nand_disposable_copy.WriteArray(data)) + { + return false; + } + } + if (!m_nand_disposable_copy.Flush()) + return false; + } for (const auto& [page, data] : pages) m_nand_overlay.insert_or_assign(page, data); return true; @@ -4545,6 +4645,11 @@ bool StarletMemory::ReadNANDStatus(u32 command) bool StarletMemory::StageNANDProgram(u32 command, bool random_data_input) { + if (m_nand_read_only) + { + ERROR_LOG_FMT(IOS, "Read-only bootstrap NAND refused program command {:#010x}", command); + return false; + } if (!(command & NAND_CTRL_WRITE)) return false; @@ -4590,7 +4695,7 @@ bool StarletMemory::StageNANDProgram(u32 command, bool random_data_input) bool StarletMemory::CommitNANDProgram() { Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::NANDPrograms); - if (!m_nand_program_pending) + if (m_nand_read_only || !m_nand_program_pending) return false; NANDPage raw{}; @@ -4628,6 +4733,11 @@ bool StarletMemory::CommitNANDProgram() bool StarletMemory::StageNANDErase() { + if (m_nand_read_only) + { + ERROR_LOG_FMT(IOS, "Read-only bootstrap NAND refused erase setup"); + return false; + } m_nand_erase_page = ReadRegister(NAND_ADDR2) & 0xffffff; m_nand_erase_pending = m_nand_erase_page < NAND_PAGE_COUNT; if (m_nand_erase_pending) @@ -4638,7 +4748,7 @@ bool StarletMemory::StageNANDErase() bool StarletMemory::CommitNANDErase() { Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::NANDErases); - if (!m_nand_erase_pending) + if (m_nand_read_only || !m_nand_erase_pending) return false; const u32 block_start = m_nand_erase_page & ~(NAND_PAGES_PER_BLOCK - 1); diff --git a/Source/Core/Core/IOS/Starlet/StarletMemory.h b/Source/Core/Core/IOS/Starlet/StarletMemory.h index 9e007a2e4c..03ea2df92a 100644 --- a/Source/Core/Core/IOS/Starlet/StarletMemory.h +++ b/Source/Core/Core/IOS/Starlet/StarletMemory.h @@ -56,6 +56,22 @@ public: } bool Init(const std::string& dump_directory, std::string* error); + // Set up empty SRAM with BOOT0 hidden and the IOS-style high SRAM split for an external ELF. + void PrepareBootstrap(); + // Bootstrap-ELF mode only: expose a raw NAND image to the NAND controller strictly read-only. + // No journal is opened; every program or erase command then fails with the controller error + // bit, so a Revolution IOS read-only driver can be validated without any write path. + bool AttachReadOnlyNAND(const std::string& path, std::string* error); + // Revolution IOS Phase 06H, emulator only: a disposable writable NAND. `source` is opened + // read-only exactly as above and is never written. `copy` must not exist; it is created as a + // byte-for-byte copy of the source, and every committed program or erase is mirrored into it + // before the in-memory overlay changes, so it holds the mutated image for independent + // checking. No journal is opened. + bool AttachDisposableNAND(const std::string& source, const std::string& copy, + std::string* error); + // Revolution IOS Phase 04C: bootstrap mode loads no keys, so the OTP reads zero. This + // loads a 1024-byte BootMii keys.bin so OTP reads return the console's own words. + bool AttachKeys(const std::string& path, std::string* error); void Reset(); void DoState(PointerWrap& p); @@ -321,10 +337,13 @@ private: u64 m_arm_cycles = 0; u32 m_timer_offset = 0; bool m_initialized = false; + bool m_nand_read_only = false; bool m_boot0_mapped = true; bool m_sram_split_mode = false; // Host persistence is not serialized; savestates contain the complete overlay above. std::unique_ptr m_nand_journal; + // Phase 06H: the disposable copy mirroring committed NAND pages (bootstrap mode only). + File::IOFile m_nand_disposable_copy; std::unique_ptr m_ethernet; std::unique_ptr m_ethernet_network; }; diff --git a/Source/Core/DolphinNoGUI/PlatformHeadless.cpp b/Source/Core/DolphinNoGUI/PlatformHeadless.cpp index 9bf3383032..009b6cc22b 100644 --- a/Source/Core/DolphinNoGUI/PlatformHeadless.cpp +++ b/Source/Core/DolphinNoGUI/PlatformHeadless.cpp @@ -4,7 +4,9 @@ #include #include +#include "Common/Logging/Log.h" #include "Core/Core.h" +#include "Core/HW/Memmap.h" #include "Core/System.h" #include "DolphinNoGUI/Platform.h" @@ -26,10 +28,43 @@ void PlatformHeadless::SetTitle(const std::string& title) void PlatformHeadless::MainLoop() { + bool diff_observed = false; while (m_running.IsSet()) { UpdateRunningFlag(); - Core::HostDispatchJobs(Core::System::GetInstance()); + auto& system = Core::System::GetInstance(); + Core::HostDispatchJobs(system); + if (!diff_observed && Core::IsRunning(system)) + { + auto& memory = system.GetMemory(); + const u32 status = memory.Read_U32(0x00003c00); + if (status == 0x52500004 || (status & 0xffffff00) == 0x5250e100) + { + diff_observed = true; + INFO_LOG_FMT(IOS, "IOS_PROBE_DIFF_DONE status={:#010x}", status); + constexpr u32 trace_base = 0x00003d00; + const u32 count = memory.Read_U32(trace_base); + if (count > 8) + { + ERROR_LOG_FMT(IOS, "IOS_PROBE_DIFF_BAD_COUNT count={}", count); + } + else + { + for (u32 index = 0; index < count; ++index) + { + const u32 record = trace_base + 0x20 + index * 24; + INFO_LOG_FMT(IOS, + "IOS_PROBE_JSON {{\"schema\":1,\"index\":{},\"operation\":{}," + "\"argument\":{},\"return\":{},\"output\":{},\"ticks\":{}," + "\"status\":{}}}", + index, memory.Read_U32(record), memory.Read_U32(record + 4), + static_cast(memory.Read_U32(record + 8)), + memory.Read_U32(record + 12), memory.Read_U32(record + 16), + memory.Read_U32(record + 20)); + } + } + } + } std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } diff --git a/Source/UnitTests/CMakeLists.txt b/Source/UnitTests/CMakeLists.txt index 402a3bb124..dca383a026 100644 --- a/Source/UnitTests/CMakeLists.txt +++ b/Source/UnitTests/CMakeLists.txt @@ -1,27 +1,32 @@ -enable_testing() -add_custom_target(unittests) -add_custom_command(TARGET unittests POST_BUILD COMMAND ${CMAKE_CTEST_COMMAND} "--output-on-failure") - -string(APPEND CMAKE_RUNTIME_OUTPUT_DIRECTORY "/Tests") - -add_executable(tests EXCLUDE_FROM_ALL UnitTestsMain.cpp StubHost.cpp) -set_target_properties(tests PROPERTIES FOLDER Tests) -target_link_libraries(tests PRIVATE fmt::fmt gtest::gtest core uicommon) -add_test(NAME tests COMMAND tests) -add_custom_command(TARGET tests POST_BUILD - COMMAND ${CMAKE_COMMAND} -E remove_directory "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Sys" - COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/Data/Sys" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Sys" -) +enable_testing() +add_custom_target(unittests) +add_custom_command(TARGET unittests POST_BUILD COMMAND ${CMAKE_CTEST_COMMAND} "--output-on-failure") + +string(APPEND CMAKE_RUNTIME_OUTPUT_DIRECTORY "/Tests") + +add_executable(tests EXCLUDE_FROM_ALL UnitTestsMain.cpp StubHost.cpp) +set_target_properties(tests PROPERTIES FOLDER Tests) +target_link_libraries(tests PRIVATE fmt::fmt gtest::gtest core uicommon) +add_test(NAME tests COMMAND tests) +add_custom_command(TARGET tests POST_BUILD + COMMAND ${CMAKE_COMMAND} -E remove_directory "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Sys" + COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/Data/Sys" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Sys" +) add_dependencies(unittests tests) -macro(add_dolphin_test target) - add_library(${target} OBJECT ${ARGN}) - target_link_libraries(${target} PUBLIC fmt::fmt gtest::gtest PRIVATE core uicommon) - target_link_libraries(tests PRIVATE ${target}) -endmacro() - -add_subdirectory(Common) -add_subdirectory(Core) +# Runs a real Starlet ELF through the fork's CPU and SRAM bus without NAND or a frontend. +add_executable(StarletBootstrapProbe EXCLUDE_FROM_ALL + ${CMAKE_SOURCE_DIR}/Tools/StarletBootstrapProbe/StarletBootstrapProbe.cpp StubHost.cpp) +target_link_libraries(StarletBootstrapProbe PRIVATE fmt::fmt core uicommon) + +macro(add_dolphin_test target) + add_library(${target} OBJECT ${ARGN}) + target_link_libraries(${target} PUBLIC fmt::fmt gtest::gtest PRIVATE core uicommon) + target_link_libraries(tests PRIVATE ${target}) +endmacro() + +add_subdirectory(Common) +add_subdirectory(Core) add_subdirectory(VideoCommon) # Opt-in local trace qualification; never starts a console or accesses a NAND dump. diff --git a/Source/UnitTests/Core/CMakeLists.txt b/Source/UnitTests/Core/CMakeLists.txt index a198a28e56..50457d2a91 100644 --- a/Source/UnitTests/Core/CMakeLists.txt +++ b/Source/UnitTests/Core/CMakeLists.txt @@ -1,20 +1,21 @@ -add_dolphin_test(MMIOTest MMIOTest.cpp) -add_dolphin_test(PageFaultTest PageFaultTest.cpp) -add_dolphin_test(CoreTimingTest CoreTimingTest.cpp) -add_dolphin_test(PatchAllowlistTest PatchAllowlistTest.cpp) - -add_dolphin_test(DSPAcceleratorTest DSP/DSPAcceleratorTest.cpp) -add_dolphin_test(DSPAssemblyTest - DSP/DSPAssemblyTest.cpp - DSP/DSPTestBinary.cpp - DSP/DSPTestText.cpp - DSP/HermesBinary.cpp - DSP/HermesText.cpp -) - +add_dolphin_test(MMIOTest MMIOTest.cpp) +add_dolphin_test(PageFaultTest PageFaultTest.cpp) +add_dolphin_test(CoreTimingTest CoreTimingTest.cpp) +add_dolphin_test(PatchAllowlistTest PatchAllowlistTest.cpp) + +add_dolphin_test(DSPAcceleratorTest DSP/DSPAcceleratorTest.cpp) +add_dolphin_test(DSPAssemblyTest + DSP/DSPAssemblyTest.cpp + DSP/DSPTestBinary.cpp + DSP/DSPTestText.cpp + DSP/HermesBinary.cpp + DSP/HermesText.cpp +) + add_dolphin_test(ESFormatsTest IOS/ES/FormatsTest.cpp) add_dolphin_test(StarletARMCoreTest IOS/Starlet/ARMCoreTest.cpp) +add_dolphin_test(StarletBootstrapELFTest IOS/Starlet/BootstrapELFTest.cpp) add_dolphin_test(StarletDMATest IOS/Starlet/DMATest.cpp) add_dolphin_test(StarletSRAMReadTest IOS/Starlet/SRAMReadTest.cpp) add_dolphin_test(StarletSRAMWriteTest IOS/Starlet/SRAMWriteTest.cpp) @@ -24,36 +25,36 @@ add_dolphin_test(StarletEthernetTest IOS/Starlet/AX88772Test.cpp) target_include_directories(StarletEthernetTest PRIVATE ${PROJECT_SOURCE_DIR}/Externals/libslirp/include) add_dolphin_test(FileSystemTest IOS/FS/FileSystemTest.cpp) - -add_dolphin_test(SkylandersTest IOS/USB/SkylandersTest.cpp) - -if(_M_X86_64) - add_dolphin_test(PowerPCTest - PowerPC/DivUtilsTest.cpp - PowerPC/PageTableHostMappingTest.cpp + +add_dolphin_test(SkylandersTest IOS/USB/SkylandersTest.cpp) + +if(_M_X86_64) + add_dolphin_test(PowerPCTest + PowerPC/DivUtilsTest.cpp + PowerPC/PageTableHostMappingTest.cpp PowerPC/Jit64Common/ConvertDoubleToSingle.cpp PowerPC/Jit64Common/DCache.cpp - PowerPC/Jit64Common/Fres.cpp - PowerPC/Jit64Common/Frsqrte.cpp - ) -elseif(_M_ARM_64) - add_dolphin_test(PowerPCTest - PowerPC/DivUtilsTest.cpp - PowerPC/PageTableHostMappingTest.cpp - PowerPC/JitArm64/ConvertSingleDouble.cpp - PowerPC/JitArm64/FPRF.cpp - PowerPC/JitArm64/Fres.cpp - PowerPC/JitArm64/Frsqrte.cpp - PowerPC/JitArm64/MovI2R.cpp - ) -else() - add_dolphin_test(PowerPCTest - PowerPC/DivUtilsTest.cpp - PowerPC/PageTableHostMappingTest.cpp - ) -endif() - -target_sources(PowerPCTest PRIVATE - PowerPC/TestValues.h - StubJit.h -) + PowerPC/Jit64Common/Fres.cpp + PowerPC/Jit64Common/Frsqrte.cpp + ) +elseif(_M_ARM_64) + add_dolphin_test(PowerPCTest + PowerPC/DivUtilsTest.cpp + PowerPC/PageTableHostMappingTest.cpp + PowerPC/JitArm64/ConvertSingleDouble.cpp + PowerPC/JitArm64/FPRF.cpp + PowerPC/JitArm64/Fres.cpp + PowerPC/JitArm64/Frsqrte.cpp + PowerPC/JitArm64/MovI2R.cpp + ) +else() + add_dolphin_test(PowerPCTest + PowerPC/DivUtilsTest.cpp + PowerPC/PageTableHostMappingTest.cpp + ) +endif() + +target_sources(PowerPCTest PRIVATE + PowerPC/TestValues.h + StubJit.h +) diff --git a/Source/UnitTests/Core/IOS/Starlet/BootstrapELFTest.cpp b/Source/UnitTests/Core/IOS/Starlet/BootstrapELFTest.cpp new file mode 100644 index 0000000000..cbdf391d9e --- /dev/null +++ b/Source/UnitTests/Core/IOS/Starlet/BootstrapELFTest.cpp @@ -0,0 +1,236 @@ +// Copyright 2026 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include + +#include "Core/IOS/Starlet/ARMCore.h" +#include "Core/IOS/Starlet/BootstrapELF.h" + +namespace IOS::LLE +{ +namespace +{ +constexpr u32 TEXT_BASE = 0xffff0000; +constexpr u32 DATA_BASE = 0xfffe0000; + +class RecordingBus final : public ARMBus +{ +public: + u8 Read8(u32 address) override + { + const auto it = bytes.find(address); + return it == bytes.end() ? 0 : it->second; + } + void Write8(u32 address, u8 value) override { bytes[address] = value; } + + std::unordered_map bytes; +}; + +void Put16(std::array& image, size_t offset, u16 value) +{ + image[offset] = static_cast(value >> 8); + image[offset + 1] = static_cast(value); +} + +void Put32(std::array& image, size_t offset, u32 value) +{ + image[offset] = static_cast(value >> 24); + image[offset + 1] = static_cast(value >> 16); + image[offset + 2] = static_cast(value >> 8); + image[offset + 3] = static_cast(value); +} + +std::array MakeImage() +{ + std::array image{}; + image[0] = 0x7f; + image[1] = 'E'; + image[2] = 'L'; + image[3] = 'F'; + image[4] = 1; // ELF32 + image[5] = 2; // big endian + image[6] = 1; + Put16(image, 16, 2); // ET_EXEC + Put16(image, 18, 40); // ARM + Put32(image, 20, 1); // ELF version + Put32(image, 24, TEXT_BASE); + Put32(image, 28, 52); // program headers + Put32(image, 32, 0x200); // section headers + Put16(image, 40, 52); + Put16(image, 42, 32); + Put16(image, 44, 2); + Put16(image, 46, 40); + Put16(image, 48, 3); + + Put32(image, 52, 1); + Put32(image, 56, 0x100); + Put32(image, 60, TEXT_BASE); + Put32(image, 64, TEXT_BASE); + Put32(image, 68, 4); + Put32(image, 72, 8); + Put32(image, 76, 5); + image[0x100] = 0xea; // ARM branch instruction + + Put32(image, 84, 1); + Put32(image, 88, 0x110); + Put32(image, 92, DATA_BASE); + Put32(image, 96, DATA_BASE); + Put32(image, 100, 4); + Put32(image, 104, 0x420); + Put32(image, 108, 6); + image[0x110] = 0x12; + image[0x111] = 0x34; + image[0x112] = 0x56; + image[0x113] = 0x78; + + constexpr char names[] = "\0REV_TRACE\0__fault_code\0__fault_lr\0__fault_spsr"; + for (size_t index = 0; index < sizeof(names); ++index) + image[0x180 + index] = static_cast(names[index]); + Put32(image, 0x228 + 4, 3); // SHT_STRTAB + Put32(image, 0x228 + 16, 0x180); + Put32(image, 0x228 + 20, sizeof(names)); + Put32(image, 0x250 + 4, 2); // SHT_SYMTAB + Put32(image, 0x250 + 16, 0x120); + Put32(image, 0x250 + 20, 5 * 16); + Put32(image, 0x250 + 24, 1); + Put32(image, 0x250 + 36, 16); + + const auto symbol = [&image](size_t index, u32 name, u32 address, u32 size) { + const size_t offset = 0x120 + index * 16; + Put32(image, offset, name); + Put32(image, offset + 4, address); + Put32(image, offset + 8, size); + }; + symbol(1, 1, DATA_BASE + 8, 1032); + symbol(2, 11, DATA_BASE + 0x410, 4); + symbol(3, 24, DATA_BASE + 0x414, 4); + symbol(4, 35, DATA_BASE + 0x418, 4); + return image; +} + +TEST(BootstrapELFTest, LoadsSegmentsAndZerosUninitializedData) +{ + const auto image = MakeImage(); + RecordingBus bus; + BootstrapELFInfo info; + std::string error; + ASSERT_TRUE(LoadBootstrapELF(image, bus, &info, &error)) << error; + EXPECT_EQ(info.entry, TEXT_BASE); + EXPECT_EQ(info.trace, DATA_BASE + 8); + EXPECT_EQ(info.fault_spsr, DATA_BASE + 0x418); + EXPECT_EQ(bus.Read8(TEXT_BASE), 0xea); + EXPECT_EQ(bus.Read8(TEXT_BASE + 4), 0); + EXPECT_EQ(bus.Read32(DATA_BASE), 0x12345678u); + EXPECT_EQ(bus.Read32(DATA_BASE + 0x41c), 0u); + EXPECT_EQ(bus.bytes.size(), size_t{8 + 0x420}); +} + +TEST(BootstrapELFTest, RejectsMalformedImageBeforeWritingMemory) +{ + auto image = MakeImage(); + RecordingBus bus; + BootstrapELFInfo info; + std::string error; + image[5] = 1; + EXPECT_FALSE(LoadBootstrapELF(image, bus, &info, &error)); + EXPECT_TRUE(bus.bytes.empty()); + + image = MakeImage(); + Put32(image, 0x120 + 2 * 16 + 4, DATA_BASE + 0x420); + EXPECT_FALSE(LoadBootstrapELF(image, bus, &info, &error)); + EXPECT_TRUE(bus.bytes.empty()); + + image = MakeImage(); + Put32(image, 88, 0x300); + EXPECT_FALSE(LoadBootstrapELF(image, bus, &info, &error)); + EXPECT_TRUE(bus.bytes.empty()); +} + +// Revolution IOS Phase 06H: the optional MEM2 text segment of the emulator-only +// disposable-image kernel, accepted only when the caller allows it. +constexpr u32 MEM2_TEXT = 0x12600000; + +std::array MakeMem2Image(u32 address, u32 flags) +{ + auto image = MakeImage(); + Put16(image, 44, 3); + Put32(image, 116, 1); + Put32(image, 120, 0x114); + Put32(image, 124, address); + Put32(image, 128, address); + Put32(image, 132, 4); + Put32(image, 136, 0x10); + Put32(image, 140, flags); + Put32(image, 0x114, 0xe12fff1e); // BX LR + return image; +} + +TEST(BootstrapELFTest, Mem2TextSegmentOnlyWhenAllowed) +{ + RecordingBus bus; + BootstrapELFInfo info; + std::string error; + auto image = MakeMem2Image(MEM2_TEXT, 5); + EXPECT_FALSE(LoadBootstrapELF(image, bus, &info, &error)); + EXPECT_TRUE(bus.bytes.empty()); + + ASSERT_TRUE(LoadBootstrapELF(image, bus, &info, &error, true)) << error; + EXPECT_EQ(bus.Read32(MEM2_TEXT), 0xe12fff1eu); + EXPECT_EQ(bus.Read32(MEM2_TEXT + 0xc), 0u); + EXPECT_EQ(bus.Read8(TEXT_BASE), 0xea); + EXPECT_EQ(bus.Read32(DATA_BASE), 0x12345678u); + EXPECT_EQ(bus.bytes.size(), size_t{8 + 0x420 + 0x10}); + + RecordingBus plain; + const auto two_segments = MakeImage(); + EXPECT_TRUE(LoadBootstrapELF(two_segments, plain, &info, &error, true)) << error; +} + +TEST(BootstrapELFTest, RejectsInvalidMem2TextSegmentBeforeWritingMemory) +{ + RecordingBus bus; + BootstrapELFInfo info; + std::string error; + // Writable, outside MEM2, crossing its end, or a second SRAM text segment. + for (const auto& image : {MakeMem2Image(MEM2_TEXT, 6), MakeMem2Image(0x0fff0000, 5), + MakeMem2Image(0x13fffff8, 5), MakeMem2Image(TEXT_BASE, 5)}) + { + EXPECT_FALSE(LoadBootstrapELF(image, bus, &info, &error, true)); + EXPECT_TRUE(bus.bytes.empty()); + } +} + +TEST(ARMCoreTest, Armv5DoublewordTransfersAndAlignmentAbort) +{ + RecordingBus bus; + bus.Write32(TEXT_BASE, 0xe1c020d0); // LDRD r2, r3, [r0] + bus.Write32(TEXT_BASE + 4, 0xe1c120f0); // STRD r2, r3, [r1] + bus.Write32(DATA_BASE, 0x11223344); + bus.Write32(DATA_BASE + 4, 0x55667788); + + ARMCore core(bus); + core.Reset(TEXT_BASE); + core.SetBigEndian(true); + core.SetRegister(0, DATA_BASE); + core.SetRegister(1, DATA_BASE + 8); + core.Step(); + EXPECT_EQ(core.GetRegister(2), 0x11223344u); + EXPECT_EQ(core.GetRegister(3), 0x55667788u); + core.Step(); + EXPECT_EQ(bus.Read32(DATA_BASE + 8), 0x11223344u); + EXPECT_EQ(bus.Read32(DATA_BASE + 12), 0x55667788u); + + core.Reset(TEXT_BASE); + core.SetBigEndian(true); + core.SetRegister(0, DATA_BASE + 4); + core.Step(); + EXPECT_EQ(core.GetRegister(15), TEXT_BASE + 0x10); + EXPECT_EQ(core.GetRegister(14), TEXT_BASE + 8); + EXPECT_EQ(core.GetCP15State().fault_address, DATA_BASE + 4); +} +} // namespace +} // namespace IOS::LLE diff --git a/Source/UnitTests/Core/IOS/Starlet/DMATest.cpp b/Source/UnitTests/Core/IOS/Starlet/DMATest.cpp index 7a6e5ad9b9..5762a6e581 100644 --- a/Source/UnitTests/Core/IOS/Starlet/DMATest.cpp +++ b/Source/UnitTests/Core/IOS/Starlet/DMATest.cpp @@ -135,6 +135,21 @@ protected: EXPECT_EQ(m_memory->GetDMAMemoryPointer(address, 4), nullptr); } + void CheckBootstrapStoredKeys() + { + m_memory->PrepareBootstrap(); + for (const u8 read_all : {0, 1}) + { + SCOPED_TRACE(read_all); + m_memory->m_ohci1_hci_events.clear(); + m_memory->ExecuteOHCI1HCICommand({0x0d, 0x0c, 7, 0, 0, 0, 0, 0, 0, read_all}); + ASSERT_EQ(m_memory->m_ohci1_hci_events.size(), 1u); + // Command Complete: capacity 255, zero stored keys, no Return Link Keys event. + EXPECT_EQ(m_memory->m_ohci1_hci_events.front(), + (std::vector{0x0e, 8, 1, 0x0d, 0x0c, 0, 0xff, 0, 0, 0})); + } + } + void CheckOHCI() { for (const u32 base : {0x10000U, 0x10010000U, 0x0d404000U}) @@ -381,6 +396,11 @@ TEST_F(StarletDMATest, OHCIUsesTwoPhysicalPagesAndLittleEndianDescriptors) CheckOHCI(); } +TEST_F(StarletDMATest, BootstrapReadStoredKeysHasNoWiimoteDevices) +{ + CheckBootstrapStoredKeys(); +} + TEST_F(StarletDMATest, NANDSeparatesDataSpareAndECCIncludingPartialTransfers) { CheckNAND(); diff --git a/Tools/StarletBootstrapProbe/StarletBootstrapProbe.cpp b/Tools/StarletBootstrapProbe/StarletBootstrapProbe.cpp new file mode 100644 index 0000000000..0ced1683af --- /dev/null +++ b/Tools/StarletBootstrapProbe/StarletBootstrapProbe.cpp @@ -0,0 +1,170 @@ +// Copyright 2026 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include + +#include "Core/Core.h" +#include "Core/HW/WII_IPC.h" +#include "Core/IOS/Starlet/ARMCore.h" +#include "Core/IOS/Starlet/BootstrapELF.h" +#include "Core/IOS/Starlet/StarletMemory.h" +#include "Core/System.h" + +namespace +{ +enum class ExpectedOutcome +{ + Idle, + Panic, + FatalData, + UndefinedFault, + Phase2 +}; + +bool Run(const std::string& path, bool jit, ExpectedOutcome expected) +{ + std::ifstream file(path, std::ios::binary | std::ios::ate); + if (!file || file.tellg() <= 0 || file.tellg() > 16 * 1024 * 1024) + { + std::cerr << "Expected a readable Starlet ELF of at most 16 MiB: " << path << '\n'; + return false; + } + const size_t size = static_cast(file.tellg()); + std::vector image(size); + file.seekg(0); + if (!file.read(reinterpret_cast(image.data()), size)) + { + std::cerr << "Could not read Starlet ELF: " << path << '\n'; + return false; + } + + Core::DeclareAsCPUThread(); + auto& system = Core::System::GetInstance(); + system.GetWiiIPC().Reset(); + IOS::LLE::StarletMemory memory(system); + memory.PrepareBootstrap(); + IOS::LLE::BootstrapELFInfo info; + std::string error; + if (!IOS::LLE::LoadBootstrapELF(image, memory, &info, &error)) + { + std::cerr << error << '\n'; + return false; + } + + IOS::LLE::ARMCore core(memory); + core.SetJitEnabled(jit); + core.Reset(info.entry); + core.SetBigEndian(true); + constexpr u32 TRACE_MAGIC = 0x52455631; + constexpr u32 TRACE_CAPACITY = 1024; + for (int slice = 0; slice < 512; ++slice) + { + core.SetIRQLine(system.GetWiiIPC().IsStarletIRQAsserted()); + core.SetFIQLine(system.GetWiiIPC().IsStarletFIQAsserted()); + core.RunCycles(4096); + const u32 fault_code = memory.Read32(info.fault_code); + if (fault_code != 0) + { + std::cerr << "Starlet exception " << fault_code << " LR=" << std::hex + << memory.Read32(info.fault_lr) << " SPSR=" << memory.Read32(info.fault_spsr) + << " PC=" << core.GetRegister(15) << std::dec << '\n'; + return expected == ExpectedOutcome::UndefinedFault && fault_code == 1 && + memory.Read32(info.fault_lr) != 0 && memory.Read32(info.fault_spsr) != 0; + } + if (memory.Read32(info.trace) != TRACE_MAGIC) + continue; + const u32 length = memory.Read32(info.trace + 4); + if (length > TRACE_CAPACITY) + { + std::cerr << "Invalid REV_TRACE length: " << length << '\n'; + return false; + } + std::string trace(length, '\0'); + for (u32 index = 0; index < length; ++index) + trace[index] = static_cast(memory.Read8(info.trace + 8 + index)); + if (trace.find("REV:BOOT:IDLE\n") == std::string::npos && + trace.find("REV:PANIC") == std::string::npos && + trace.find("REV:FATAL") == std::string::npos) + { + continue; + } + std::cout << trace; + std::cout << "PC=0x" << std::hex << core.GetRegister(15) << std::dec + << " instructions=" << core.GetExecutedInstructions() << '\n'; + const bool early_startup_ok = trace.find("REV:BOOT:ENTRY\n") != std::string::npos && + trace.find("REV:BOOT:BSS_OK\n") != std::string::npos; + if (expected == ExpectedOutcome::FatalData) + { + return early_startup_ok && trace.find("REV:FATAL:DATA\n") != std::string::npos && + trace.find("REV:BOOT:DATA_OK\n") == std::string::npos; + } + const bool startup_ok = early_startup_ok && + trace.find("REV:BOOT:DATA_OK\n") != std::string::npos && + trace.find("REV:BOOT:STACK_OK\n") != std::string::npos && + trace.find("REV:BOOT:RUST\n") != std::string::npos; + if (expected == ExpectedOutcome::Phase2) + { + const std::string_view required[] = { + "REV:P2:START\n", "ARCH PASS\n", "CRITICAL PASS\n", + "TIMER PASS\n", "MONOTONIC PASS\n", "MEM PASS\n", + "IRQ PASS\n", "BARRIER PASS\n", "LOGGING PASS\n", + "REV:P2:PASS\n", "REV:P2:KERNEL_READY\n", "REV:BOOT:IDLE\n"}; + bool phase2_ok = true; + for (const std::string_view marker : required) + phase2_ok &= trace.find(marker) != std::string::npos; + return startup_ok && phase2_ok && trace.find(" FAIL\n") == std::string::npos; + } + const bool outcome_ok = + expected == ExpectedOutcome::Panic ? + trace.find("REV:PANIC") != std::string::npos : + expected == ExpectedOutcome::Idle && trace.find("REV:BOOT:IDLE\n") != std::string::npos; + return startup_ok && outcome_ok; + } + std::cerr << "No terminal bootstrap marker after 2097152 ARM cycles, PC=0x" << std::hex + << core.GetRegister(15) << std::dec << '\n'; + return false; +} +} // namespace + +int main(int argc, char** argv) +{ + if (argc < 2 || argc > 4) + { + std::cerr << "Usage: StarletBootstrapProbe [--jit] " + "[--expect-panic|--expect-fatal-data|--expect-undefined-fault|--expect-phase2]\n"; + return 2; + } + bool jit = false; + ExpectedOutcome expected = ExpectedOutcome::Idle; + bool outcome_selected = false; + for (int index = 2; index < argc; ++index) + { + const std::string_view option = argv[index]; + if (option == "--jit") + jit = true; + else if (option == "--expect-panic" || option == "--expect-fatal-data" || + option == "--expect-undefined-fault" || option == "--expect-phase2") + { + if (outcome_selected) + { + std::cerr << "Select only one expected outcome\n"; + return 2; + } + outcome_selected = true; + expected = option == "--expect-panic" ? ExpectedOutcome::Panic : + option == "--expect-fatal-data" ? ExpectedOutcome::FatalData : + option == "--expect-undefined-fault" ? ExpectedOutcome::UndefinedFault : + ExpectedOutcome::Phase2; + } + else + { + std::cerr << "Unknown option: " << option << '\n'; + return 2; + } + } + return Run(argv[1], jit, expected) ? 0 : 1; +} diff --git a/docs/StarletOS_Checkpoint.json b/docs/StarletOS_Checkpoint.json new file mode 100644 index 0000000000..e1d3f23baf --- /dev/null +++ b/docs/StarletOS_Checkpoint.json @@ -0,0 +1,126 @@ +{ + "recordedAt": "2026-10-03", + "purpose": "Publish the exact existing StarletOS integration, without emulator behavior changes", + "sourceBaseCommit": "a8dbbab62de0ec429d9d887c39b9669c75f51c21", + "previousRemoteMaster": "b931671fde1e0461cbca37eeb8348a25d44ab76e", + "companionRepository": "https://git.yaya48.xyz/Yaya48/StarletOS", + "companionCheckpoint": "efa8b310da2c3e3bd36d762080d42d410a5413c9", + "files": [ + { + "path": "CMakeLists.txt", + "sha256": "1ed308f4ccc9d6a4405d9ec232a4ac64fb88aa9c69a4773ee93fe7c62b357d4d", + "bytes": 34657 + }, + { + "path": "Run-Wii-Starlet-Bootstrap.ps1", + "sha256": "486f99f899fde18371b7bfc2b67fcf12a64ceb748285cc00ae4a71716349d07a", + "bytes": 18583 + }, + { + "path": "Source/Core/Core/Boot/Boot.cpp", + "sha256": "43a0b1c2e60c9a362fe3637ba0aa7b23e8b75ce5e5517cd3515b50ec596c43a5", + "bytes": 27009 + }, + { + "path": "Source/Core/Core/CMakeLists.txt", + "sha256": "32dc85b995c9beb3739cc1ea554c308042e16b6c270071550728d18c85ada79e", + "bytes": 24388 + }, + { + "path": "Source/Core/Core/Config/MainSettings.cpp", + "sha256": "e331e8f7145ebfce5cd3d2be1b3dabe1dce597e8040132402f04a0152526ce56", + "bytes": 47583 + }, + { + "path": "Source/Core/Core/Config/MainSettings.h", + "sha256": "9052fbc4a1da18ea3e8a11a93d62ef06423da74671b1f9b1091bb62f1fcff62b", + "bytes": 18437 + }, + { + "path": "Source/Core/Core/ConfigManager.cpp", + "sha256": "7d369d0264f5a1ad0df5ac44e9b6ea0f514a196ec7f80952218f9439ab17ea10", + "bytes": 19807 + }, + { + "path": "Source/Core/Core/HW/HW.cpp", + "sha256": "7e9bcfff87f526510aad149e781ae57140e32f7b32beafec00271fbcdb152848", + "bytes": 4526 + }, + { + "path": "Source/Core/Core/IOS/Starlet/ARMCore.cpp", + "sha256": "40d45aed7c5edb4540105dcdee4dd8dcde88c21ba7f3279eff0a9564f2d02a29", + "bytes": 75597 + }, + { + "path": "Source/Core/Core/IOS/Starlet/BootstrapELF.cpp", + "sha256": "344e6e38922b64fa58d9755c2df5fd752b00cf3dc73114809f6e9cada724c42a", + "bytes": 8554 + }, + { + "path": "Source/Core/Core/IOS/Starlet/BootstrapELF.h", + "sha256": "b25184cb4d3934e02220417b8e4b8005fc57010f4d3ab7af697c98f11afb305f", + "bytes": 943 + }, + { + "path": "Source/Core/Core/IOS/Starlet/Starlet.cpp", + "sha256": "78e3b006501af06fbef220d95213690f8a31ed5b3c36699c6e07ba58be3ba852", + "bytes": 26039 + }, + { + "path": "Source/Core/Core/IOS/Starlet/Starlet.h", + "sha256": "5dbdd1a854fd17377a339886949d80ebb1969d6453a4bdfd5a6f9a503bbb4f2a", + "bytes": 3553 + }, + { + "path": "Source/Core/Core/IOS/Starlet/StarletMemory.cpp", + "sha256": "34900b6c340e412f99ddb3ce6ba17bdb4d85ba7b27b2adc522315b158b675fca", + "bytes": 189757 + }, + { + "path": "Source/Core/Core/IOS/Starlet/StarletMemory.h", + "sha256": "cfa3aee13f260590ae9f7cdd48f8f08d1c67e4e6d647ef3eca6e01d472dbdd29", + "bytes": 14873 + }, + { + "path": "Source/Core/DolphinNoGUI/PlatformHeadless.cpp", + "sha256": "1390cff0a139f0b0a90559e0fda855446d279190827a6c5079718421924f4041", + "bytes": 2533 + }, + { + "path": "Source/UnitTests/CMakeLists.txt", + "sha256": "bb0aeb694c2f328b891e5e961fe0af16c5b30ff1a5f02d4a8d84bdfa5930b581", + "bytes": 1659 + }, + { + "path": "Source/UnitTests/Core/CMakeLists.txt", + "sha256": "563899ae142ba4d0e146bb125bd35c8387a77d25db5ac48cf8ba48a32cb30314", + "bytes": 2043 + }, + { + "path": "Source/UnitTests/Core/IOS/Starlet/BootstrapELFTest.cpp", + "sha256": "62d483a737b68cca00e51c4a93511b4863022ed2f4b90e721ff36325630cfc3a", + "bytes": 7057 + }, + { + "path": "Source/UnitTests/Core/IOS/Starlet/DMATest.cpp", + "sha256": "15b0816292fff6dcf48d485466f7bf495f2128ba625f3e7c1f225614be631e84", + "bytes": 16450 + }, + { + "path": "Tools/StarletBootstrapProbe/StarletBootstrapProbe.cpp", + "sha256": "83873691164d9b92e3be62f699146feaf8ab713bcb45d25821975e7454f56ede", + "bytes": 6179 + }, + { + "path": "docs/Wii_Starlet_Bootstrap.md", + "sha256": "bd7ec585472dc44815da8fcb2be6d726b91f04dd0964d82097842fb4ba764021", + "bytes": 4937 + } + ], + "checks": { + "sourceSnapshotMatchesPriorStarletOSArchive": true, + "unpublishedHistoricalFileVersionsScanned": 67, + "privateFindings": 0, + "newFullBuildOrHardwareTest": "NOT RUN: publication of existing qualified bytes only; see companion phase reports" + } +} diff --git a/docs/StarletOS_Handoff.md b/docs/StarletOS_Handoff.md new file mode 100644 index 0000000000..407198d218 --- /dev/null +++ b/docs/StarletOS_Handoff.md @@ -0,0 +1,45 @@ +# StarletOS integration handoff — 2026-10-03 + +This checkpoint publishes the existing emulator changes used by the companion +[StarletOS repository](https://git.yaya48.xyz/Yaya48/StarletOS). It is a recovered +source checkpoint, not a new claim that stock IOS or physical hardware is fully +supported. The Rust project's current phase is 10C, stock NCD probe H02B awaiting +SD staging and its physical result; consult its HANDOFF and phase reports. + +## Published work + +- The existing local commits c230971f07 (NAND conformance/diagnostic tools) and + a8dbbab62d (SD CMD6/conformance report) precede this integration checkpoint. +- 16 modified files and 6 previously untracked files are now tracked here: + ARM bootstrap ELF loader and tests, direct PPC/Starlet launch, read-only NAND + and explicit disposable-copy support, diagnostics and regression runner, + ARMv5TE LDRD/STRD handling, and the existing MSVC dependency-detection fix. +- `docs/StarletOS_Checkpoint.json` records SHA-256s of the 22 exact source files. + The companion repository also pins the published Dolphin commit. +- Every runtime byte matches the source snapshot captured before publication. + Only Git attributes, ignore rules and handoff metadata were added this turn. +- Inspection of 67 changed file versions in the two pending commits found no + available private NAND key/content markers, device identifiers or credentials. + The staged integration files were inspected separately before publication. +- No full rebuild, new emulator test or Wii test is claimed by this Git import. + Historical qualification remains in the companion project's phase reports. + +## Other computers + +Clone this fork beside `starlet-ios-rust`, checkout the commit pinned in +`StarletOS/support/dolphin/manifest.json`, then follow the companion +`docs/DEVELOPMENT.md`. The previous private base bundle and source overlay are +no longer needed to retrieve these sources: their commits are on this server. +Use `docs/Wii_Starlet_Bootstrap.md` for the existing build and test runner. + +NAND, keys, user profiles, traces and generated builds remain local/ignored. +The `.starlet_user3` profile is needed only for the full Menu scenarios; the +standalone Starlet bootstrap probe does not need a NAND or keys. +The old `.before-p10a-h07b` script is retained locally as an ignored backup. + +## Future sessions + +Read AGENTS.md. Record the starting commit, changed behavior, actual tests and +remaining work. Update this handoff and any changed file hashes, commit to this +fork, then update the pinned dependency in StarletOS. Never label a build, a +source comparison or a fake emulator device as a physical Wii observation. diff --git a/docs/Wii_Starlet_Bootstrap.md b/docs/Wii_Starlet_Bootstrap.md new file mode 100644 index 0000000000..0d43cc4794 --- /dev/null +++ b/docs/Wii_Starlet_Bootstrap.md @@ -0,0 +1,98 @@ +# Starlet Rust bootstrap in this Dolphin fork + +This fork can load the Phase 01 Revolution IOS ELF directly into Starlet SRAM. The loader uses +the ELF's two `PT_LOAD` segments, zeroes their uninitialized tails, starts the ARM core at +`0xffff0000` in big-endian supervisor mode, and publishes `REV_TRACE` lines in the IOS log. +No boot0, keys, or NAND dump is needed for the standalone probe. + +## Fast development loop + +Build the Rust ELF from `starlet-ios-rust`: + +```powershell +cargo build --release +``` + +Configure this Dolphin fork with `ENABLE_TESTS=ON`, then build `StarletBootstrapProbe`. Run +the build commands from an x64 Native Tools Command Prompt (or after `vcvars64.bat`). For the +local MSVC configuration: + +```powershell +cmake --build .starlet_msvc2 --target StarletBootstrapProbe +powershell -NoProfile -ExecutionPolicy Bypass -File .\Run-Wii-Starlet-Bootstrap.ps1 +``` + +The script defaults to the sibling Rust repository's release ELF. It prints every trace line, +the stopping PC, and the ARM instruction count; it exits nonzero for a missing marker, fault, +or invalid ELF. `-JIT` exercises the fork's ARM JIT. + +To check the panic path without replacing the ordinary release artifact: + +```powershell +cd ..\starlet-ios-rust +cargo build --release --features panic-smoke --target-dir target/panic-smoke +cd ..\dolphin +powershell -NoProfile -ExecutionPolicy Bypass -File .\Run-Wii-Starlet-Bootstrap.ps1 ` + -ELF '../starlet-ios-rust/target/panic-smoke/armebv5te-none-eabi/release/revolution-ios-bootstrap' ` + -ExpectedOutcome Panic +``` + +To exercise a Rust fatal check and a synchronous ARM exception, derive two diagnostic +ELF copies from the release artifact: + +```powershell +cd ..\starlet-ios-rust +py -3 tools/make_smoke_elf.py target/armebv5te-none-eabi/release/revolution-ios-bootstrap target/phase01-smoke +cd ..\dolphin +powershell -NoProfile -ExecutionPolicy Bypass -File .\Run-Wii-Starlet-Bootstrap.ps1 ` + -ELF '../starlet-ios-rust/target/phase01-smoke/fatal-data.elf' -ExpectedOutcome Fatal +powershell -NoProfile -ExecutionPolicy Bypass -File .\Run-Wii-Starlet-Bootstrap.ps1 ` + -ELF '../starlet-ios-rust/target/phase01-smoke/undefined-entry.elf' -ExpectedOutcome Fault +``` + +Each command also accepts `-JIT` and `-FullDolphin`. The fatal image changes only the loaded +`.data` probe, producing `REV:FATAL:DATA`. The exception image replaces the reset branch with +an undefined instruction; the fault record reports code 1 and raw LR/SPSR. Both passed in the +standalone probe and full Dolphin, with interpreter and JIT, on 2026-09-23. The source release +ELF remains unchanged. + +The probe uses the actual `ARMCore` and `StarletMemory` implementations. It initializes the +high SRAM mapping, runs 4096-cycle slices, checks `__fault_code`, and decodes `REV_TRACE`. It +does not start Broadway, graphics, USB, or a Wii title. The two ELF parser unit tests run with: + +```powershell +cmake --build .starlet_msvc2 --target tests +.starlet_msvc2\Binaries\Tests\tests.exe --gtest_filter=BootstrapELFTest.* +``` + +## Full Dolphin run + +Set `Dolphin.General.WiiStarletBootstrapELF` to the absolute ELF path and +`Dolphin.Core.WiiIOSLLE=True`. This bypasses the boot0/keys/NAND dump initialization in +`StarletMemory` and starts the loaded program at its ELF entry. To run through `DolphinNoGUI` +using the existing `.starlet_user3` Wii profile: + +```powershell +powershell -NoProfile -ExecutionPolicy Bypass -File .\Run-Wii-Starlet-Bootstrap.ps1 -FullDolphin +``` + +This mode launches the Wii System Menu title only to initialize the full emulator; the Starlet +runtime is the supplied Rust ELF. It uses the Null video backend and headless platform, reads +new IOS log lines, and stops its own process after the expected marker. A populated Wii user +profile is required for this full frontend path. The standalone probe is the reproducible path +for the Phase 01 CPU and SRAM contract. + +The accepted ELF is ELF32, big-endian ARM `ET_EXEC`, entry `0xffff0000`, with exactly one +executable segment at `0xffff0000` (up to 64 KiB) and one writable segment at `0xfffe0000` +(up to 16 KiB). It must expose `REV_TRACE`, `__fault_code`, `__fault_lr`, and +`__fault_spsr` in the data segment's loaded memory span. Rejected images never write to SRAM. + +The Rust code reaches `REV:BOOT:IDLE` in both interpreter and JIT; the panic-smoke image +reaches `REV:PANIC`, and the diagnostic images reach fatal and fault states. All four outcomes +pass through the full `DolphinNoGUI` path. An initial +incremental MSVC build crashed before the first slice because `HW.cpp` still allocated the old +`Starlet` class size; AddressSanitizer identified the overflow. A forced rebuild fixed it, +and `Starlet::Create` now keeps allocation in the same translation unit as the class definition. +The CMake configuration also corrects a French MSVC `/showIncludes` prefix that made Ninja +record zero header dependencies. After rebuilding `HW.cpp`, Ninja records `Starlet.h` as a +dependency.