From 67633917bfe97f94caf4e92da3bb96eb4f3cfd31 Mon Sep 17 00:00:00 2001 From: "Skyth (Asilkan)" <19259897+blueskythlikesclouds@users.noreply.github.com> Date: Sat, 21 Dec 2024 00:44:05 +0300 Subject: [PATCH] Linux support. (#54) * Initial Linux attempt. * Add clang toolchain & make tools compile. * vcpkg as submodule. * First implementation of IO rewrite. (#31) * Fix directory iteration resolving symlinks. * Refactor kernel objects to be lock-free. * Implement guest critical sections using std::atomic. * Make D3D12 support optional. (#33) * Make D3D12 support optional. * Update ShaderRecomp, fix macros. * Replace QueryPerformanceCounter. (#35) * Add Linux home path for GetUserPath(). (#36) * Cross-platform Sleep. (#37) * Add mmap implementations for virtual allocation. (#38) * Cross-platform TLS. (#34) * Cross-platform TLS. * Fix front() to back(), use Mutex. * Fix global variable namings. --------- Co-authored-by: Skyth <19259897+blueskythlikesclouds@users.noreply.github.com> * Unicode support. (#39) * Replace CreateDirectoryA with Unicode version. * Cross platform thread implementation. (#41) * Cross-platform thread implementation. * Put set thread name calls behind a Win32 macro. * Cross-platform semaphore implementation. (#43) * xam: use SDL for keyboard input * Cross-platform atomic operations. (#44) * Cross-platform spin lock implementation. * Cross-platform reference counting. * Cross-platform event implementation. (#47) * Compiling and running on Linux. (#49) * Current work trying to get it to compile. * Update vcpkg.json baseline. * vcpkg, memory mapped file. * Bitscan forward. * Fix localtime_s. * FPS patches high res clock. * Rename Window to GameWindow. Fix guest pointers. * GetCurrentThreadID gone. * Code cache pointers, RenderWindow type. * Add Linux stubs. * Refactor Config. * Fix paths. * Add linux-release config. * FS fixes. * Fix Windows compilation errors & unicode converter crash. * Rename physical memory allocation functions to not clash with X11. * Fix NULL character being added on RtlMultiByteToUnicodeN. * Use std::exit. * Add protection to memory on Linux. * Convert majority of dependencies to submodules. (#48) * Convert majority of dependencies to submodules. * Don't compile header-only libraries. * Fix a few incorrect data types. * Fix config directory. * Unicode fixes & sizeof asserts. * Change the exit function to not call static destructors. * Fix files picker. * Add RelWithDebInfo preset for Linux. * Implement OS Restart on Linux. (#50) --------- Co-authored-by: Dario * Update PowerRecomp. * Add Env Var detection for VCPKG_ROOT, add DLC detection. * Use error code version on DLC directory iterator. * Set D3D12MA::ALLOCATOR_FLAG_DONT_PREFER_SMALL_BUFFERS_COMMITTED flag. * Linux flatpak. (#51) * Add flatpak support. * Add game install directory override for flatpak. * Flatpak'ing. * Flatpak it some more. * We flat it, we pak it. * Flatpak'd. * The Marvelous Misadventures of Flatpak. * Attempt to change logic of NFD and show error. * Flattenpakken. * Use game install directory instead of current path. * Attempt to fix line endings. * Update io.github.hedge_dev.unleashedrecomp.json * Fix system time query implementation. * Add Present Wait to Vulkan to improve frame pacing and reduce latency. (#53) * Add present wait support to Vulkan. * Default to triple buffering if presentWait is supported. * Bracey fellas. * Update paths.h * SDL2 audio (again). (#52) * Implement SDL2 audio (again). * Call timeBeginPeriod/timeEndPeriod. * Replace miniaudio with SDL mixer. * Queue audio samples in a separate thread. * Enable CMake option override policy & fix compilation error. * Fix compilation error on Linux. * Fix but also trim shared strings. * Wayland support. (#55) * Make channel index a global variable in embedded player. * Fix SDL Audio selection for OGG on Flatpak. * Minor installer wizard fixes. * Fix compilation error. * Yield in model consumer and pipeline compiler threads. * Special case Sleep(0) to yield on Linux. * Add App Id hint. * Correct implementation for auto reset events. (#57) --------- Co-authored-by: Dario Co-authored-by: Hyper <34012267+hyperbx@users.noreply.github.com> --- .gitmodules | 45 +- CMakeLists.txt | 4 + CMakePresets.json | 59 ++ UnleashedRecomp/CMakeLists.txt | 927 +++++++++--------- .../api/Hedgehog/Base/System/hhAllocator.h | 8 +- .../Base/Type/detail/hhStringHolder.h | 28 +- .../Base/Type/detail/hhStringHolder.inl | 121 +-- .../api/Hedgehog/Base/Type/hhSharedString.h | 50 - .../api/Hedgehog/Base/Type/hhSharedString.inl | 367 +------ .../api/boost/smart_ptr/shared_ptr.h | 12 +- UnleashedRecomp/app.cpp | 10 +- UnleashedRecomp/app.h | 2 +- UnleashedRecomp/apu/audio.cpp | 4 +- UnleashedRecomp/apu/audio.h | 4 +- .../apu/driver/miniaudio_driver.cpp | 53 - UnleashedRecomp/apu/driver/sdl2_driver.cpp | 128 +++ .../{miniaudio_driver.h => sdl2_driver.h} | 0 UnleashedRecomp/apu/driver/xaudio_driver.cpp | 2 +- UnleashedRecomp/apu/embedded_player.cpp | 259 +---- UnleashedRecomp/cpu/code_cache.cpp | 21 +- UnleashedRecomp/cpu/code_cache.h | 2 +- UnleashedRecomp/cpu/guest_thread.cpp | 125 ++- UnleashedRecomp/cpu/guest_thread.h | 43 +- UnleashedRecomp/cpu/ppc_context.h | 6 +- UnleashedRecomp/exports.cpp | 6 +- UnleashedRecomp/framework.h | 18 +- UnleashedRecomp/gpu/imgui/imgui_snapshot.cpp | 2 +- UnleashedRecomp/gpu/rhi/plume_d3d12.cpp | 3 +- .../gpu/rhi/plume_render_interface_types.h | 6 + UnleashedRecomp/gpu/rhi/plume_vulkan.cpp | 59 +- UnleashedRecomp/gpu/rhi/plume_vulkan.h | 19 +- UnleashedRecomp/gpu/video.cpp | 185 ++-- UnleashedRecomp/gpu/video.h | 14 +- UnleashedRecomp/hid/driver/sdl_hid.cpp | 9 +- UnleashedRecomp/install/installer.cpp | 12 +- .../install/memory_mapped_file.cpp | 6 +- UnleashedRecomp/install/xex_patcher.cpp | 4 +- UnleashedRecomp/kernel/function.h | 40 +- UnleashedRecomp/kernel/heap.cpp | 9 +- UnleashedRecomp/kernel/heap.h | 4 +- UnleashedRecomp/kernel/imports.cpp | 519 ++++++---- UnleashedRecomp/kernel/io/file_system.cpp | 360 +++++-- UnleashedRecomp/kernel/memory.cpp | 21 + UnleashedRecomp/kernel/memory.h | 5 + UnleashedRecomp/kernel/xam.cpp | 215 ++-- UnleashedRecomp/kernel/xam.h | 106 +- UnleashedRecomp/kernel/xdm.cpp | 59 +- UnleashedRecomp/kernel/xdm.h | 165 +++- UnleashedRecomp/locale/config_locale.cpp | 1 - UnleashedRecomp/locale/locale.cpp | 1 + UnleashedRecomp/locale/locale.h | 10 +- UnleashedRecomp/main.cpp | 57 +- UnleashedRecomp/misc_impl.cpp | 27 +- UnleashedRecomp/mutex.h | 10 +- UnleashedRecomp/os/linux/logger_linux.cpp | 17 + UnleashedRecomp/os/linux/media_linux.cpp | 7 + UnleashedRecomp/os/linux/process_linux.cpp | 57 ++ UnleashedRecomp/os/linux/version_linux.cpp | 7 + UnleashedRecomp/os/win32/process_win32.cpp | 21 +- UnleashedRecomp/patches/camera_patches.cpp | 6 +- UnleashedRecomp/patches/fps_patches.cpp | 4 +- UnleashedRecomp/patches/misc_patches.cpp | 2 +- UnleashedRecomp/patches/player_patches.cpp | 6 +- UnleashedRecomp/patches/video_patches.cpp | 4 +- UnleashedRecomp/stdafx.cpp | 6 - UnleashedRecomp/stdafx.h | 30 +- UnleashedRecomp/ui/achievement_menu.cpp | 11 +- .../ui/{window.cpp => game_window.cpp} | 90 +- .../ui/{window.h => game_window.h} | 15 +- UnleashedRecomp/ui/installer_wizard.cpp | 141 +-- UnleashedRecomp/ui/installer_wizard.h | 2 +- UnleashedRecomp/ui/options_menu.cpp | 16 +- UnleashedRecomp/ui/window_events.h | 2 +- UnleashedRecomp/user/config.cpp | 4 +- UnleashedRecomp/user/config.h | 589 ++++++++++- UnleashedRecomp/user/config_detail.cpp | 193 ---- UnleashedRecomp/user/config_detail.h | 436 -------- UnleashedRecomp/user/paths.h | 34 +- UnleashedRecompLib/CMakeLists.txt | 16 +- flatpak/README.md | 10 + ...o.github.hedge_dev.unleashedrecomp.desktop | 7 + .../io.github.hedge_dev.unleashedrecomp.json | 56 ++ ...hub.hedge_dev.unleashedrecomp.metainfo.xml | 26 + thirdparty/CMakeLists.txt | 19 +- thirdparty/D3D12MemoryAllocator | 1 + thirdparty/SDL | 1 + thirdparty/SDL_mixer | 1 + thirdparty/Vulkan-Headers | 1 + thirdparty/VulkanMemoryAllocator | 1 + thirdparty/concurrentqueue | 1 + thirdparty/imgui | 1 + thirdparty/magic_enum | 1 + thirdparty/miniaudio | 1 - thirdparty/nativefiledialog-extended | 1 + thirdparty/stb | 1 + thirdparty/tiny-AES-c | 1 + thirdparty/unordered_dense | 1 + thirdparty/vcpkg | 1 + thirdparty/volk | 1 + toolchains/linux-clang.cmake | 86 ++ tools/PowerRecomp | 2 +- tools/ShaderRecomp | 2 +- tools/bc_diff/CMakeLists.txt | 1 - tools/bc_diff/bc_diff.cpp | 2 + tools/file_to_c/CMakeLists.txt | 3 +- tools/file_to_c/file_to_c.cpp | 7 +- tools/fshasher/CMakeLists.txt | 2 - tools/fshasher/plainargs.h | 4 +- vcpkg.json | 33 +- 109 files changed, 3373 insertions(+), 2850 deletions(-) delete mode 100644 UnleashedRecomp/apu/driver/miniaudio_driver.cpp create mode 100644 UnleashedRecomp/apu/driver/sdl2_driver.cpp rename UnleashedRecomp/apu/driver/{miniaudio_driver.h => sdl2_driver.h} (100%) create mode 100644 UnleashedRecomp/os/linux/logger_linux.cpp create mode 100644 UnleashedRecomp/os/linux/media_linux.cpp create mode 100644 UnleashedRecomp/os/linux/process_linux.cpp create mode 100644 UnleashedRecomp/os/linux/version_linux.cpp rename UnleashedRecomp/ui/{window.cpp => game_window.cpp} (61%) rename UnleashedRecomp/ui/{window.h => game_window.h} (94%) delete mode 100644 UnleashedRecomp/user/config_detail.cpp delete mode 100644 UnleashedRecomp/user/config_detail.h create mode 100644 flatpak/README.md create mode 100644 flatpak/io.github.hedge_dev.unleashedrecomp.desktop create mode 100644 flatpak/io.github.hedge_dev.unleashedrecomp.json create mode 100644 flatpak/io.github.hedge_dev.unleashedrecomp.metainfo.xml create mode 160000 thirdparty/D3D12MemoryAllocator create mode 160000 thirdparty/SDL create mode 160000 thirdparty/SDL_mixer create mode 160000 thirdparty/Vulkan-Headers create mode 160000 thirdparty/VulkanMemoryAllocator create mode 160000 thirdparty/concurrentqueue create mode 160000 thirdparty/imgui create mode 160000 thirdparty/magic_enum delete mode 160000 thirdparty/miniaudio create mode 160000 thirdparty/nativefiledialog-extended create mode 160000 thirdparty/stb create mode 160000 thirdparty/tiny-AES-c create mode 160000 thirdparty/unordered_dense create mode 160000 thirdparty/vcpkg create mode 160000 thirdparty/volk create mode 100644 toolchains/linux-clang.cmake diff --git a/.gitmodules b/.gitmodules index 5ca29d40..6981e9f4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,6 +16,45 @@ [submodule "thirdparty/msdf-atlas-gen"] path = thirdparty/msdf-atlas-gen url = https://github.com/Chlumsky/msdf-atlas-gen.git -[submodule "thirdparty/miniaudio"] - path = thirdparty/miniaudio - url = https://github.com/mackron/miniaudio +[submodule "thirdparty/vcpkg"] + path = thirdparty/vcpkg + url = https://github.com/microsoft/vcpkg +[submodule "thirdparty/volk"] + path = thirdparty/volk + url = https://github.com/zeux/volk +[submodule "thirdparty/SDL"] + path = thirdparty/SDL + url = https://github.com/libsdl-org/SDL.git +[submodule "thirdparty/Vulkan-Headers"] + path = thirdparty/Vulkan-Headers + url = https://github.com/KhronosGroup/Vulkan-Headers.git +[submodule "thirdparty/VulkanMemoryAllocator"] + path = thirdparty/VulkanMemoryAllocator + url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git +[submodule "thirdparty/D3D12MemoryAllocator"] + path = thirdparty/D3D12MemoryAllocator + url = https://github.com/GPUOpen-LibrariesAndSDKs/D3D12MemoryAllocator.git +[submodule "thirdparty/stb"] + path = thirdparty/stb + url = https://github.com/nothings/stb.git +[submodule "thirdparty/concurrentqueue"] + path = thirdparty/concurrentqueue + url = https://github.com/cameron314/concurrentqueue.git +[submodule "thirdparty/tiny-AES-c"] + path = thirdparty/tiny-AES-c + url = https://github.com/kokke/tiny-AES-c.git +[submodule "thirdparty/magic_enum"] + path = thirdparty/magic_enum + url = https://github.com/Neargye/magic_enum.git +[submodule "thirdparty/nativefiledialog-extended"] + path = thirdparty/nativefiledialog-extended + url = https://github.com/btzy/nativefiledialog-extended.git +[submodule "thirdparty/imgui"] + path = thirdparty/imgui + url = https://github.com/ocornut/imgui.git +[submodule "thirdparty/unordered_dense"] + path = thirdparty/unordered_dense + url = https://github.com/martinus/unordered_dense.git +[submodule "thirdparty/SDL_mixer"] + path = thirdparty/SDL_mixer + url = https://github.com/libsdl-org/SDL_mixer \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a7f85b7..652f3acd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,9 @@ cmake_minimum_required (VERSION 3.20) +if(NOT DEFINED ENV{VCPKG_ROOT}) + message(FATAL_ERROR "VCPKG_ROOT is not defined!") +endif() + include($ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake) set(SWA_THIRDPARTY_ROOT ${CMAKE_SOURCE_DIR}/thirdparty) set(SWA_TOOLS_ROOT ${CMAKE_SOURCE_DIR}/tools) diff --git a/CMakePresets.json b/CMakePresets.json index 6bd632e2..3c2d0987 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -16,6 +16,9 @@ "type": "FILEPATH" } }, + "environment": { + "VCPKG_ROOT": "${sourceDir}/thirdparty/vcpkg" + }, "condition": { "type": "equals", "lhs": "${hostSystemName}", @@ -55,6 +58,62 @@ "CMAKE_BUILD_TYPE": "Release", "CMAKE_INTERPROCEDURAL_OPTIMIZATION": true } + }, + { + "name": "linux-base", + "hidden": true, + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "${sourceDir}/out/install/${presetName}", + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": { + "value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", + "type": "FILEPATH" + }, + "VCPKG_TARGET_TRIPLET": { + "value": "x64-linux", + "type": "STRING" + }, + "VCPKG_CHAINLOAD_TOOLCHAIN_FILE": "${sourceDir}/toolchains/linux-clang.cmake" + }, + "environment": { + "VCPKG_ROOT": "${sourceDir}/thirdparty/vcpkg" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + }, + "vendor": { + "microsoft.com/VisualStudioRemoteSettings/CMake/2.0": { + "remoteSourceRootDir": "$env{HOME}/.vs/$ms{projectDirName}" + } + } + }, + { + "name": "linux-debug", + "displayName": "Linux-Debug", + "inherits": "linux-base", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "linux-relwithdebinfo", + "displayName": "Linux-RelWithDebInfo", + "inherits": "linux-base", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } + }, + { + "name": "linux-release", + "displayName": "Linux-Release", + "inherits": "linux-base", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_INTERPROCEDURAL_OPTIMIZATION": true + } } ] } diff --git a/UnleashedRecomp/CMakeLists.txt b/UnleashedRecomp/CMakeLists.txt index 0dbeca6e..395523fc 100644 --- a/UnleashedRecomp/CMakeLists.txt +++ b/UnleashedRecomp/CMakeLists.txt @@ -1,439 +1,488 @@ -project("UnleashedRecomp") -set(TARGET_NAME "SWA") - -option(SWA_XAUDIO2 "Use XAudio2 for audio playback" OFF) - -function(BIN2C) - cmake_parse_arguments(BIN2C_ARGS "" "TARGET_OBJ;SOURCE_FILE;DEST_FILE;ARRAY_NAME;COMPRESSION_TYPE" "" ${ARGN}) - - if(NOT BIN2C_ARGS_TARGET_OBJ) - message(FATAL_ERROR "TARGET_OBJ not specified.") - endif() - - if(NOT BIN2C_ARGS_SOURCE_FILE) - message(FATAL_ERROR "SOURCE_FILE not specified.") - endif() - - if(NOT BIN2C_ARGS_DEST_FILE) - set(BIN2C_ARGS_DEST_FILE "${BIN2C_ARGS_SOURCE_FILE}") - endif() - - if(NOT BIN2C_ARGS_COMPRESSION_TYPE) - set(BIN2C_ARGS_COMPRESSION_TYPE "none") - endif() - - add_custom_command(OUTPUT "${BIN2C_ARGS_DEST_FILE}.c" - COMMAND file_to_c "${BIN2C_ARGS_SOURCE_FILE}" "${BIN2C_ARGS_ARRAY_NAME}" "${BIN2C_ARGS_COMPRESSION_TYPE}" "${BIN2C_ARGS_DEST_FILE}.c" "${BIN2C_ARGS_DEST_FILE}.h" - DEPENDS "${BIN2C_ARGS_SOURCE_FILE}" file_to_c - BYPRODUCTS "${BIN2C_ARGS_DEST_FILE}.h" - COMMENT "Generating binary header for ${BIN2C_ARGS_SOURCE_FILE}..." - ) - - set_source_files_properties(${BIN2C_ARGS_DEST_FILE}.c PROPERTIES SKIP_PRECOMPILE_HEADERS ON) - target_sources(${BIN2C_ARGS_TARGET_OBJ} PRIVATE ${BIN2C_ARGS_DEST_FILE}.c) -endfunction() - -add_compile_options( - /fp:strict - -march=sandybridge - -fno-strict-aliasing - - -Wno-switch - -Wno-unused-function - -Wno-unused-variable - -Wno-unused-but-set-variable - -Wno-void-pointer-to-int-cast - -Wno-int-to-void-pointer-cast - -Wno-invalid-offsetof -) - -add_compile_definitions( - SWA_IMPL - SDL_MAIN_HANDLED - _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR # Microsoft wtf? - _CRT_SECURE_NO_WARNINGS) - -set(SWA_PRECOMPILED_HEADERS - "stdafx.h" -) - -set(SWA_KERNEL_CXX_SOURCES - "kernel/imports.cpp" - "kernel/xdm.cpp" - "kernel/heap.cpp" - "kernel/memory.cpp" - "kernel/xam.cpp" - "kernel/io/file_system.cpp" -) - -set(SWA_LOCALE_CXX_SOURCES - "locale/config_locale.cpp" - "locale/locale.cpp" -) - -set(SWA_OS_CXX_SOURCES - "os/logger.cpp" - "os/media.cpp" - "os/process.cpp" - "os/version.cpp" -) - -if (WIN32) - list(APPEND SWA_OS_CXX_SOURCES - "os/win32/logger_win32.cpp" - "os/win32/media_win32.cpp" - "os/win32/process_win32.cpp" - "os/win32/version_win32.cpp" - ) -endif() - -set(SWA_CPU_CXX_SOURCES - "cpu/guest_thread.cpp" - "cpu/code_cache.cpp" -) - -set(SWA_GPU_CXX_SOURCES - "gpu/video.cpp" - "gpu/imgui/imgui_common.cpp" - "gpu/imgui/imgui_font_builder.cpp" - "gpu/imgui/imgui_snapshot.cpp" - "gpu/rhi/plume_d3d12.cpp" - "gpu/rhi/plume_vulkan.cpp" -) - -set(SWA_APU_CXX_SOURCES - "apu/audio.cpp" - "apu/embedded_player.cpp" -) - -if (SWA_XAUDIO2) - list(APPEND SWA_APU_CXX_SOURCES "apu/driver/xaudio_driver.cpp") -else() - list(APPEND SWA_APU_CXX_SOURCES "apu/driver/miniaudio_driver.cpp") -endif() - -set(SWA_HID_CXX_SOURCES - "hid/hid.cpp" - "hid/driver/sdl_hid.cpp" -) - -set(SWA_PATCHES_CXX_SOURCES - "patches/ui/CHudPause_patches.cpp" - "patches/ui/CTitleStateIntro_patches.cpp" - "patches/ui/CTitleStateMenu_patches.cpp" - "patches/ui/frontend_listener.cpp" - "patches/audio_patches.cpp" - "patches/camera_patches.cpp" - "patches/fps_patches.cpp" - "patches/misc_patches.cpp" - "patches/object_patches.cpp" - "patches/player_patches.cpp" - "patches/resident_patches.cpp" - "patches/video_patches.cpp" -) - -set(SWA_UI_CXX_SOURCES - "ui/achievement_menu.cpp" - "ui/achievement_overlay.cpp" - "ui/installer_wizard.cpp" - "ui/button_guide.cpp" - "ui/fader.cpp" - "ui/message_window.cpp" - "ui/options_menu_thumbnails.cpp" - "ui/options_menu.cpp" - "ui/sdl_listener.cpp" - "ui/window.cpp" -) - -set(SWA_INSTALL_CXX_SOURCES - "install/installer.cpp" - "install/iso_file_system.cpp" - "install/memory_mapped_file.cpp" - "install/xcontent_file_system.cpp" - "install/xex_patcher.cpp" - "install/hashes/apotos_shamar.cpp" - "install/hashes/chunnan.cpp" - "install/hashes/empire_city_adabat.cpp" - "install/hashes/game.cpp" - "install/hashes/holoska.cpp" - "install/hashes/mazuri.cpp" - "install/hashes/spagonia.cpp" - "install/hashes/update.cpp" -) - -set(LIBMSPACK_PATH ${SWA_THIRDPARTY_ROOT}/libmspack/libmspack/mspack) - -set(LIBMSPACK_C_SOURCES - ${LIBMSPACK_PATH}/lzxd.c -) - -set_source_files_properties(${LIBMSPACK_C_SOURCES} PROPERTIES SKIP_PRECOMPILE_HEADERS ON) - -set(SMOLV_SOURCE_DIR "${SWA_TOOLS_ROOT}/ShaderRecomp/thirdparty/smol-v/source") -set(BC_DIFF_SOURCE_DIR "${SWA_TOOLS_ROOT}/bc_diff") - -set(MINIAUDIO_INCLUDE_DIRS "${SWA_THIRDPARTY_ROOT}/miniaudio") - -set(SWA_USER_CXX_SOURCES - "user/achievement_data.cpp" - "user/config.cpp" - "user/config_detail.cpp" -) - -set(SWA_CXX_SOURCES - "app.cpp" - "exports.cpp" - "main.cpp" - "misc_impl.cpp" - "stdafx.cpp" - - ${SWA_KERNEL_CXX_SOURCES} - ${SWA_LOCALE_CXX_SOURCES} - ${SWA_OS_CXX_SOURCES} - ${SWA_CPU_CXX_SOURCES} - ${SWA_GPU_CXX_SOURCES} - ${SWA_APU_CXX_SOURCES} - ${SWA_HID_CXX_SOURCES} - ${SWA_PATCHES_CXX_SOURCES} - ${SWA_UI_CXX_SOURCES} - ${SWA_INSTALL_CXX_SOURCES} - ${LIBMSPACK_C_SOURCES} - "${SMOLV_SOURCE_DIR}/smolv.cpp" - ${SWA_USER_CXX_SOURCES} -) - -if (WIN32) - # Set up Win32 resources for application icon. - set(ICON_PATH "${PROJECT_SOURCE_DIR}/../UnleashedRecompResources/images/game_icon.ico") - configure_file("res/win32/res.rc" "${CMAKE_BINARY_DIR}/res.rc" @ONLY) - add_executable(UnleashedRecomp ${SWA_CXX_SOURCES} "${CMAKE_BINARY_DIR}/res.rc") -else() - add_executable(UnleashedRecomp ${SWA_CXX_SOURCES}) -endif() - -set_target_properties(UnleashedRecomp PROPERTIES OUTPUT_NAME ${TARGET_NAME}) - -find_package(directx-headers CONFIG REQUIRED) -find_package(directx12-agility CONFIG REQUIRED) -find_package(d3d12-memory-allocator CONFIG REQUIRED) -find_package(SDL2 CONFIG REQUIRED) -find_package(unordered_dense CONFIG REQUIRED) -find_package(VulkanHeaders CONFIG) -find_package(volk CONFIG REQUIRED) -find_package(VulkanMemoryAllocator CONFIG REQUIRED) -find_package(xxHash CONFIG REQUIRED) -find_package(PkgConfig REQUIRED) -pkg_check_modules(tomlplusplus REQUIRED IMPORTED_TARGET tomlplusplus) -find_package(directx-dxc REQUIRED) -find_package(zstd CONFIG REQUIRED) -find_package(Stb REQUIRED) -find_package(unofficial-concurrentqueue REQUIRED) -find_package(imgui CONFIG REQUIRED) -find_package(magic_enum CONFIG REQUIRED) -find_package(unofficial-tiny-aes-c CONFIG REQUIRED) -find_package(nfd CONFIG REQUIRED) -find_package(Vorbis CONFIG REQUIRED) -find_package(fmt CONFIG REQUIRED) - -file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/D3D12) -add_custom_command(TARGET UnleashedRecomp POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_CURRENT_BINARY_DIR}/D3D12 - COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_CURRENT_BINARY_DIR}/D3D12 - COMMAND_EXPAND_LISTS -) - -file(COPY ${PACKAGE_PREFIX_DIR}/bin/dxil.dll DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) - -target_link_libraries(UnleashedRecomp PRIVATE - Microsoft::DirectX-Headers - Microsoft::DirectX-Guids - Microsoft::DirectX12-Agility - Microsoft::DirectXShaderCompiler - comctl32 - dxgi - Vulkan::Headers - volk::volk - volk::volk_headers - GPUOpen::VulkanMemoryAllocator - ntdll - o1heap - PowerUtils - SDL2::SDL2-static - UnleashedRecompLib - unofficial::D3D12MemoryAllocator - unordered_dense::unordered_dense - winmm - xxHash::xxhash - PkgConfig::tomlplusplus - zstd::libzstd_static - unofficial::concurrentqueue::concurrentqueue - Synchronization - imgui::imgui - magic_enum::magic_enum - unofficial::tiny-aes-c::tiny-aes-c - nfd::nfd - msdf-atlas-gen::msdf-atlas-gen - Vorbis::vorbisfile - fmt::fmt -) - -target_include_directories(UnleashedRecomp PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/api - ${SWA_THIRDPARTY_ROOT}/ddspp - ${SWA_THIRDPARTY_ROOT}/TinySHA1 - ${LIBMSPACK_PATH} - ${Stb_INCLUDE_DIR} - ${SMOLV_SOURCE_DIR} - ${MINIAUDIO_INCLUDE_DIRS} - ${BC_DIFF_SOURCE_DIR} -) - -target_precompile_headers(UnleashedRecomp PUBLIC ${SWA_PRECOMPILED_HEADERS}) - -function(compile_shader FILE_PATH TARGET_NAME) - set(FILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/gpu/shader/${FILE_PATH}.hlsl) - cmake_path(GET FILE_PATH STEM VARIABLE_NAME) - add_custom_command( - OUTPUT ${FILE_PATH}.dxil.h - COMMAND ${DIRECTX_DXC_TOOL} -T ${TARGET_NAME} -HV 2021 -all-resources-bound -Wno-ignored-attributes -Fh ${FILE_PATH}.dxil.h ${FILE_PATH} -Vn g_${VARIABLE_NAME}_dxil - DEPENDS ${FILE_PATH} - ) - add_custom_command( - OUTPUT ${FILE_PATH}.spirv.h - COMMAND ${DIRECTX_DXC_TOOL} -T ${TARGET_NAME} -HV 2021 -all-resources-bound -spirv -fvk-use-dx-layout ${ARGN} -Fh ${FILE_PATH}.spirv.h ${FILE_PATH} -Vn g_${VARIABLE_NAME}_spirv - DEPENDS ${FILE_PATH} - ) - target_sources(UnleashedRecomp PRIVATE ${FILE_PATH}.dxil.h ${FILE_PATH}.spirv.h) -endfunction() - -function(compile_vertex_shader FILE_PATH) - compile_shader(${FILE_PATH} vs_6_0 -fvk-invert-y) -endfunction() - -function(compile_pixel_shader FILE_PATH) - compile_shader(${FILE_PATH} ps_6_0) -endfunction() - -compile_vertex_shader(copy_vs) -compile_pixel_shader(csd_filter_ps) -compile_pixel_shader(enhanced_motion_blur_ps) -compile_pixel_shader(gaussian_blur_3x3) -compile_pixel_shader(gaussian_blur_5x5) -compile_pixel_shader(gaussian_blur_7x7) -compile_pixel_shader(gaussian_blur_9x9) -compile_pixel_shader(gamma_correction_ps) -compile_pixel_shader(imgui_ps) -compile_vertex_shader(imgui_vs) -compile_pixel_shader(movie_ps) -compile_vertex_shader(movie_vs) -compile_pixel_shader(resolve_msaa_depth_2x) -compile_pixel_shader(resolve_msaa_depth_4x) -compile_pixel_shader(resolve_msaa_depth_8x) - -function(generate_aggregate_header INPUT_DIRECTORY OUTPUT_FILE) - get_filename_component(ABS_OUTPUT_FILE "${OUTPUT_FILE}" ABSOLUTE) - file(GLOB_RECURSE HEADER_FILES "${INPUT_DIRECTORY}/*.h") - set(HEADER_CONTENT "#pragma once\n\n") - - foreach(HEADER_FILE IN LISTS HEADER_FILES) - get_filename_component(ABS_HEADER_FILE "${HEADER_FILE}" ABSOLUTE) - if (ABS_HEADER_FILE STREQUAL ABS_OUTPUT_FILE) - continue() - endif() - file(RELATIVE_PATH RELATIVE_HEADER_FILE "${INPUT_DIRECTORY}" "${HEADER_FILE}") - string(APPEND HEADER_CONTENT "#include \"${RELATIVE_HEADER_FILE}\"\n") - endforeach() - - if (EXISTS "${OUTPUT_FILE}") - file(READ "${OUTPUT_FILE}" EXISTING_CONTENT) - if (EXISTING_CONTENT STREQUAL HEADER_CONTENT) - return() - endif() - endif() - - file(WRITE "${OUTPUT_FILE}" "${HEADER_CONTENT}") -endfunction() - -generate_aggregate_header( - "${CMAKE_CURRENT_SOURCE_DIR}/api" - "${CMAKE_CURRENT_SOURCE_DIR}/api/SWA.h" -) - -set(RESOURCES_SOURCE_PATH "${PROJECT_SOURCE_DIR}/../UnleashedRecompResources") -set(RESOURCES_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/res") - -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/bc_diff/button_bc_diff.bin" DEST_FILE "${RESOURCES_OUTPUT_PATH}/bc_diff/button_bc_diff.bin" ARRAY_NAME "g_button_bc_diff" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/font/im_font_atlas.bin" DEST_FILE "${RESOURCES_OUTPUT_PATH}/font/im_font_atlas.bin" ARRAY_NAME "g_im_font_atlas" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/font/im_font_atlas.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/font/im_font_atlas.dds" ARRAY_NAME "g_im_font_atlas_texture" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/achievements_menu/trophy.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/achievements_menu/trophy.dds" ARRAY_NAME "g_trophy" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/controller.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/controller.dds" ARRAY_NAME "g_controller" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/general_window.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/general_window.dds" ARRAY_NAME "g_general_window" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/kbm.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/kbm.dds" ARRAY_NAME "g_kbm" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/select_fade.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/select_fade.dds" ARRAY_NAME "g_select_fade" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/select_fill.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/select_fill.dds" ARRAY_NAME "g_select_fill" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/arrow_circle.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/arrow_circle.dds" ARRAY_NAME "g_arrow_circle" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_001.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_001.dds" ARRAY_NAME "g_install_001" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_002.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_002.dds" ARRAY_NAME "g_install_002" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_003.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_003.dds" ARRAY_NAME "g_install_003" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_004.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_004.dds" ARRAY_NAME "g_install_004" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_005.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_005.dds" ARRAY_NAME "g_install_005" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_006.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_006.dds" ARRAY_NAME "g_install_006" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_007.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_007.dds" ARRAY_NAME "g_install_007" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_008.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_008.dds" ARRAY_NAME "g_install_008" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/miles_electric_icon.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/miles_electric_icon.dds" ARRAY_NAME "g_miles_electric_icon" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/pulse_install.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/pulse_install.dds" ARRAY_NAME "g_pulse_install" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/achievement_notifications.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/achievement_notifications.dds" ARRAY_NAME "g_achievement_notifications" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/allow_background_input.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/allow_background_input.dds" ARRAY_NAME "g_allow_background_input" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/allow_dpad_movement.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/allow_dpad_movement.dds" ARRAY_NAME "g_allow_dpad_movement" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/antialiasing.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/antialiasing.dds" ARRAY_NAME "g_antialiasing" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/aspect_ratio.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/aspect_ratio.dds" ARRAY_NAME "g_aspect_ratio" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/battle_theme.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/battle_theme.dds" ARRAY_NAME "g_battle_theme" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/brightness.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/brightness.dds" ARRAY_NAME "g_brightness" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/control_tutorial.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/control_tutorial.dds" ARRAY_NAME "g_control_tutorial" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/controller_icons.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/controller_icons.dds" ARRAY_NAME "g_controller_icons" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/default.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/default.dds" ARRAY_NAME "g_default" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/effects_volume.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/effects_volume.dds" ARRAY_NAME "g_effects_volume" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/fps.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/fps.dds" ARRAY_NAME "g_fps" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/fullscreen.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/fullscreen.dds" ARRAY_NAME "g_fullscreen" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/gi_texture_filtering_bilinear.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/gi_texture_filtering_bilinear.dds" ARRAY_NAME "g_gi_texture_filtering_bilinear" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/gi_texture_filtering_bicubic.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/gi_texture_filtering_bicubic.dds" ARRAY_NAME "g_gi_texture_filtering_bicubic" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/hints.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/hints.dds" ARRAY_NAME "g_hints" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/invert_camera_x.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/invert_camera_x.dds" ARRAY_NAME "g_invert_camera_x" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/invert_camera_y.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/invert_camera_y.dds" ARRAY_NAME "g_invert_camera_y" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/language.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/language.dds" ARRAY_NAME "g_language" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/monitor.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/monitor.dds" ARRAY_NAME "g_monitor" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/motion_blur_off.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/motion_blur_off.dds" ARRAY_NAME "g_motion_blur_off" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/motion_blur_original.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/motion_blur_original.dds" ARRAY_NAME "g_motion_blur_original" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/motion_blur_enhanced.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/motion_blur_enhanced.dds" ARRAY_NAME "g_motion_blur_enhanced" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/movie_scale_mode.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/movie_scale_mode.dds" ARRAY_NAME "g_movie_scale_mode" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/music_attenuation.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/music_attenuation.dds" ARRAY_NAME "g_music_attenuation" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/music_volume.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/music_volume.dds" ARRAY_NAME "g_music_volume" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/resolution_scale.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/resolution_scale.dds" ARRAY_NAME "g_resolution_scale" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/shadow_resolution_original.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/shadow_resolution_original.dds" ARRAY_NAME "g_shadow_resolution_original" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/shadow_resolution_x512.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/shadow_resolution_x512.dds" ARRAY_NAME "g_shadow_resolution_x512" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/shadow_resolution_x1024.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/shadow_resolution_x1024.dds" ARRAY_NAME "g_shadow_resolution_x1024" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/shadow_resolution_x2048.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/shadow_resolution_x2048.dds" ARRAY_NAME "g_shadow_resolution_x2048" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/shadow_resolution_x4096.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/shadow_resolution_x4096.dds" ARRAY_NAME "g_shadow_resolution_x4096" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/shadow_resolution_x8192.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/shadow_resolution_x8192.dds" ARRAY_NAME "g_shadow_resolution_x8192" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/subtitles.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/subtitles.dds" ARRAY_NAME "g_subtitles" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/time_of_day_transition_xbox.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/time_of_day_transition_xbox.dds" ARRAY_NAME "g_time_of_day_transition_xbox" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/time_of_day_transition_playstation.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/time_of_day_transition_playstation.dds" ARRAY_NAME "g_time_of_day_transition_playstation" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/transparency_antialiasing_false.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/transparency_antialiasing_false.dds" ARRAY_NAME "g_transparency_antialiasing_false" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/transparency_antialiasing_true.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/transparency_antialiasing_true.dds" ARRAY_NAME "g_transparency_antialiasing_true" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/ui_scale_mode.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/ui_scale_mode.dds" ARRAY_NAME "g_ui_scale_mode" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/voice_language.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/voice_language.dds" ARRAY_NAME "g_voice_language" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/vsync.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/vsync.dds" ARRAY_NAME "g_vsync" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/window_size.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/window_size.dds" ARRAY_NAME "g_window_size" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/xbox_color_correction_false.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/xbox_color_correction_false.dds" ARRAY_NAME "g_xbox_color_correction_false" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/xbox_color_correction_true.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/xbox_color_correction_true.dds" ARRAY_NAME "g_xbox_color_correction_true" COMPRESSION_TYPE "zstd") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/game_icon.bmp" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/game_icon.bmp" ARRAY_NAME "g_game_icon") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/game_icon_night.bmp" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/game_icon_night.bmp" ARRAY_NAME "g_game_icon_night") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_worldmap_cursor.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_worldmap_cursor.ogg" ARRAY_NAME "g_sys_worldmap_cursor") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_worldmap_finaldecide.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_worldmap_finaldecide.ogg" ARRAY_NAME "g_sys_worldmap_finaldecide") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_actstg_pausecansel.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_actstg_pausecansel.ogg" ARRAY_NAME "g_sys_actstg_pausecansel") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_actstg_pausecursor.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_actstg_pausecursor.ogg" ARRAY_NAME "g_sys_actstg_pausecursor") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_actstg_pausedecide.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_actstg_pausedecide.ogg" ARRAY_NAME "g_sys_actstg_pausedecide") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_actstg_pausewinclose.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_actstg_pausewinclose.ogg" ARRAY_NAME "g_sys_actstg_pausewinclose") -BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_actstg_pausewinopen.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_actstg_pausewinopen.ogg" ARRAY_NAME "g_sys_actstg_pausewinopen") - +project("UnleashedRecomp") +set(TARGET_NAME "SWA") + +if (WIN32) + option(SWA_D3D12 "Add D3D12 support for rendering" ON) +endif() + +if (CMAKE_SYSTEM_NAME MATCHES "Linux") + option(SWA_FLATPAK "Configure the build for Flatpak compatibility." OFF) +endif() + +option(SWA_XAUDIO2 "Use XAudio2 for audio playback" OFF) + +function(BIN2C) + cmake_parse_arguments(BIN2C_ARGS "" "TARGET_OBJ;SOURCE_FILE;DEST_FILE;ARRAY_NAME;COMPRESSION_TYPE" "" ${ARGN}) + + if(NOT BIN2C_ARGS_TARGET_OBJ) + message(FATAL_ERROR "TARGET_OBJ not specified.") + endif() + + if(NOT BIN2C_ARGS_SOURCE_FILE) + message(FATAL_ERROR "SOURCE_FILE not specified.") + endif() + + if(NOT BIN2C_ARGS_DEST_FILE) + set(BIN2C_ARGS_DEST_FILE "${BIN2C_ARGS_SOURCE_FILE}") + endif() + + if(NOT BIN2C_ARGS_COMPRESSION_TYPE) + set(BIN2C_ARGS_COMPRESSION_TYPE "none") + endif() + + add_custom_command(OUTPUT "${BIN2C_ARGS_DEST_FILE}.c" + COMMAND $ "${BIN2C_ARGS_SOURCE_FILE}" "${BIN2C_ARGS_ARRAY_NAME}" "${BIN2C_ARGS_COMPRESSION_TYPE}" "${BIN2C_ARGS_DEST_FILE}.c" "${BIN2C_ARGS_DEST_FILE}.h" + DEPENDS "${BIN2C_ARGS_SOURCE_FILE}" + BYPRODUCTS "${BIN2C_ARGS_DEST_FILE}.h" + COMMENT "Generating binary header for ${BIN2C_ARGS_SOURCE_FILE}..." + ) + + set_source_files_properties(${BIN2C_ARGS_DEST_FILE}.c PROPERTIES SKIP_PRECOMPILE_HEADERS ON) + target_sources(${BIN2C_ARGS_TARGET_OBJ} PRIVATE ${BIN2C_ARGS_DEST_FILE}.c) +endfunction() + +add_compile_options( + -march=sandybridge + -fno-strict-aliasing + + -Wno-switch + -Wno-unused-function + -Wno-unused-variable + -Wno-unused-but-set-variable + -Wno-void-pointer-to-int-cast + -Wno-int-to-void-pointer-cast + -Wno-invalid-offsetof + -Wno-null-arithmetic + -Wno-null-conversion + -Wno-tautological-undefined-compare +) + +if (WIN32) + add_compile_options(/fp:strict) +else() + add_compile_options(-ffp-model=strict) +endif() + +add_compile_definitions( + SWA_IMPL + SDL_MAIN_HANDLED + _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR # Microsoft wtf? + _CRT_SECURE_NO_WARNINGS) + +set(SWA_PRECOMPILED_HEADERS + "stdafx.h" +) + +set(SWA_KERNEL_CXX_SOURCES + "kernel/imports.cpp" + "kernel/xdm.cpp" + "kernel/heap.cpp" + "kernel/memory.cpp" + "kernel/xam.cpp" + "kernel/io/file_system.cpp" +) + +set(SWA_LOCALE_CXX_SOURCES + "locale/config_locale.cpp" + "locale/locale.cpp" +) + +set(SWA_OS_CXX_SOURCES + "os/logger.cpp" + "os/media.cpp" + "os/process.cpp" + "os/version.cpp" +) + +if (WIN32) + list(APPEND SWA_OS_CXX_SOURCES + "os/win32/logger_win32.cpp" + "os/win32/media_win32.cpp" + "os/win32/process_win32.cpp" + "os/win32/version_win32.cpp" + ) +elseif (CMAKE_SYSTEM_NAME MATCHES "Linux") + list(APPEND SWA_OS_CXX_SOURCES + "os/linux/logger_linux.cpp" + "os/linux/media_linux.cpp" + "os/linux/process_linux.cpp" + "os/linux/version_linux.cpp" + ) +endif() + +set(SWA_CPU_CXX_SOURCES + "cpu/guest_thread.cpp" + "cpu/code_cache.cpp" +) + +set(SWA_GPU_CXX_SOURCES + "gpu/video.cpp" + "gpu/imgui/imgui_common.cpp" + "gpu/imgui/imgui_font_builder.cpp" + "gpu/imgui/imgui_snapshot.cpp" + "gpu/rhi/plume_vulkan.cpp" +) + +if (SWA_D3D12) + list(APPEND SWA_GPU_CXX_SOURCES + "gpu/rhi/plume_d3d12.cpp" + ) +endif() + +set(SWA_APU_CXX_SOURCES + "apu/audio.cpp" + "apu/embedded_player.cpp" +) + +if (SWA_XAUDIO2) + list(APPEND SWA_APU_CXX_SOURCES "apu/driver/xaudio_driver.cpp") +else() + list(APPEND SWA_APU_CXX_SOURCES "apu/driver/sdl2_driver.cpp") +endif() + +set(SWA_HID_CXX_SOURCES + "hid/hid.cpp" + "hid/driver/sdl_hid.cpp" +) + +set(SWA_PATCHES_CXX_SOURCES + "patches/ui/CHudPause_patches.cpp" + "patches/ui/CTitleStateIntro_patches.cpp" + "patches/ui/CTitleStateMenu_patches.cpp" + "patches/ui/frontend_listener.cpp" + "patches/audio_patches.cpp" + "patches/camera_patches.cpp" + "patches/fps_patches.cpp" + "patches/misc_patches.cpp" + "patches/object_patches.cpp" + "patches/player_patches.cpp" + "patches/resident_patches.cpp" + "patches/video_patches.cpp" +) + +set(SWA_UI_CXX_SOURCES + "ui/achievement_menu.cpp" + "ui/achievement_overlay.cpp" + "ui/installer_wizard.cpp" + "ui/button_guide.cpp" + "ui/fader.cpp" + "ui/message_window.cpp" + "ui/options_menu_thumbnails.cpp" + "ui/options_menu.cpp" + "ui/sdl_listener.cpp" + "ui/game_window.cpp" +) + +set(SWA_INSTALL_CXX_SOURCES + "install/installer.cpp" + "install/iso_file_system.cpp" + "install/memory_mapped_file.cpp" + "install/xcontent_file_system.cpp" + "install/xex_patcher.cpp" + "install/hashes/apotos_shamar.cpp" + "install/hashes/chunnan.cpp" + "install/hashes/empire_city_adabat.cpp" + "install/hashes/game.cpp" + "install/hashes/holoska.cpp" + "install/hashes/mazuri.cpp" + "install/hashes/spagonia.cpp" + "install/hashes/update.cpp" +) + +set(SWA_USER_CXX_SOURCES + "user/achievement_data.cpp" + "user/config.cpp" +) + +set(SWA_THIRDPARTY_SOURCES + "${SWA_THIRDPARTY_ROOT}/imgui/backends/imgui_impl_sdl2.cpp" + "${SWA_THIRDPARTY_ROOT}/imgui/imgui.cpp" + "${SWA_THIRDPARTY_ROOT}/imgui/imgui_demo.cpp" + "${SWA_THIRDPARTY_ROOT}/imgui/imgui_draw.cpp" + "${SWA_THIRDPARTY_ROOT}/imgui/imgui_tables.cpp" + "${SWA_THIRDPARTY_ROOT}/imgui/imgui_widgets.cpp" + "${SWA_THIRDPARTY_ROOT}/libmspack/libmspack/mspack/lzxd.c" + "${SWA_THIRDPARTY_ROOT}/tiny-AES-c/aes.c" + "${SWA_TOOLS_ROOT}/ShaderRecomp/thirdparty/smol-v/source/smolv.cpp" +) + +set(SWA_THIRDPARTY_INCLUDES + "${SWA_THIRDPARTY_ROOT}/concurrentqueue" + "${SWA_THIRDPARTY_ROOT}/ddspp" + "${SWA_THIRDPARTY_ROOT}/imgui" + "${SWA_THIRDPARTY_ROOT}/libmspack/libmspack/mspack" + "${SWA_THIRDPARTY_ROOT}/magic_enum/include" + "${SWA_THIRDPARTY_ROOT}/stb" + "${SWA_THIRDPARTY_ROOT}/tiny-AES-c" + "${SWA_THIRDPARTY_ROOT}/TinySHA1" + "${SWA_THIRDPARTY_ROOT}/unordered_dense/include" + "${SWA_THIRDPARTY_ROOT}/volk" + "${SWA_THIRDPARTY_ROOT}/Vulkan-Headers/include" + "${SWA_THIRDPARTY_ROOT}/VulkanMemoryAllocator/include" + "${SWA_TOOLS_ROOT}/bc_diff" + "${SWA_TOOLS_ROOT}/ShaderRecomp/thirdparty/smol-v/source" +) + +if (SWA_D3D12) + list(APPEND SWA_THIRDPARTY_INCLUDES "${SWA_THIRDPARTY_ROOT}/D3D12MemoryAllocator/include") + list(APPEND SWA_THIRDPARTY_SOURCES "${SWA_THIRDPARTY_ROOT}/D3D12MemoryAllocator/src/D3D12MemAlloc.cpp") +endif() + +set_source_files_properties(${SWA_THIRDPARTY_SOURCES} PROPERTIES SKIP_PRECOMPILE_HEADERS ON) + +set(SWA_CXX_SOURCES + "app.cpp" + "exports.cpp" + "main.cpp" + "misc_impl.cpp" + "stdafx.cpp" + + ${SWA_KERNEL_CXX_SOURCES} + ${SWA_LOCALE_CXX_SOURCES} + ${SWA_OS_CXX_SOURCES} + ${SWA_CPU_CXX_SOURCES} + ${SWA_GPU_CXX_SOURCES} + ${SWA_APU_CXX_SOURCES} + ${SWA_HID_CXX_SOURCES} + ${SWA_PATCHES_CXX_SOURCES} + ${SWA_UI_CXX_SOURCES} + ${SWA_INSTALL_CXX_SOURCES} + ${SWA_USER_CXX_SOURCES} + ${SWA_THIRDPARTY_SOURCES} +) + +if (WIN32) + # Set up Win32 resources for application icon. + set(ICON_PATH "${PROJECT_SOURCE_DIR}/../UnleashedRecompResources/images/game_icon.ico") + configure_file("res/win32/res.rc" "${CMAKE_BINARY_DIR}/res.rc" @ONLY) + add_executable(UnleashedRecomp ${SWA_CXX_SOURCES} "${CMAKE_BINARY_DIR}/res.rc") +else() + add_executable(UnleashedRecomp ${SWA_CXX_SOURCES}) +endif() + +set_target_properties(UnleashedRecomp PROPERTIES OUTPUT_NAME ${TARGET_NAME}) + +if (SWA_FLATPAK) + target_compile_definitions(UnleashedRecomp PRIVATE "GAME_INSTALL_DIRECTORY=\"/var/data\"") +endif() + +if (SWA_D3D12) + find_package(directx-headers CONFIG REQUIRED) + find_package(directx12-agility CONFIG REQUIRED) + target_compile_definitions(UnleashedRecomp PRIVATE SWA_D3D12) +endif() + +if (CMAKE_SYSTEM_NAME MATCHES "Linux") + target_compile_definitions(UnleashedRecomp PRIVATE SDL_VULKAN_ENABLED) +endif() + +find_package(directx-dxc REQUIRED) + +if (SWA_D3D12) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/D3D12) + add_custom_command(TARGET UnleashedRecomp POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_CURRENT_BINARY_DIR}/D3D12 + COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_CURRENT_BINARY_DIR}/D3D12 + COMMAND_EXPAND_LISTS + ) + + file(COPY ${PACKAGE_PREFIX_DIR}/bin/dxil.dll DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + + target_link_libraries(UnleashedRecomp PRIVATE + Microsoft::DirectX-Headers + Microsoft::DirectX-Guids + Microsoft::DirectX12-Agility + Microsoft::DirectXShaderCompiler + Microsoft::DXIL + dxgi + ) +endif() + +file(CHMOD ${DIRECTX_DXC_TOOL} PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE) + +if (WIN32) + target_link_libraries(UnleashedRecomp PRIVATE + comctl32 + ntdll + winmm + Synchronization + ) +endif() + +target_link_libraries(UnleashedRecomp PRIVATE + fmt::fmt + libzstd_static + msdf-atlas-gen::msdf-atlas-gen + nfd::nfd + o1heap + PowerUtils + SDL2::SDL2-static + SDL2_mixer + tomlplusplus::tomlplusplus + UnleashedRecompLib + xxHash::xxhash +) + +target_include_directories(UnleashedRecomp PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + "${CMAKE_CURRENT_SOURCE_DIR}/api" + ${SWA_THIRDPARTY_INCLUDES} +) + +if (CMAKE_SYSTEM_NAME MATCHES "Linux") + find_package(X11 REQUIRED) + target_include_directories(UnleashedRecomp PRIVATE ${X11_INCLUDE_DIR}) + target_link_libraries(UnleashedRecomp PRIVATE ${X11_LIBRARIES}) +endif() + +target_precompile_headers(UnleashedRecomp PUBLIC ${SWA_PRECOMPILED_HEADERS}) + +function(compile_shader FILE_PATH TARGET_NAME) + set(FILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/gpu/shader/${FILE_PATH}.hlsl) + cmake_path(GET FILE_PATH STEM VARIABLE_NAME) + if (SWA_D3D12) + add_custom_command( + OUTPUT ${FILE_PATH}.dxil.h + COMMAND ${DIRECTX_DXC_TOOL} -T ${TARGET_NAME} -HV 2021 -all-resources-bound -Wno-ignored-attributes -Fh ${FILE_PATH}.dxil.h ${FILE_PATH} -Vn g_${VARIABLE_NAME}_dxil + DEPENDS ${FILE_PATH} + ) + target_sources(UnleashedRecomp PRIVATE ${FILE_PATH}.dxil.h) + endif() + add_custom_command( + OUTPUT ${FILE_PATH}.spirv.h + COMMAND ${DIRECTX_DXC_TOOL} -T ${TARGET_NAME} -HV 2021 -all-resources-bound -spirv -fvk-use-dx-layout ${ARGN} -Fh ${FILE_PATH}.spirv.h ${FILE_PATH} -Vn g_${VARIABLE_NAME}_spirv + DEPENDS ${FILE_PATH} + ) + target_sources(UnleashedRecomp PRIVATE ${FILE_PATH}.spirv.h) +endfunction() + +function(compile_vertex_shader FILE_PATH) + compile_shader(${FILE_PATH} vs_6_0 -fvk-invert-y) +endfunction() + +function(compile_pixel_shader FILE_PATH) + compile_shader(${FILE_PATH} ps_6_0) +endfunction() + +compile_vertex_shader(copy_vs) +compile_pixel_shader(csd_filter_ps) +compile_pixel_shader(enhanced_motion_blur_ps) +compile_pixel_shader(gaussian_blur_3x3) +compile_pixel_shader(gaussian_blur_5x5) +compile_pixel_shader(gaussian_blur_7x7) +compile_pixel_shader(gaussian_blur_9x9) +compile_pixel_shader(gamma_correction_ps) +compile_pixel_shader(imgui_ps) +compile_vertex_shader(imgui_vs) +compile_pixel_shader(movie_ps) +compile_vertex_shader(movie_vs) +compile_pixel_shader(resolve_msaa_depth_2x) +compile_pixel_shader(resolve_msaa_depth_4x) +compile_pixel_shader(resolve_msaa_depth_8x) + +function(generate_aggregate_header INPUT_DIRECTORY OUTPUT_FILE) + get_filename_component(ABS_OUTPUT_FILE "${OUTPUT_FILE}" ABSOLUTE) + file(GLOB_RECURSE HEADER_FILES "${INPUT_DIRECTORY}/*.h") + set(HEADER_CONTENT "#pragma once\n\n") + + foreach(HEADER_FILE IN LISTS HEADER_FILES) + get_filename_component(ABS_HEADER_FILE "${HEADER_FILE}" ABSOLUTE) + if (ABS_HEADER_FILE STREQUAL ABS_OUTPUT_FILE) + continue() + endif() + file(RELATIVE_PATH RELATIVE_HEADER_FILE "${INPUT_DIRECTORY}" "${HEADER_FILE}") + string(APPEND HEADER_CONTENT "#include \"${RELATIVE_HEADER_FILE}\"\n") + endforeach() + + if (EXISTS "${OUTPUT_FILE}") + file(READ "${OUTPUT_FILE}" EXISTING_CONTENT) + if (EXISTING_CONTENT STREQUAL HEADER_CONTENT) + return() + endif() + endif() + + file(WRITE "${OUTPUT_FILE}" "${HEADER_CONTENT}") +endfunction() + +generate_aggregate_header( + "${CMAKE_CURRENT_SOURCE_DIR}/api" + "${CMAKE_CURRENT_SOURCE_DIR}/api/SWA.h" +) + +set(RESOURCES_SOURCE_PATH "${PROJECT_SOURCE_DIR}/../UnleashedRecompResources") +set(RESOURCES_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/res") + +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/bc_diff/button_bc_diff.bin" DEST_FILE "${RESOURCES_OUTPUT_PATH}/bc_diff/button_bc_diff.bin" ARRAY_NAME "g_button_bc_diff" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/font/im_font_atlas.bin" DEST_FILE "${RESOURCES_OUTPUT_PATH}/font/im_font_atlas.bin" ARRAY_NAME "g_im_font_atlas" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/font/im_font_atlas.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/font/im_font_atlas.dds" ARRAY_NAME "g_im_font_atlas_texture" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/achievements_menu/trophy.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/achievements_menu/trophy.dds" ARRAY_NAME "g_trophy" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/controller.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/controller.dds" ARRAY_NAME "g_controller" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/general_window.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/general_window.dds" ARRAY_NAME "g_general_window" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/kbm.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/kbm.dds" ARRAY_NAME "g_kbm" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/select_fade.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/select_fade.dds" ARRAY_NAME "g_select_fade" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/common/select_fill.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/common/select_fill.dds" ARRAY_NAME "g_select_fill" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/arrow_circle.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/arrow_circle.dds" ARRAY_NAME "g_arrow_circle" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_001.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_001.dds" ARRAY_NAME "g_install_001" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_002.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_002.dds" ARRAY_NAME "g_install_002" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_003.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_003.dds" ARRAY_NAME "g_install_003" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_004.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_004.dds" ARRAY_NAME "g_install_004" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_005.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_005.dds" ARRAY_NAME "g_install_005" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_006.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_006.dds" ARRAY_NAME "g_install_006" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_007.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_007.dds" ARRAY_NAME "g_install_007" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/install_008.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/install_008.dds" ARRAY_NAME "g_install_008" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/miles_electric_icon.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/miles_electric_icon.dds" ARRAY_NAME "g_miles_electric_icon" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/installer/pulse_install.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/installer/pulse_install.dds" ARRAY_NAME "g_pulse_install" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/achievement_notifications.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/achievement_notifications.dds" ARRAY_NAME "g_achievement_notifications" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/allow_background_input.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/allow_background_input.dds" ARRAY_NAME "g_allow_background_input" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/allow_dpad_movement.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/allow_dpad_movement.dds" ARRAY_NAME "g_allow_dpad_movement" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/antialiasing.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/antialiasing.dds" ARRAY_NAME "g_antialiasing" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/aspect_ratio.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/aspect_ratio.dds" ARRAY_NAME "g_aspect_ratio" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/battle_theme.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/battle_theme.dds" ARRAY_NAME "g_battle_theme" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/brightness.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/brightness.dds" ARRAY_NAME "g_brightness" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/control_tutorial.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/control_tutorial.dds" ARRAY_NAME "g_control_tutorial" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/controller_icons.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/controller_icons.dds" ARRAY_NAME "g_controller_icons" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/default.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/default.dds" ARRAY_NAME "g_default" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/effects_volume.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/effects_volume.dds" ARRAY_NAME "g_effects_volume" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/fps.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/fps.dds" ARRAY_NAME "g_fps" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/fullscreen.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/fullscreen.dds" ARRAY_NAME "g_fullscreen" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/gi_texture_filtering_bilinear.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/gi_texture_filtering_bilinear.dds" ARRAY_NAME "g_gi_texture_filtering_bilinear" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/gi_texture_filtering_bicubic.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/gi_texture_filtering_bicubic.dds" ARRAY_NAME "g_gi_texture_filtering_bicubic" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/hints.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/hints.dds" ARRAY_NAME "g_hints" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/invert_camera_x.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/invert_camera_x.dds" ARRAY_NAME "g_invert_camera_x" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/invert_camera_y.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/invert_camera_y.dds" ARRAY_NAME "g_invert_camera_y" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/language.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/language.dds" ARRAY_NAME "g_language" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/monitor.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/monitor.dds" ARRAY_NAME "g_monitor" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/motion_blur_off.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/motion_blur_off.dds" ARRAY_NAME "g_motion_blur_off" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/motion_blur_original.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/motion_blur_original.dds" ARRAY_NAME "g_motion_blur_original" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/motion_blur_enhanced.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/motion_blur_enhanced.dds" ARRAY_NAME "g_motion_blur_enhanced" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/movie_scale_mode.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/movie_scale_mode.dds" ARRAY_NAME "g_movie_scale_mode" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/music_attenuation.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/music_attenuation.dds" ARRAY_NAME "g_music_attenuation" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/music_volume.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/music_volume.dds" ARRAY_NAME "g_music_volume" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/resolution_scale.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/resolution_scale.dds" ARRAY_NAME "g_resolution_scale" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/shadow_resolution_original.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/shadow_resolution_original.dds" ARRAY_NAME "g_shadow_resolution_original" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/shadow_resolution_x512.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/shadow_resolution_x512.dds" ARRAY_NAME "g_shadow_resolution_x512" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/shadow_resolution_x1024.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/shadow_resolution_x1024.dds" ARRAY_NAME "g_shadow_resolution_x1024" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/shadow_resolution_x2048.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/shadow_resolution_x2048.dds" ARRAY_NAME "g_shadow_resolution_x2048" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/shadow_resolution_x4096.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/shadow_resolution_x4096.dds" ARRAY_NAME "g_shadow_resolution_x4096" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/shadow_resolution_x8192.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/shadow_resolution_x8192.dds" ARRAY_NAME "g_shadow_resolution_x8192" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/subtitles.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/subtitles.dds" ARRAY_NAME "g_subtitles" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/time_of_day_transition_xbox.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/time_of_day_transition_xbox.dds" ARRAY_NAME "g_time_of_day_transition_xbox" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/time_of_day_transition_playstation.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/time_of_day_transition_playstation.dds" ARRAY_NAME "g_time_of_day_transition_playstation" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/transparency_antialiasing_false.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/transparency_antialiasing_false.dds" ARRAY_NAME "g_transparency_antialiasing_false" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/transparency_antialiasing_true.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/transparency_antialiasing_true.dds" ARRAY_NAME "g_transparency_antialiasing_true" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/ui_scale_mode.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/ui_scale_mode.dds" ARRAY_NAME "g_ui_scale_mode" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/voice_language.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/voice_language.dds" ARRAY_NAME "g_voice_language" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/vsync.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/vsync.dds" ARRAY_NAME "g_vsync" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/window_size.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/window_size.dds" ARRAY_NAME "g_window_size" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/xbox_color_correction_false.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/xbox_color_correction_false.dds" ARRAY_NAME "g_xbox_color_correction_false" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/options_menu/thumbnails/xbox_color_correction_true.dds" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/options_menu/thumbnails/xbox_color_correction_true.dds" ARRAY_NAME "g_xbox_color_correction_true" COMPRESSION_TYPE "zstd") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/game_icon.bmp" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/game_icon.bmp" ARRAY_NAME "g_game_icon") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/images/game_icon_night.bmp" DEST_FILE "${RESOURCES_OUTPUT_PATH}/images/game_icon_night.bmp" ARRAY_NAME "g_game_icon_night") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_worldmap_cursor.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_worldmap_cursor.ogg" ARRAY_NAME "g_sys_worldmap_cursor") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_worldmap_finaldecide.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_worldmap_finaldecide.ogg" ARRAY_NAME "g_sys_worldmap_finaldecide") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_actstg_pausecansel.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_actstg_pausecansel.ogg" ARRAY_NAME "g_sys_actstg_pausecansel") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_actstg_pausecursor.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_actstg_pausecursor.ogg" ARRAY_NAME "g_sys_actstg_pausecursor") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_actstg_pausedecide.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_actstg_pausedecide.ogg" ARRAY_NAME "g_sys_actstg_pausedecide") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_actstg_pausewinclose.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_actstg_pausewinclose.ogg" ARRAY_NAME "g_sys_actstg_pausewinclose") +BIN2C(TARGET_OBJ UnleashedRecomp SOURCE_FILE "${RESOURCES_SOURCE_PATH}/sounds/sys_actstg_pausewinopen.ogg" DEST_FILE "${RESOURCES_OUTPUT_PATH}/sounds/sys_actstg_pausewinopen.ogg" ARRAY_NAME "g_sys_actstg_pausewinopen") + diff --git a/UnleashedRecomp/api/Hedgehog/Base/System/hhAllocator.h b/UnleashedRecomp/api/Hedgehog/Base/System/hhAllocator.h index baa56d99..044aad47 100644 --- a/UnleashedRecomp/api/Hedgehog/Base/System/hhAllocator.h +++ b/UnleashedRecomp/api/Hedgehog/Base/System/hhAllocator.h @@ -2,14 +2,14 @@ #include "SWA.inl" -inline static void* __HH_ALLOC(const uint32_t in_Size) +inline void* __HH_ALLOC(const uint32_t in_Size) { - return GuestToHostFunction(0x82DFA0B0, in_Size, nullptr, 0, 0); + return GuestToHostFunction(sub_822C0988, in_Size); } -inline static void __HH_FREE(const void* in_pData) +inline void __HH_FREE(const void* in_pData) { - GuestToHostFunction(0x82DF9E50, in_pData); + GuestToHostFunction(sub_822C0270, in_pData); } namespace Hedgehog::Base diff --git a/UnleashedRecomp/api/Hedgehog/Base/Type/detail/hhStringHolder.h b/UnleashedRecomp/api/Hedgehog/Base/Type/detail/hhStringHolder.h index 4fd1cd25..bc3dbc2b 100644 --- a/UnleashedRecomp/api/Hedgehog/Base/Type/detail/hhStringHolder.h +++ b/UnleashedRecomp/api/Hedgehog/Base/Type/detail/hhStringHolder.h @@ -6,38 +6,16 @@ namespace Hedgehog::Base { struct SStringHolder { - union - { - struct - { - be Length; - be RefCount; - }; - - be RefCountAndLength; - }; - - char aStr[1u]; + be RefCount; + char aStr[]; static SStringHolder* GetHolder(const char* in_pStr); - - static size_t GetMemorySize(const size_t in_Length); - static size_t GetMemorySizeAligned(const size_t in_Length); - - static SStringHolder* Make(const char* in_pStr, const size_t in_Length); - - static SStringHolder* Concat( - const char* in_pStrA, const size_t in_LengthA, - const char* in_pStrB, const size_t in_LengthB); + static SStringHolder* Make(const char* in_pStr); void AddRef(); void Release(); bool IsUnique() const; - - bool TryInplaceAssign(const char* in_pStr, const size_t in_Length); - bool TryInplaceAppend(const char* in_pStr, const size_t in_Length); - bool TryInplacePrepend(const char* in_pStr, const size_t in_Length); }; } diff --git a/UnleashedRecomp/api/Hedgehog/Base/Type/detail/hhStringHolder.inl b/UnleashedRecomp/api/Hedgehog/Base/Type/detail/hhStringHolder.inl index 287f530d..0daf6e6f 100644 --- a/UnleashedRecomp/api/Hedgehog/Base/Type/detail/hhStringHolder.inl +++ b/UnleashedRecomp/api/Hedgehog/Base/Type/detail/hhStringHolder.inl @@ -2,66 +2,41 @@ namespace Hedgehog::Base { inline SStringHolder* SStringHolder::GetHolder(const char* in_pStr) { - return (SStringHolder*)((size_t)in_pStr - sizeof(RefCountAndLength)); + return (SStringHolder*)((size_t)in_pStr - sizeof(RefCount)); } - inline size_t SStringHolder::GetMemorySize(const size_t in_Length) + inline SStringHolder* SStringHolder::Make(const char* in_pStr) { - return sizeof(RefCountAndLength) + in_Length; - } - - inline size_t SStringHolder::GetMemorySizeAligned(const size_t in_Length) - { - return (GetMemorySize(in_Length) + 0x10) & ~0x0F; - } - - inline SStringHolder* SStringHolder::Make(const char* in_pStr, const size_t in_Length) - { - const size_t memSize = GetMemorySize(in_Length); - const size_t memSizeAligned = GetMemorySizeAligned(in_Length); - - auto pHolder = (SStringHolder*)__HH_ALLOC(memSizeAligned); + auto pHolder = (SStringHolder*)__HH_ALLOC(sizeof(RefCount) + strlen(in_pStr) + 1); pHolder->RefCount = 1; - pHolder->Length = (uint16_t)in_Length; - - if (in_pStr) - memcpy(pHolder->aStr, in_pStr, in_Length); - - memset(&pHolder->aStr[in_Length], 0, memSizeAligned - memSize); - - return pHolder; - } - - inline SStringHolder* SStringHolder::Concat(const char* in_pStrA, const size_t in_LengthA, const char* in_pStrB, const size_t in_LengthB) - { - SStringHolder* pHolder = Make(nullptr, in_LengthA + in_LengthB); - - memcpy(pHolder->aStr, in_pStrA, in_LengthA); - memcpy(&pHolder->aStr[in_LengthA], in_pStrB, in_LengthB); - + strcpy(pHolder->aStr, in_pStr); return pHolder; } inline void SStringHolder::AddRef() { - uint32_t originalValue, incrementedValue; + std::atomic_ref refCount(RefCount.value); + + be original, incremented; do { - originalValue = RefCountAndLength.value; - incrementedValue = ByteSwap(ByteSwap(originalValue) + 1); - } while (InterlockedCompareExchange(reinterpret_cast(&RefCountAndLength), incrementedValue, originalValue) != originalValue); + original = RefCount; + incremented = original + 1; + } while (!refCount.compare_exchange_weak(original.value, incremented.value)); } inline void SStringHolder::Release() { - uint32_t originalValue, decrementedValue; + std::atomic_ref refCount(RefCount.value); + + be original, decremented; do { - originalValue = RefCountAndLength.value; - decrementedValue = ByteSwap(ByteSwap(originalValue) - 1); - } while (InterlockedCompareExchange(reinterpret_cast(&RefCountAndLength), decrementedValue, originalValue) != originalValue); + original = RefCount; + decremented = original - 1; + } while (!refCount.compare_exchange_weak(original.value, decremented.value)); - if ((decrementedValue & 0xFFFF0000) == 0) + if (decremented == 0) __HH_FREE(this); } @@ -69,66 +44,4 @@ namespace Hedgehog::Base { return RefCount == 1; } - - inline bool SStringHolder::TryInplaceAssign(const char* in_pStr, const size_t in_Length) - { - if (!IsUnique()) - return false; - - const size_t memSizeAligned = GetMemorySizeAligned(in_Length); - - if (memSizeAligned > GetMemorySizeAligned(Length)) - return false; - - if (in_pStr) - memcpy(aStr, in_pStr, in_Length); - - memset(&aStr[in_Length], 0, memSizeAligned - GetMemorySize(in_Length)); - - Length = (uint16_t)in_Length; - - return true; - } - - inline bool SStringHolder::TryInplaceAppend(const char* in_pStr, const size_t in_Length) - { - if (!IsUnique()) - return false; - - const size_t memSizeAligned = GetMemorySizeAligned(Length + in_Length); - - if (memSizeAligned > GetMemorySizeAligned(Length)) - return false; - - if (in_pStr) - memcpy(&aStr[Length], in_pStr, in_Length); - - memset(&aStr[Length + in_Length], 0, memSizeAligned - GetMemorySize(Length + in_Length)); - - Length = (uint16_t)(Length + in_Length); - - return true; - } - - inline bool SStringHolder::TryInplacePrepend(const char* in_pStr, const size_t in_Length) - { - if (!IsUnique()) - return false; - - const size_t memSizeAligned = GetMemorySizeAligned(in_Length + Length); - - if (memSizeAligned > GetMemorySizeAligned(Length)) - return false; - - memmove(&aStr[in_Length], aStr, Length); - - if (in_pStr) - memcpy(aStr, in_pStr, in_Length); - - memset(&aStr[in_Length + Length], 0, memSizeAligned - GetMemorySize(in_Length + Length)); - - Length = (uint16_t)(in_Length + Length); - - return true; - } } diff --git a/UnleashedRecomp/api/Hedgehog/Base/Type/hhSharedString.h b/UnleashedRecomp/api/Hedgehog/Base/Type/hhSharedString.h index 54a22e5b..8ce1201c 100644 --- a/UnleashedRecomp/api/Hedgehog/Base/Type/hhSharedString.h +++ b/UnleashedRecomp/api/Hedgehog/Base/Type/hhSharedString.h @@ -32,56 +32,6 @@ namespace Hedgehog::Base const char* begin() const; const char* end() const; - - CSharedString substr(size_t pos = 0, size_t len = npos) const; - - size_t find(char c, size_t pos = 0) const; - size_t find(const char* s, size_t pos = 0) const; - size_t rfind(char c, size_t pos = npos) const; - - size_t find_first_of(const char* s, size_t pos = 0) const; - size_t find_last_of(const char* s, size_t pos = npos) const; - size_t find_first_not_of(const char* s, size_t pos = 0) const; - size_t find_last_not_of(const char* s, size_t pos = npos) const; - - size_t find(const CSharedString& str, size_t pos = 0) const; - size_t rfind(const CSharedString& str, size_t pos = npos) const; - - size_t find_first_of(const CSharedString& str, size_t pos = 0) const; - size_t find_last_of(const CSharedString& str, size_t pos = npos) const; - size_t find_first_not_of(const CSharedString& str, size_t pos = 0) const; - size_t find_last_not_of(const CSharedString& str, size_t pos = npos) const; - - void assign(const CSharedString& in_rOther); - void assign(const char* in_pStr); - void assign(CSharedString&& io_rOther); - - void append(const CSharedString& in_rOther); - void append(const char* in_pStr); - - void prepend(const CSharedString& in_rOther); - void prepend(const char* in_pStr); - - int compare(const CSharedString& in_rOther) const; - - CSharedString& operator=(const CSharedString& in_rOther); - CSharedString& operator=(const char* in_pStr); - CSharedString& operator=(CSharedString&& io_rOther); - CSharedString& operator+=(const CSharedString& in_rOther); - CSharedString& operator+=(const char* in_pStr); - - friend CSharedString operator+(const CSharedString& in_rLeft, const CSharedString& in_rRight); - friend CSharedString operator+(const CSharedString& in_rLeft, const char* in_pRight); - friend CSharedString operator+(const char* in_pLeft, const CSharedString& in_pRight); - - bool operator>(const CSharedString& in_rOther) const; - bool operator>=(const CSharedString& in_rOther) const; - bool operator<(const CSharedString& in_rOther) const; - bool operator<=(const CSharedString& in_rOther) const; - bool operator==(const CSharedString& in_rOther) const; - bool operator!=(const CSharedString& in_rOther) const; - bool operator==(const char* in_pOther) const; - bool operator!=(const char* in_pOther) const; }; } diff --git a/UnleashedRecomp/api/Hedgehog/Base/Type/hhSharedString.inl b/UnleashedRecomp/api/Hedgehog/Base/Type/hhSharedString.inl index 1e971f10..2bddc716 100644 --- a/UnleashedRecomp/api/Hedgehog/Base/Type/hhSharedString.inl +++ b/UnleashedRecomp/api/Hedgehog/Base/Type/hhSharedString.inl @@ -18,7 +18,7 @@ namespace Hedgehog::Base size_t length; if (in_pStr && (length = strlen(in_pStr)) != 0) - m_pStr.ptr = g_memory.MapVirtual(SStringHolder::Make(in_pStr, length)->aStr); + m_pStr.ptr = g_memory.MapVirtual(SStringHolder::Make(in_pStr)->aStr); } inline CSharedString::CSharedString(const CSharedString& in_rOther) : m_pStr(in_rOther.m_pStr) @@ -53,7 +53,7 @@ namespace Hedgehog::Base inline size_t CSharedString::size() const { - return GetHolder()->Length; + return strlen(m_pStr); } inline size_t CSharedString::length() const @@ -73,367 +73,6 @@ namespace Hedgehog::Base inline const char* CSharedString::end() const { - return &m_pStr[GetHolder()->Length]; - } - - inline CSharedString CSharedString::substr(size_t pos, size_t len) const - { - if (len == 0) - return CSharedString(); - - if (len > (GetHolder()->Length - pos)) - len = GetHolder()->Length - pos; - - if (pos == 0 && len == GetHolder()->Length) - return *this; - - return SStringHolder::Make(&m_pStr[pos], len); - } - - inline size_t CSharedString::find(char c, size_t pos) const - { - for (size_t i = pos; i < GetHolder()->Length; i++) - { - if (m_pStr[i] == c) - return i; - } - - return npos; - } - - inline size_t CSharedString::find(const char* s, size_t pos) const - { - size_t len = strlen(s); - for (size_t i = pos; i < GetHolder()->Length - len + 1; i++) - { - if (strncmp(m_pStr + i, s, len) == 0) - return i; - } - - return npos; - } - - inline size_t CSharedString::rfind(char c, size_t pos) const - { - if (pos >= GetHolder()->Length) - pos = GetHolder()->Length - 1; - - for (size_t i = pos; i != static_cast(-1); i--) - { - if (m_pStr[i] == c) - return i; - } - - return npos; - } - - inline size_t CSharedString::find_first_of(const char* s, size_t pos) const - { - size_t len = strlen(s); - for (size_t i = pos; i < GetHolder()->Length; i++) - { - for (size_t j = 0; j < len; j++) - { - if (m_pStr[i] == s[j]) - return i; - } - } - - return npos; - } - - inline size_t CSharedString::find_last_of(const char* s, size_t pos) const - { - if (pos >= GetHolder()->Length) - pos = GetHolder()->Length - 1; - - size_t len = strlen(s); - for (size_t i = pos; i != static_cast(-1); i--) - { - for (size_t j = 0; j < len; j++) - { - if (m_pStr[i] == s[j]) - return i; - } - } - - return npos; - } - - inline size_t CSharedString::find_first_not_of(const char* s, size_t pos) const - { - size_t len = strlen(s); - for (size_t i = pos; i < GetHolder()->Length; i++) - { - bool found = false; - for (size_t j = 0; j < len; j++) - { - if (m_pStr[i] == s[j]) - { - found = true; - break; - } - } - - if (!found) - return i; - } - - return npos; - } - - inline size_t CSharedString::find_last_not_of(const char* s, size_t pos) const - { - if (pos >= GetHolder()->Length) - pos = GetHolder()->Length - 1; - - size_t len = strlen(s); - for (size_t i = pos; i != static_cast(-1); i--) - { - bool found = false; - for (size_t j = 0; j < len; j++) - { - if (m_pStr[i] == s[j]) - { - found = true; - break; - } - } - - if (!found) - return i; - } - - return npos; - } - - inline size_t CSharedString::find(const CSharedString& str, size_t pos) const - { - return find(str.c_str(), pos); - } - - inline size_t CSharedString::find_first_of(const CSharedString& str, size_t pos) const - { - return find_first_of(str.c_str(), pos); - } - - inline size_t CSharedString::find_last_of(const CSharedString& str, size_t pos) const - { - return find_last_of(str.c_str(), pos); - } - - inline size_t CSharedString::find_first_not_of(const CSharedString& str, size_t pos) const - { - return find_first_not_of(str.c_str(), pos); - } - - inline size_t CSharedString::find_last_not_of(const CSharedString& str, size_t pos) const - { - return find_last_not_of(str.c_str(), pos); - } - - inline void CSharedString::assign(const CSharedString& in_rOther) - { - GetHolder()->Release(); - m_pStr = in_rOther.m_pStr; - GetHolder()->AddRef(); - } - - inline void CSharedString::assign(const char* in_pStr) - { - size_t length; - - if (in_pStr && (length = strlen(in_pStr)) != 0) - { - if (!GetHolder()->TryInplaceAssign(in_pStr, length)) - { - GetHolder()->Release(); - m_pStr = SStringHolder::Make(in_pStr, length)->aStr; - } - } - else - { - GetHolder()->Release(); - m_pStr = nullptr; - } - } - - inline void CSharedString::assign(CSharedString&& io_rOther) - { - m_pStr = io_rOther.m_pStr; - io_rOther.m_pStr = nullptr; - } - - inline void CSharedString::append(const CSharedString& in_rOther) - { - if (!GetHolder()->TryInplaceAppend(in_rOther.GetHolder()->aStr, in_rOther.GetHolder()->Length)) - { - SStringHolder* pHolder = SStringHolder::Concat( - GetHolder()->aStr, GetHolder()->Length, - in_rOther.GetHolder()->aStr, in_rOther.GetHolder()->Length); - - GetHolder()->Release(); - m_pStr = pHolder->aStr; - } - } - - inline void CSharedString::append(const char* in_pStr) - { - size_t length; - - if (in_pStr && (length = strlen(in_pStr)) != 0) - { - if (!GetHolder()->TryInplaceAppend(in_pStr, length)) - { - SStringHolder* pHolder = SStringHolder::Concat( - GetHolder()->aStr, GetHolder()->Length, in_pStr, length); - - GetHolder()->Release(); - m_pStr = pHolder->aStr; - } - } - } - - inline void CSharedString::prepend(const CSharedString& in_rOther) - { - if (!GetHolder()->TryInplacePrepend(in_rOther.GetHolder()->aStr, in_rOther.GetHolder()->Length)) - { - SStringHolder* pHolder = SStringHolder::Concat( - in_rOther.GetHolder()->aStr, in_rOther.GetHolder()->Length, - GetHolder()->aStr, GetHolder()->Length); - - GetHolder()->Release(); - m_pStr = pHolder->aStr; - } - } - - inline void CSharedString::prepend(const char* in_pStr) - { - size_t length; - - if (in_pStr && (length = strlen(in_pStr)) != 0) - { - if (!GetHolder()->TryInplacePrepend(in_pStr, length)) - { - SStringHolder* pHolder = SStringHolder::Concat( - in_pStr, length, GetHolder()->aStr, GetHolder()->Length); - - GetHolder()->Release(); - m_pStr = pHolder->aStr; - } - } - } - - inline int CSharedString::compare(const CSharedString& in_rOther) const - { - // TODO: DO NOT PASS BY REFERENCE. - return GuestToHostFunction(0x82DFB028, this, &in_rOther); - } - - inline CSharedString& CSharedString::operator=(const CSharedString& in_rOther) - { - assign(in_rOther); - return *this; - } - - inline CSharedString& CSharedString::operator=(const char* in_pStr) - { - assign(in_pStr); - return *this; - } - - inline CSharedString& CSharedString::operator=(CSharedString&& io_rOther) - { - assign(std::move(io_rOther)); - return *this; - } - - inline CSharedString& CSharedString::operator+=(const CSharedString& in_rOther) - { - append(in_rOther); - return *this; - } - - inline CSharedString& CSharedString::operator+=(const char* in_pStr) - { - append(in_pStr); - return *this; - } - - inline CSharedString operator+(const CSharedString& in_rLeft, const CSharedString& in_rRight) - { - return SStringHolder::Concat( - in_rLeft.GetHolder()->aStr, in_rLeft.GetHolder()->Length, - in_rRight.GetHolder()->aStr, in_rRight.GetHolder()->Length); - } - - inline CSharedString operator+(const CSharedString& in_rLeft, const char* in_pRight) - { - size_t length; - - if (in_pRight && (length = strlen(in_pRight)) != 0) - { - return SStringHolder::Concat( - in_rLeft.GetHolder()->aStr, in_rLeft.GetHolder()->Length, in_pRight, length); - } - else - { - return in_rLeft; - } - } - - inline CSharedString operator+(const char* in_pLeft, const CSharedString& in_pRight) - { - size_t length; - - if (in_pLeft && (length = strlen(in_pLeft)) != 0) - { - return SStringHolder::Concat( - in_pLeft, length, in_pRight.GetHolder()->aStr, in_pRight.GetHolder()->Length); - } - else - { - return in_pRight; - } - } - - inline bool CSharedString::operator>(const CSharedString& in_rOther) const - { - return compare(in_rOther) > 0; - } - - inline bool CSharedString::operator>=(const CSharedString& in_rOther) const - { - return compare(in_rOther) >= 0; - } - - inline bool CSharedString::operator<(const CSharedString& in_rOther) const - { - return compare(in_rOther) < 0; - } - - inline bool CSharedString::operator<=(const CSharedString& in_rOther) const - { - return compare(in_rOther) <= 0; - } - - inline bool CSharedString::operator==(const CSharedString& in_rOther) const - { - return compare(in_rOther) == 0; - } - - inline bool CSharedString::operator!=(const CSharedString& in_rOther) const - { - return !(*this == in_rOther); - } - - inline bool CSharedString::operator==(const char* in_pOther) const - { - return strcmp(c_str(), in_pOther) == 0; - } - - inline bool CSharedString::operator!=(const char* in_pOther) const - { - return !(*this == in_pOther); + return &m_pStr[size()]; } } diff --git a/UnleashedRecomp/api/boost/smart_ptr/shared_ptr.h b/UnleashedRecomp/api/boost/smart_ptr/shared_ptr.h index adec62eb..9e62fdbb 100644 --- a/UnleashedRecomp/api/boost/smart_ptr/shared_ptr.h +++ b/UnleashedRecomp/api/boost/smart_ptr/shared_ptr.h @@ -28,22 +28,26 @@ namespace boost void add_ref() { + std::atomic_ref useCount(use_count_.value); + be original, incremented; do { original = use_count_; incremented = original + 1; - } while (InterlockedCompareExchange((unsigned long*)&use_count_, incremented.value, original.value) != original.value); + } while (!useCount.compare_exchange_weak(original.value, incremented.value)); } void release() { + std::atomic_ref useCount(use_count_.value); + be original, decremented; do { original = use_count_; decremented = original - 1; - } while (InterlockedCompareExchange((unsigned long*)&use_count_, decremented.value, original.value) != original.value); + } while (!useCount.compare_exchange_weak(original.value, decremented.value)); if (decremented == 0) { @@ -54,12 +58,14 @@ namespace boost void weak_release() { + std::atomic_ref weakCount(weak_count_.value); + be original, decremented; do { original = weak_count_; decremented = original - 1; - } while (InterlockedCompareExchange((unsigned long*)&weak_count_, decremented.value, original.value) != original.value); + } while (!weakCount.compare_exchange_weak(original.value, decremented.value)); if (decremented == 0) { diff --git a/UnleashedRecomp/app.cpp b/UnleashedRecomp/app.cpp index 94cc4191..fa8c0982 100644 --- a/UnleashedRecomp/app.cpp +++ b/UnleashedRecomp/app.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include #include @@ -16,9 +16,11 @@ void App::Exit() { Config::Save(); -#if _WIN32 - ExitProcess(0); +#ifdef _WIN32 + timeEndPeriod(1); #endif + + std::_Exit(0); } // CApplication::Ctor @@ -41,7 +43,7 @@ PPC_FUNC(sub_822C1130) SDL_PumpEvents(); SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT); - Window::Update(); + GameWindow::Update(); AudioPatches::Update(App::s_deltaTime); __imp__sub_822C1130(ctx, base); diff --git a/UnleashedRecomp/app.h b/UnleashedRecomp/app.h index e5ea4343..b6541358 100644 --- a/UnleashedRecomp/app.h +++ b/UnleashedRecomp/app.h @@ -1,6 +1,6 @@ #pragma once -#include +#include class App { diff --git a/UnleashedRecomp/apu/audio.cpp b/UnleashedRecomp/apu/audio.cpp index 582231a9..9f0f2de8 100644 --- a/UnleashedRecomp/apu/audio.cpp +++ b/UnleashedRecomp/apu/audio.cpp @@ -14,7 +14,7 @@ std::ofstream g_audioDumpStream; #endif -uint32_t XAudioRegisterRenderDriverClient(XLPDWORD callback, XLPDWORD driver) +uint32_t XAudioRegisterRenderDriverClient(be* callback, be* driver) { #ifdef AUDIO_DUMP_SAMPLES_PATH g_audioDumpStream.open(AUDIO_DUMP_SAMPLES_PATH, std::ios::binary); @@ -25,7 +25,7 @@ uint32_t XAudioRegisterRenderDriverClient(XLPDWORD callback, XLPDWORD driver) return 0; } -uint32_t XAudioUnregisterRenderDriverClient(DWORD driver) +uint32_t XAudioUnregisterRenderDriverClient(uint32_t driver) { return 0; } diff --git a/UnleashedRecomp/apu/audio.h b/UnleashedRecomp/apu/audio.h index c09a1f8a..229264f5 100644 --- a/UnleashedRecomp/apu/audio.h +++ b/UnleashedRecomp/apu/audio.h @@ -13,6 +13,6 @@ void XAudioRegisterClient(PPCFunc* callback, uint32_t param); void XAudioSubmitFrame(void* samples); #endif -uint32_t XAudioRegisterRenderDriverClient(XLPDWORD callback, XLPDWORD driver); -uint32_t XAudioUnregisterRenderDriverClient(DWORD driver); +uint32_t XAudioRegisterRenderDriverClient(be* callback, be* driver); +uint32_t XAudioUnregisterRenderDriverClient(uint32_t driver); uint32_t XAudioSubmitRenderDriverFrame(uint32_t driver, void* samples); diff --git a/UnleashedRecomp/apu/driver/miniaudio_driver.cpp b/UnleashedRecomp/apu/driver/miniaudio_driver.cpp deleted file mode 100644 index 8ac823d3..00000000 --- a/UnleashedRecomp/apu/driver/miniaudio_driver.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "miniaudio_driver.h" -#include -#include -#include -#include - -static PPCFunc* g_clientCallback{}; -static DWORD g_clientCallbackParam{}; // pointer in guest memory -static ma_device g_audioDevice{}; -static std::unique_ptr g_audioCtx; -static uint32_t* g_audioOutput; - -static void AudioCallback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) -{ - if (g_audioCtx == nullptr) - g_audioCtx = std::make_unique(0); - - g_audioCtx->ppcContext.r3.u64 = g_clientCallbackParam; - g_audioOutput = reinterpret_cast(pOutput); - (*g_clientCallback)(g_audioCtx->ppcContext, reinterpret_cast(g_memory.base)); -} - -void XAudioInitializeSystem() -{ - ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback); - deviceConfig.sampleRate = XAUDIO_SAMPLES_HZ; - deviceConfig.periodSizeInFrames = XAUDIO_NUM_SAMPLES; - deviceConfig.noPreSilencedOutputBuffer = true; - deviceConfig.dataCallback = AudioCallback; - deviceConfig.playback.format = ma_format_f32; - deviceConfig.playback.channels = XAUDIO_NUM_CHANNELS; - ma_device_init(nullptr, &deviceConfig, &g_audioDevice); -} - -void XAudioRegisterClient(PPCFunc* callback, uint32_t param) -{ - auto* pClientParam = static_cast(g_userHeap.Alloc(sizeof(param))); - ByteSwapInplace(param); - *pClientParam = param; - g_clientCallbackParam = g_memory.MapVirtual(pClientParam); - g_clientCallback = callback; - - ma_device_start(&g_audioDevice); -} - -void XAudioSubmitFrame(void* samples) -{ - for (size_t i = 0; i < XAUDIO_NUM_SAMPLES; i++) - { - for (size_t j = 0; j < XAUDIO_NUM_CHANNELS; j++) - g_audioOutput[i * XAUDIO_NUM_CHANNELS + j] = ByteSwap(((uint32_t*)samples)[j * XAUDIO_NUM_SAMPLES + i]); - } -} diff --git a/UnleashedRecomp/apu/driver/sdl2_driver.cpp b/UnleashedRecomp/apu/driver/sdl2_driver.cpp new file mode 100644 index 00000000..7c8777e7 --- /dev/null +++ b/UnleashedRecomp/apu/driver/sdl2_driver.cpp @@ -0,0 +1,128 @@ +#include "sdl2_driver.h" +#include +#include +#include +#include + +static PPCFunc* g_clientCallback{}; +static uint32_t g_clientCallbackParam{}; // pointer in guest memory +static SDL_AudioDeviceID g_audioDevice{}; +static bool g_downMixToStereo; + +void XAudioInitializeSystem() +{ + SDL_SetHint(SDL_HINT_AUDIO_CATEGORY, "playback"); + SDL_SetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME, "Unleashed Recompiled"); + SDL_InitSubSystem(SDL_INIT_AUDIO); + + SDL_AudioSpec desired{}, obtained{}; + desired.freq = XAUDIO_SAMPLES_HZ; + desired.format = AUDIO_F32SYS; + desired.channels = XAUDIO_NUM_CHANNELS; + desired.samples = XAUDIO_NUM_SAMPLES; + g_audioDevice = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, SDL_AUDIO_ALLOW_CHANNELS_CHANGE); + + if (obtained.channels != 2 && obtained.channels != XAUDIO_NUM_CHANNELS) + { + SDL_CloseAudioDevice(g_audioDevice); + g_audioDevice = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, 0); + } + + g_downMixToStereo = (obtained.channels == 2); +} + +static std::unique_ptr g_audioThread; + +static void AudioThread() +{ + using namespace std::chrono_literals; + + GuestThreadContext ctx(0); + + size_t channels = g_downMixToStereo ? 2 : XAUDIO_NUM_CHANNELS; + + constexpr double INTERVAL = double(XAUDIO_NUM_SAMPLES) / double(XAUDIO_SAMPLES_HZ); + auto start = std::chrono::steady_clock::now(); + size_t iteration = 1; + + while (true) + { + uint32_t queuedAudioSize = SDL_GetQueuedAudioSize(g_audioDevice); + constexpr size_t MAX_LATENCY = 10; + const size_t callbackAudioSize = channels * XAUDIO_NUM_SAMPLES * sizeof(float); + + if ((queuedAudioSize / callbackAudioSize) <= MAX_LATENCY) + { + ctx.ppcContext.r3.u32 = g_clientCallbackParam; + g_clientCallback(ctx.ppcContext, reinterpret_cast(g_memory.base)); + } + + auto next = start + std::chrono::duration(iteration * INTERVAL); + auto now = std::chrono::steady_clock::now(); + + if ((next - now) > 1s) + next = now; + + std::this_thread::sleep_until(next); + + iteration = std::chrono::duration(std::chrono::steady_clock::now() - start).count() / INTERVAL + 1; + } +} + +void XAudioRegisterClient(PPCFunc* callback, uint32_t param) +{ + auto* pClientParam = static_cast(g_userHeap.Alloc(sizeof(param))); + ByteSwapInplace(param); + *pClientParam = param; + g_clientCallbackParam = g_memory.MapVirtual(pClientParam); + g_clientCallback = callback; + + SDL_PauseAudioDevice(g_audioDevice, 0); + g_audioThread = std::make_unique(AudioThread); +} + +void XAudioSubmitFrame(void* samples) +{ + if (g_downMixToStereo) + { + // 0: left 1.0f, right 0.0f + // 1: left 0.0f, right 1.0f + // 2: left 0.75f, right 0.75f + // 3: left 0.0f, right 0.0f + // 4: left 1.0f, right 0.0f + // 5: left 0.0f, right 1.0f + + auto floatSamples = reinterpret_cast*>(samples); + + std::array audioFrames; + + for (size_t i = 0; i < XAUDIO_NUM_SAMPLES; i++) + { + float ch0 = floatSamples[0 * XAUDIO_NUM_SAMPLES + i]; + float ch1 = floatSamples[1 * XAUDIO_NUM_SAMPLES + i]; + float ch2 = floatSamples[2 * XAUDIO_NUM_SAMPLES + i]; + float ch3 = floatSamples[3 * XAUDIO_NUM_SAMPLES + i]; + float ch4 = floatSamples[4 * XAUDIO_NUM_SAMPLES + i]; + float ch5 = floatSamples[5 * XAUDIO_NUM_SAMPLES + i]; + + audioFrames[i * 2 + 0] = ch0 + ch2 * 0.75f + ch4; + audioFrames[i * 2 + 1] = ch1 + ch2 * 0.75f + ch5; + } + + SDL_QueueAudio(g_audioDevice, &audioFrames, sizeof(audioFrames)); + } + else + { + auto rawSamples = reinterpret_cast*>(samples); + + std::array audioFrames; + + for (size_t i = 0; i < XAUDIO_NUM_SAMPLES; i++) + { + for (size_t j = 0; j < XAUDIO_NUM_CHANNELS; j++) + audioFrames[i * XAUDIO_NUM_CHANNELS + j] = rawSamples[j * XAUDIO_NUM_SAMPLES + i]; + } + + SDL_QueueAudio(g_audioDevice, &audioFrames, sizeof(audioFrames)); + } +} diff --git a/UnleashedRecomp/apu/driver/miniaudio_driver.h b/UnleashedRecomp/apu/driver/sdl2_driver.h similarity index 100% rename from UnleashedRecomp/apu/driver/miniaudio_driver.h rename to UnleashedRecomp/apu/driver/sdl2_driver.h diff --git a/UnleashedRecomp/apu/driver/xaudio_driver.cpp b/UnleashedRecomp/apu/driver/xaudio_driver.cpp index 2e59a9ef..1d98ca94 100644 --- a/UnleashedRecomp/apu/driver/xaudio_driver.cpp +++ b/UnleashedRecomp/apu/driver/xaudio_driver.cpp @@ -90,7 +90,7 @@ void XAudioInitializeSystem() g_sourceVoice->Start(); KeInsertHostFunction(XAUDIO_DRIVER_KEY, DriverLoop); - GuestThread::Start(XAUDIO_DRIVER_KEY, 0, 0, &g_driverThread); + GuestThread::Start({ XAUDIO_DRIVER_KEY, 0, 0 }, nullptr); } void XAudioRegisterClient(PPCFunc* callback, uint32_t param) diff --git a/UnleashedRecomp/apu/embedded_player.cpp b/UnleashedRecomp/apu/embedded_player.cpp index 2fb899d0..5e6c7ea9 100644 --- a/UnleashedRecomp/apu/embedded_player.cpp +++ b/UnleashedRecomp/apu/embedded_player.cpp @@ -10,82 +10,6 @@ #include #include -#pragma region libvorbis -static ma_result ma_decoding_backend_init__libvorbis(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) -{ - ma_result result; - ma_libvorbis* pVorbis; - - (void)pUserData; - - pVorbis = (ma_libvorbis*)ma_malloc(sizeof(*pVorbis), pAllocationCallbacks); - if (pVorbis == NULL) { - return MA_OUT_OF_MEMORY; - } - - result = ma_libvorbis_init(onRead, onSeek, onTell, pReadSeekTellUserData, pConfig, pAllocationCallbacks, pVorbis); - if (result != MA_SUCCESS) { - ma_free(pVorbis, pAllocationCallbacks); - return result; - } - - *ppBackend = pVorbis; - - return MA_SUCCESS; -} - -static ma_result ma_decoding_backend_init_file__libvorbis(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend) -{ - ma_result result; - ma_libvorbis* pVorbis; - - (void)pUserData; - - pVorbis = (ma_libvorbis*)ma_malloc(sizeof(*pVorbis), pAllocationCallbacks); - if (pVorbis == NULL) { - return MA_OUT_OF_MEMORY; - } - - result = ma_libvorbis_init_file(pFilePath, pConfig, pAllocationCallbacks, pVorbis); - if (result != MA_SUCCESS) { - ma_free(pVorbis, pAllocationCallbacks); - return result; - } - - *ppBackend = pVorbis; - - return MA_SUCCESS; -} - -static void ma_decoding_backend_uninit__libvorbis(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks) -{ - ma_libvorbis* pVorbis = (ma_libvorbis*)pBackend; - - (void)pUserData; - - ma_libvorbis_uninit(pVorbis, pAllocationCallbacks); - ma_free(pVorbis, pAllocationCallbacks); -} - -static ma_result ma_decoding_backend_get_channel_map__libvorbis(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap) -{ - ma_libvorbis* pVorbis = (ma_libvorbis*)pBackend; - - (void)pUserData; - - return ma_libvorbis_get_data_format(pVorbis, NULL, NULL, NULL, pChannelMap, channelMapCap); -} - -static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_libvorbis = -{ - ma_decoding_backend_init__libvorbis, - ma_decoding_backend_init_file__libvorbis, - NULL, /* onInitFileW() */ - NULL, /* onInitMemory() */ - ma_decoding_backend_uninit__libvorbis -}; -#pragma endregion - enum class EmbeddedSound { SysWorldMapCursor, @@ -100,13 +24,9 @@ enum class EmbeddedSound struct EmbeddedSoundData { - static const int SimultaneousLimit = 4; - std::array, SimultaneousLimit> sounds; - std::array, SimultaneousLimit> decoders; - int oldestIndex = 0; + Mix_Chunk* chunk{}; }; -static ma_engine g_audioEngine = {}; static std::array g_embeddedSoundData = {}; static const std::unordered_map g_embeddedSoundMap = { @@ -119,114 +39,61 @@ static const std::unordered_map g_embeddedSound { "sys_actstg_pausewinopen", EmbeddedSound::SysActStgPauseWinOpen }, }; +static size_t g_channelIndex; + static void PlayEmbeddedSound(EmbeddedSound s) { EmbeddedSoundData &data = g_embeddedSoundData[size_t(s)]; - int pickedIndex = -1; - for (int i = 0; (i < EmbeddedSoundData::SimultaneousLimit) && (pickedIndex < 0); i++) + if (data.chunk == nullptr) { - if (data.sounds[i] == nullptr) + // The sound hasn't been created yet, create it and pick it. + const void *soundData = nullptr; + size_t soundDataSize = 0; + switch (s) { - // The sound hasn't been created yet, create it and pick it. - const void *soundData = nullptr; - size_t soundDataSize = 0; - switch (s) - { - case EmbeddedSound::SysWorldMapCursor: - soundData = g_sys_worldmap_cursor; - soundDataSize = sizeof(g_sys_worldmap_cursor); - break; - case EmbeddedSound::SysWorldMapFinalDecide: - soundData = g_sys_worldmap_finaldecide; - soundDataSize = sizeof(g_sys_worldmap_finaldecide); - break; - case EmbeddedSound::SysActStgPauseCansel: - soundData = g_sys_actstg_pausecansel; - soundDataSize = sizeof(g_sys_actstg_pausecansel); - break; - case EmbeddedSound::SysActStgPauseCursor: - soundData = g_sys_actstg_pausecursor; - soundDataSize = sizeof(g_sys_actstg_pausecursor); - break; - case EmbeddedSound::SysActStgPauseDecide: - soundData = g_sys_actstg_pausedecide; - soundDataSize = sizeof(g_sys_actstg_pausedecide); - break; - case EmbeddedSound::SysActStgPauseWinClose: - soundData = g_sys_actstg_pausewinclose; - soundDataSize = sizeof(g_sys_actstg_pausewinclose); - break; - case EmbeddedSound::SysActStgPauseWinOpen: - soundData = g_sys_actstg_pausewinopen; - soundDataSize = sizeof(g_sys_actstg_pausewinopen); - break; - default: - assert(false && "Unknown embedded sound."); - return; - } - - ma_decoding_backend_vtable* pCustomBackendVTables[] = - { - &g_ma_decoding_backend_vtable_libvorbis - }; - - ma_decoder_config decoderConfig = ma_decoder_config_init_default(); - decoderConfig.pCustomBackendUserData = NULL; - decoderConfig.ppCustomBackendVTables = pCustomBackendVTables; - decoderConfig.customBackendCount = std::size(pCustomBackendVTables); - - ma_result res; - data.decoders[i] = std::make_unique(); - res = ma_decoder_init_memory(soundData, soundDataSize, &decoderConfig, data.decoders[i].get()); - if (res != MA_SUCCESS) - { - fprintf(stderr, "ma_decoder_init_memory failed with error code %d.\n", res); - return; - } - - data.sounds[i] = std::make_unique(); - res = ma_sound_init_from_data_source(&g_audioEngine, data.decoders[i].get(), MA_SOUND_FLAG_DECODE, nullptr, data.sounds[i].get()); - if (res != MA_SUCCESS) - { - fprintf(stderr, "ma_sound_init_from_data_source failed with error code %d.\n", res); - return; - } - - pickedIndex = i; + case EmbeddedSound::SysWorldMapCursor: + soundData = g_sys_worldmap_cursor; + soundDataSize = sizeof(g_sys_worldmap_cursor); + break; + case EmbeddedSound::SysWorldMapFinalDecide: + soundData = g_sys_worldmap_finaldecide; + soundDataSize = sizeof(g_sys_worldmap_finaldecide); + break; + case EmbeddedSound::SysActStgPauseCansel: + soundData = g_sys_actstg_pausecansel; + soundDataSize = sizeof(g_sys_actstg_pausecansel); + break; + case EmbeddedSound::SysActStgPauseCursor: + soundData = g_sys_actstg_pausecursor; + soundDataSize = sizeof(g_sys_actstg_pausecursor); + break; + case EmbeddedSound::SysActStgPauseDecide: + soundData = g_sys_actstg_pausedecide; + soundDataSize = sizeof(g_sys_actstg_pausedecide); + break; + case EmbeddedSound::SysActStgPauseWinClose: + soundData = g_sys_actstg_pausewinclose; + soundDataSize = sizeof(g_sys_actstg_pausewinclose); + break; + case EmbeddedSound::SysActStgPauseWinOpen: + soundData = g_sys_actstg_pausewinopen; + soundDataSize = sizeof(g_sys_actstg_pausewinopen); + break; + default: + assert(false && "Unknown embedded sound."); + return; } - else if (ma_sound_at_end(data.sounds[i].get())) - { - // A sound has reached the end, pick it. - pickedIndex = i; - } - } - if (pickedIndex < 0) - { - // No free slots are available, pick the oldest one. - pickedIndex = data.oldestIndex; - data.oldestIndex = (data.oldestIndex + 1) % EmbeddedSoundData::SimultaneousLimit; - } - - if (data.sounds[pickedIndex] != nullptr) - { - ma_sound_set_volume(data.sounds[pickedIndex].get(), Config::EffectsVolume); - ma_sound_seek_to_pcm_frame(data.sounds[pickedIndex].get(), 0); - ma_sound_start(data.sounds[pickedIndex].get()); + data.chunk = Mix_LoadWAV_RW(SDL_RWFromConstMem(soundData, soundDataSize), 1); } + + Mix_PlayChannel(g_channelIndex % MIX_CHANNELS, data.chunk, 0); + ++g_channelIndex; } void EmbeddedPlayer::Init() { - ma_engine_config engineConfig = ma_engine_config_init(); - engineConfig.channels = XAUDIO_NUM_CHANNELS; - engineConfig.sampleRate = XAUDIO_SAMPLES_HZ; - - ma_result res = ma_engine_init(&engineConfig, &g_audioEngine); - if (res != MA_SUCCESS) - { - fprintf(stderr, "ma_engine_init failed with error code %d.\n", res); - } + Mix_OpenAudio(XAUDIO_SAMPLES_HZ, AUDIO_F32SYS, XAUDIO_NUM_CHANNELS, 256); s_isActive = true; } @@ -241,11 +108,6 @@ void EmbeddedPlayer::Play(const char *name) return; } - if (g_audioEngine.pDevice == nullptr) - { - return; - } - PlayEmbeddedSound(it->second); } @@ -253,37 +115,12 @@ void EmbeddedPlayer::Shutdown() { for (EmbeddedSoundData &data : g_embeddedSoundData) { - for (auto &sound : data.sounds) - { - if (sound != nullptr) - { - if (sound->pDataSource != nullptr) - { - ma_sound_uninit(sound.get()); - } - - sound.reset(); - } - } - - for (auto &decoder : data.decoders) - { - if (decoder != nullptr) - { - if (decoder->pBackend != nullptr) - { - ma_decoder_uninit(decoder.get()); - } - - decoder.reset(); - } - } + if (data.chunk != nullptr) + Mix_FreeChunk(data.chunk); } - if (g_audioEngine.pDevice != nullptr) - { - ma_engine_uninit(&g_audioEngine); - } + Mix_CloseAudio(); + Mix_Quit(); s_isActive = false; } diff --git a/UnleashedRecomp/cpu/code_cache.cpp b/UnleashedRecomp/cpu/code_cache.cpp index 2175f0b8..bbbebc4a 100644 --- a/UnleashedRecomp/cpu/code_cache.cpp +++ b/UnleashedRecomp/cpu/code_cache.cpp @@ -4,13 +4,22 @@ CodeCache::CodeCache() { +#ifdef _WIN32 bucket = (char*)VirtualAlloc(nullptr, 0x200000000, MEM_RESERVE, PAGE_READWRITE); - assert(bucket); + assert(bucket != nullptr); +#else + bucket = (char*)mmap(NULL, 0x200000000, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); + assert(bucket != (char*)MAP_FAILED); +#endif } CodeCache::~CodeCache() { +#ifdef _WIN32 VirtualFree(bucket, 0, MEM_RELEASE); +#else + munmap(bucket, 0x200000000); +#endif } void CodeCache::Init() @@ -19,16 +28,20 @@ void CodeCache::Init() { if (PPCFuncMappings[i].host != nullptr) { +#ifdef _WIN32 VirtualAlloc(bucket + PPCFuncMappings[i].guest * 2, sizeof(void*), MEM_COMMIT, PAGE_READWRITE); +#endif *(void**)(bucket + PPCFuncMappings[i].guest * 2) = (void*)PPCFuncMappings[i].host; } } } -void CodeCache::Insert(uint32_t guest, const void* host) +void CodeCache::Insert(uint32_t guest, PPCFunc* host) { +#ifdef _WIN32 VirtualAlloc(bucket + static_cast(guest) * 2, sizeof(void*), MEM_COMMIT, PAGE_READWRITE); - *reinterpret_cast(bucket + static_cast(guest) * 2) = host; +#endif + *reinterpret_cast(bucket + static_cast(guest) * 2) = host; } void* CodeCache::Find(uint32_t guest) const @@ -43,5 +56,5 @@ SWA_API PPCFunc* KeFindHostFunction(uint32_t guest) SWA_API void KeInsertHostFunction(uint32_t guest, PPCFunc* function) { - g_codeCache.Insert(guest, (const void*)function); + g_codeCache.Insert(guest, function); } diff --git a/UnleashedRecomp/cpu/code_cache.h b/UnleashedRecomp/cpu/code_cache.h index 35d610bf..e2a1d5ac 100644 --- a/UnleashedRecomp/cpu/code_cache.h +++ b/UnleashedRecomp/cpu/code_cache.h @@ -8,7 +8,7 @@ struct CodeCache ~CodeCache(); void Init(); - void Insert(uint32_t guest, const void* host); + void Insert(uint32_t guest, PPCFunc* host); void* Find(uint32_t guest) const; }; diff --git a/UnleashedRecomp/cpu/guest_thread.cpp b/UnleashedRecomp/cpu/guest_thread.cpp index 9d37a9ca..290fc51c 100644 --- a/UnleashedRecomp/cpu/guest_thread.cpp +++ b/UnleashedRecomp/cpu/guest_thread.cpp @@ -27,11 +27,12 @@ GuestThreadContext::GuestThreadContext(uint32_t cpuNumber) *(thread + 0x10C) = cpuNumber; *(uint32_t*)(thread + PCR_SIZE + 0x10) = 0xFFFFFFFF; // that one TLS entry that felt quirky - *(uint32_t*)(thread + PCR_SIZE + TLS_SIZE + 0x14C) = ByteSwap(GetCurrentThreadId()); // thread id + *(uint32_t*)(thread + PCR_SIZE + TLS_SIZE + 0x14C) = ByteSwap(GuestThread::GetCurrentThreadId()); // thread id ppcContext.fn = (uint8_t*)g_codeCache.bucket; ppcContext.r1.u64 = g_memory.MapVirtual(thread + PCR_SIZE + TLS_SIZE + TEB_SIZE + STACK_SIZE); // stack pointer ppcContext.r13.u64 = g_memory.MapVirtual(thread); + ppcContext.fpscr.loadFromHost(); assert(GetPPCContext() == nullptr); SetPPCContext(ppcContext); @@ -42,42 +43,84 @@ GuestThreadContext::~GuestThreadContext() g_userHeap.Free(thread); } -DWORD GuestThread::Start(uint32_t function) +static void GuestThreadFunc(GuestThreadHandle* hThread) { - const GuestThreadParameter parameter{ function }; - return Start(parameter); + hThread->suspended.wait(true); + GuestThread::Start(hThread->params); } -DWORD GuestThread::Start(const GuestThreadParameter& parameter) +GuestThreadHandle::GuestThreadHandle(const GuestThreadParams& params) + : params(params), suspended((params.flags & 0x1) != 0), thread(GuestThreadFunc, this) { - const auto procMask = (uint8_t)(parameter.flags >> 24); +} + +GuestThreadHandle::~GuestThreadHandle() +{ + if (thread.joinable()) + thread.join(); +} + +uint32_t GuestThreadHandle::Wait(uint32_t timeout) +{ + assert(timeout == INFINITE); + + if (thread.joinable()) + thread.join(); + + return STATUS_WAIT_0; +} + +uint32_t GuestThread::Start(const GuestThreadParams& params) +{ + const auto procMask = (uint8_t)(params.flags >> 24); const auto cpuNumber = procMask == 0 ? 0 : 7 - std::countl_zero(procMask); GuestThreadContext ctx(cpuNumber); - ctx.ppcContext.r3.u64 = parameter.value; + ctx.ppcContext.r3.u64 = params.value; - GuestCode::Run(g_codeCache.Find(parameter.function), &ctx.ppcContext, g_memory.Translate(0)); + reinterpret_cast(g_codeCache.Find(params.function))(ctx.ppcContext, reinterpret_cast(g_memory.base)); - return (DWORD)ctx.ppcContext.r3.u64; + return ctx.ppcContext.r3.u32; } -DWORD HostThreadStart(void* pParameter) +static uint32_t GetThreadId(const std::thread::id& id) { - auto* parameter = static_cast(pParameter); - const auto result = GuestThread::Start(*parameter); - - delete parameter; - return result; + if constexpr (sizeof(id) == 4) + return *reinterpret_cast(&id); + else + return XXH32(&id, sizeof(id), 0); } -HANDLE GuestThread::Start(uint32_t function, uint32_t parameter, uint32_t flags, LPDWORD threadId) +GuestThreadHandle* GuestThread::Start(const GuestThreadParams& params, uint32_t* threadId) { - const auto hostCreationFlags = (flags & 1) != 0 ? CREATE_SUSPENDED : 0; - //return CreateThread(nullptr, 0, Start, (void*)((uint64_t(parameter) << 32) | function), suspended ? CREATE_SUSPENDED : 0, threadId); - return CreateThread(nullptr, 0, HostThreadStart, new GuestThreadParameter{ function, parameter, flags }, hostCreationFlags, threadId); + auto hThread = CreateKernelObject(params); + + if (threadId != nullptr) + *threadId = GetThreadId(hThread->thread.get_id()); + + return hThread; } -void GuestThread::SetThreadName(uint32_t id, const char* name) +uint32_t GuestThread::GetCurrentThreadId() +{ + return GetThreadId(std::this_thread::get_id()); +} + +void GuestThread::SetLastError(uint32_t error) +{ + auto* thread = (char*)g_memory.Translate(GetPPCContext()->r13.u32); + if (*(uint32_t*)(thread + 0x150)) + { + // Program doesn't want errors + return; + } + + // TEB + 0x160 : Win32LastError + *(uint32_t*)(thread + TEB_OFFSET + 0x160) = ByteSwap(error); +} + +#ifdef _WIN32 +void GuestThread::SetThreadName(uint32_t threadId, const char* name) { #pragma pack(push,8) const DWORD MS_VC_EXCEPTION = 0x406D1388; @@ -94,7 +137,7 @@ void GuestThread::SetThreadName(uint32_t id, const char* name) THREADNAME_INFO info; info.dwType = 0x1000; info.szName = name; - info.dwThreadID = id; + info.dwThreadID = threadId; info.dwFlags = 0; __try @@ -105,41 +148,31 @@ void GuestThread::SetThreadName(uint32_t id, const char* name) { } } - -void GuestThread::SetLastError(DWORD error) -{ - auto* thread = (char*)g_memory.Translate(GetPPCContext()->r13.u32); - if (*(DWORD*)(thread + 0x150)) - { - // Program doesn't want errors - return; - } - - // TEB + 0x160 : Win32LastError - *(DWORD*)(thread + TEB_OFFSET + 0x160) = ByteSwap(error); -} - -PPCContext* GuestThread::Invoke(uint32_t address) -{ - auto* ctx = GetPPCContext(); - GuestCode::Run(g_codeCache.Find(address), ctx); - - return ctx; -} +#endif void SetThreadNameImpl(uint32_t a1, uint32_t threadId, uint32_t* name) { +#ifdef _WIN32 GuestThread::SetThreadName(threadId, (const char*)g_memory.Translate(ByteSwap(*name))); +#endif } -int GetThreadPriorityImpl(uint32_t hThread) +int GetThreadPriorityImpl(GuestThreadHandle* hThread) { - return GetThreadPriority((HANDLE)hThread); +#ifdef _WIN32 + return GetThreadPriority(hThread == GetKernelObject(CURRENT_THREAD_HANDLE) ? GetCurrentThread() : hThread->thread.native_handle()); +#else + return 0; +#endif } -DWORD SetThreadIdealProcessorImpl(uint32_t hThread, DWORD dwIdealProcessor) +uint32_t SetThreadIdealProcessorImpl(GuestThreadHandle* hThread, uint32_t dwIdealProcessor) { - return SetThreadIdealProcessor((HANDLE)hThread, dwIdealProcessor); +#ifdef _WIN32 + return SetThreadIdealProcessor(hThread == GetKernelObject(CURRENT_THREAD_HANDLE) ? GetCurrentThread() : hThread->thread.native_handle(), dwIdealProcessor); +#else + return 0; +#endif } GUEST_FUNCTION_HOOK(sub_82DFA2E8, SetThreadNameImpl); diff --git a/UnleashedRecomp/cpu/guest_thread.h b/UnleashedRecomp/cpu/guest_thread.h index 7fb6e9b5..15d16bf6 100644 --- a/UnleashedRecomp/cpu/guest_thread.h +++ b/UnleashedRecomp/cpu/guest_thread.h @@ -1,12 +1,8 @@ #pragma once -struct PPCContext; -struct GuestThreadParameter -{ - uint32_t function; - uint32_t value; - uint32_t flags; -}; +#include + +#define CURRENT_THREAD_HANDLE uint32_t(-2) struct GuestThreadContext { @@ -17,13 +13,34 @@ struct GuestThreadContext ~GuestThreadContext(); }; +struct GuestThreadParams +{ + uint32_t function; + uint32_t value; + uint32_t flags; +}; + +struct GuestThreadHandle : KernelObject +{ + GuestThreadParams params; + std::atomic suspended; + std::thread thread; + + GuestThreadHandle(const GuestThreadParams& params); + ~GuestThreadHandle() override; + + uint32_t Wait(uint32_t timeout) override; +}; + struct GuestThread { - static DWORD Start(uint32_t function); - static DWORD Start(const GuestThreadParameter& parameter); - static HANDLE Start(uint32_t function, uint32_t parameter, uint32_t flags, LPDWORD threadId); + static uint32_t Start(const GuestThreadParams& params); + static GuestThreadHandle* Start(const GuestThreadParams& params, uint32_t* threadId); - static void SetThreadName(uint32_t id, const char* name); - static void SetLastError(DWORD error); - static PPCContext* Invoke(uint32_t address); + static uint32_t GetCurrentThreadId(); + static void SetLastError(uint32_t error); + +#ifdef _WIN32 + static void SetThreadName(uint32_t threadId, const char* name); +#endif }; diff --git a/UnleashedRecomp/cpu/ppc_context.h b/UnleashedRecomp/cpu/ppc_context.h index 65a9de89..c6a67835 100644 --- a/UnleashedRecomp/cpu/ppc_context.h +++ b/UnleashedRecomp/cpu/ppc_context.h @@ -1,13 +1,13 @@ #pragma once -inline thread_local PPCContext* gPPCContext; +inline thread_local PPCContext* g_ppcContext; inline PPCContext* GetPPCContext() { - return gPPCContext; + return g_ppcContext; } inline void SetPPCContext(PPCContext& ctx) { - gPPCContext = &ctx; + g_ppcContext = &ctx; } diff --git a/UnleashedRecomp/exports.cpp b/UnleashedRecomp/exports.cpp index 84cfba20..65b231a5 100644 --- a/UnleashedRecomp/exports.cpp +++ b/UnleashedRecomp/exports.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include SWA_API void Game_PlaySound(const char* pName) @@ -31,10 +31,10 @@ SWA_API void Game_PlaySound(const char* pName) SWA_API void Window_SetDisplay(int displayIndex) { - Window::SetDisplay(displayIndex); + GameWindow::SetDisplay(displayIndex); } SWA_API void Window_SetFullscreen(bool isEnabled) { - Window::SetFullscreen(isEnabled); + GameWindow::SetFullscreen(isEnabled); } diff --git a/UnleashedRecomp/framework.h b/UnleashedRecomp/framework.h index a8234114..68ad36f7 100644 --- a/UnleashedRecomp/framework.h +++ b/UnleashedRecomp/framework.h @@ -4,8 +4,9 @@ #define SWA_DLLEXPORT __declspec(dllexport) #define SWA_DLLIMPORT __declspec(dllimport) #else - #define SWA_DLLEXPORT __attribute__((dllexport)) - #define SWA_DLLIMPORT __attribute__((dllimport)) + // TODO + #define SWA_DLLEXPORT + #define SWA_DLLIMPORT #endif #ifdef SWA_IMPL @@ -33,19 +34,6 @@ inline T RoundDown(const T& in_rValue, uint32_t in_round) return in_rValue & ~(in_round - 1); } -inline bool FileExists(const char* path) -{ - const auto attributes = GetFileAttributesA(path); - return attributes != INVALID_FILE_ATTRIBUTES && !(attributes & FILE_ATTRIBUTE_DIRECTORY); - -} - -inline bool DirectoryExists(const char* path) -{ - const auto attributes = GetFileAttributesA(path); - return attributes != INVALID_FILE_ATTRIBUTES && !!(attributes & FILE_ATTRIBUTE_DIRECTORY); -} - inline size_t StringHash(const std::string_view& str) { return XXH3_64bits(str.data(), str.size()); diff --git a/UnleashedRecomp/gpu/imgui/imgui_snapshot.cpp b/UnleashedRecomp/gpu/imgui/imgui_snapshot.cpp index cdb1c714..5d5deff7 100644 --- a/UnleashedRecomp/gpu/imgui/imgui_snapshot.cpp +++ b/UnleashedRecomp/gpu/imgui/imgui_snapshot.cpp @@ -196,7 +196,7 @@ void ImFontAtlasSnapshot::GenerateGlyphRanges() { std::vector localeStrings; - for (auto& config : Config::Definitions) + for (auto& config : g_configDefinitions) config->GetLocaleStrings(localeStrings); std::set glyphs; diff --git a/UnleashedRecomp/gpu/rhi/plume_d3d12.cpp b/UnleashedRecomp/gpu/rhi/plume_d3d12.cpp index b08af837..86d006d2 100644 --- a/UnleashedRecomp/gpu/rhi/plume_d3d12.cpp +++ b/UnleashedRecomp/gpu/rhi/plume_d3d12.cpp @@ -3345,7 +3345,8 @@ namespace plume { D3D12MA::ALLOCATOR_DESC allocatorDesc = {}; allocatorDesc.pDevice = d3d; allocatorDesc.pAdapter = adapter; - allocatorDesc.Flags = D3D12MA::ALLOCATOR_FLAG_DEFAULT_POOLS_NOT_ZEROED | D3D12MA::ALLOCATOR_FLAG_MSAA_TEXTURES_ALWAYS_COMMITTED; + allocatorDesc.Flags = D3D12MA::ALLOCATOR_FLAG_DEFAULT_POOLS_NOT_ZEROED | + D3D12MA::ALLOCATOR_FLAG_MSAA_TEXTURES_ALWAYS_COMMITTED | D3D12MA::ALLOCATOR_FLAG_DONT_PREFER_SMALL_BUFFERS_COMMITTED; res = D3D12MA::CreateAllocator(&allocatorDesc, &allocator); if (FAILED(res)) { diff --git a/UnleashedRecomp/gpu/rhi/plume_render_interface_types.h b/UnleashedRecomp/gpu/rhi/plume_render_interface_types.h index f0e94fd3..c4e87727 100644 --- a/UnleashedRecomp/gpu/rhi/plume_render_interface_types.h +++ b/UnleashedRecomp/gpu/rhi/plume_render_interface_types.h @@ -29,12 +29,18 @@ typedef struct _NSWindow NSWindow; #endif +#ifdef SDL_VULKAN_ENABLED +#include +#endif + namespace plume { #if defined(_WIN64) // Native HWND handle to the target window. typedef HWND RenderWindow; #elif defined(__ANDROID__) typedef ANativeWindow* RenderWindow; +#elif defined(SDL_VULKAN_ENABLED) + typedef SDL_Window *RenderWindow; #elif defined(__linux__) struct RenderWindow { Display* display; diff --git a/UnleashedRecomp/gpu/rhi/plume_vulkan.cpp b/UnleashedRecomp/gpu/rhi/plume_vulkan.cpp index a669001a..89334cef 100644 --- a/UnleashedRecomp/gpu/rhi/plume_vulkan.cpp +++ b/UnleashedRecomp/gpu/rhi/plume_vulkan.cpp @@ -1989,6 +1989,13 @@ namespace plume { fprintf(stderr, "vkCreateWin32SurfaceKHR failed with error code 0x%X.\n", res); return; } +# elif defined(SDL_VULKAN_ENABLED) + VulkanInterface *renderInterface = commandQueue->device->renderInterface; + SDL_bool sdlRes = SDL_Vulkan_CreateSurface(renderWindow, renderInterface->instance, &surface); + if (sdlRes == SDL_FALSE) { + fprintf(stderr, "SDL_Vulkan_CreateSurface failed with error %s.\n", SDL_GetError()); + return; + } # elif defined(__ANDROID__) assert(renderWindow != nullptr); VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo = {}; @@ -2124,6 +2131,12 @@ namespace plume { } bool VulkanSwapChain::present(uint32_t textureIndex, RenderCommandSemaphore **waitSemaphores, uint32_t waitSemaphoreCount) { + constexpr uint64_t MaxFrameDelay = 1; + if (commandQueue->device->capabilities.presentWait && (currentPresentId > MaxFrameDelay)) { + constexpr uint64_t waitTimeout = 100000000; + vkWaitForPresentKHR(commandQueue->device->vk, vk, currentPresentId - MaxFrameDelay, waitTimeout); + } + thread_local std::vector waitSemaphoresVector; waitSemaphoresVector.clear(); for (uint32_t i = 0; i < waitSemaphoreCount; i++) { @@ -2138,6 +2151,15 @@ namespace plume { presentInfo.pImageIndices = &textureIndex; presentInfo.pWaitSemaphores = !waitSemaphoresVector.empty() ? waitSemaphoresVector.data() : nullptr; presentInfo.waitSemaphoreCount = uint32_t(waitSemaphoresVector.size()); + + VkPresentIdKHR presentId = {}; + if (commandQueue->device->capabilities.presentWait) { + currentPresentId++; + presentId.sType = VK_STRUCTURE_TYPE_PRESENT_ID_KHR; + presentId.pPresentIds = ¤tPresentId; + presentId.swapchainCount = 1; + presentInfo.pNext = &presentId; + } VkResult res; { @@ -2297,6 +2319,8 @@ namespace plume { GetClientRect(renderWindow, &rect); dstWidth = rect.right - rect.left; dstHeight = rect.bottom - rect.top; +# elif defined(SDL_VULKAN_ENABLED) + SDL_GetWindowSize(renderWindow, (int *)(&dstWidth), (int *)(&dstHeight)); # elif defined(__ANDROID__) dstWidth = ANativeWindow_getWidth(renderWindow); dstHeight = ANativeWindow_getHeight(renderWindow); @@ -4058,7 +4082,11 @@ namespace plume { // VulkanInterface +#if SDL_VULKAN_ENABLED + VulkanInterface::VulkanInterface(RenderWindow sdlWindow) { +#else VulkanInterface::VulkanInterface() { +#endif VkResult res = volkInitialize(); if (res != VK_SUCCESS) { fprintf(stderr, "volkInitialize failed with error code 0x%X.\n", res); @@ -4085,11 +4113,31 @@ namespace plume { std::vector availableExtensions(extensionCount); vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, availableExtensions.data()); - std::unordered_set missingRequiredExtensions = RequiredInstanceExtensions; + std::unordered_set requiredExtensions = RequiredInstanceExtensions; std::unordered_set supportedOptionalExtensions; # if DLSS_ENABLED const std::unordered_set dlssExtensions = DLSS::getRequiredInstanceExtensionsVulkan(); # endif + +# if SDL_VULKAN_ENABLED + // Push the extensions specified by SDL as required. + // SDL2 has this awkward requirement for the window to pull the extensions from. + // This can be removed when upgrading to SDL3. + if (sdlWindow != nullptr) { + uint32_t sdlVulkanExtensionCount = 0; + if (SDL_Vulkan_GetInstanceExtensions(sdlWindow, &sdlVulkanExtensionCount, nullptr)) { + std::vector sdlVulkanExtensions; + sdlVulkanExtensions.resize(sdlVulkanExtensionCount); + if (SDL_Vulkan_GetInstanceExtensions(sdlWindow, &sdlVulkanExtensionCount, (const char **)(sdlVulkanExtensions.data()))) { + for (char *sdlVulkanExtension : sdlVulkanExtensions) { + requiredExtensions.insert(sdlVulkanExtension); + } + } + } + } +# endif + + std::unordered_set missingRequiredExtensions = requiredExtensions; for (uint32_t i = 0; i < extensionCount; i++) { const std::string extensionName(availableExtensions[i].extensionName); missingRequiredExtensions.erase(extensionName); @@ -4114,7 +4162,7 @@ namespace plume { } std::vector enabledExtensions; - for (const std::string &extension : RequiredInstanceExtensions) { + for (const std::string &extension : requiredExtensions) { enabledExtensions.push_back(extension.c_str()); } @@ -4177,8 +4225,15 @@ namespace plume { // Global creation function. +#if SDL_VULKAN_ENABLED + std::unique_ptr CreateVulkanInterface(RenderWindow sdlWindow) { + std::unique_ptr createdInterface = std::make_unique(sdlWindow); + return createdInterface->isValid() ? std::move(createdInterface) : nullptr; + } +#else std::unique_ptr CreateVulkanInterface() { std::unique_ptr createdInterface = std::make_unique(); return createdInterface->isValid() ? std::move(createdInterface) : nullptr; } +#endif }; diff --git a/UnleashedRecomp/gpu/rhi/plume_vulkan.h b/UnleashedRecomp/gpu/rhi/plume_vulkan.h index 0e93536e..6ffe5aa3 100644 --- a/UnleashedRecomp/gpu/rhi/plume_vulkan.h +++ b/UnleashedRecomp/gpu/rhi/plume_vulkan.h @@ -22,9 +22,18 @@ #define VK_USE_PLATFORM_XLIB_KHR #endif -#include "volk.h" +#include -#include "vk_mem_alloc.h" +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnullability-completeness" +#endif + +#include + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif namespace plume { struct VulkanCommandQueue; @@ -220,6 +229,7 @@ namespace plume { VkPresentModeKHR requiredPresentMode = VK_PRESENT_MODE_FIFO_KHR; VkCompositeAlphaFlagBitsKHR pickedAlphaFlag = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; std::vector textures; + uint64_t currentPresentId = 0; bool immediatePresentModeSupported = false; VulkanSwapChain(VulkanCommandQueue *commandQueue, RenderWindow renderWindow, uint32_t textureCount, RenderFormat format); @@ -412,7 +422,12 @@ namespace plume { VkApplicationInfo appInfo = {}; RenderInterfaceCapabilities capabilities; +# if SDL_VULKAN_ENABLED + VulkanInterface(RenderWindow sdlWindow); +# else VulkanInterface(); +# endif + ~VulkanInterface() override; std::unique_ptr createDevice() override; const RenderInterfaceCapabilities &getCapabilities() const override; diff --git a/UnleashedRecomp/gpu/video.cpp b/UnleashedRecomp/gpu/video.cpp index 4196d5fa..887aaa2f 100644 --- a/UnleashedRecomp/gpu/video.cpp +++ b/UnleashedRecomp/gpu/video.cpp @@ -26,56 +26,68 @@ #include #include #include -#include +#include #include #include #if defined(ASYNC_PSO_DEBUG) || defined(PSO_CACHING) -#include +#include #endif #include "../../tools/ShaderRecomp/ShaderRecomp/shader_common.h" + +#ifdef SWA_D3D12 #include "shader/copy_vs.hlsl.dxil.h" -#include "shader/copy_vs.hlsl.spirv.h" #include "shader/csd_filter_ps.hlsl.dxil.h" -#include "shader/csd_filter_ps.hlsl.spirv.h" #include "shader/enhanced_motion_blur_ps.hlsl.dxil.h" -#include "shader/enhanced_motion_blur_ps.hlsl.spirv.h" #include "shader/gamma_correction_ps.hlsl.dxil.h" -#include "shader/gamma_correction_ps.hlsl.spirv.h" #include "shader/gaussian_blur_3x3.hlsl.dxil.h" -#include "shader/gaussian_blur_3x3.hlsl.spirv.h" #include "shader/gaussian_blur_5x5.hlsl.dxil.h" -#include "shader/gaussian_blur_5x5.hlsl.spirv.h" #include "shader/gaussian_blur_7x7.hlsl.dxil.h" -#include "shader/gaussian_blur_7x7.hlsl.spirv.h" #include "shader/gaussian_blur_9x9.hlsl.dxil.h" -#include "shader/gaussian_blur_9x9.hlsl.spirv.h" #include "shader/imgui_ps.hlsl.dxil.h" -#include "shader/imgui_ps.hlsl.spirv.h" #include "shader/imgui_vs.hlsl.dxil.h" -#include "shader/imgui_vs.hlsl.spirv.h" #include "shader/movie_ps.hlsl.dxil.h" -#include "shader/movie_ps.hlsl.spirv.h" #include "shader/movie_vs.hlsl.dxil.h" -#include "shader/movie_vs.hlsl.spirv.h" #include "shader/resolve_msaa_depth_2x.hlsl.dxil.h" -#include "shader/resolve_msaa_depth_2x.hlsl.spirv.h" #include "shader/resolve_msaa_depth_4x.hlsl.dxil.h" -#include "shader/resolve_msaa_depth_4x.hlsl.spirv.h" #include "shader/resolve_msaa_depth_8x.hlsl.dxil.h" +#endif + +#include "shader/copy_vs.hlsl.spirv.h" +#include "shader/csd_filter_ps.hlsl.spirv.h" +#include "shader/enhanced_motion_blur_ps.hlsl.spirv.h" +#include "shader/gamma_correction_ps.hlsl.spirv.h" +#include "shader/gaussian_blur_3x3.hlsl.spirv.h" +#include "shader/gaussian_blur_5x5.hlsl.spirv.h" +#include "shader/gaussian_blur_7x7.hlsl.spirv.h" +#include "shader/gaussian_blur_9x9.hlsl.spirv.h" +#include "shader/imgui_ps.hlsl.spirv.h" +#include "shader/imgui_vs.hlsl.spirv.h" +#include "shader/movie_ps.hlsl.spirv.h" +#include "shader/movie_vs.hlsl.spirv.h" +#include "shader/resolve_msaa_depth_2x.hlsl.spirv.h" +#include "shader/resolve_msaa_depth_4x.hlsl.spirv.h" #include "shader/resolve_msaa_depth_8x.hlsl.spirv.h" +#ifdef _WIN32 extern "C" { __declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001; __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; } +#endif namespace plume { +#ifdef SWA_D3D12 extern std::unique_ptr CreateD3D12Interface(); +#endif +#ifdef SDL_VULKAN_ENABLED + extern std::unique_ptr CreateVulkanInterface(RenderWindow sdlWindow); +#else extern std::unique_ptr CreateVulkanInterface(); +#endif } #pragma pack(push, 1) @@ -165,7 +177,7 @@ struct DirtyStates static DirtyStates g_dirtyStates(true); template -static FORCEINLINE void SetDirtyValue(bool& dirtyState, T& dest, const T& src) +static void SetDirtyValue(bool& dirtyState, T& dest, const T& src) { if (dest != src) { @@ -174,7 +186,12 @@ static FORCEINLINE void SetDirtyValue(bool& dirtyState, T& dest, const T& src) } } -static bool g_vulkan; +#ifdef SWA_D3D12 +static bool g_vulkan = false; +#else +static constexpr bool g_vulkan = true; +#endif + static std::unique_ptr g_interface; static std::unique_ptr g_device; @@ -197,7 +214,6 @@ static std::unique_ptr g_copyCommandFence; static std::unique_ptr g_swapChain; static bool g_swapChainValid; -static bool g_needsResize; static constexpr RenderFormat BACKBUFFER_FORMAT = RenderFormat::B8G8R8A8_UNORM; @@ -545,7 +561,7 @@ static void DestructTempResources() g_tempBuffers[g_frame].clear(); } -static uint32_t g_mainThreadId; +static std::thread::id g_mainThreadId; static ankerl::unordered_dense::map g_barrierMap; @@ -579,13 +595,18 @@ static std::unique_ptr g_buttonBcDiff; static void LoadEmbeddedResources() { - const size_t decompressedSize = g_vulkan ? g_spirvCacheDecompressedSize : g_dxilCacheDecompressedSize; - g_shaderCache = std::make_unique(decompressedSize); - - ZSTD_decompress(g_shaderCache.get(), - decompressedSize, - g_vulkan ? g_compressedSpirvCache : g_compressedDxilCache, - g_vulkan ? g_spirvCacheCompressedSize : g_dxilCacheCompressedSize); + if (g_vulkan) + { + g_shaderCache = std::make_unique(g_spirvCacheDecompressedSize); + ZSTD_decompress(g_shaderCache.get(), g_spirvCacheDecompressedSize, g_compressedSpirvCache, g_spirvCacheCompressedSize); + } +#ifdef SWA_D3D12 + else + { + g_shaderCache = std::make_unique(g_dxilCacheDecompressedSize); + ZSTD_decompress(g_shaderCache.get(), g_dxilCacheDecompressedSize, g_compressedDxilCache, g_dxilCacheCompressedSize); + } +#endif g_buttonBcDiff = decompressZstd(g_button_bc_diff, g_button_bc_diff_uncompressed_size); } @@ -1023,7 +1044,7 @@ static void ProcSetRenderState(const RenderCommand& cmd) } } -static const std::pair g_setRenderStateFunctions[] = +static const std::pair g_setRenderStateFunctions[] = { { D3DRS_ZENABLE, HostToGuestFunction> }, { D3DRS_ZWRITEENABLE, HostToGuestFunction> }, @@ -1062,6 +1083,8 @@ static GuestShader* g_csdShader; static std::unique_ptr g_enhancedMotionBlurShader; +#ifdef SWA_D3D12 + #define CREATE_SHADER(NAME) \ g_device->createShader( \ g_vulkan ? g_##NAME##_spirv : g_##NAME##_dxil, \ @@ -1069,11 +1092,20 @@ static std::unique_ptr g_enhancedMotionBlurShader; "main", \ g_vulkan ? RenderShaderFormat::SPIRV : RenderShaderFormat::DXIL) +#else + +#define CREATE_SHADER(NAME) \ + g_device->createShader(g_##NAME##_spirv, sizeof(g_##NAME##_spirv), "main", RenderShaderFormat::SPIRV); + +#endif + +#ifdef _WIN32 static bool DetectWine() { HMODULE dllHandle = GetModuleHandle("ntdll.dll"); return dllHandle != nullptr && GetProcAddress(dllHandle, "wine_get_version") != nullptr; } +#endif static constexpr size_t TEXTURE_DESCRIPTOR_SIZE = 65536; static constexpr size_t SAMPLER_DESCRIPTOR_SIZE = 1024; @@ -1136,7 +1168,7 @@ static void CreateImGuiBackend() OptionsMenu::Init(); InstallerWizard::Init(); - ImGui_ImplSDL2_InitForOther(Window::s_pWindow); + ImGui_ImplSDL2_InitForOther(GameWindow::s_pWindow); #ifdef ENABLE_IM_FONT_ATLAS_SNAPSHOT g_imFontTexture = LoadTexture( @@ -1278,7 +1310,7 @@ static void CreateImGuiBackend() static void BeginCommandList(); -void Video::CreateHostDevice() +void Video::CreateHostDevice(bool sdlVideoDefault) { for (uint32_t i = 0; i < 16; i++) g_inputSlots[i].index = i; @@ -1286,13 +1318,25 @@ void Video::CreateHostDevice() IMGUI_CHECKVERSION(); ImGui::CreateContext(); - Window::Init(); + GameWindow::Init(sdlVideoDefault); +#ifdef SWA_D3D12 g_vulkan = DetectWine() || Config::GraphicsAPI == EGraphicsAPI::Vulkan; +#endif LoadEmbeddedResources(); - g_interface = g_vulkan ? CreateVulkanInterface() : CreateD3D12Interface(); + if (g_vulkan) +#ifdef SDL_VULKAN_ENABLED + g_interface = CreateVulkanInterface(GameWindow::s_renderWindow); +#else + g_interface = CreateVulkanInterface(); +#endif +#ifdef SWA_D3D12 + else + g_interface = CreateD3D12Interface(); +#endif + g_device = g_interface->createDevice(); g_triangleFanSupported = g_device->getCapabilities().triangleFan; @@ -1314,7 +1358,17 @@ void Video::CreateHostDevice() switch (Config::TripleBuffering) { case ETripleBuffering::Auto: - bufferCount = g_vulkan ? 2 : 3; // Defaulting to 3 is fine on D3D12 thanks to flip discard model. + if (g_vulkan) + { + // Defaulting to 3 is fine if presentWait as supported, as the maximum frame latency allowed is only 1. + bufferCount = g_device->getCapabilities().presentWait ? 3 : 2; + } + else + { + // Defaulting to 3 is fine on D3D12 thanks to flip discard model. + bufferCount = 3; + } + break; case ETripleBuffering::On: bufferCount = 3; @@ -1324,7 +1378,7 @@ void Video::CreateHostDevice() break; } - g_swapChain = g_queue->createSwapChain(Window::s_handle, bufferCount, BACKBUFFER_FORMAT); + g_swapChain = g_queue->createSwapChain(GameWindow::s_renderWindow, bufferCount, BACKBUFFER_FORMAT); g_swapChain->setVsyncEnabled(Config::VSync); g_swapChainValid = !g_swapChain->needsResize(); @@ -1334,7 +1388,7 @@ void Video::CreateHostDevice() for (auto& renderSemaphore : g_renderSemaphores) renderSemaphore = g_device->createCommandSemaphore(); - g_mainThreadId = GetCurrentThreadId(); + g_mainThreadId = std::this_thread::get_id(); RenderPipelineLayoutBuilder pipelineLayoutBuilder; pipelineLayoutBuilder.begin(false, true); @@ -1626,9 +1680,9 @@ static uint32_t CreateDevice(uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, memset(device, 0, sizeof(*device)); uint32_t functionOffset = 0x443344; // D3D - g_codeCache.Insert(functionOffset, reinterpret_cast(HostToGuestFunction)); + g_codeCache.Insert(functionOffset, HostToGuestFunction); - for (size_t i = 0; i < _countof(device->setRenderStateFunctions); i++) + for (size_t i = 0; i < std::size(device->setRenderStateFunctions); i++) device->setRenderStateFunctions[i] = functionOffset; for (auto& [state, function] : g_setRenderStateFunctions) @@ -1638,7 +1692,7 @@ static uint32_t CreateDevice(uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, device->setRenderStateFunctions[state / 4] = functionOffset; } - for (size_t i = 0; i < _countof(device->setSamplerStateFunctions); i++) + for (size_t i = 0; i < std::size(device->setSamplerStateFunctions); i++) device->setSamplerStateFunctions[i] = *reinterpret_cast(g_memory.Translate(0x8330F3DC + i * 0xC)); device->viewport.width = 1280.0f; @@ -1763,7 +1817,7 @@ static void UnlockBuffer(GuestBuffer* buffer) { if (!buffer->lockedReadOnly) { - if (GetCurrentThreadId() == g_mainThreadId) + if (std::this_thread::get_id() == g_mainThreadId) { RenderCommand cmd; cmd.type = (sizeof(T) == 2) ? RenderCommandType::UnlockBuffer16 : RenderCommandType::UnlockBuffer32; @@ -2765,9 +2819,6 @@ static void ProcSetScissorRect(const RenderCommand& cmd) SetDirtyValue(g_dirtyStates.scissorRect, g_scissorRect.right, args.right); } -static Mutex g_compiledSpecConstantLibraryBlobMutex; -static ankerl::unordered_dense::map> g_compiledSpecConstantLibraryBlobs; - static RenderShader* GetOrLinkShader(GuestShader* guestShader, uint32_t specConstants) { if (g_vulkan || @@ -2808,8 +2859,12 @@ static RenderShader* GetOrLinkShader(GuestShader* guestShader, uint32_t specCons shader = guestShader->linkedShaders[specConstants].get(); } +#ifdef SWA_D3D12 if (shader == nullptr) { + static Mutex g_compiledSpecConstantLibraryBlobMutex; + static ankerl::unordered_dense::map> g_compiledSpecConstantLibraryBlobs; + thread_local ComPtr s_dxcCompiler; thread_local ComPtr s_dxcLinker; thread_local ComPtr s_dxcUtils; @@ -2916,6 +2971,7 @@ static RenderShader* GetOrLinkShader(GuestShader* guestShader, uint32_t specCons shader = linkedShader.get(); } } +#endif return shader; } @@ -4013,8 +4069,9 @@ static void ProcSetPixelShader(const RenderCommand& cmd) static std::thread g_renderThread([] { +#ifdef _WIN32 GuestThread::SetThreadName(GetCurrentThreadId(), "Render Thread"); - +#endif RenderCommand commands[32]; while (true) @@ -4821,40 +4878,20 @@ struct PipelineStateQueueItem static moodycamel::BlockingConcurrentQueue g_pipelineStateQueue; -struct MinimalGuestThreadContext -{ - uint8_t* stack = nullptr; - PPCContext ppcContext{}; - - ~MinimalGuestThreadContext() - { - if (stack != nullptr) - g_userHeap.Free(stack); - } - - void ensureValid() - { - if (stack == nullptr) - { - stack = reinterpret_cast(g_userHeap.Alloc(0x4000)); - ppcContext.fn = (uint8_t*)g_codeCache.bucket; - ppcContext.r1.u64 = g_memory.MapVirtual(stack + 0x4000); - SetPPCContext(ppcContext); - } - } -}; - static void PipelineCompilerThread() { +#ifdef _WIN32 GuestThread::SetThreadName(GetCurrentThreadId(), "Pipeline Compiler Thread"); - MinimalGuestThreadContext ctx; +#endif + std::unique_ptr ctx; while (true) { PipelineStateQueueItem queueItem; g_pipelineStateQueue.wait_dequeue(queueItem); - ctx.ensureValid(); + if (ctx == nullptr) + ctx = std::make_unique(0); auto pipeline = CreateGraphicsPipeline(queueItem.pipelineState); #ifdef ASYNC_PSO_DEBUG @@ -4867,6 +4904,8 @@ static void PipelineCompilerThread() cmd.addPipeline.hash = queueItem.pipelineHash; cmd.addPipeline.pipeline = pipeline.release(); g_renderQueue.enqueue(cmd); + + std::this_thread::yield(); } } @@ -5496,10 +5535,11 @@ static bool CheckMadeAll(const T& modelData) static void ModelConsumerThread() { +#ifdef _WIN32 GuestThread::SetThreadName(GetCurrentThreadId(), "Model Consumer Thread"); - +#endif std::vector> localPendingDataQueue; - MinimalGuestThreadContext ctx; + std::unique_ptr ctx; while (true) { @@ -5508,7 +5548,8 @@ static void ModelConsumerThread() while ((pendingDataCount = g_pendingDataCount.load()) == 0) g_pendingDataCount.wait(pendingDataCount); - ctx.ensureValid(); + if (ctx == nullptr) + ctx = std::make_unique(0); if (g_pendingPipelineStateCache) { @@ -5673,6 +5714,8 @@ static void ModelConsumerThread() if (allHandled) localPendingDataQueue.clear(); + + std::this_thread::yield(); } } diff --git a/UnleashedRecomp/gpu/video.h b/UnleashedRecomp/gpu/video.h index a42b61d0..9270cf44 100644 --- a/UnleashedRecomp/gpu/video.h +++ b/UnleashedRecomp/gpu/video.h @@ -14,7 +14,7 @@ using namespace plume; struct Video { - static void CreateHostDevice(); + static void CreateHostDevice(bool sdlVideoDefault); static void HostPresent(); static void StartPipelinePrecompilation(); static void WaitForGPU(); @@ -84,22 +84,26 @@ struct GuestResource void AddRef() { + std::atomic_ref atomicRef(refCount.value); + uint32_t originalValue, incrementedValue; do { originalValue = refCount.value; incrementedValue = ByteSwap(ByteSwap(originalValue) + 1); - } while (InterlockedCompareExchange(reinterpret_cast(&refCount), incrementedValue, originalValue) != originalValue); + } while (!atomicRef.compare_exchange_weak(originalValue, incrementedValue)); } void Release() { + std::atomic_ref atomicRef(refCount.value); + uint32_t originalValue, decrementedValue; do { originalValue = refCount.value; decrementedValue = ByteSwap(ByteSwap(originalValue) - 1); - } while (InterlockedCompareExchange(reinterpret_cast(&refCount), decrementedValue, originalValue) != originalValue); + } while (!atomicRef.compare_exchange_weak(originalValue, decrementedValue)); // Normally we are supposed to release here, so only use this // function when you know you won't be the one destructing it. @@ -274,8 +278,10 @@ struct GuestShader : GuestResource std::unique_ptr shader; struct ShaderCacheEntry* shaderCacheEntry = nullptr; ankerl::unordered_dense::map> linkedShaders; +#ifdef SWA_D3D12 std::vector> shaderBlobs; ComPtr libraryBlob; +#endif #ifdef ASYNC_PSO_DEBUG const char* name = ""; #endif @@ -390,7 +396,7 @@ enum GuestTextureAddress D3DTADDRESS_BORDER = 6 }; -extern bool g_needsResize; +inline bool g_needsResize; extern std::unique_ptr LoadTexture(const uint8_t* data, size_t dataSize, RenderComponentMapping componentMapping = RenderComponentMapping()); diff --git a/UnleashedRecomp/hid/driver/sdl_hid.cpp b/UnleashedRecomp/hid/driver/sdl_hid.cpp index f7c142de..ac140824 100644 --- a/UnleashedRecomp/hid/driver/sdl_hid.cpp +++ b/UnleashedRecomp/hid/driver/sdl_hid.cpp @@ -2,7 +2,8 @@ #include #include #include -#include +#include +#include #define TRANSLATE_INPUT(S, X) SDL_GameControllerGetButton(controller, S) << FirstBitLow(X) #define VIBRATION_TIMEOUT_MS 5000 @@ -65,7 +66,7 @@ public: bool CanPoll() { - return controller && (Window::s_isFocused || Config::AllowBackgroundInput); + return controller && (GameWindow::s_isFocused || Config::AllowBackgroundInput); } void PollAxis() @@ -128,7 +129,7 @@ public: std::array g_controllers; Controller* g_activeController; -inline Controller* EnsureController(DWORD dwUserIndex) +inline Controller* EnsureController(uint32_t dwUserIndex) { if (!g_controllers[dwUserIndex].controller) return nullptr; @@ -260,7 +261,7 @@ void hid::detail::Init() uint32_t hid::detail::GetState(uint32_t dwUserIndex, XAMINPUT_STATE* pState) { - static DWORD packet; + static uint32_t packet; if (!pState) return ERROR_BAD_ARGUMENTS; diff --git a/UnleashedRecomp/install/installer.cpp b/UnleashedRecomp/install/installer.cpp index 433adcc4..fd561ed8 100644 --- a/UnleashedRecomp/install/installer.cpp +++ b/UnleashedRecomp/install/installer.cpp @@ -52,7 +52,7 @@ static std::unique_ptr createFileSystemFromPath(const std::fi { return XContentFileSystem::create(path); } - else if (toLower(path.extension().string()) == ISOExtension) + else if (toLower(fromPath(path.extension())) == ISOExtension) { return ISOFileSystem::create(path); } @@ -122,7 +122,7 @@ static bool copyFile(const FilePair &pair, const uint64_t *fileHashes, VirtualFi if (!outStream.is_open()) { journal.lastResult = Journal::Result::FileCreationFailed; - journal.lastErrorMessage = fmt::format("Failed to create file at {}.", targetPath.string()); + journal.lastErrorMessage = fmt::format("Failed to create file at {}.", fromPath(targetPath)); return false; } @@ -132,7 +132,7 @@ static bool copyFile(const FilePair &pair, const uint64_t *fileHashes, VirtualFi if (outStream.bad()) { journal.lastResult = Journal::Result::FileWriteFailed; - journal.lastErrorMessage = fmt::format("Failed to create file at {}.", targetPath.string()); + journal.lastErrorMessage = fmt::format("Failed to create file at {}.", fromPath(targetPath)); return false; } @@ -162,7 +162,7 @@ static DLC detectDLC(const std::filesystem::path &sourcePath, VirtualFileSystem if (typeStartLocation == nullptr || typeEndLocation == nullptr) { journal.lastResult = Journal::Result::DLCParsingFailed; - journal.lastErrorMessage = "Failed to find DLC type for " + sourcePath.string() + "."; + journal.lastErrorMessage = "Failed to find DLC type for " + fromPath(sourcePath) + "."; return DLC::Unknown; } @@ -171,7 +171,7 @@ static DLC detectDLC(const std::filesystem::path &sourcePath, VirtualFileSystem if (typeNumberCount != 1) { journal.lastResult = Journal::Result::UnknownDLCType; - journal.lastErrorMessage = "DLC type for " + sourcePath.string() + " is unknown."; + journal.lastErrorMessage = "DLC type for " + fromPath(sourcePath) + " is unknown."; return DLC::Unknown; } @@ -191,7 +191,7 @@ static DLC detectDLC(const std::filesystem::path &sourcePath, VirtualFileSystem return DLC::EmpireCityAdabat; default: journal.lastResult = Journal::Result::UnknownDLCType; - journal.lastErrorMessage = "DLC type for " + sourcePath.string() + " is unknown."; + journal.lastErrorMessage = "DLC type for " + fromPath(sourcePath) + " is unknown."; return DLC::Unknown; } } diff --git a/UnleashedRecomp/install/memory_mapped_file.cpp b/UnleashedRecomp/install/memory_mapped_file.cpp index b96ea543..ba3c5d8a 100644 --- a/UnleashedRecomp/install/memory_mapped_file.cpp +++ b/UnleashedRecomp/install/memory_mapped_file.cpp @@ -97,7 +97,7 @@ bool MemoryMappedFile::open(const std::filesystem::path &path) if (fileSize == (off_t)(-1)) { fprintf(stderr, "lseek failed with error %s.\n", strerror(errno)); - close(fileHandle); + ::close(fileHandle); fileHandle = -1; return false; } @@ -106,7 +106,7 @@ bool MemoryMappedFile::open(const std::filesystem::path &path) if (fileView == MAP_FAILED) { fprintf(stderr, "mmap failed with error %s.\n", strerror(errno)); - close(fileHandle); + ::close(fileHandle); fileHandle = -1; return false; } @@ -140,7 +140,7 @@ void MemoryMappedFile::close() if (fileHandle != -1) { - close(fileHandle); + ::close(fileHandle); } #endif } diff --git a/UnleashedRecomp/install/xex_patcher.cpp b/UnleashedRecomp/install/xex_patcher.cpp index 8d2ea1b6..4e35c1cb 100644 --- a/UnleashedRecomp/install/xex_patcher.cpp +++ b/UnleashedRecomp/install/xex_patcher.cpp @@ -308,14 +308,14 @@ inline bool bitScanForward(uint64_t v, uint32_t *outFirstSetIndex) inline bool bitScanForward(uint32_t v, uint32_t *outFirstSetIndex) { int i = ffs(v); - *out_first_set_index = i - 1; + *outFirstSetIndex = i - 1; return i != 0; } inline bool bitScanForward(uint64_t v, uint32_t *outFirstSetIndex) { int i = __builtin_ffsll(v); - *out_first_set_index = i - 1; + *outFirstSetIndex = i - 1; return i != 0; } #endif diff --git a/UnleashedRecomp/kernel/function.h b/UnleashedRecomp/kernel/function.h index 14e88ea7..ecd246bc 100644 --- a/UnleashedRecomp/kernel/function.h +++ b/UnleashedRecomp/kernel/function.h @@ -39,7 +39,7 @@ std::enable_if_t<(I < sizeof...(TArgs)), void> _tuple_for(std::tuple& struct ArgTranslator { - FORCEINLINE constexpr static uint64_t GetIntegerArgumentValue(const PPCContext& ctx, uint8_t* base, size_t arg) noexcept + constexpr static uint64_t GetIntegerArgumentValue(const PPCContext& ctx, uint8_t* base, size_t arg) noexcept { if (arg <= 7) { @@ -57,10 +57,10 @@ struct ArgTranslator } } - return *reinterpret_cast(base + ctx.r1.u32 + 0x54 + ((arg - 8) * 8)); + return *reinterpret_cast*>(base + ctx.r1.u32 + 0x54 + ((arg - 8) * 8)); } - FORCEINLINE static double GetPrecisionArgumentValue(const PPCContext& ctx, uint8_t* base, size_t arg) noexcept + static double GetPrecisionArgumentValue(const PPCContext& ctx, uint8_t* base, size_t arg) noexcept { switch (arg) { @@ -84,7 +84,7 @@ struct ArgTranslator return 0; } - FORCEINLINE constexpr static void SetIntegerArgumentValue(PPCContext& ctx, uint8_t* base, size_t arg, uint64_t value) noexcept + constexpr static void SetIntegerArgumentValue(PPCContext& ctx, uint8_t* base, size_t arg, uint64_t value) noexcept { if (arg <= 7) { @@ -105,7 +105,7 @@ struct ArgTranslator assert(arg < 7 && "Pushing to stack memory is not yet supported."); } - FORCEINLINE static void SetPrecisionArgumentValue(PPCContext& ctx, uint8_t* base, size_t arg, double value) noexcept + static void SetPrecisionArgumentValue(PPCContext& ctx, uint8_t* base, size_t arg, double value) noexcept { switch (arg) { @@ -129,7 +129,7 @@ struct ArgTranslator } template - FORCEINLINE constexpr static std::enable_if_t, T> GetValue(PPCContext& ctx, uint8_t* base, size_t idx) noexcept + constexpr static std::enable_if_t, T> GetValue(PPCContext& ctx, uint8_t* base, size_t idx) noexcept { if constexpr (is_precise_v) { @@ -142,7 +142,7 @@ struct ArgTranslator } template - FORCEINLINE constexpr static std::enable_if_t, T> GetValue(PPCContext& ctx, uint8_t* base, size_t idx) noexcept + constexpr static std::enable_if_t, T> GetValue(PPCContext& ctx, uint8_t* base, size_t idx) noexcept { const auto v = GetIntegerArgumentValue(ctx, base, idx); if (!v) @@ -154,7 +154,7 @@ struct ArgTranslator } template - FORCEINLINE constexpr static std::enable_if_t, void> SetValue(PPCContext& ctx, uint8_t* base, size_t idx, T value) noexcept + constexpr static std::enable_if_t, void> SetValue(PPCContext& ctx, uint8_t* base, size_t idx, T value) noexcept { if constexpr (is_precise_v) { @@ -175,7 +175,7 @@ struct ArgTranslator } template - FORCEINLINE constexpr static std::enable_if_t, void> SetValue(PPCContext& ctx, uint8_t* base, size_t idx, T value) noexcept + constexpr static std::enable_if_t, void> SetValue(PPCContext& ctx, uint8_t* base, size_t idx, T value) noexcept { const auto v = g_memory.MapVirtual((void*)value); if (!v) @@ -240,13 +240,13 @@ struct arg_ordinal_t }; template -FORCEINLINE void _translate_args_to_host(PPCContext& ctx, uint8_t* base, std::tuple&) noexcept +void _translate_args_to_host(PPCContext& ctx, uint8_t* base, std::tuple&) noexcept requires (I >= sizeof...(TArgs)) { } template -FORCEINLINE std::enable_if_t<(I < sizeof...(TArgs)), void> _translate_args_to_host(PPCContext& ctx, uint8_t* base, std::tuple& tpl) noexcept +std::enable_if_t<(I < sizeof...(TArgs)), void> _translate_args_to_host(PPCContext& ctx, uint8_t* base, std::tuple& tpl) noexcept { using T = std::tuple_element_t>; std::get(tpl) = ArgTranslator::GetValue(ctx, base, arg_ordinal_t::value); @@ -255,13 +255,13 @@ FORCEINLINE std::enable_if_t<(I < sizeof...(TArgs)), void> _translate_args_to_ho } template -FORCEINLINE void _translate_args_to_guest(PPCContext& ctx, uint8_t* base, std::tuple&) noexcept +void _translate_args_to_guest(PPCContext& ctx, uint8_t* base, std::tuple&) noexcept requires (I >= sizeof...(TArgs)) { } template -FORCEINLINE std::enable_if_t<(I < sizeof...(TArgs)), void> _translate_args_to_guest(PPCContext& ctx, uint8_t* base, std::tuple& tpl) noexcept +std::enable_if_t<(I < sizeof...(TArgs)), void> _translate_args_to_guest(PPCContext& ctx, uint8_t* base, std::tuple& tpl) noexcept { using T = std::tuple_element_t>; ArgTranslator::SetValue(ctx, base, GatherFunctionArguments(std::tuple{})[I].ordinal, std::get(tpl)); @@ -270,7 +270,7 @@ FORCEINLINE std::enable_if_t<(I < sizeof...(TArgs)), void> _translate_args_to_gu } template -FORCEINLINE PPC_FUNC(HostToGuestFunction) +PPC_FUNC(HostToGuestFunction) { using ret_t = decltype(std::apply(Func, function_args(Func))); @@ -293,7 +293,7 @@ FORCEINLINE PPC_FUNC(HostToGuestFunction) } else { - ctx.r3.u64 = NULL; + ctx.r3.u64 = 0; } } else if constexpr (is_precise_v) @@ -308,7 +308,7 @@ FORCEINLINE PPC_FUNC(HostToGuestFunction) } template -FORCEINLINE T GuestToHostFunction(const TFunction& func, TArgs&&... argv) +T GuestToHostFunction(const TFunction& func, TArgs&&... argv) { auto args = std::make_tuple(std::forward(argv)...); auto& currentCtx = *GetPPCContext(); @@ -331,11 +331,7 @@ FORCEINLINE T GuestToHostFunction(const TFunction& func, TArgs&&... argv) currentCtx.fpscr = newCtx.fpscr; SetPPCContext(currentCtx); - if constexpr (std::is_void_v) - { - return; - } - else if constexpr (std::is_pointer_v) + if constexpr (std::is_pointer_v) { return reinterpret_cast((uint64_t)g_memory.Translate(newCtx.r3.u32)); } @@ -349,7 +345,7 @@ FORCEINLINE T GuestToHostFunction(const TFunction& func, TArgs&&... argv) } else { - static_assert(false, "Unsupported return type."); + static_assert(std::is_void_v, "Unsupported return type."); } } diff --git a/UnleashedRecomp/kernel/heap.cpp b/UnleashedRecomp/kernel/heap.cpp index efa59a61..56718f25 100644 --- a/UnleashedRecomp/kernel/heap.cpp +++ b/UnleashedRecomp/kernel/heap.cpp @@ -103,7 +103,7 @@ uint32_t RtlSizeHeap(uint32_t heapHandle, uint32_t flags, uint32_t memoryPointer return 0; } -SWA_API uint32_t XAlloc(uint32_t size, uint32_t flags) +SWA_API uint32_t XAllocMem(uint32_t size, uint32_t flags) { void* ptr = (flags & 0x80000000) != 0 ? g_userHeap.AllocPhysical(size, (1ull << ((flags >> 24) & 0xF))) : @@ -116,7 +116,7 @@ SWA_API uint32_t XAlloc(uint32_t size, uint32_t flags) return g_memory.MapVirtual(ptr); } -SWA_API void XFree(uint32_t baseAddress, uint32_t flags) +SWA_API void XFreeMem(uint32_t baseAddress, uint32_t flags) { if (baseAddress != NULL) g_userHeap.Free(g_memory.Translate(baseAddress)); @@ -130,6 +130,5 @@ GUEST_FUNCTION_HOOK(sub_82BD8600, RtlFreeHeap); GUEST_FUNCTION_HOOK(sub_82BD88F0, RtlReAllocateHeap); GUEST_FUNCTION_HOOK(sub_82BD6FD0, RtlSizeHeap); -// Seems like these handle allocation of virtual and physical pages -GUEST_FUNCTION_HOOK(sub_831CC9C8, XAlloc); -GUEST_FUNCTION_HOOK(sub_831CCA60, XFree); +GUEST_FUNCTION_HOOK(sub_831CC9C8, XAllocMem); +GUEST_FUNCTION_HOOK(sub_831CCA60, XFreeMem); diff --git a/UnleashedRecomp/kernel/heap.h b/UnleashedRecomp/kernel/heap.h index 4e583be5..1710c298 100644 --- a/UnleashedRecomp/kernel/heap.h +++ b/UnleashedRecomp/kernel/heap.h @@ -19,7 +19,7 @@ struct Heap size_t Size(void* ptr); template - T* Alloc(Args... args) + T* Alloc(Args&&... args) { T* obj = (T*)Alloc(sizeof(T)); new (obj) T(std::forward(args)...); @@ -27,7 +27,7 @@ struct Heap } template - T* AllocPhysical(Args... args) + T* AllocPhysical(Args&&... args) { T* obj = (T*)AllocPhysical(sizeof(T), alignof(T)); new (obj) T(std::forward(args)...); diff --git a/UnleashedRecomp/kernel/imports.cpp b/UnleashedRecomp/kernel/imports.cpp index 542f0971..dfa16642 100644 --- a/UnleashedRecomp/kernel/imports.cpp +++ b/UnleashedRecomp/kernel/imports.cpp @@ -10,11 +10,129 @@ #include #include "xam.h" #include "xdm.h" -#include #include #include +#ifdef _WIN32 #include +#endif + +struct Event final : KernelObject, HostObject +{ + bool manualReset; + std::atomic signaled; + + Event(XKEVENT* header) + : manualReset(!header->Type), signaled(!!header->SignalState) + { + } + + Event(bool manualReset, bool initialState) + : manualReset(manualReset), signaled(initialState) + { + } + + uint32_t Wait(uint32_t timeout) override + { + if (timeout == 0) + { + if (manualReset) + { + if (!signaled) + return STATUS_TIMEOUT; + } + else + { + bool expected = true; + if (!signaled.compare_exchange_strong(expected, false)) + return STATUS_TIMEOUT; + } + } + else if (timeout == INFINITE) + { + if (manualReset) + { + signaled.wait(false); + } + else + { + while (true) + { + bool expected = true; + if (signaled.compare_exchange_weak(expected, false)) + break; + + signaled.wait(expected); + } + } + } + else + { + assert(false && "Unhandled timeout value."); + } + + return STATUS_SUCCESS; + } + + bool Set() + { + signaled = true; + + if (manualReset) + signaled.notify_all(); + else + signaled.notify_one(); + + return TRUE; + } + + bool Reset() + { + signaled = false; + return TRUE; + } +}; + +static std::atomic g_keSetEventGeneration; + +struct Semaphore final : KernelObject, HostObject +{ + std::counting_semaphore<> semaphore; + uint32_t maximumCount; + + Semaphore(XKSEMAPHORE* semaphore) + : semaphore(semaphore->Header.SignalState), maximumCount(semaphore->Limit) + { + } + + Semaphore(uint32_t count, uint32_t maximumCount) + : semaphore(count), maximumCount(maximumCount) + { + } + + uint32_t Wait(uint32_t timeout) override + { + if (timeout == 0) + { + return semaphore.try_acquire() ? STATUS_SUCCESS : STATUS_TIMEOUT; + } + else if (timeout == INFINITE) + { + semaphore.acquire(); + return STATUS_SUCCESS; + } + else + { + assert(false && "Unhandled timeout value."); + return STATUS_TIMEOUT; + } + } + + void Release(uint32_t releaseCount, uint32_t* previousCount) + { + semaphore.release(releaseCount); + } +}; inline void CloseKernelObject(XDISPATCHER_HEADER& header) { @@ -23,10 +141,10 @@ inline void CloseKernelObject(XDISPATCHER_HEADER& header) return; } - ObCloseHandle(header.WaitListHead.Blink); + DestroyKernelObject(header.WaitListHead.Blink); } -DWORD GuestTimeoutToMilliseconds(XLPQWORD timeout) +uint32_t GuestTimeoutToMilliseconds(be* timeout) { return timeout ? (*timeout * -1) / 10000 : INFINITE; } @@ -84,7 +202,7 @@ uint32_t XGetGameRegion() return 0x03FF; } -uint32_t XMsgStartIORequest(DWORD App, DWORD Message, XXOVERLAPPED* lpOverlapped, void* Buffer, DWORD szBuffer) +uint32_t XMsgStartIORequest(uint32_t App, uint32_t Message, XXOVERLAPPED* lpOverlapped, void* Buffer, uint32_t szBuffer) { return STATUS_SUCCESS; } @@ -104,8 +222,7 @@ void XamContentDelete() LOG_UTILITY("!!! STUB !!!"); } - -uint32_t XamContentGetCreator(DWORD userIndex, const XCONTENT_DATA* contentData, PBOOL isCreator, XLPQWORD xuid, XXOVERLAPPED* overlapped) +uint32_t XamContentGetCreator(uint32_t userIndex, const XCONTENT_DATA* contentData, be* isCreator, be* xuid, XXOVERLAPPED* overlapped) { if (isCreator) *isCreator = true; @@ -146,7 +263,7 @@ uint32_t XamShowDeviceSelectorUI uint32_t contentType, uint32_t contentFlags, uint64_t totalRequested, - XDWORD* deviceId, + be* deviceId, XXOVERLAPPED* overlapped ) { @@ -214,10 +331,10 @@ void RtlInitAnsiString(XANSI_STRING* destination, char* source) destination->Buffer = source; } -DWORD NtCreateFile +uint32_t NtCreateFile ( - XLPDWORD FileHandle, - DWORD DesiredAccess, + be* FileHandle, + uint32_t DesiredAccess, XOBJECT_ATTRIBUTES* Attributes, XIO_STATUS_BLOCK* IoStatusBlock, uint64_t* AllocationSize, @@ -232,16 +349,19 @@ DWORD NtCreateFile uint32_t NtClose(uint32_t handle) { - if (handle == (uint32_t)INVALID_HANDLE_VALUE) + if (handle == GUEST_INVALID_HANDLE_VALUE) return 0xFFFFFFFF; - if (CHECK_GUEST_HANDLE(handle)) + if (IsKernelObject(handle)) { - ObCloseHandle(HOST_HANDLE(handle)); + DestroyKernelObject(handle); return 0; } - - return CloseHandle((HANDLE)handle) ? 0 : 0xFFFFFFFF; + else + { + assert(false && "Unrecognized kernel object."); + return 0xFFFFFFFF; + } } void NtSetInformationFile() @@ -254,20 +374,21 @@ uint32_t FscSetCacheElementCount() return 0; } -DWORD NtWaitForSingleObjectEx(DWORD Handle, DWORD WaitMode, DWORD Alertable, XLPQWORD Timeout) +uint32_t NtWaitForSingleObjectEx(uint32_t Handle, uint32_t WaitMode, uint32_t Alertable, be* Timeout) { - const auto status = WaitForSingleObjectEx((HANDLE)Handle, GuestTimeoutToMilliseconds(Timeout), Alertable); + uint32_t timeout = GuestTimeoutToMilliseconds(Timeout); + assert(timeout == 0 || timeout == INFINITE); - if (status == WAIT_IO_COMPLETION) + if (IsKernelObject(Handle)) { - return STATUS_USER_APC; + return GetKernelObject(Handle)->Wait(timeout); } - else if (status) + else { - return STATUS_ALERTED; + assert(false && "Unrecognized handle value."); } - return STATUS_SUCCESS; + return STATUS_TIMEOUT; } void NtWriteFile() @@ -280,7 +401,7 @@ void vsprintf_x() LOG_UTILITY("!!! STUB !!!"); } -uint32_t ExGetXConfigSetting(uint16_t Category, uint16_t Setting, void* Buffer, uint16_t SizeOfBuffer, XLPDWORD RequiredSize) +uint32_t ExGetXConfigSetting(uint16_t Category, uint16_t Setting, void* Buffer, uint16_t SizeOfBuffer, be* RequiredSize) { uint32_t data[4]{}; @@ -357,9 +478,9 @@ void MmQueryStatistics() LOG_UTILITY("!!! STUB !!!"); } -uint32_t NtCreateEvent(uint32_t* handle, void* objAttributes, uint32_t eventType, uint32_t initialState) +uint32_t NtCreateEvent(be* handle, void* objAttributes, uint32_t eventType, uint32_t initialState) { - *handle = ByteSwap((uint32_t)CreateEventA(nullptr, !eventType, !!initialState, nullptr)); + *handle = GetKernelHandle(CreateKernelObject(!eventType, !!initialState)); return 0; } @@ -393,9 +514,9 @@ void XexGetModuleSection() LOG_UTILITY("!!! STUB !!!"); } -NTSTATUS RtlUnicodeToMultiByteN(PCHAR MultiByteString, DWORD MaxBytesInMultiByteString, XLPDWORD BytesInMultiByteString, PCWCH UnicodeString, ULONG BytesInUnicodeString) +uint32_t RtlUnicodeToMultiByteN(char* MultiByteString, uint32_t MaxBytesInMultiByteString, be* BytesInMultiByteString, const be* UnicodeString, uint32_t BytesInUnicodeString) { - const auto reqSize = BytesInUnicodeString / sizeof(wchar_t); + const auto reqSize = BytesInUnicodeString / sizeof(uint16_t); if (BytesInMultiByteString) *BytesInMultiByteString = reqSize; @@ -405,7 +526,7 @@ NTSTATUS RtlUnicodeToMultiByteN(PCHAR MultiByteString, DWORD MaxBytesInMultiByte for (size_t i = 0; i < reqSize; i++) { - const auto c = ByteSwap(UnicodeString[i]); + const auto c = UnicodeString[i].get(); MultiByteString[i] = c < 256 ? c : '?'; } @@ -413,24 +534,22 @@ NTSTATUS RtlUnicodeToMultiByteN(PCHAR MultiByteString, DWORD MaxBytesInMultiByte return STATUS_SUCCESS; } -DWORD KeDelayExecutionThread(DWORD WaitMode, bool Alertable, XLPQWORD Timeout) +uint32_t KeDelayExecutionThread(uint32_t WaitMode, bool Alertable, be* Timeout) { // We don't do async file reads. if (Alertable) return STATUS_USER_APC; - timeBeginPeriod(1); - const auto status = SleepEx(GuestTimeoutToMilliseconds(Timeout), Alertable); - timeEndPeriod(1); + uint32_t timeout = GuestTimeoutToMilliseconds(Timeout); - if (status == WAIT_IO_COMPLETION) - { - return STATUS_USER_APC; - } - else if (status) - { - return STATUS_ALERTED; - } +#ifdef _WIN32 + Sleep(timeout); +#else + if (timeout == 0) + std::this_thread::yield(); + else + std::this_thread::sleep_for(std::chrono::milliseconds(timeout)); +#endif return STATUS_SUCCESS; } @@ -485,8 +604,9 @@ void ObDereferenceObject() LOG_UTILITY("!!! STUB !!!"); } -void KeSetBasePriorityThread(uint32_t thread, int priority) +void KeSetBasePriorityThread(GuestThreadHandle* hThread, int priority) { +#ifdef _WIN32 if (priority == 16) { priority = 15; @@ -496,10 +616,11 @@ void KeSetBasePriorityThread(uint32_t thread, int priority) priority = -15; } - SetThreadPriority((HANDLE)thread, priority); + SetThreadPriority(hThread == GetKernelObject(CURRENT_THREAD_HANDLE) ? GetCurrentThread() : hThread->thread.native_handle(), priority); +#endif } -uint32_t ObReferenceObjectByHandle(uint32_t handle, uint32_t objectType, XLPDWORD object) +uint32_t ObReferenceObjectByHandle(uint32_t handle, uint32_t objectType, be* object) { *object = handle; return 0; @@ -510,20 +631,17 @@ void KeQueryBasePriorityThread() LOG_UTILITY("!!! STUB !!!"); } -uint32_t NtSuspendThread(uint32_t hThread, uint32_t* suspendCount) +uint32_t NtSuspendThread(GuestThreadHandle* hThread, uint32_t* suspendCount) { - DWORD count = SuspendThread((HANDLE)hThread); + assert(hThread != GetKernelObject(CURRENT_THREAD_HANDLE) && hThread->thread.get_id() == std::this_thread::get_id()); - if (count == (DWORD)-1) - return E_FAIL; - - if (suspendCount != nullptr) - *suspendCount = ByteSwap(count); + hThread->suspended = true; + hThread->suspended.wait(true); return S_OK; } -uint32_t KeSetAffinityThread(DWORD Thread, DWORD Affinity, XLPDWORD lpPreviousAffinity) +uint32_t KeSetAffinityThread(uint32_t Thread, uint32_t Affinity, be* lpPreviousAffinity) { if (lpPreviousAffinity) *lpPreviousAffinity = 2; @@ -531,38 +649,6 @@ uint32_t KeSetAffinityThread(DWORD Thread, DWORD Affinity, XLPDWORD lpPreviousAf return 0; } -struct Event : HostObject -{ - HANDLE handle; - - Event(XKEVENT* header) - { - handle = CreateEventA(nullptr, !header->Type, !!header->SignalState, nullptr); - } - - bool Set() - { - return SetEvent(handle); - } - - bool Reset() - { - return ResetEvent(handle); - } -}; - -struct Semaphore : HostObject -{ - HANDLE handle; - - Semaphore(XKSEMAPHORE* semaphore) - { - handle = CreateSemaphoreA(nullptr, semaphore->Header.SignalState, semaphore->Limit, nullptr); - } -}; - -// https://devblogs.microsoft.com/oldnewthing/20160825-00/?p=94165 - void RtlLeaveCriticalSection(XRTL_CRITICAL_SECTION* cs) { cs->RecursionCount--; @@ -570,25 +656,29 @@ void RtlLeaveCriticalSection(XRTL_CRITICAL_SECTION* cs) if (cs->RecursionCount != 0) return; - InterlockedExchange(&cs->OwningThread, 0); - WakeByAddressSingle(&cs->OwningThread); + std::atomic_ref owningThread(cs->OwningThread); + owningThread.store(0); + owningThread.notify_one(); } void RtlEnterCriticalSection(XRTL_CRITICAL_SECTION* cs) { - DWORD thisThread = GetCurrentThreadId(); + uint32_t thisThread = g_ppcContext->r13.u32; + assert(thisThread != NULL); + + std::atomic_ref owningThread(cs->OwningThread); while (true) { - DWORD previousOwner = InterlockedCompareExchangeAcquire(&cs->OwningThread, thisThread, 0); + uint32_t previousOwner = 0; - if (previousOwner == 0 || previousOwner == thisThread) + if (owningThread.compare_exchange_weak(previousOwner, thisThread) || previousOwner == thisThread) { cs->RecursionCount++; return; } - WaitOnAddress(&cs->OwningThread, &previousOwner, sizeof(previousOwner), INFINITE); + owningThread.wait(previousOwner); } } @@ -609,7 +699,7 @@ void RtlFillMemoryUlong() void KeBugCheckEx() { - __debugbreak(); + __builtin_debugtrap(); } uint32_t KeGetCurrentProcessType() @@ -639,15 +729,22 @@ void RtlRaiseException_x() void KfReleaseSpinLock(uint32_t* spinLock) { - InterlockedExchange((volatile long*)spinLock, 0); + std::atomic_ref spinLockRef(*spinLock); + spinLockRef = 0; } void KfAcquireSpinLock(uint32_t* spinLock) { - const auto ctx = GetPPCContext(); + std::atomic_ref spinLockRef(*spinLock); - while (InterlockedCompareExchange((volatile long*)spinLock, ByteSwap(*(uint32_t*)(g_memory.Translate(ctx->r13.u32 + 0x110))), 0) != 0) - Sleep(0); + while (true) + { + uint32_t expected = 0; + if (spinLockRef.compare_exchange_weak(expected, g_ppcContext->r13.u32)) + break; + + std::this_thread::yield(); + } } uint64_t KeQueryPerformanceFrequency() @@ -679,15 +776,22 @@ void VdGetSystemCommandBuffer() void KeReleaseSpinLockFromRaisedIrql(uint32_t* spinLock) { - InterlockedExchange((volatile long*)spinLock, 0); + std::atomic_ref spinLockRef(*spinLock); + spinLockRef = 0; } void KeAcquireSpinLockAtRaisedIrql(uint32_t* spinLock) { - const auto ctx = GetPPCContext(); + std::atomic_ref spinLockRef(*spinLock); - while (InterlockedCompareExchange((volatile long*)spinLock, ByteSwap(*(uint32_t*)(g_memory.Translate(ctx->r13.u32 + 0x110))), 0) != 0) - Sleep(0); + while (true) + { + uint32_t expected = 0; + if (spinLockRef.compare_exchange_weak(expected, g_ppcContext->r13.u32)) + break; + + std::this_thread::yield(); + } } uint32_t KiApcNormalRoutineNop() @@ -851,7 +955,7 @@ void VdEnableDisableClockGating() void KeBugCheck() { - __debugbreak(); + __builtin_debugtrap(); } void KeLockL2() @@ -864,62 +968,95 @@ void KeUnlockL2() LOG_UTILITY("!!! STUB !!!"); } -bool KeSetEvent(XKEVENT* pEvent, DWORD Increment, bool Wait) +bool KeSetEvent(XKEVENT* pEvent, uint32_t Increment, bool Wait) { - return ObQueryObject(*pEvent)->Set(); + bool result = QueryKernelObject(*pEvent)->Set(); + + ++g_keSetEventGeneration; + g_keSetEventGeneration.notify_all(); + + return result; } bool KeResetEvent(XKEVENT* pEvent) { - return ObQueryObject(*pEvent)->Reset(); + return QueryKernelObject(*pEvent)->Reset(); } -DWORD KeWaitForSingleObject(XDISPATCHER_HEADER* Object, DWORD WaitReason, DWORD WaitMode, bool Alertable, XLPQWORD Timeout) +uint32_t KeWaitForSingleObject(XDISPATCHER_HEADER* Object, uint32_t WaitReason, uint32_t WaitMode, bool Alertable, be* Timeout) { - const uint64_t timeout = GuestTimeoutToMilliseconds(Timeout); - - HANDLE handle = nullptr; + const uint32_t timeout = GuestTimeoutToMilliseconds(Timeout); + assert(timeout == INFINITE); switch (Object->Type) { case 0: case 1: - handle = ObQueryObject(*Object)->handle; + QueryKernelObject(*Object)->Wait(timeout); break; case 5: - handle = ObQueryObject(*Object)->handle; + QueryKernelObject(*Object)->Wait(timeout); break; default: - assert(false); - break; + assert(false && "Unrecognized kernel object type."); + return STATUS_TIMEOUT; } - return WaitForSingleObjectEx(handle, timeout, Alertable); + return STATUS_SUCCESS; } -uint32_t KeTlsGetValue(DWORD dwTlsIndex) +static std::vector g_tlsFreeIndices; +static size_t g_tlsNextIndex = 0; +static Mutex g_tlsAllocationMutex; + +static uint32_t& KeTlsGetValueRef(size_t index) { - return (uint32_t)TlsGetValue(dwTlsIndex); + // Having this a global thread_local variable + // for some reason crashes on boot in debug builds. + thread_local std::vector s_tlsValues; + + if (s_tlsValues.size() <= index) + { + s_tlsValues.resize(index + 1, 0); + } + + return s_tlsValues[index]; } -BOOL KeTlsSetValue(DWORD dwTlsIndex, DWORD lpTlsValue) +uint32_t KeTlsGetValue(uint32_t dwTlsIndex) { - return TlsSetValue(dwTlsIndex, (LPVOID)lpTlsValue); + return KeTlsGetValueRef(dwTlsIndex); } -DWORD KeTlsAlloc() +uint32_t KeTlsSetValue(uint32_t dwTlsIndex, uint32_t lpTlsValue) { - return TlsAlloc(); + KeTlsGetValueRef(dwTlsIndex) = lpTlsValue; + return TRUE; } -BOOL KeTlsFree(DWORD dwTlsIndex) +uint32_t KeTlsAlloc() { - return TlsFree(dwTlsIndex); + std::lock_guard lock(g_tlsAllocationMutex); + if (!g_tlsFreeIndices.empty()) + { + size_t index = g_tlsFreeIndices.back(); + g_tlsFreeIndices.pop_back(); + return index; + } + + return g_tlsNextIndex++; } -DWORD XMsgInProcessCall(uint32_t app, uint32_t message, XDWORD* param1, XDWORD* param2) +uint32_t KeTlsFree(uint32_t dwTlsIndex) +{ + std::lock_guard lock(g_tlsAllocationMutex); + g_tlsFreeIndices.push_back(dwTlsIndex); + return TRUE; +} + +uint32_t XMsgInProcessCall(uint32_t app, uint32_t message, be* param1, be* param2) { if (message == 0x7001B) { @@ -939,7 +1076,7 @@ void XamUserReadProfileSettings uint64_t* xuids, uint32_t settingCount, uint32_t* settingIds, - XDWORD* bufferSize, + be* bufferSize, void* buffer, void* overlapped ) @@ -1036,10 +1173,14 @@ void XexGetModuleHandle() bool RtlTryEnterCriticalSection(XRTL_CRITICAL_SECTION* cs) { - DWORD thisThread = GetCurrentThreadId(); - DWORD previousOwner = InterlockedCompareExchangeAcquire(&cs->OwningThread, thisThread, 0); + uint32_t thisThread = g_ppcContext->r13.u32; + assert(thisThread != NULL); - if (previousOwner == 0 || previousOwner == thisThread) + std::atomic_ref owningThread(cs->OwningThread); + + uint32_t previousOwner = 0; + + if (owningThread.compare_exchange_weak(previousOwner, thisThread) || previousOwner == thisThread) { cs->RecursionCount++; return true; @@ -1116,19 +1257,15 @@ void NtQueryFullAttributesFile() LOG_UTILITY("!!! STUB !!!"); } -NTSTATUS RtlMultiByteToUnicodeN(PWCH UnicodeString, ULONG MaxBytesInUnicodeString, XLPDWORD BytesInUnicodeString, const CHAR* MultiByteString, ULONG BytesInMultiByteString) +uint32_t RtlMultiByteToUnicodeN(be* UnicodeString, uint32_t MaxBytesInUnicodeString, be* BytesInUnicodeString, const char* MultiByteString, uint32_t BytesInMultiByteString) { - // i am lazy - const auto n = MultiByteToWideChar(CP_UTF8, 0, MultiByteString, BytesInMultiByteString, UnicodeString, MaxBytesInUnicodeString); + uint32_t length = std::min(MaxBytesInUnicodeString / 2, BytesInMultiByteString); - if (BytesInUnicodeString) - *BytesInUnicodeString = n * sizeof(wchar_t); + for (size_t i = 0; i < length; i++) + UnicodeString[i] = MultiByteString[i]; - if (n) - { - for (size_t i = 0; i < n; i++) - UnicodeString[i] = ByteSwap(UnicodeString[i]); - } + if (BytesInUnicodeString != nullptr) + *BytesInUnicodeString = length * 2; return STATUS_SUCCESS; } @@ -1143,41 +1280,41 @@ void MmQueryAllocationSize() LOG_UTILITY("!!! STUB !!!"); } -uint32_t NtClearEvent(uint32_t handle, uint32_t* previousState) +uint32_t NtClearEvent(Event* handle, uint32_t* previousState) { - return ResetEvent((HANDLE)handle) ? 0 : 0xFFFFFFFF; + handle->Reset(); + return 0; } -uint32_t NtResumeThread(uint32_t hThread, uint32_t* suspendCount) +uint32_t NtResumeThread(GuestThreadHandle* hThread, uint32_t* suspendCount) { - DWORD count = ResumeThread((HANDLE)hThread); + assert(hThread != GetKernelObject(CURRENT_THREAD_HANDLE)); - if (count == (DWORD)-1) - return E_FAIL; - - if (suspendCount != nullptr) - *suspendCount = ByteSwap(count); + hThread->suspended = false; + hThread->suspended.notify_all(); return S_OK; } -uint32_t NtSetEvent(uint32_t handle, uint32_t* previousState) +uint32_t NtSetEvent(Event* handle, uint32_t* previousState) { - return SetEvent((HANDLE)handle) ? 0 : 0xFFFFFFFF; + handle->Set(); + return 0; } -NTSTATUS NtCreateSemaphore(XLPDWORD Handle, XOBJECT_ATTRIBUTES* ObjectAttributes, DWORD InitialCount, DWORD MaximumCount) +uint32_t NtCreateSemaphore(be* Handle, XOBJECT_ATTRIBUTES* ObjectAttributes, uint32_t InitialCount, uint32_t MaximumCount) { - *Handle = (uint32_t)CreateSemaphoreA(nullptr, InitialCount, MaximumCount, nullptr); + *Handle = GetKernelHandle(CreateKernelObject(InitialCount, MaximumCount)); return STATUS_SUCCESS; } -NTSTATUS NtReleaseSemaphore(uint32_t Handle, DWORD ReleaseCount, LONG* PreviousCount) +uint32_t NtReleaseSemaphore(Semaphore* Handle, uint32_t ReleaseCount, int32_t* PreviousCount) { - ReleaseSemaphore((HANDLE)Handle, ReleaseCount, PreviousCount); + uint32_t previousCount; + Handle->Release(ReleaseCount, &previousCount); - if (PreviousCount) - *PreviousCount = ByteSwap(*PreviousCount); + if (PreviousCount != nullptr) + *PreviousCount = ByteSwap(previousCount); return STATUS_SUCCESS; } @@ -1212,11 +1349,17 @@ void NtFlushBuffersFile() LOG_UTILITY("!!! STUB !!!"); } -void KeQuerySystemTime(uint64_t* time) +void KeQuerySystemTime(be* time) { - FILETIME t; - GetSystemTimeAsFileTime(&t); - *time = ByteSwap((uint64_t(t.dwHighDateTime) << 32) | t.dwLowDateTime); + constexpr int64_t FILETIME_EPOCH_DIFFERENCE = 116444736000000000LL; + + auto now = std::chrono::system_clock::now(); + auto timeSinceEpoch = now.time_since_epoch(); + + int64_t currentTime100ns = std::chrono::duration_cast>>(timeSinceEpoch).count(); + currentTime100ns += FILETIME_EPOCH_DIFFERENCE; + + *time = currentTime100ns; } void RtlTimeToTimeFields() @@ -1244,14 +1387,14 @@ void ExTerminateThread() LOG_UTILITY("!!! STUB !!!"); } -uint32_t ExCreateThread(XLPDWORD handle, uint32_t stackSize, XLPDWORD threadId, uint32_t xApiThreadStartup, uint32_t startAddress, uint32_t startContext, uint32_t creationFlags) +uint32_t ExCreateThread(be* handle, uint32_t stackSize, be* threadId, uint32_t xApiThreadStartup, uint32_t startAddress, uint32_t startContext, uint32_t creationFlags) { LOGF_UTILITY("0x{:X}, 0x{:X}, 0x{:X}, 0x{:X}, 0x{:X}, 0x{:X}, 0x{:X}", (intptr_t)handle, stackSize, (intptr_t)threadId, xApiThreadStartup, startAddress, startContext, creationFlags); - DWORD hostThreadId; + uint32_t hostThreadId; - *handle = (uint32_t)GuestThread::Start(startAddress, startContext, creationFlags, &hostThreadId); + *handle = GetKernelHandle(GuestThread::Start({ startAddress, startContext, creationFlags }, &hostThreadId)); if (threadId != nullptr) *threadId = hostThreadId; @@ -1329,21 +1472,43 @@ void NetDll_XNetGetTitleXnAddr() LOG_UTILITY("!!! STUB !!!"); } -DWORD KeWaitForMultipleObjects(DWORD Count, xpointer* Objects, DWORD WaitType, DWORD WaitReason, DWORD WaitMode, DWORD Alertable, XLPQWORD Timeout) +uint32_t KeWaitForMultipleObjects(uint32_t Count, xpointer* Objects, uint32_t WaitType, uint32_t WaitReason, uint32_t WaitMode, uint32_t Alertable, be* Timeout) { - // TODO: create actual objects by type. + // FIXME: This function is only accounting for events. + const uint64_t timeout = GuestTimeoutToMilliseconds(Timeout); + assert(timeout == INFINITE); - thread_local std::vector events; - events.resize(Count); - - for (size_t i = 0; i < Count; i++) + if (WaitType == 0) // Wait all { - assert(Objects[i]->Type <= 1); - events[i] = ObQueryObject(*Objects[i].get())->handle; + for (size_t i = 0; i < Count; i++) + QueryKernelObject(*Objects[i])->Wait(timeout); + } + else + { + thread_local std::vector s_events; + s_events.resize(Count); + + for (size_t i = 0; i < Count; i++) + s_events[i] = QueryKernelObject(*Objects[i]); + + while (true) + { + uint32_t generation = g_keSetEventGeneration.load(); + + for (size_t i = 0; i < Count; i++) + { + if (s_events[i]->Wait(0) == STATUS_SUCCESS) + { + return STATUS_WAIT_0 + i; + } + } + + g_keSetEventGeneration.wait(generation); + } } - return WaitForMultipleObjectsEx(Count, events.data(), WaitType == 0, timeout, Alertable); + return STATUS_SUCCESS; } uint32_t KeRaiseIrqlToDpcLevel() @@ -1355,8 +1520,9 @@ void KfLowerIrql() { } uint32_t KeReleaseSemaphore(XKSEMAPHORE* semaphore, uint32_t increment, uint32_t adjustment, uint32_t wait) { - auto* object = ObQueryObject(semaphore->Header); - return ReleaseSemaphore(object->handle, adjustment, nullptr) ? 0 : 0xFFFFFFFF; + auto* object = QueryKernelObject(semaphore->Header); + object->Release(adjustment, nullptr); + return STATUS_SUCCESS; } void XAudioGetVoiceCategoryVolume() @@ -1364,16 +1530,19 @@ void XAudioGetVoiceCategoryVolume() LOG_UTILITY("!!! STUB !!!"); } -DWORD XAudioGetVoiceCategoryVolumeChangeMask(DWORD Driver, XLPDWORD Mask) +uint32_t XAudioGetVoiceCategoryVolumeChangeMask(uint32_t Driver, be* Mask) { *Mask = 0; return 0; } -uint32_t KeResumeThread(uint32_t object) +uint32_t KeResumeThread(GuestThreadHandle* object) { - LOGF_UTILITY("0x{:x}", object); - return ResumeThread((HANDLE)object); + assert(object != GetKernelObject(CURRENT_THREAD_HANDLE)); + + object->suspended = false; + object->suspended.notify_all(); + return 0; } void KeInitializeSemaphore(XKSEMAPHORE* semaphore, uint32_t count, uint32_t limit) @@ -1382,7 +1551,7 @@ void KeInitializeSemaphore(XKSEMAPHORE* semaphore, uint32_t count, uint32_t limi semaphore->Header.SignalState = count; semaphore->Limit = limit; - auto* object = ObQueryObject(semaphore->Header); + auto* object = QueryKernelObject(semaphore->Header); } void XMAReleaseContext() @@ -1395,7 +1564,7 @@ void XMACreateContext() LOG_UTILITY("!!! STUB !!!"); } -// uint32_t XAudioRegisterRenderDriverClient(XLPDWORD callback, XLPDWORD driver) +// uint32_t XAudioRegisterRenderDriverClient(be* callback, be* driver) // { // //printf("XAudioRegisterRenderDriverClient(): %x %x\n"); // diff --git a/UnleashedRecomp/kernel/io/file_system.cpp b/UnleashedRecomp/kernel/io/file_system.cpp index 3ff0d41c..71d7f2dd 100644 --- a/UnleashedRecomp/kernel/io/file_system.cpp +++ b/UnleashedRecomp/kernel/io/file_system.cpp @@ -6,78 +6,140 @@ #include #include -bool FindHandleCloser(void* handle) +struct FileHandle : KernelObject { - FindClose(handle); - return false; -} + std::fstream stream; + std::filesystem::path path; +}; -SWA_API uint32_t XCreateFileA +struct FindHandle : KernelObject +{ + std::error_code ec; + std::filesystem::path searchPath; + std::filesystem::directory_iterator iterator; + + void fillFindData(WIN32_FIND_DATAA* lpFindFileData) + { + if (iterator->is_directory()) + lpFindFileData->dwFileAttributes = ByteSwap(FILE_ATTRIBUTE_DIRECTORY); + else if (iterator->is_regular_file()) + lpFindFileData->dwFileAttributes = ByteSwap(FILE_ATTRIBUTE_NORMAL); + + std::u8string pathU8Str = iterator->path().lexically_relative(searchPath).u8string(); + uint64_t fileSize = iterator->file_size(ec); + strncpy(lpFindFileData->cFileName, (const char *)(pathU8Str.c_str()), sizeof(lpFindFileData->cFileName)); + lpFindFileData->nFileSizeLow = ByteSwap(uint32_t(fileSize >> 32U)); + lpFindFileData->nFileSizeHigh = ByteSwap(uint32_t(fileSize)); + lpFindFileData->ftCreationTime = {}; + lpFindFileData->ftLastAccessTime = {}; + lpFindFileData->ftLastWriteTime = {}; + } +}; + +SWA_API FileHandle* XCreateFileA ( - LPCSTR lpFileName, - DWORD dwDesiredAccess, - DWORD dwShareMode, - LPSECURITY_ATTRIBUTES lpSecurityAttributes, - DWORD dwCreationDisposition, - DWORD dwFlagsAndAttributes + const char* lpFileName, + uint32_t dwDesiredAccess, + uint32_t dwShareMode, + void* lpSecurityAttributes, + uint32_t dwCreationDisposition, + uint32_t dwFlagsAndAttributes ) { - const auto handle = (uint32_t)CreateFileA( - FileSystem::TransformPath(lpFileName), - dwDesiredAccess, - dwShareMode, - nullptr, - dwCreationDisposition, - dwFlagsAndAttributes & ~(FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED), - nullptr); + assert(((dwDesiredAccess & ~(GENERIC_READ | GENERIC_WRITE | FILE_READ_DATA)) == 0) && "Unknown desired access bits."); + assert(((dwShareMode & ~(FILE_SHARE_READ | FILE_SHARE_WRITE)) == 0) && "Unknown share mode bits."); + assert(((dwCreationDisposition & ~(CREATE_NEW | CREATE_ALWAYS)) == 0) && "Unknown creation disposition bits."); - GuestThread::SetLastError(GetLastError()); + std::filesystem::path filePath = std::u8string_view((const char8_t*)(FileSystem::TransformPath(lpFileName))); + std::fstream fileStream; + std::ios::openmode fileOpenMode = std::ios::binary; + if (dwDesiredAccess & (GENERIC_READ | FILE_READ_DATA)) + { + fileOpenMode |= std::ios::in; + } - LOGF_UTILITY("\"{}\", 0x{:X}, 0x{:X}, 0x{:X}, 0x{:X}, 0x{:X} -> 0x{:X}", - lpFileName, dwDesiredAccess, dwShareMode, (intptr_t)lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, handle); + if (dwDesiredAccess & GENERIC_WRITE) + { + fileOpenMode |= std::ios::out; + } - return handle; + fileStream.open(filePath, fileOpenMode); + if (!fileStream.is_open()) + { +#ifdef _WIN32 + GuestThread::SetLastError(GetLastError()); +#endif + return GetInvalidKernelObject(); + } + + FileHandle *fileHandle = CreateKernelObject(); + fileHandle->stream = std::move(fileStream); + fileHandle->path = std::move(filePath); + return fileHandle; } -static DWORD XGetFileSizeA(uint32_t hFile, LPDWORD lpFileSizeHigh) +static uint32_t XGetFileSizeA(FileHandle* hFile, be* lpFileSizeHigh) { - DWORD fileSize = GetFileSize((HANDLE)hFile, lpFileSizeHigh); + std::error_code ec; + auto fileSize = std::filesystem::file_size(hFile->path, ec); + if (!ec) + { + if (lpFileSizeHigh != nullptr) + { + *lpFileSizeHigh = uint32_t(fileSize >> 32U); + } + + return (uint32_t)(fileSize); + } - if (lpFileSizeHigh != nullptr) - *lpFileSizeHigh = ByteSwap(*lpFileSizeHigh); - - return fileSize; + return INVALID_FILE_SIZE; } -BOOL XGetFileSizeExA(uint32_t hFile, PLARGE_INTEGER lpFileSize) +uint32_t XGetFileSizeExA(FileHandle* hFile, LARGE_INTEGER* lpFileSize) { - BOOL result = GetFileSizeEx((HANDLE)hFile, lpFileSize); + std::error_code ec; + auto fileSize = std::filesystem::file_size(hFile->path, ec); + if (!ec) + { + if (lpFileSize != nullptr) + { + lpFileSize->QuadPart = ByteSwap(fileSize); + } - if (result) - lpFileSize->QuadPart = ByteSwap(lpFileSize->QuadPart); + return TRUE; + } - return result; + return FALSE; } -BOOL XReadFile +uint32_t XReadFile ( - uint32_t hFile, - LPVOID lpBuffer, - DWORD nNumberOfBytesToRead, - XLPDWORD lpNumberOfBytesRead, + FileHandle* hFile, + void* lpBuffer, + uint32_t nNumberOfBytesToRead, + be* lpNumberOfBytesRead, XOVERLAPPED* lpOverlapped ) { + uint32_t result = FALSE; if (lpOverlapped != nullptr) { - LONG distanceToMoveHigh = lpOverlapped->OffsetHigh; - - if (SetFilePointer((HANDLE)hFile, lpOverlapped->Offset, &distanceToMoveHigh, FILE_BEGIN) == INVALID_SET_FILE_POINTER) + std::streamoff streamOffset = lpOverlapped->Offset + (std::streamoff(lpOverlapped->OffsetHigh.get()) << 32U); + hFile->stream.clear(); + hFile->stream.seekg(streamOffset, std::ios::beg); + if (hFile->stream.bad()) + { return FALSE; + } } - DWORD numberOfBytesRead; - BOOL result = ReadFile((HANDLE)hFile, lpBuffer, nNumberOfBytesToRead, &numberOfBytesRead, nullptr); + uint32_t numberOfBytesRead; + hFile->stream.read((char *)(lpBuffer), nNumberOfBytesToRead); + if (!hFile->stream.bad()) + { + numberOfBytesRead = uint32_t(hFile->stream.gcount()); + result = TRUE; + } if (result) { @@ -85,9 +147,6 @@ BOOL XReadFile { lpOverlapped->Internal = 0; lpOverlapped->InternalHigh = numberOfBytesRead; - - if (lpOverlapped->hEvent != NULL) - SetEvent((HANDLE)lpOverlapped->hEvent.get()); } else if (lpNumberOfBytesRead != nullptr) { @@ -95,115 +154,197 @@ BOOL XReadFile } } - // printf("ReadFile(): %x %x %x %x %x %x\n", hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, lpOverlapped, result); - return result; } -DWORD XSetFilePointer(uint32_t hFile, LONG lDistanceToMove, PLONG lpDistanceToMoveHigh, DWORD dwMoveMethod) +uint32_t XSetFilePointer(FileHandle* hFile, int32_t lDistanceToMove, be* lpDistanceToMoveHigh, uint32_t dwMoveMethod) { - LONG distanceToMoveHigh = lpDistanceToMoveHigh ? ByteSwap(*lpDistanceToMoveHigh) : 0; - DWORD result = SetFilePointer((HANDLE)hFile, lDistanceToMove, lpDistanceToMoveHigh ? &distanceToMoveHigh : nullptr, dwMoveMethod); + int32_t distanceToMoveHigh = lpDistanceToMoveHigh ? lpDistanceToMoveHigh->get() : 0; + std::streamoff streamOffset = lDistanceToMove + (std::streamoff(distanceToMoveHigh) << 32U); + std::fstream::seekdir streamSeekDir = {}; + switch (dwMoveMethod) + { + case FILE_BEGIN: + streamSeekDir = std::ios::beg; + break; + case FILE_CURRENT: + streamSeekDir = std::ios::cur; + break; + case FILE_END: + streamSeekDir = std::ios::end; + break; + default: + assert(false && "Unknown move method."); + break; + } + hFile->stream.clear(); + hFile->stream.seekg(streamOffset, streamSeekDir); + if (hFile->stream.bad()) + { + return INVALID_SET_FILE_POINTER; + } + + std::streampos streamPos = hFile->stream.tellg(); if (lpDistanceToMoveHigh != nullptr) - *lpDistanceToMoveHigh = ByteSwap(distanceToMoveHigh); + *lpDistanceToMoveHigh = int32_t(streamPos >> 32U); - return result; + return uint32_t(streamPos); } -BOOL XSetFilePointerEx(uint32_t hFile, LONG lDistanceToMove, PLARGE_INTEGER lpNewFilePointer, DWORD dwMoveMethod) +uint32_t XSetFilePointerEx(FileHandle* hFile, int32_t lDistanceToMove, LARGE_INTEGER* lpNewFilePointer, uint32_t dwMoveMethod) { - LARGE_INTEGER distanceToMove; - distanceToMove.QuadPart = lDistanceToMove; + std::fstream::seekdir streamSeekDir = {}; + switch (dwMoveMethod) + { + case FILE_BEGIN: + streamSeekDir = std::ios::beg; + break; + case FILE_CURRENT: + streamSeekDir = std::ios::cur; + break; + case FILE_END: + streamSeekDir = std::ios::end; + break; + default: + assert(false && "Unknown move method."); + break; + } - DWORD result = SetFilePointerEx((HANDLE)hFile, distanceToMove, lpNewFilePointer, dwMoveMethod); + hFile->stream.clear(); + hFile->stream.seekg(lDistanceToMove, streamSeekDir); + if (hFile->stream.bad()) + { + return FALSE; + } if (lpNewFilePointer != nullptr) - lpNewFilePointer->QuadPart = ByteSwap(lpNewFilePointer->QuadPart); + { + lpNewFilePointer->QuadPart = ByteSwap(int64_t(hFile->stream.tellg())); + } - return result; + return TRUE; } -uint32_t XFindFirstFileA(LPCSTR lpFileName, LPWIN32_FIND_DATAA lpFindFileData) +FindHandle* XFindFirstFileA(const char* lpFileName, WIN32_FIND_DATAA* lpFindFileData) { - auto& data = *lpFindFileData; - const auto handle = FindFirstFileA(FileSystem::TransformPath(lpFileName), &data); + const char *transformedPath = FileSystem::TransformPath(lpFileName); + size_t transformedPathLength = strlen(transformedPath); + if (transformedPathLength == 0) + return (FindHandle*)GUEST_INVALID_HANDLE_VALUE; - GuestThread::SetLastError(GetLastError()); + std::filesystem::path dirPath; + if (strstr(transformedPath, "\\*") == (&transformedPath[transformedPathLength - 2]) || strstr(transformedPath, "/*") == (&transformedPath[transformedPathLength - 2])) + { + dirPath = std::u8string_view((const char8_t*)(transformedPath), transformedPathLength - 2); + } + else if (strstr(transformedPath, "\\*.*") == (&transformedPath[transformedPathLength - 4]) || strstr(transformedPath, "/*.*") == (&transformedPath[transformedPathLength - 4])) + { + dirPath = std::u8string_view((const char8_t *)(transformedPath), transformedPathLength - 4); + } + else + { + dirPath = std::u8string_view((const char8_t *)(transformedPath), transformedPathLength); + assert(!dirPath.has_extension() && "Unknown search pattern."); + } - if (handle == INVALID_HANDLE_VALUE) - return 0xFFFFFFFF; + if (!std::filesystem::is_directory(dirPath)) + return GetInvalidKernelObject(); - ByteSwapInplace(data.dwFileAttributes); - ByteSwapInplace(*(uint64_t*)&data.ftCreationTime); - ByteSwapInplace(*(uint64_t*)&data.ftLastAccessTime); - ByteSwapInplace(*(uint64_t*)&data.ftLastWriteTime); - ByteSwapInplace(*(uint64_t*)&data.nFileSizeHigh); + std::filesystem::directory_iterator dirIterator(dirPath); + if (dirIterator == std::filesystem::directory_iterator()) + return GetInvalidKernelObject(); - return GUEST_HANDLE(ObInsertObject(handle, FindHandleCloser)); + FindHandle *findHandle = CreateKernelObject(); + findHandle->searchPath = std::move(dirPath); + findHandle->iterator = std::move(dirIterator); + findHandle->fillFindData(lpFindFileData); + return findHandle; } -uint32_t XFindNextFileA(uint32_t Handle, LPWIN32_FIND_DATAA lpFindFileData) +uint32_t XFindNextFileA(FindHandle* Handle, WIN32_FIND_DATAA* lpFindFileData) { - auto* handle = ObQueryObject(HOST_HANDLE(Handle)); - auto& data = *lpFindFileData; - const auto result = FindNextFileA(handle, &data); + Handle->iterator++; - ByteSwapInplace(data.dwFileAttributes); - ByteSwapInplace(*(uint64_t*)&data.ftCreationTime); - ByteSwapInplace(*(uint64_t*)&data.ftLastAccessTime); - ByteSwapInplace(*(uint64_t*)&data.ftLastWriteTime); - ByteSwapInplace(*(uint64_t*)&data.nFileSizeHigh); - - return result; + if (Handle->iterator == std::filesystem::directory_iterator()) + { + return FALSE; + } + else + { + Handle->fillFindData(lpFindFileData); + return TRUE; + } } -BOOL XReadFileEx(uint32_t hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, XOVERLAPPED* lpOverlapped, uint32_t lpCompletionRoutine) +uint32_t XReadFileEx(FileHandle* hFile, void* lpBuffer, uint32_t nNumberOfBytesToRead, XOVERLAPPED* lpOverlapped, uint32_t lpCompletionRoutine) { - LONG distanceToMoveHigh = lpOverlapped->OffsetHigh; - - if (SetFilePointer((HANDLE)hFile, lpOverlapped->Offset, &distanceToMoveHigh, FILE_BEGIN) == INVALID_SET_FILE_POINTER) + uint32_t result = FALSE; + uint32_t numberOfBytesRead; + std::streamoff streamOffset = lpOverlapped->Offset + (std::streamoff(lpOverlapped->OffsetHigh.get()) << 32U); + hFile->stream.clear(); + hFile->stream.seekg(streamOffset, std::ios::beg); + if (hFile->stream.bad()) return FALSE; - DWORD numberOfBytesRead; - BOOL result = ReadFile((HANDLE)hFile, lpBuffer, nNumberOfBytesToRead, &numberOfBytesRead, nullptr); + hFile->stream.read((char *)(lpBuffer), nNumberOfBytesToRead); + if (!hFile->stream.bad()) + { + numberOfBytesRead = uint32_t(hFile->stream.gcount()); + result = TRUE; + } if (result) { lpOverlapped->Internal = 0; lpOverlapped->InternalHigh = numberOfBytesRead; - - if (lpOverlapped->hEvent != NULL) - SetEvent((HANDLE)lpOverlapped->hEvent.get()); } - // printf("ReadFileEx(): %x %x %x %x %x %x\n", hFile, lpBuffer, nNumberOfBytesToRead, lpOverlapped, lpCompletionRoutine, result); - return result; } -DWORD XGetFileAttributesA(LPCSTR lpFileName) +uint32_t XGetFileAttributesA(const char* lpFileName) { - return GetFileAttributesA(FileSystem::TransformPath(lpFileName)); + std::filesystem::path filePath(std::u8string_view((const char8_t*)(FileSystem::TransformPath(lpFileName)))); + if (std::filesystem::is_directory(filePath)) + return FILE_ATTRIBUTE_DIRECTORY; + else if (std::filesystem::is_regular_file(filePath)) + return FILE_ATTRIBUTE_NORMAL; + else + return INVALID_FILE_ATTRIBUTES; } -BOOL XWriteFile(uint32_t hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped) +uint32_t XWriteFile(FileHandle* hFile, const void* lpBuffer, uint32_t nNumberOfBytesToWrite, be* lpNumberOfBytesWritten, void* lpOverlapped) { - assert(lpOverlapped == nullptr); + assert(lpOverlapped == nullptr && "Overlapped not implemented."); - BOOL result = WriteFile((HANDLE)hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, nullptr); + hFile->stream.write((const char *)(lpBuffer), nNumberOfBytesToWrite); + if (hFile->stream.bad()) + return FALSE; - if (result && lpNumberOfBytesWritten != nullptr) - ByteSwapInplace(*lpNumberOfBytesWritten); + if (lpNumberOfBytesWritten != nullptr) + *lpNumberOfBytesWritten = uint32_t(hFile->stream.gcount()); - return result; + return TRUE; +} + +static void fixSlashes(char *path) +{ + while (*path != 0) + { + if (*path == '\\') + { + *path = '/'; + } + + path++; + } } const char* FileSystem::TransformPath(const char* path) { thread_local char builtPath[2048]{}; const char* relativePath = strstr(path, ":\\"); - if (relativePath != nullptr) { // rooted folder, handle direction @@ -215,12 +356,19 @@ const char* FileSystem::TransformPath(const char* path) strncpy(builtPath, newRoot.data(), newRoot.size()); builtPath[newRoot.size()] = '\\'; strcpy(builtPath + newRoot.size() + 1, relativePath + 2); - - return builtPath; + } + else + { + strncpy(builtPath, relativePath + 2, sizeof(builtPath)); } } + else + { + strncpy(builtPath, path, sizeof(builtPath)); + } - return relativePath != nullptr ? relativePath + 2 : path; + fixSlashes(builtPath); + return builtPath; } SWA_API const char* XExpandFilePathA(const char* path) diff --git a/UnleashedRecomp/kernel/memory.cpp b/UnleashedRecomp/kernel/memory.cpp index 1e7b12e9..76f99256 100644 --- a/UnleashedRecomp/kernel/memory.cpp +++ b/UnleashedRecomp/kernel/memory.cpp @@ -3,25 +3,46 @@ Memory::Memory(void* address, size_t size) : size(size) { +#ifdef _WIN32 base = (char*)VirtualAlloc(address, size, MEM_RESERVE, PAGE_READWRITE); if (base == nullptr) base = (char*)VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE); +#else + base = (char*)mmap(address, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); + + if (base == (char*)MAP_FAILED) + base = (char*)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); + + mprotect(base, 4096, PROT_NONE); +#endif } void* Memory::Alloc(size_t offset, size_t size, uint32_t type) { +#ifdef _WIN32 return VirtualAlloc(base + offset, size, type, PAGE_READWRITE); +#else + return base + offset; +#endif } void* Memory::Commit(size_t offset, size_t size) { +#ifdef _WIN32 return Alloc(offset, size, MEM_COMMIT); +#else + return base + offset; +#endif } void* Memory::Reserve(size_t offset, size_t size) { +#ifdef _WIN32 return Alloc(offset, size, MEM_RESERVE); +#else + return base + offset; +#endif } void* MmGetHostAddress(uint32_t ptr) diff --git a/UnleashedRecomp/kernel/memory.h b/UnleashedRecomp/kernel/memory.h index 0a1eba22..98349a44 100644 --- a/UnleashedRecomp/kernel/memory.h +++ b/UnleashedRecomp/kernel/memory.h @@ -1,5 +1,10 @@ #pragma once +#ifndef _WIN32 +#define MEM_COMMIT 0x00001000 +#define MEM_RESERVE 0x00002000 +#endif + class Memory { public: diff --git a/UnleashedRecomp/kernel/xam.cpp b/UnleashedRecomp/kernel/xam.cpp index 9541b4dc..21732034 100644 --- a/UnleashedRecomp/kernel/xam.cpp +++ b/UnleashedRecomp/kernel/xam.cpp @@ -2,16 +2,92 @@ #include "xam.h" #include "xdm.h" #include -#include +#include #include #include #include -#include #include "xxHashMap.h" #include +#include +#ifdef _WIN32 +#include // Needed for commctrl #pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"") +#endif + +struct XamListener : KernelObject +{ + uint32_t id{}; + uint64_t areas{}; + std::vector> notifications; + + XamListener(const XamListener&) = delete; + XamListener& operator=(const XamListener&) = delete; + + XamListener(); + ~XamListener(); +}; + +struct XamEnumeratorBase : KernelObject +{ + virtual uint32_t Next(void* buffer) + { + return -1; + } +}; + +template::iterator> +struct XamEnumerator : XamEnumeratorBase +{ + uint32_t fetch; + size_t size; + TIterator position; + TIterator begin; + TIterator end; + + XamEnumerator() = default; + XamEnumerator(uint32_t fetch, size_t size, TIterator begin, TIterator end) : fetch(fetch), size(size), position(begin), begin(begin), end(end) + { + + } + + uint32_t Next(void* buffer) override + { + if (position == end) + { + return -1; + } + + if (buffer == nullptr) + { + for (size_t i = 0; i < fetch; i++) + { + if (position == end) + { + return i == 0 ? -1 : i; + } + + ++position; + } + } + + for (size_t i = 0; i < fetch; i++) + { + if (position == end) + { + return i == 0 ? -1 : i; + } + + memcpy(buffer, &*position, size); + + ++position; + buffer = (void*)((size_t)buffer + size); + } + + return fetch; + } +}; std::array, 3> gContentRegistry{}; std::unordered_set gListeners{}; @@ -42,7 +118,7 @@ XamListener::~XamListener() gListeners.erase(this); } -XCONTENT_DATA XamMakeContent(DWORD type, const std::string_view& name) +XCONTENT_DATA XamMakeContent(uint32_t type, const std::string_view& name) { XCONTENT_DATA data{ 1, type }; @@ -58,7 +134,7 @@ void XamRegisterContent(const XCONTENT_DATA& data, const std::string_view& root) gContentRegistry[idx].emplace(StringHash(data.szFileName), XHOSTCONTENT_DATA{ data }).first->second.szRoot = root; } -void XamRegisterContent(DWORD type, const std::string_view name, const std::string_view& root) +void XamRegisterContent(uint32_t type, const std::string_view name, const std::string_view& root) { XCONTENT_DATA data{ 1, type, {}, "" }; @@ -67,17 +143,16 @@ void XamRegisterContent(DWORD type, const std::string_view name, const std::stri XamRegisterContent(data, root); } -SWA_API DWORD XamNotifyCreateListener(uint64_t qwAreas) +SWA_API uint32_t XamNotifyCreateListener(uint64_t qwAreas) { - int handle; - auto* listener = ObCreateObject(handle); + auto* listener = CreateKernelObject(); listener->areas = qwAreas; - return GUEST_HANDLE(handle); + return GetKernelHandle(listener); } -SWA_API void XamNotifyEnqueueEvent(DWORD dwId, DWORD dwParam) +SWA_API void XamNotifyEnqueueEvent(uint32_t dwId, uint32_t dwParam) { for (const auto& listener : gListeners) { @@ -88,9 +163,9 @@ SWA_API void XamNotifyEnqueueEvent(DWORD dwId, DWORD dwParam) } } -SWA_API bool XNotifyGetNext(DWORD hNotification, DWORD dwMsgFilter, XDWORD* pdwId, XDWORD* pParam) +SWA_API bool XNotifyGetNext(uint32_t hNotification, uint32_t dwMsgFilter, be* pdwId, be* pParam) { - auto& listener = *ObTryQueryObject(HOST_HANDLE(hNotification)); + auto& listener = *GetKernelObject(hNotification); if (dwMsgFilter) { @@ -129,9 +204,12 @@ SWA_API bool XNotifyGetNext(DWORD hNotification, DWORD dwMsgFilter, XDWORD* pdwI return false; } -SWA_API uint32_t XamShowMessageBoxUI(DWORD dwUserIndex, XWORD* wszTitle, XWORD* wszText, DWORD cButtons, - xpointer* pwszButtons, DWORD dwFocusButton, DWORD dwFlags, XLPDWORD pResult, XXOVERLAPPED* pOverlapped) +SWA_API uint32_t XamShowMessageBoxUI(uint32_t dwUserIndex, be* wszTitle, be* wszText, uint32_t cButtons, + xpointer>* pwszButtons, uint32_t dwFocusButton, uint32_t dwFlags, be* pResult, XXOVERLAPPED* pOverlapped) { + int button{}; + +#ifdef _WIN32 std::vector texts{}; std::vector buttons{}; @@ -154,20 +232,19 @@ SWA_API uint32_t XamShowMessageBoxUI(DWORD dwUserIndex, XWORD* wszTitle, XWORD* TASKDIALOGCONFIG config{}; config.cbSize = sizeof(config); - // config.hwndParent = Window::s_hWnd; config.pszWindowTitle = texts[0].c_str(); config.pszContent = texts[1].c_str(); config.cButtons = cButtons; config.pButtons = buttons.data(); - int button{}; TaskDialogIndirect(&config, &button, nullptr, nullptr); +#endif *pResult = button; if (pOverlapped) { - pOverlapped->dwCompletionContext = GetCurrentThreadId(); + pOverlapped->dwCompletionContext = GuestThread::GetCurrentThreadId(); pOverlapped->Error = 0; pOverlapped->Length = -1; } @@ -177,8 +254,8 @@ SWA_API uint32_t XamShowMessageBoxUI(DWORD dwUserIndex, XWORD* wszTitle, XWORD* return 0; } -SWA_API uint32_t XamContentCreateEnumerator(DWORD dwUserIndex, DWORD DeviceID, DWORD dwContentType, - DWORD dwContentFlags, DWORD cItem, XLPDWORD pcbBuffer, XLPDWORD phEnum) +SWA_API uint32_t XamContentCreateEnumerator(uint32_t dwUserIndex, uint32_t DeviceID, uint32_t dwContentType, + uint32_t dwContentFlags, uint32_t cItem, be* pcbBuffer, be* phEnum) { if (dwUserIndex != 0) { @@ -188,19 +265,19 @@ SWA_API uint32_t XamContentCreateEnumerator(DWORD dwUserIndex, DWORD DeviceID, D const auto& registry = gContentRegistry[dwContentType - 1]; const auto& values = registry | std::views::values; - const int handle = ObInsertObject(new XamEnumerator(cItem, sizeof(_XCONTENT_DATA), values.begin(), values.end())); + auto* enumerator = CreateKernelObject>(cItem, sizeof(_XCONTENT_DATA), values.begin(), values.end()); if (pcbBuffer) *pcbBuffer = sizeof(_XCONTENT_DATA) * cItem; - *phEnum = GUEST_HANDLE(handle); + *phEnum = GetKernelHandle(enumerator); return 0; } -SWA_API uint32_t XamEnumerate(uint32_t hEnum, DWORD dwFlags, PVOID pvBuffer, DWORD cbBuffer, XLPDWORD pcItemsReturned, XXOVERLAPPED* pOverlapped) +SWA_API uint32_t XamEnumerate(uint32_t hEnum, uint32_t dwFlags, void* pvBuffer, uint32_t cbBuffer, be* pcItemsReturned, XXOVERLAPPED* pOverlapped) { - auto* enumerator = ObTryQueryObject(HOST_HANDLE(hEnum)); + auto* enumerator = GetKernelObject(hEnum); const auto count = enumerator->Next(pvBuffer); if (count == -1) @@ -212,9 +289,9 @@ SWA_API uint32_t XamEnumerate(uint32_t hEnum, DWORD dwFlags, PVOID pvBuffer, DWO return ERROR_SUCCESS; } -SWA_API uint32_t XamContentCreateEx(DWORD dwUserIndex, LPCSTR szRootName, const XCONTENT_DATA* pContentData, - DWORD dwContentFlags, XLPDWORD pdwDisposition, XLPDWORD pdwLicenseMask, - DWORD dwFileCacheSize, uint64_t uliContentSize, PXXOVERLAPPED pOverlapped) +SWA_API uint32_t XamContentCreateEx(uint32_t dwUserIndex, const char* szRootName, const XCONTENT_DATA* pContentData, + uint32_t dwContentFlags, be* pdwDisposition, be* pdwLicenseMask, + uint32_t dwFileCacheSize, uint64_t uliContentSize, PXXOVERLAPPED pOverlapped) { const auto& registry = gContentRegistry[pContentData->dwContentType - 1]; const auto exists = registry.contains(StringHash(pContentData->szFileName)); @@ -231,19 +308,23 @@ SWA_API uint32_t XamContentCreateEx(DWORD dwUserIndex, LPCSTR szRootName, const if (pContentData->dwContentType == XCONTENTTYPE_SAVEDATA) { - root = GetSavePath().string(); + std::u8string savePathU8 = GetSavePath().u8string(); + root = (const char *)(savePathU8.c_str()); } else if (pContentData->dwContentType == XCONTENTTYPE_DLC) { - root = ".\\dlc"; + root = GAME_INSTALL_DIRECTORY "/dlc"; } else { - root = "."; + root = GAME_INSTALL_DIRECTORY; } XamRegisterContent(*pContentData, root); - CreateDirectoryA(root.c_str(), nullptr); + + std::error_code ec; + std::filesystem::create_directory(std::u8string_view((const char8_t*)(root.c_str())), ec); + XamRootCreate(szRootName, root); } else @@ -277,13 +358,13 @@ SWA_API uint32_t XamContentCreateEx(DWORD dwUserIndex, LPCSTR szRootName, const return ERROR_PATH_NOT_FOUND; } -SWA_API uint32_t XamContentClose(LPCSTR szRootName, XXOVERLAPPED* pOverlapped) +SWA_API uint32_t XamContentClose(const char* szRootName, XXOVERLAPPED* pOverlapped) { gRootMap.erase(StringHash(szRootName)); return 0; } -SWA_API uint32_t XamContentGetDeviceData(DWORD DeviceID, XDEVICE_DATA* pDeviceData) +SWA_API uint32_t XamContentGetDeviceData(uint32_t DeviceID, XDEVICE_DATA* pDeviceData) { pDeviceData->DeviceID = DeviceID; pDeviceData->DeviceType = XCONTENTDEVICETYPE_HDD; @@ -320,73 +401,63 @@ SWA_API uint32_t XamInputGetCapabilities(uint32_t unk, uint32_t userIndex, uint3 SWA_API uint32_t XamInputGetState(uint32_t userIndex, uint32_t flags, XAMINPUT_STATE* state) { + memset(state, 0, sizeof(*state)); + uint32_t result = hid::GetState(userIndex, state); - if (result == ERROR_SUCCESS) + if (GameWindow::s_isFocused) { - ByteSwapInplace(state->dwPacketNumber); - ByteSwapInplace(state->Gamepad.wButtons); - ByteSwapInplace(state->Gamepad.sThumbLX); - ByteSwapInplace(state->Gamepad.sThumbLY); - ByteSwapInplace(state->Gamepad.sThumbRX); - ByteSwapInplace(state->Gamepad.sThumbRY); - } - else if (userIndex == 0) - { - if (!Window::s_isFocused) - return ERROR_SUCCESS; + auto keyboardState = SDL_GetKeyboardState(NULL); - memset(state, 0, sizeof(*state)); - if (GetAsyncKeyState('W') & 0x8000) + if (keyboardState[SDL_SCANCODE_W]) state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_Y; - if (GetAsyncKeyState('A') & 0x8000) + if (keyboardState[SDL_SCANCODE_A]) state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_X; - if (GetAsyncKeyState('S') & 0x8000) + if (keyboardState[SDL_SCANCODE_S]) state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_A; - if (GetAsyncKeyState('D') & 0x8000) + if (keyboardState[SDL_SCANCODE_D]) state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_B; - if (GetAsyncKeyState('Q') & 0x8000) + + if (keyboardState[SDL_SCANCODE_Q]) state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_LEFT_SHOULDER; - if (GetAsyncKeyState('E') & 0x8000) + if (keyboardState[SDL_SCANCODE_E]) state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_RIGHT_SHOULDER; - if (GetAsyncKeyState('1') & 0x8000) + if (keyboardState[SDL_SCANCODE_1]) state->Gamepad.bLeftTrigger = 0xFF; - if (GetAsyncKeyState('3') & 0x8000) + if (keyboardState[SDL_SCANCODE_3]) state->Gamepad.bRightTrigger = 0xFF; - if (GetAsyncKeyState('I') & 0x8000) + if (keyboardState[SDL_SCANCODE_I]) state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_DPAD_UP; - if (GetAsyncKeyState('J') & 0x8000) + if (keyboardState[SDL_SCANCODE_J]) state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_DPAD_LEFT; - if (GetAsyncKeyState('K') & 0x8000) + if (keyboardState[SDL_SCANCODE_K]) state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_DPAD_DOWN; - if (GetAsyncKeyState('L') & 0x8000) + if (keyboardState[SDL_SCANCODE_L]) state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_DPAD_RIGHT; - if (GetAsyncKeyState(VK_UP) & 0x8000) + if (keyboardState[SDL_SCANCODE_UP]) state->Gamepad.sThumbLY = 32767; - if (GetAsyncKeyState(VK_LEFT) & 0x8000) + if (keyboardState[SDL_SCANCODE_LEFT]) state->Gamepad.sThumbLX = -32768; - if (GetAsyncKeyState(VK_DOWN) & 0x8000) + if (keyboardState[SDL_SCANCODE_DOWN]) state->Gamepad.sThumbLY = -32768; - if (GetAsyncKeyState(VK_RIGHT) & 0x8000) + if (keyboardState[SDL_SCANCODE_RIGHT]) state->Gamepad.sThumbLX = 32767; - if (GetAsyncKeyState(VK_RETURN) & 0x8000) - state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_START; - if (GetAsyncKeyState(VK_BACK) & 0x8000) + if (keyboardState[SDL_SCANCODE_RETURN]) + state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_START; + if (keyboardState[SDL_SCANCODE_BACKSPACE]) state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_BACK; - - ByteSwapInplace(state->Gamepad.wButtons); - ByteSwapInplace(state->Gamepad.sThumbLX); - ByteSwapInplace(state->Gamepad.sThumbLY); - ByteSwapInplace(state->Gamepad.sThumbRX); - ByteSwapInplace(state->Gamepad.sThumbRY); - - result = ERROR_SUCCESS; } - return result; + ByteSwapInplace(state->Gamepad.wButtons); + ByteSwapInplace(state->Gamepad.sThumbLX); + ByteSwapInplace(state->Gamepad.sThumbLY); + ByteSwapInplace(state->Gamepad.sThumbRX); + ByteSwapInplace(state->Gamepad.sThumbRY); + + return ERROR_SUCCESS; } SWA_API uint32_t XamInputSetState(uint32_t userIndex, uint32_t flags, XAMINPUT_VIBRATION* vibration) diff --git a/UnleashedRecomp/kernel/xam.h b/UnleashedRecomp/kernel/xam.h index 6fd7e262..6d1f2572 100644 --- a/UnleashedRecomp/kernel/xam.h +++ b/UnleashedRecomp/kernel/xam.h @@ -1,108 +1,32 @@ #pragma once #include -#define MSGID(Area, Number) (DWORD)((WORD)(Area) << 16 | (WORD)(Number)) +#define MSGID(Area, Number) (uint32_t)((uint16_t)(Area) << 16 | (uint16_t)(Number)) #define MSG_AREA(msgid) (((msgid) >> 16) & 0xFFFF) #define MSG_NUMBER(msgid) ((msgid) & 0xFFFF) -struct XamListener -{ - uint32_t id{}; - uint64_t areas{}; - std::vector> notifications; - - XamListener(const XamListener&) = delete; - XamListener& operator=(const XamListener&) = delete; - - XamListener(); - ~XamListener(); -}; - -class XamEnumeratorBase -{ -public: - virtual ~XamEnumeratorBase() = default; - virtual uint32_t Next(void* buffer) - { - return -1; - } -}; - -template::iterator> -class XamEnumerator : public XamEnumeratorBase -{ -public: - uint32_t fetch; - size_t size; - TIterator position; - TIterator begin; - TIterator end; - - XamEnumerator() = default; - XamEnumerator(uint32_t fetch, size_t size, TIterator begin, TIterator end) : fetch(fetch), size(size), position(begin), begin(begin), end(end) - { - - } - - uint32_t Next(void* buffer) override - { - if (position == end) - { - return -1; - } - - if (buffer == nullptr) - { - for (size_t i = 0; i < fetch; i++) - { - if (position == end) - { - return i == 0 ? -1 : i; - } - - ++position; - } - } - - for (size_t i = 0; i < fetch; i++) - { - if (position == end) - { - return i == 0 ? -1 : i; - } - - memcpy(buffer, &*position, size); - - ++position; - buffer = (void*)((size_t)buffer + size); - } - - return fetch; - } -}; - -XCONTENT_DATA XamMakeContent(DWORD type, const std::string_view& name); +XCONTENT_DATA XamMakeContent(uint32_t type, const std::string_view& name); void XamRegisterContent(const XCONTENT_DATA& data, const std::string_view& root); std::string_view XamGetRootPath(const std::string_view& root); void XamRootCreate(const std::string_view& root, const std::string_view& path); -SWA_API DWORD XamNotifyCreateListener(uint64_t qwAreas); -SWA_API void XamNotifyEnqueueEvent(DWORD dwId, DWORD dwParam); // i made it the fuck up -SWA_API bool XNotifyGetNext(DWORD hNotification, DWORD dwMsgFilter, XDWORD* pdwId, XDWORD* pParam); +SWA_API uint32_t XamNotifyCreateListener(uint64_t qwAreas); +SWA_API void XamNotifyEnqueueEvent(uint32_t dwId, uint32_t dwParam); // i made it the fuck up +SWA_API bool XNotifyGetNext(uint32_t hNotification, uint32_t dwMsgFilter, be* pdwId, be* pParam); -SWA_API uint32_t XamShowMessageBoxUI(DWORD dwUserIndex, XWORD* wszTitle, XWORD* wszText, DWORD cButtons, - xpointer* pwszButtons, DWORD dwFocusButton, DWORD dwFlags, XLPDWORD pResult, XXOVERLAPPED* pOverlapped); +SWA_API uint32_t XamShowMessageBoxUI(uint32_t dwUserIndex, be* wszTitle, be* wszText, uint32_t cButtons, + xpointer>* pwszButtons, uint32_t dwFocusButton, uint32_t dwFlags, be* pResult, XXOVERLAPPED* pOverlapped); -SWA_API uint32_t XamContentCreateEnumerator(DWORD dwUserIndex, DWORD DeviceID, DWORD dwContentType, - DWORD dwContentFlags, DWORD cItem, XLPDWORD pcbBuffer, XLPDWORD phEnum); -SWA_API uint32_t XamEnumerate(uint32_t hEnum, DWORD dwFlags, PVOID pvBuffer, DWORD cbBuffer, XLPDWORD pcItemsReturned, XXOVERLAPPED* pOverlapped); +SWA_API uint32_t XamContentCreateEnumerator(uint32_t dwUserIndex, uint32_t DeviceID, uint32_t dwContentType, + uint32_t dwContentFlags, uint32_t cItem, be* pcbBuffer, be* phEnum); +SWA_API uint32_t XamEnumerate(uint32_t hEnum, uint32_t dwFlags, void* pvBuffer, uint32_t cbBuffer, be* pcItemsReturned, XXOVERLAPPED* pOverlapped); -SWA_API uint32_t XamContentCreateEx(DWORD dwUserIndex, LPCSTR szRootName, const XCONTENT_DATA* pContentData, - DWORD dwContentFlags, XLPDWORD pdwDisposition, XLPDWORD pdwLicenseMask, - DWORD dwFileCacheSize, uint64_t uliContentSize, PXXOVERLAPPED pOverlapped); -SWA_API uint32_t XamContentGetDeviceData(DWORD DeviceID, XDEVICE_DATA* pDeviceData); -SWA_API uint32_t XamContentClose(LPCSTR szRootName, XXOVERLAPPED* pOverlapped); +SWA_API uint32_t XamContentCreateEx(uint32_t dwUserIndex, const char* szRootName, const XCONTENT_DATA* pContentData, + uint32_t dwContentFlags, be* pdwDisposition, be* pdwLicenseMask, + uint32_t dwFileCacheSize, uint64_t uliContentSize, PXXOVERLAPPED pOverlapped); +SWA_API uint32_t XamContentGetDeviceData(uint32_t DeviceID, XDEVICE_DATA* pDeviceData); +SWA_API uint32_t XamContentClose(const char* szRootName, XXOVERLAPPED* pOverlapped); SWA_API uint32_t XamInputGetCapabilities(uint32_t unk, uint32_t userIndex, uint32_t flags, XAMINPUT_CAPABILITIES* caps); SWA_API uint32_t XamInputGetState(uint32_t userIndex, uint32_t flags, XAMINPUT_STATE* state); diff --git a/UnleashedRecomp/kernel/xdm.cpp b/UnleashedRecomp/kernel/xdm.cpp index 10a99b19..9285ec5a 100644 --- a/UnleashedRecomp/kernel/xdm.cpp +++ b/UnleashedRecomp/kernel/xdm.cpp @@ -2,47 +2,36 @@ #include "xdm.h" #include "freelist.h" -FreeList, TypeDestructor_t>> gKernelObjects; -Mutex gKernelLock; +Mutex g_kernelLock; -void* ObQueryObject(size_t handle) +void DestroyKernelObject(KernelObject* obj) { - std::lock_guard guard{ gKernelLock }; - - if (handle >= gKernelObjects.items.size()) - return nullptr; - - return std::get<0>(gKernelObjects[handle]).get(); + obj->~KernelObject(); + g_userHeap.Free(obj); } -uint32_t ObInsertObject(void* object, TypeDestructor_t destructor) +uint32_t GetKernelHandle(KernelObject* obj) { - std::lock_guard guard{ gKernelLock }; - - const auto handle = gKernelObjects.Alloc(); - - auto& holder = gKernelObjects[handle]; - - std::get<0>(holder).reset(static_cast(object)); - std::get<1>(holder) = destructor; - - return handle; + assert(obj != GetInvalidKernelObject()); + return g_memory.MapVirtual(obj); } -void ObCloseHandle(uint32_t handle) +void DestroyKernelObject(uint32_t handle) { - std::lock_guard guard{ gKernelLock }; - - auto& obj = gKernelObjects[handle]; - - if (std::get<1>(obj)(std::get<0>(obj).get())) - { - std::get<0>(obj).reset(); - } - else - { - std::get<0>(obj).release(); - } - - gKernelObjects.Free(handle); + DestroyKernelObject(GetKernelObject(handle)); +} + +bool IsKernelObject(uint32_t handle) +{ + return (handle & 0x80000000) != 0; +} + +bool IsKernelObject(void* obj) +{ + return IsKernelObject(g_memory.MapVirtual(obj)); +} + +bool IsInvalidKernelObject(void* obj) +{ + return obj == GetInvalidKernelObject(); } diff --git a/UnleashedRecomp/kernel/xdm.h b/UnleashedRecomp/kernel/xdm.h index 32ffaa4e..5569952e 100644 --- a/UnleashedRecomp/kernel/xdm.h +++ b/UnleashedRecomp/kernel/xdm.h @@ -1,56 +1,153 @@ #pragma once -#define DUMMY_HANDLE (DWORD)('HAND') -#define OBJECT_SIGNATURE (DWORD)'XBOX' -extern Mutex gKernelLock; +#include "heap.h" +#include "memory.h" -void* ObQueryObject(size_t handle); -uint32_t ObInsertObject(void* object, TypeDestructor_t destructor); -void ObCloseHandle(uint32_t handle); +#define OBJECT_SIGNATURE (uint32_t)'XBOX' +#define GUEST_INVALID_HANDLE_VALUE 0xFFFFFFFF + +#ifndef _WIN32 + +#define S_OK 0x00000000 +#define FALSE 0x00000000 +#define TRUE 0x00000001 +#define STATUS_SUCCESS 0x00000000 +#define STATUS_WAIT_0 0x00000000 +#define STATUS_USER_APC 0x000000C0 +#define STATUS_TIMEOUT 0x00000102 +#define STATUS_FAIL_CHECK 0xC0000229 +#define INFINITE 0xFFFFFFFF +#define FILE_ATTRIBUTE_DIRECTORY 0x00000010 +#define FILE_ATTRIBUTE_NORMAL 0x00000080 +#define GENERIC_READ 0x80000000 +#define GENERIC_WRITE 0x40000000 +#define FILE_READ_DATA 0x0001 +#define FILE_SHARE_READ 0x00000001 +#define FILE_SHARE_WRITE 0x00000002 +#define CREATE_NEW 1 +#define CREATE_ALWAYS 2 +#define OPEN_EXISTING 3 +#define INVALID_FILE_SIZE 0xFFFFFFFF +#define INVALID_SET_FILE_POINTER 0xFFFFFFFF +#define INVALID_FILE_ATTRIBUTES 0xFFFFFFFF +#define FILE_BEGIN 0 +#define FILE_CURRENT 1 +#define FILE_END 2 +#define ERROR_NO_MORE_FILES 0x12 +#define ERROR_NO_SUCH_USER 0x525 +#define ERROR_SUCCESS 0x0 +#define ERROR_PATH_NOT_FOUND 0x3 +#define ERROR_BAD_ARGUMENTS 0xA0 +#define ERROR_DEVICE_NOT_CONNECTED 0x48F +#define PAGE_READWRITE 0x04 + +typedef union _LARGE_INTEGER { + struct { + uint32_t LowPart; + int32_t HighPart; + }; + struct { + uint32_t LowPart; + int32_t HighPart; + } u; + int64_t QuadPart; +} LARGE_INTEGER; + +static_assert(sizeof(LARGE_INTEGER) == 8); + +typedef struct _FILETIME +{ + uint32_t dwLowDateTime; + uint32_t dwHighDateTime; +} FILETIME; + +static_assert(sizeof(FILETIME) == 8); + +typedef struct _WIN32_FIND_DATAA +{ + uint32_t dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + uint32_t nFileSizeHigh; + uint32_t nFileSizeLow; + uint32_t dwReserved0; + uint32_t dwReserved1; + char cFileName[260]; + char cAlternateFileName[14]; +} WIN32_FIND_DATAA; + +static_assert(sizeof(WIN32_FIND_DATAA) == 320); + +#endif + +struct KernelObject +{ + virtual ~KernelObject() + { + } + + virtual uint32_t Wait(uint32_t timeout) + { + assert(false && "Wait not implemented for this kernel object."); + return STATUS_TIMEOUT; + } +}; + +template +inline T* CreateKernelObject(Args&&... args) +{ + static_assert(std::is_base_of_v); + return g_userHeap.AllocPhysical(std::forward(args)...); +} + +template +inline T* GetKernelObject(uint32_t handle) +{ + assert(handle != GUEST_INVALID_HANDLE_VALUE); + return reinterpret_cast(g_memory.Translate(handle)); +} + +uint32_t GetKernelHandle(KernelObject* obj); + +void DestroyKernelObject(KernelObject* obj); +void DestroyKernelObject(uint32_t handle); + +bool IsKernelObject(uint32_t handle); +bool IsKernelObject(void* obj); + +bool IsInvalidKernelObject(void* obj); + +template +inline T* GetInvalidKernelObject() +{ + return reinterpret_cast(g_memory.Translate(GUEST_INVALID_HANDLE_VALUE)); +} + +extern Mutex g_kernelLock; template -T* ObQueryObject(XDISPATCHER_HEADER& header) +inline T* QueryKernelObject(XDISPATCHER_HEADER& header) { - std::lock_guard guard{ gKernelLock }; + std::lock_guard guard{ g_kernelLock }; if (header.WaitListHead.Flink != OBJECT_SIGNATURE) { header.WaitListHead.Flink = OBJECT_SIGNATURE; - auto* obj = new T(reinterpret_cast(&header)); - header.WaitListHead.Blink = ObInsertObject(obj, DestroyObject); + auto* obj = CreateKernelObject(reinterpret_cast(&header)); + header.WaitListHead.Blink = g_memory.MapVirtual(obj); return obj; } - return static_cast(ObQueryObject(header.WaitListHead.Blink.get())); -} - -template -size_t ObInsertObject(T* object) -{ - return ObInsertObject(object, DestroyObject); -} - -template -T* ObCreateObject(int& handle) -{ - auto* obj = new T(); - handle = ::ObInsertObject(obj, DestroyObject); - - return obj; + return static_cast(g_memory.Translate(header.WaitListHead.Blink.get())); } // Get object without initialisation template -T* ObTryQueryObject(XDISPATCHER_HEADER& header) +inline T* TryQueryKernelObject(XDISPATCHER_HEADER& header) { if (header.WaitListHead.Flink != OBJECT_SIGNATURE) return nullptr; - return static_cast(ObQueryObject(header.WaitListHead.Blink)); + return static_cast(g_memory.Translate(header.WaitListHead.Blink.get())); } - -template -T* ObTryQueryObject(int handle) -{ - return static_cast(ObQueryObject(handle)); -} \ No newline at end of file diff --git a/UnleashedRecomp/locale/config_locale.cpp b/UnleashedRecomp/locale/config_locale.cpp index 9a904c00..e591186a 100644 --- a/UnleashedRecomp/locale/config_locale.cpp +++ b/UnleashedRecomp/locale/config_locale.cpp @@ -1,5 +1,4 @@ #include -#include #define CONFIG_DEFINE_LOCALE(name) \ CONFIG_LOCALE Config::g_##name##_locale = diff --git a/UnleashedRecomp/locale/locale.cpp b/UnleashedRecomp/locale/locale.cpp index bb29c80b..9ad4ad1e 100644 --- a/UnleashedRecomp/locale/locale.cpp +++ b/UnleashedRecomp/locale/locale.cpp @@ -1,3 +1,4 @@ +#include #include std::unordered_map> g_locale = diff --git a/UnleashedRecomp/locale/locale.h b/UnleashedRecomp/locale/locale.h index 0c0986a1..b7f716c8 100644 --- a/UnleashedRecomp/locale/locale.h +++ b/UnleashedRecomp/locale/locale.h @@ -1,6 +1,14 @@ #pragma once -#include +enum class ELanguage : uint32_t +{ + English = 1, + Japanese, + German, + French, + Spanish, + Italian +}; inline std::string g_localeMissing = ""; diff --git a/UnleashedRecomp/main.cpp b/UnleashedRecomp/main.cpp index a847beee..b5ab376e 100644 --- a/UnleashedRecomp/main.cpp +++ b/UnleashedRecomp/main.cpp @@ -50,15 +50,17 @@ void KiSystemStartup() { const auto gameContent = XamMakeContent(XCONTENTTYPE_RESERVED, "Game"); const auto updateContent = XamMakeContent(XCONTENTTYPE_RESERVED, "Update"); - XamRegisterContent(gameContent, DirectoryExists(".\\game") ? ".\\game" : "."); - XamRegisterContent(updateContent, ".\\update"); + XamRegisterContent(gameContent, GAME_INSTALL_DIRECTORY "/game"); + XamRegisterContent(updateContent, GAME_INSTALL_DIRECTORY "/update"); const auto savePath = GetSavePath(); const auto saveName = "SYS-DATA"; - // TODO: implement save slots? if (std::filesystem::exists(savePath / saveName)) - XamRegisterContent(XamMakeContent(XCONTENTTYPE_SAVEDATA, saveName), savePath.string()); + { + std::u8string savePathU8 = savePath.u8string(); + XamRegisterContent(XamMakeContent(XCONTENTTYPE_SAVEDATA, saveName), (const char *)(savePathU8.c_str())); + } // Mount game XamContentCreateEx(0, "game", &gameContent, OPEN_EXISTING, nullptr, nullptr, 0, 0, nullptr); @@ -67,33 +69,23 @@ void KiSystemStartup() // OS mounts game data to D: XamContentCreateEx(0, "D", &gameContent, OPEN_EXISTING, nullptr, nullptr, 0, 0, nullptr); - WIN32_FIND_DATAA fdata; - const auto findHandle = FindFirstFileA(".\\dlc\\*.*", &fdata); - if (findHandle != INVALID_HANDLE_VALUE) + std::error_code ec; + for (auto& file : std::filesystem::directory_iterator(GAME_INSTALL_DIRECTORY "/dlc", ec)) { - char strBuf[256]; - do + if (file.is_directory()) { - if (strcmp(fdata.cFileName, ".") == 0 || strcmp(fdata.cFileName, "..") == 0) - { - continue; - } - - if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - { - snprintf(strBuf, sizeof(strBuf), ".\\dlc\\%s", fdata.cFileName); - XamRegisterContent(XamMakeContent(XCONTENTTYPE_DLC, fdata.cFileName), strBuf); - } - } while (FindNextFileA(findHandle, &fdata)); - FindClose(findHandle); + std::u8string fileNameU8 = file.path().filename().u8string(); + std::u8string filePathU8 = file.path().u8string(); + XamRegisterContent(XamMakeContent(XCONTENTTYPE_DLC, (const char*)(fileNameU8.c_str())), (const char*)(filePathU8.c_str())); + } } XAudioInitializeSystem(); } -uint32_t LdrLoadModule(const char* path) +uint32_t LdrLoadModule(const std::filesystem::path &path) { - auto loadResult = LoadFile(FileSystem::TransformPath(GAME_XEX_PATH)); + auto loadResult = LoadFile(path); if (loadResult.empty()) { assert("Failed to load module" && false); @@ -145,27 +137,33 @@ uint32_t LdrLoadModule(const char* path) int main(int argc, char *argv[]) { +#ifdef _WIN32 + timeBeginPeriod(1); +#endif + os::logger::Init(); bool forceInstaller = false; bool forceDLCInstaller = false; + bool sdlVideoDefault = false; for (uint32_t i = 1; i < argc; i++) { forceInstaller = forceInstaller || (strcmp(argv[i], "--install") == 0); forceDLCInstaller = forceDLCInstaller || (strcmp(argv[i], "--install-dlc") == 0); + sdlVideoDefault = sdlVideoDefault || (strcmp(argv[i], "--sdl-video-default") == 0); } Config::Load(); HostStartup(); - bool isGameInstalled = Installer::checkGameInstall("."); + bool isGameInstalled = Installer::checkGameInstall(GAME_INSTALL_DIRECTORY); bool runInstallerWizard = forceInstaller || forceDLCInstaller || !isGameInstalled; if (runInstallerWizard) { - Video::CreateHostDevice(); + Video::CreateHostDevice(sdlVideoDefault); - if (!InstallerWizard::Run(isGameInstalled && forceDLCInstaller)) + if (!InstallerWizard::Run(GAME_INSTALL_DIRECTORY, isGameInstalled && forceDLCInstaller)) { return 1; } @@ -175,14 +173,15 @@ int main(int argc, char *argv[]) KiSystemStartup(); - uint32_t entry = LdrLoadModule(FileSystem::TransformPath(GAME_XEX_PATH)); + const char *modulePath = FileSystem::TransformPath(GAME_XEX_PATH); + uint32_t entry = LdrLoadModule(std::u8string_view((const char8_t*)(modulePath))); if (!runInstallerWizard) - Video::CreateHostDevice(); + Video::CreateHostDevice(sdlVideoDefault); Video::StartPipelinePrecompilation(); - GuestThread::Start(entry); + GuestThread::Start({ entry, 0, 0 }); return 0; } diff --git a/UnleashedRecomp/misc_impl.cpp b/UnleashedRecomp/misc_impl.cpp index a89f2664..a58b58fd 100644 --- a/UnleashedRecomp/misc_impl.cpp +++ b/UnleashedRecomp/misc_impl.cpp @@ -1,18 +1,23 @@ #include "stdafx.h" #include +#include -BOOL QueryPerformanceCounterImpl(LARGE_INTEGER* lpPerformanceCount) +uint32_t QueryPerformanceCounterImpl(LARGE_INTEGER* lpPerformanceCount) { - BOOL result = QueryPerformanceCounter(lpPerformanceCount); - ByteSwapInplace(lpPerformanceCount->QuadPart); - return result; + lpPerformanceCount->QuadPart = ByteSwap(std::chrono::steady_clock::now().time_since_epoch().count()); + return TRUE; } -BOOL QueryPerformanceFrequencyImpl(LARGE_INTEGER* lpFrequency) +uint32_t QueryPerformanceFrequencyImpl(LARGE_INTEGER* lpFrequency) { - BOOL result = QueryPerformanceFrequency(lpFrequency); - ByteSwapInplace(lpFrequency->QuadPart); - return result; + constexpr auto Frequency = std::chrono::steady_clock::period::den / std::chrono::steady_clock::period::num; + lpFrequency->QuadPart = ByteSwap(Frequency); + return TRUE; +} + +uint32_t GetTickCountImpl() +{ + return uint32_t(std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count()); } void GlobalMemoryStatusImpl(XLPMEMORYSTATUS lpMemoryStatus) @@ -38,11 +43,15 @@ GUEST_FUNCTION_HOOK(sub_831B5E00, memmove); GUEST_FUNCTION_HOOK(sub_831B0BA0, memset); GUEST_FUNCTION_HOOK(sub_831CCAA0, memset); +#ifdef _WIN32 GUEST_FUNCTION_HOOK(sub_82BD4CA8, OutputDebugStringA); +#else +GUEST_FUNCTION_STUB(sub_82BD4CA8); +#endif GUEST_FUNCTION_HOOK(sub_82BD4AC8, QueryPerformanceCounterImpl); GUEST_FUNCTION_HOOK(sub_831CD040, QueryPerformanceFrequencyImpl); -GUEST_FUNCTION_HOOK(sub_831CDAD0, GetTickCount); +GUEST_FUNCTION_HOOK(sub_831CDAD0, GetTickCountImpl); GUEST_FUNCTION_HOOK(sub_82BD4BC0, GlobalMemoryStatusImpl); diff --git a/UnleashedRecomp/mutex.h b/UnleashedRecomp/mutex.h index 9cc12b36..9656d521 100644 --- a/UnleashedRecomp/mutex.h +++ b/UnleashedRecomp/mutex.h @@ -1,5 +1,7 @@ #pragma once +#ifdef _WIN32 + struct Mutex : CRITICAL_SECTION { Mutex() @@ -20,4 +22,10 @@ struct Mutex : CRITICAL_SECTION { LeaveCriticalSection(this); } -}; \ No newline at end of file +}; + +#else + +using Mutex = std::mutex; + +#endif diff --git a/UnleashedRecomp/os/linux/logger_linux.cpp b/UnleashedRecomp/os/linux/logger_linux.cpp new file mode 100644 index 00000000..910307ba --- /dev/null +++ b/UnleashedRecomp/os/linux/logger_linux.cpp @@ -0,0 +1,17 @@ +#include + +void os::logger::detail::Init() +{ +} + +void os::logger::detail::Log(const std::string_view str, detail::ELogType type, const char* func) +{ + if (func) + { + fmt::println("[{}] {}", func, str); + } + else + { + fmt::println("{}", str); + } +} diff --git a/UnleashedRecomp/os/linux/media_linux.cpp b/UnleashedRecomp/os/linux/media_linux.cpp new file mode 100644 index 00000000..74ec9961 --- /dev/null +++ b/UnleashedRecomp/os/linux/media_linux.cpp @@ -0,0 +1,7 @@ +#include + +bool os::media::detail::IsExternalMediaPlaying() +{ + // This functionality is not supported in Linux. + return false; +} diff --git a/UnleashedRecomp/os/linux/process_linux.cpp b/UnleashedRecomp/os/linux/process_linux.cpp new file mode 100644 index 00000000..ed02c9e9 --- /dev/null +++ b/UnleashedRecomp/os/linux/process_linux.cpp @@ -0,0 +1,57 @@ +#include + +#include + +std::filesystem::path os::process::detail::GetExecutablePath() +{ + char exePath[PATH_MAX] = {}; + if (readlink("/proc/self/exe", exePath, PATH_MAX) > 0) + { + return std::filesystem::path(std::u8string_view((const char8_t*)(exePath))); + } + else + { + return std::filesystem::path(); + } +} + +std::filesystem::path os::process::detail::GetWorkingDirectory() +{ + char cwd[PATH_MAX] = {}; + char *res = getcwd(cwd, sizeof(cwd)); + if (res != nullptr) + { + return std::filesystem::path(std::u8string_view((const char8_t*)(cwd))); + } + else + { + return std::filesystem::path(); + } +} + +bool os::process::detail::StartProcess(const std::filesystem::path path, const std::vector args, std::filesystem::path work) +{ + pid_t pid = fork(); + if (pid < 0) + return false; + + if (pid == 0) + { + setsid(); + + std::u8string workU8 = work.u8string(); + chdir((const char*)(workU8.c_str())); + + std::u8string pathU8 = path.u8string(); + std::vector argStrs; + argStrs.push_back((char*)(pathU8.c_str())); + for (const std::string& arg : args) + argStrs.push_back((char *)(arg.c_str())); + + argStrs.push_back(nullptr); + execvp((const char*)(pathU8.c_str()), argStrs.data()); + raise(SIGKILL); + } + + return true; +} diff --git a/UnleashedRecomp/os/linux/version_linux.cpp b/UnleashedRecomp/os/linux/version_linux.cpp new file mode 100644 index 00000000..17b5b120 --- /dev/null +++ b/UnleashedRecomp/os/linux/version_linux.cpp @@ -0,0 +1,7 @@ +#include + +os::version::detail::OSVersion os::version::detail::GetOSVersion() +{ + assert(false && "Unimplemented."); + return os::version::detail::OSVersion(); +} diff --git a/UnleashedRecomp/os/win32/process_win32.cpp b/UnleashedRecomp/os/win32/process_win32.cpp index 147127af..5e7a567f 100644 --- a/UnleashedRecomp/os/win32/process_win32.cpp +++ b/UnleashedRecomp/os/win32/process_win32.cpp @@ -2,9 +2,9 @@ std::filesystem::path os::process::detail::GetExecutablePath() { - char exePath[MAX_PATH]; + WCHAR exePath[MAX_PATH]; - if (!GetModuleFileNameA(nullptr, exePath, MAX_PATH)) + if (!GetModuleFileNameW(nullptr, exePath, MAX_PATH)) return std::filesystem::path(); return std::filesystem::path(exePath); @@ -12,9 +12,9 @@ std::filesystem::path os::process::detail::GetExecutablePath() std::filesystem::path os::process::detail::GetWorkingDirectory() { - char workPath[MAX_PATH]; + WCHAR workPath[MAX_PATH]; - if (!GetCurrentDirectoryA(MAX_PATH, workPath)) + if (!GetCurrentDirectoryW(MAX_PATH, workPath)) return std::filesystem::path(); return std::filesystem::path(workPath); @@ -28,14 +28,17 @@ bool os::process::detail::StartProcess(const std::filesystem::path path, const s if (work.empty()) work = path.parent_path(); - auto cli = path.string(); + auto cli = path.wstring(); + + // NOTE: We assume the CLI arguments only contain ASCII characters. for (auto& arg : args) - cli += " " + arg; + cli += L" " + std::wstring(arg.begin(), arg.end()); - STARTUPINFOA startInfo{ sizeof(STARTUPINFOA) }; + STARTUPINFOW startInfo{ sizeof(STARTUPINFOW) }; PROCESS_INFORMATION procInfo{}; - - if (!CreateProcessA(path.string().c_str(), cli.data(), nullptr, nullptr, false, 0, nullptr, work.string().c_str(), &startInfo, &procInfo)) + std::wstring pathW = path.wstring(); + std::wstring workW = work.wstring(); + if (!CreateProcessW(pathW.c_str(), cli.data(), nullptr, nullptr, false, 0, nullptr, workW.c_str(), &startInfo, &procInfo)) return false; CloseHandle(procInfo.hProcess); diff --git a/UnleashedRecomp/patches/camera_patches.cpp b/UnleashedRecomp/patches/camera_patches.cpp index 9b70e525..83ec4dfa 100644 --- a/UnleashedRecomp/patches/camera_patches.cpp +++ b/UnleashedRecomp/patches/camera_patches.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include constexpr float m_baseAspectRatio = 16.0f / 9.0f; @@ -8,7 +8,7 @@ constexpr float m_baseAspectRatio = 16.0f / 9.0f; bool CameraAspectRatioMidAsmHook(PPCRegister& r31) { auto pCamera = (SWA::CCamera*)g_memory.Translate(r31.u32); - auto newAspectRatio = (float)Window::s_width / (float)Window::s_height; + auto newAspectRatio = (float)GameWindow::s_width / (float)GameWindow::s_height; // Dynamically adjust horizontal aspect ratio to window dimensions. pCamera->m_HorzAspectRatio = newAspectRatio; @@ -27,7 +27,7 @@ bool CameraBoostAspectRatioMidAsmHook(PPCRegister& r31, PPCRegister& f0, PPCRegi { auto pCamera = (SWA::CCamera*)g_memory.Translate(r31.u32); - if (Window::s_width < Window::s_height) + if (GameWindow::s_width < GameWindow::s_height) { pCamera->m_VertFieldOfView = pCamera->m_HorzFieldOfView + f10.f64; } diff --git a/UnleashedRecomp/patches/fps_patches.cpp b/UnleashedRecomp/patches/fps_patches.cpp index 33055db6..684421cb 100644 --- a/UnleashedRecomp/patches/fps_patches.cpp +++ b/UnleashedRecomp/patches/fps_patches.cpp @@ -1,11 +1,11 @@ #include #include -#include +#include #include #include float m_lastLoadingFrameDelta = 0.0f; -std::chrono::steady_clock::time_point m_lastLoadingFrameTime; +std::chrono::high_resolution_clock::time_point m_lastLoadingFrameTime; void DownForceDeltaTimeFixMidAsmHook(PPCRegister& f0) { diff --git a/UnleashedRecomp/patches/misc_patches.cpp b/UnleashedRecomp/patches/misc_patches.cpp index 3c69e3bc..7a368849 100644 --- a/UnleashedRecomp/patches/misc_patches.cpp +++ b/UnleashedRecomp/patches/misc_patches.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include diff --git a/UnleashedRecomp/patches/player_patches.cpp b/UnleashedRecomp/patches/player_patches.cpp index dfa1142d..d33887a6 100644 --- a/UnleashedRecomp/patches/player_patches.cpp +++ b/UnleashedRecomp/patches/player_patches.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include @@ -126,7 +126,7 @@ PPC_FUNC(sub_823B49D8) { __imp__sub_823B49D8(ctx, base); - SDL_User_EvilSonic(Window::s_pWindow, true); + SDL_User_EvilSonic(GameWindow::s_pWindow, true); } // SWA::Player::CEvilSonicContext::Dtor @@ -135,5 +135,5 @@ PPC_FUNC(sub_823B4590) { __imp__sub_823B4590(ctx, base); - SDL_User_EvilSonic(Window::s_pWindow, false); + SDL_User_EvilSonic(GameWindow::s_pWindow, false); } diff --git a/UnleashedRecomp/patches/video_patches.cpp b/UnleashedRecomp/patches/video_patches.cpp index 5e407feb..e0ff0e05 100644 --- a/UnleashedRecomp/patches/video_patches.cpp +++ b/UnleashedRecomp/patches/video_patches.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include // TODO: to be removed. constexpr float m_baseAspectRatio = 16.0f / 9.0f; @@ -12,7 +12,7 @@ void CSDAspectRatioMidAsmHook(PPCRegister& f1, PPCRegister& f2) if (Config::UIScaleMode == EUIScaleMode::Stretch) return; - auto newAspectRatio = (float)Window::s_width / (float)Window::s_height; + auto newAspectRatio = (float)GameWindow::s_width / (float)GameWindow::s_height; if (newAspectRatio > m_baseAspectRatio) { diff --git a/UnleashedRecomp/stdafx.cpp b/UnleashedRecomp/stdafx.cpp index 90aaf593..7a2e063c 100644 --- a/UnleashedRecomp/stdafx.cpp +++ b/UnleashedRecomp/stdafx.cpp @@ -1,10 +1,4 @@ #define STB_IMAGE_IMPLEMENTATION #include -#define MINIAUDIO_IMPLEMENTATION -#include - -#define ma_offset_pcm_frames_ptr (char*)ma_offset_pcm_frames_ptr -#include - #include "stdafx.h" diff --git a/UnleashedRecomp/stdafx.h b/UnleashedRecomp/stdafx.h index 16e8508a..929f0f58 100644 --- a/UnleashedRecomp/stdafx.h +++ b/UnleashedRecomp/stdafx.h @@ -2,9 +2,21 @@ #define NOMINMAX +#if defined(_WIN32) #include -#include #include +#include + +using Microsoft::WRL::ComPtr; +#elif defined(__linux__) +#include +#include +#endif + +#ifdef SWA_D3D12 +#include +#endif + #include #include #include @@ -22,21 +34,23 @@ #include #include #include -#include +#include #include +#include #include #include -#include +#include #include #include -#include #include #include -#include -#include #include - -using Microsoft::WRL::ComPtr; +#include +#include #include "framework.h" #include "mutex.h" + +#ifndef _WIN32 +#include +#endif diff --git a/UnleashedRecomp/ui/achievement_menu.cpp b/UnleashedRecomp/ui/achievement_menu.cpp index a14b9af7..d5c7b9d9 100644 --- a/UnleashedRecomp/ui/achievement_menu.cpp +++ b/UnleashedRecomp/ui/achievement_menu.cpp @@ -281,9 +281,14 @@ static void DrawAchievement(int rowIndex, float yOffset, Achievement& achievemen return; char buffer[32]; - struct tm time; - localtime_s(&time, ×tamp); - strftime(buffer, sizeof(buffer), "%Y/%m/%d %H:%M", &time); +#ifdef _WIN32 + tm timeStruct; + tm *timePtr = &timeStruct; + localtime_s(timePtr, ×tamp); +#else + tm *timePtr = localtime(×tamp); +#endif + strftime(buffer, sizeof(buffer), "%Y/%m/%d %H:%M", timePtr); fontSize = Scale(12); textSize = g_fntNewRodinDB->CalcTextSizeA(fontSize, FLT_MAX, 0, buffer); diff --git a/UnleashedRecomp/ui/window.cpp b/UnleashedRecomp/ui/game_window.cpp similarity index 61% rename from UnleashedRecomp/ui/window.cpp rename to UnleashedRecomp/ui/game_window.cpp index 01cab078..9b6655e0 100644 --- a/UnleashedRecomp/ui/window.cpp +++ b/UnleashedRecomp/ui/game_window.cpp @@ -1,4 +1,4 @@ -#include "window.h" +#include "game_window.h" #include "sdl_listener.h" #include #include @@ -32,15 +32,15 @@ int Window_OnSDLEvent(void*, SDL_Event* event) if (!(event->key.keysym.mod & KMOD_ALT) || !m_isFullscreenKeyReleased) break; - Config::Fullscreen = Window::SetFullscreen(!Window::IsFullscreen()); + Config::Fullscreen = GameWindow::SetFullscreen(!GameWindow::IsFullscreen()); if (Config::Fullscreen) { - Config::Monitor = Window::GetDisplay(); + Config::Monitor = GameWindow::GetDisplay(); } else { - Config::WindowState = Window::SetMaximised(Config::WindowState == EWindowState::Maximised); + Config::WindowState = GameWindow::SetMaximised(Config::WindowState == EWindowState::Maximised); } // Block holding ALT+ENTER spamming window changes. @@ -51,17 +51,17 @@ int Window_OnSDLEvent(void*, SDL_Event* event) // Restore original window dimensions on F2. case SDLK_F2: - Config::Fullscreen = Window::SetFullscreen(false); - Window::ResetDimensions(); + Config::Fullscreen = GameWindow::SetFullscreen(false); + GameWindow::ResetDimensions(); break; // Recentre window on F3. case SDLK_F3: { - if (Window::IsFullscreen()) + if (GameWindow::IsFullscreen()) break; - Window::SetDimensions(Window::s_width, Window::s_height); + GameWindow::SetDimensions(GameWindow::s_width, GameWindow::s_height); break; } @@ -86,16 +86,16 @@ int Window_OnSDLEvent(void*, SDL_Event* event) switch (event->window.event) { case SDL_WINDOWEVENT_FOCUS_LOST: - Window::s_isFocused = false; + GameWindow::s_isFocused = false; SDL_ShowCursor(SDL_ENABLE); break; case SDL_WINDOWEVENT_FOCUS_GAINED: { - Window::s_isFocused = true; + GameWindow::s_isFocused = true; - if (Window::IsFullscreen()) - SDL_ShowCursor(Window::s_isFullscreenCursorVisible ? SDL_ENABLE : SDL_DISABLE); + if (GameWindow::IsFullscreen()) + SDL_ShowCursor(GameWindow::s_isFullscreenCursorVisible ? SDL_ENABLE : SDL_DISABLE); break; } @@ -110,14 +110,14 @@ int Window_OnSDLEvent(void*, SDL_Event* event) case SDL_WINDOWEVENT_RESIZED: m_isResizing = true; - Window::s_width = event->window.data1; - Window::s_height = event->window.data2; - Window::SetTitle(fmt::format("{} - [{}x{}]", Window::GetTitle(), Window::s_width, Window::s_height).c_str()); + GameWindow::s_width = event->window.data1; + GameWindow::s_height = event->window.data2; + GameWindow::SetTitle(fmt::format("{} - [{}x{}]", GameWindow::GetTitle(), GameWindow::s_width, GameWindow::s_height).c_str()); break; case SDL_WINDOWEVENT_MOVED: - Window::s_x = event->window.data1; - Window::s_y = event->window.data2; + GameWindow::s_x = event->window.data1; + GameWindow::s_y = event->window.data2; break; } @@ -125,12 +125,12 @@ int Window_OnSDLEvent(void*, SDL_Event* event) } case SDL_USER_EVILSONIC: - Window::s_isIconNight = event->user.code; - Window::SetIcon(Window::s_isIconNight); + GameWindow::s_isIconNight = event->user.code; + GameWindow::SetIcon(GameWindow::s_isIconNight); break; } - if (!Window::IsFullscreen()) + if (!GameWindow::IsFullscreen()) { if (event->type == SDL_CONTROLLERBUTTONDOWN || event->type == SDL_CONTROLLERBUTTONUP || event->type == SDL_CONTROLLERAXISMOTION) { @@ -147,12 +147,33 @@ int Window_OnSDLEvent(void*, SDL_Event* event) return 0; } -void Window::Init() +void GameWindow::Init(bool sdlVideoDefault) { - SDL_InitSubSystem(SDL_INIT_VIDEO); +#ifdef __linux__ + SDL_SetHint("SDL_APP_ID", "io.github.hedge_dev.unleashedrecomp"); + + if (!sdlVideoDefault) + { + int videoRes = SDL_VideoInit("wayland"); + if (videoRes != 0) + sdlVideoDefault = true; + } +#else + sdlVideoDefault = true; +#endif + + if (sdlVideoDefault) + SDL_VideoInit(nullptr); + + const char* videoDriverName = SDL_GetCurrentVideoDriver(); + if (videoDriverName != nullptr) + fmt::println("SDL Video Driver: {}", videoDriverName); + SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); SDL_AddEventWatch(Window_OnSDLEvent, s_pWindow); +#ifdef _WIN32 SetProcessDPIAware(); +#endif s_x = Config::WindowX; s_y = Config::WindowY; @@ -163,7 +184,7 @@ void Window::Init() s_x = s_y = SDL_WINDOWPOS_CENTERED; if (!IsPositionValid()) - Window::ResetDimensions(); + GameWindow::ResetDimensions(); s_pWindow = SDL_CreateWindow("SWA", s_x, s_y, s_width, s_height, GetWindowFlags()); @@ -180,21 +201,28 @@ void Window::Init() SDL_VERSION(&info.version); SDL_GetWindowWMInfo(s_pWindow, &info); - s_handle = info.info.win.window; - +#if defined(_WIN32) + s_renderWindow = info.info.win.window; SetDarkTitleBar(true); +#elif defined(SDL_VULKAN_ENABLED) + s_renderWindow = s_pWindow; +#elif defined(__linux__) + s_renderWindow = { info.info.x11.display, info.info.x11.window }; +#else + static_assert(false, "Unknown platform."); +#endif SDL_ShowWindow(s_pWindow); } -void Window::Update() +void GameWindow::Update() { - if (!Window::IsFullscreen() && !Window::IsMaximised() && !s_isChangingDisplay) + if (!GameWindow::IsFullscreen() && !GameWindow::IsMaximised() && !s_isChangingDisplay) { - Config::WindowX = Window::s_x; - Config::WindowY = Window::s_y; - Config::WindowWidth = Window::s_width; - Config::WindowHeight = Window::s_height; + Config::WindowX = GameWindow::s_x; + Config::WindowY = GameWindow::s_y; + Config::WindowWidth = GameWindow::s_width; + Config::WindowHeight = GameWindow::s_height; } if (m_isResizing) diff --git a/UnleashedRecomp/ui/window.h b/UnleashedRecomp/ui/game_window.h similarity index 94% rename from UnleashedRecomp/ui/window.h rename to UnleashedRecomp/ui/game_window.h index 3d980f4d..82ad1446 100644 --- a/UnleashedRecomp/ui/window.h +++ b/UnleashedRecomp/ui/game_window.h @@ -6,6 +6,7 @@ #include #include #include +#include #if _WIN32 #include @@ -15,11 +16,11 @@ #define DEFAULT_WIDTH 1280 #define DEFAULT_HEIGHT 720 -class Window +class GameWindow { public: static inline SDL_Window* s_pWindow; - static inline HWND s_handle; + static inline plume::RenderWindow s_renderWindow; static inline int s_x; static inline int s_y; @@ -88,7 +89,7 @@ public: : 19; // DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 const DWORD useImmersiveDarkMode = isEnabled; - DwmSetWindowAttribute(s_handle, flag, &useImmersiveDarkMode, sizeof(useImmersiveDarkMode)); + DwmSetWindowAttribute(s_renderWindow, flag, &useImmersiveDarkMode, sizeof(useImmersiveDarkMode)); #endif } @@ -109,7 +110,7 @@ public: SDL_SetWindowFullscreen(s_pWindow, 0); SDL_ShowCursor(SDL_ENABLE); - SetIcon(Window::s_isIconNight); + SetIcon(GameWindow::s_isIconNight); SetDimensions(Config::WindowWidth, Config::WindowHeight, Config::WindowX, Config::WindowY); } @@ -198,6 +199,10 @@ public: if (Config::Fullscreen) flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; +#ifdef SDL_VULKAN_ENABLED + flags |= SDL_WINDOW_VULKAN; +#endif + return flags; } @@ -298,6 +303,6 @@ public: return false; } - static void Init(); + static void Init(bool sdlVideoDefault); static void Update(); }; diff --git a/UnleashedRecomp/ui/installer_wizard.cpp b/UnleashedRecomp/ui/installer_wizard.cpp index a18180fe..58328d56 100644 --- a/UnleashedRecomp/ui/installer_wizard.cpp +++ b/UnleashedRecomp/ui/installer_wizard.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include @@ -96,7 +96,7 @@ static double g_appearTime = 0.0; static double g_disappearTime = DBL_MAX; static bool g_isDisappearing = false; -static std::filesystem::path g_installPath = "."; +static std::filesystem::path g_installPath; static std::filesystem::path g_gameSourcePath; static std::filesystem::path g_updateSourcePath; static std::array g_dlcSourcePaths; @@ -133,8 +133,13 @@ static WizardPage g_firstPage = WizardPage::SelectLanguage; static WizardPage g_currentPage = g_firstPage; static std::string g_currentMessagePrompt = ""; static bool g_currentMessagePromptConfirmation = false; +static std::list g_currentPickerResults; +static std::atomic g_currentPickerResultsReady = false; +static std::string g_currentPickerErrorMessage; +static std::unique_ptr g_currentPickerThread; +static bool g_currentPickerVisible = false; +static bool g_currentPickerFolderMode = false; static int g_currentMessageResult = -1; -static bool g_filesPickerSkipUpdate = false; static ImVec2 g_joypadAxis = {}; static int g_currentCursorIndex = -1; static int g_currentCursorDefault = 0; @@ -148,7 +153,7 @@ public: { constexpr float AxisValueRange = 32767.0f; constexpr float AxisTapRange = 0.5f; - if (!InstallerWizard::s_isVisible || !g_currentMessagePrompt.empty()) + if (!InstallerWizard::s_isVisible || !g_currentMessagePrompt.empty() || g_currentPickerVisible) { return; } @@ -217,7 +222,7 @@ public: case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEMOTION: { - for (size_t i = 0; i < g_currentCursorRects.size() && !g_filesPickerSkipUpdate; i++) + for (size_t i = 0; i < g_currentCursorRects.size(); i++) { auto ¤tRect = g_currentCursorRects[i]; if (ImGui::IsMouseHoveringRect(currentRect.first, currentRect.second, false)) @@ -734,7 +739,7 @@ static void DrawButton(ImVec2 min, ImVec2 max, const char *buttonText, bool sour int baser = 0; int baseg = 0; - if (g_currentMessagePrompt.empty() && !sourceButton && buttonEnabled && (alpha >= 1.0f)) + if (g_currentMessagePrompt.empty() && !g_currentPickerVisible && !sourceButton && buttonEnabled && (alpha >= 1.0f)) { bool cursorOnButton = PushCursorRect(min, max, buttonPressed, makeDefault); if (cursorOnButton) @@ -868,58 +873,65 @@ static bool ConvertPathSet(const nfdpathset_t *pathSet, std::list &filePaths) +static void PickerThreadProcess() { - filePaths.clear(); - const nfdpathset_t *pathSet; - nfdresult_t result = NFD_OpenDialogMultipleU8(&pathSet, nullptr, 0, nullptr); - g_filesPickerSkipUpdate = true; - - if (result == NFD_OKAY) + nfdresult_t result = NFD_ERROR; + if (g_currentPickerFolderMode) { - bool pathsConverted = ConvertPathSet(pathSet, filePaths); - NFD_PathSet_Free(pathSet); - return pathsConverted; + result = NFD_PickFolderMultipleN(&pathSet, nullptr); } else { - return false; + result = NFD_OpenDialogMultipleN(&pathSet, nullptr, 0, nullptr); } + + if (result == NFD_OKAY) + { + bool pathsConverted = ConvertPathSet(pathSet, g_currentPickerResults); + NFD_PathSet_Free(pathSet); + } + else if (result == NFD_ERROR) + { + g_currentPickerErrorMessage = NFD_GetError(); + } + + g_currentPickerResultsReady = true; } -static bool ShowFoldersPicker(std::list &folderPaths) +static void ShowPicker(bool folderMode) { - folderPaths.clear(); - - const nfdpathset_t *pathSet; - nfdresult_t result = NFD_PickFolderMultipleU8(&pathSet, nullptr); - g_filesPickerSkipUpdate = true; - - if (result == NFD_OKAY) + if (g_currentPickerThread != nullptr) { - bool pathsConverted = ConvertPathSet(pathSet, folderPaths); - NFD_PathSet_Free(pathSet); - return pathsConverted; + g_currentPickerThread->join(); + g_currentPickerThread.reset(); } + + g_currentPickerResults.clear(); + g_currentPickerFolderMode = folderMode; + g_currentPickerResultsReady = false; + g_currentPickerVisible = true; + + // Optional single thread mode for testing on systems that do not interact well with the separate thread being used for NFD. + constexpr bool singleThreadMode = false; + if (singleThreadMode) + PickerThreadProcess(); else - { - return false; - } + g_currentPickerThread = std::make_unique(PickerThreadProcess); } static void ParseSourcePaths(std::list &paths) @@ -973,7 +985,8 @@ static void ParseSourcePaths(std::list &paths) stringStream << Localise("Installer_Message_InvalidFilesList") << std::endl; for (const std::filesystem::path &path : failedPaths) { - stringStream << std::endl << "- " << Truncate(path.filename().string(), 32, true, true); + std::u8string filenameU8 = path.filename().u8string(); + stringStream << std::endl << "- " << Truncate(std::string(filenameU8.begin(), filenameU8.end()), 32, true, true); } if (isFailedPathsOverLimit) @@ -1012,8 +1025,6 @@ static void DrawLanguagePicker() static void DrawSourcePickers() { - g_filesPickerSkipUpdate = false; - bool buttonPressed = false; std::list paths; if (g_currentPage == WizardPage::SelectGameAndUpdate || g_currentPage == WizardPage::SelectDLC) @@ -1027,9 +1038,9 @@ static void DrawSourcePickers() ImVec2 min = { Scale(AlignToNextGrid(CONTAINER_X) + BOTTOM_X_GAP), Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT) + BOTTOM_Y_GAP) }; ImVec2 max = { Scale(AlignToNextGrid(CONTAINER_X) + BOTTOM_X_GAP + textSize.x * squashRatio), Scale(AlignToNextGrid(CONTAINER_Y + CONTAINER_HEIGHT) + BOTTOM_Y_GAP + BUTTON_HEIGHT) }; DrawButton(min, max, addFilesText.c_str(), false, true, buttonPressed, ADD_BUTTON_MAX_TEXT_WIDTH); - if (buttonPressed && ShowFilesPicker(paths)) + if (buttonPressed) { - ParseSourcePaths(paths); + ShowPicker(false); } min.x += Scale(BOTTOM_X_GAP + textSize.x * squashRatio); @@ -1040,9 +1051,9 @@ static void DrawSourcePickers() max.x = min.x + Scale(textSize.x * squashRatio); DrawButton(min, max, addFolderText.c_str(), false, true, buttonPressed, ADD_BUTTON_MAX_TEXT_WIDTH); - if (buttonPressed && ShowFoldersPicker(paths)) + if (buttonPressed) { - ParseSourcePaths(paths); + ShowPicker(true); } } } @@ -1304,14 +1315,6 @@ static void DrawBorders() static void DrawMessagePrompt() { - if (g_filesPickerSkipUpdate) - { - // If a blocking function like the files picker is called, we must wait one update before actually showing - // the message box, as a lot of time has passed since the last real update. Otherwise, animations will play - // too quickly and input glitches might happen. - return; - } - if (g_currentMessagePrompt.empty()) { return; @@ -1341,6 +1344,25 @@ static void DrawMessagePrompt() } } +static void CheckPickerResults() +{ + if (!g_currentPickerResultsReady) + { + return; + } + + if (!g_currentPickerErrorMessage.empty()) + { + g_currentMessagePrompt = g_currentPickerErrorMessage; + g_currentMessagePromptConfirmation = false; + g_currentPickerErrorMessage.clear(); + } + + ParseSourcePaths(g_currentPickerResults); + g_currentPickerResultsReady = false; + g_currentPickerVisible = false; +} + void InstallerWizard::Init() { auto &io = ImGui::GetIO(); @@ -1379,6 +1401,7 @@ void InstallerWizard::Draw() DrawNextButton(); DrawBorders(); DrawMessagePrompt(); + CheckPickerResults(); if (g_isDisappearing) { @@ -1392,13 +1415,19 @@ void InstallerWizard::Draw() void InstallerWizard::Shutdown() { - // Wait for and erase the thread. + // Wait for and erase the threads. if (g_installerThread != nullptr) { g_installerThread->join(); g_installerThread.reset(); } + if (g_currentPickerThread != nullptr) + { + g_currentPickerThread->join(); + g_currentPickerThread.reset(); + } + // Erase the sources. g_installerSources.game.reset(); g_installerSources.update.reset(); @@ -1418,8 +1447,10 @@ void InstallerWizard::Shutdown() } } -bool InstallerWizard::Run(bool skipGame) +bool InstallerWizard::Run(std::filesystem::path installPath, bool skipGame) { + g_installPath = installPath; + EmbeddedPlayer::Init(); NFD_Init(); @@ -1438,18 +1469,18 @@ bool InstallerWizard::Run(bool skipGame) g_currentPage = g_firstPage; } - Window::SetFullscreenCursorVisibility(true); + GameWindow::SetFullscreenCursorVisibility(true); s_isVisible = true; while (s_isVisible) { SDL_PumpEvents(); SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT); - Window::Update(); + GameWindow::Update(); Video::HostPresent(); } - Window::SetFullscreenCursorVisibility(false); + GameWindow::SetFullscreenCursorVisibility(false); NFD_Quit(); InstallerWizard::Shutdown(); diff --git a/UnleashedRecomp/ui/installer_wizard.h b/UnleashedRecomp/ui/installer_wizard.h index 1b7545b8..488cc0e7 100644 --- a/UnleashedRecomp/ui/installer_wizard.h +++ b/UnleashedRecomp/ui/installer_wizard.h @@ -9,5 +9,5 @@ struct InstallerWizard static void Init(); static void Draw(); static void Shutdown(); - static bool Run(bool skipGame); + static bool Run(std::filesystem::path installPath, bool skipGame); }; diff --git a/UnleashedRecomp/ui/options_menu.cpp b/UnleashedRecomp/ui/options_menu.cpp index 3b495f8c..50e737c0 100644 --- a/UnleashedRecomp/ui/options_menu.cpp +++ b/UnleashedRecomp/ui/options_menu.cpp @@ -1,7 +1,7 @@ #include "options_menu.h" #include "options_menu_thumbnails.h" #include "imgui_utils.h" -#include "window.h" +#include "game_window.h" #include "exports.h" #include @@ -444,7 +444,7 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* conf ImVec2 min = { clipRectMin.x, clipRectMin.y + (optionHeight + optionPadding) * rowIndex + yOffset }; ImVec2 max = { min.x + optionWidth, min.y + optionHeight }; - auto configName = config->GetNameLocalised(); + auto configName = config->GetNameLocalised(Config::Language); auto size = Scale(26.0f); auto textSize = g_seuratFont->CalcTextSizeA(size, FLT_MAX, 0.0f, configName.c_str()); @@ -757,7 +757,7 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef* conf } else { - valueText = config->GetValueLocalised(); + valueText = config->GetValueLocalised(Config::Language); } size = Scale(20.0f); @@ -837,7 +837,7 @@ static void DrawConfigOptions() { // TODO: expose WindowWidth/WindowHeight as WindowSize. - auto displayCount = Window::GetDisplayCount(); + auto displayCount = GameWindow::GetDisplayCount(); auto canChangeMonitor = Config::Fullscreen && displayCount > 1; auto monitorReason = &Localise("Options_Desc_NotAvailableWindowed"); @@ -978,7 +978,7 @@ static void DrawInfoPanel() if (g_selectedItem) { - auto desc = g_selectedItem->GetDescription(); + auto desc = g_selectedItem->GetDescription(Config::Language); auto thumbnail = GetThumbnail(g_selectedItem); if (thumbnail) @@ -997,13 +997,13 @@ static void DrawInfoPanel() auto resScale = round(*(float*)g_selectedItem->GetValue() * 1000) / 1000; std::snprintf(buf, sizeof(buf), desc.c_str(), - (int)((float)Window::s_width * resScale), - (int)((float)Window::s_height * resScale)); + (int)((float)GameWindow::s_width * resScale), + (int)((float)GameWindow::s_height * resScale)); desc = buf; } - desc += "\n\n" + g_selectedItem->GetValueDescription(); + desc += "\n\n" + g_selectedItem->GetValueDescription(Config::Language); } auto size = Scale(26.0f); diff --git a/UnleashedRecomp/ui/window_events.h b/UnleashedRecomp/ui/window_events.h index 6e94e3a6..1f1acaa7 100644 --- a/UnleashedRecomp/ui/window_events.h +++ b/UnleashedRecomp/ui/window_events.h @@ -1,7 +1,7 @@ #pragma once #include -#include "ui/window.h" +#include "ui/game_window.h" #define SDL_USER_EVILSONIC (SDL_USEREVENT + 1) diff --git a/UnleashedRecomp/user/config.cpp b/UnleashedRecomp/user/config.cpp index 71df5315..7cad1414 100644 --- a/UnleashedRecomp/user/config.cpp +++ b/UnleashedRecomp/user/config.cpp @@ -20,7 +20,7 @@ void Config::Load() toml = toml::parse(tomlStream); } - for (auto def : Config::Definitions) + for (auto def : g_configDefinitions) { def->ReadValue(toml); #if _DEBUG @@ -44,7 +44,7 @@ void Config::Save() std::string result; std::string section; - for (auto def : Config::Definitions) + for (auto def : g_configDefinitions) { auto isFirstSection = section.empty(); auto isDefWithSection = section != def->GetSection(); diff --git a/UnleashedRecomp/user/config.h b/UnleashedRecomp/user/config.h index 668dcc78..685c9e06 100644 --- a/UnleashedRecomp/user/config.h +++ b/UnleashedRecomp/user/config.h @@ -1,14 +1,592 @@ #pragma once -#include +#include #include #include +class IConfigDef +{ +public: + virtual ~IConfigDef() = default; + virtual void ReadValue(toml::v3::ex::parse_result& toml) = 0; + virtual void MakeDefault() = 0; + virtual std::string_view GetSection() const = 0; + virtual std::string_view GetName() const = 0; + virtual std::string GetNameLocalised(ELanguage language) const = 0; + virtual std::string GetDescription(ELanguage language) const = 0; + virtual bool IsDefaultValue() const = 0; + virtual const void* GetValue() const = 0; + virtual std::string GetValueLocalised(ELanguage language) const = 0; + virtual std::string GetValueDescription(ELanguage language) const = 0; + virtual std::string GetDefinition(bool withSection = false) const = 0; + virtual std::string ToString(bool strWithQuotes = true) const = 0; + virtual void GetLocaleStrings(std::vector& localeStrings) const = 0; +}; + +#define CONFIG_LOCALE std::unordered_map> +#define CONFIG_ENUM_LOCALE(type) std::unordered_map>> + +#define CONFIG_DEFINE(section, type, name, defaultValue) \ + static inline ConfigDef name{section, #name, defaultValue}; + +#define CONFIG_DEFINE_LOCALISED(section, type, name, defaultValue) \ + static CONFIG_LOCALE g_##name##_locale; \ + static inline ConfigDef name{section, #name, &g_##name##_locale, defaultValue}; + +#define CONFIG_DEFINE_ENUM(section, type, name, defaultValue) \ + static inline ConfigDef name{section, #name, defaultValue, &g_##type##_template}; + +#define CONFIG_DEFINE_ENUM_LOCALISED(section, type, name, defaultValue) \ + static CONFIG_LOCALE g_##name##_locale; \ + static CONFIG_ENUM_LOCALE(type) g_##type##_locale; \ + static inline ConfigDef name{section, #name, &g_##name##_locale, defaultValue, &g_##type##_template, &g_##type##_locale}; + +#define CONFIG_DEFINE_CALLBACK(section, type, name, defaultValue, readCallback) \ + static CONFIG_LOCALE g_##name##_locale; \ + static inline ConfigDef name{section, #name, defaultValue, [](ConfigDef* def) readCallback}; + +#define CONFIG_DEFINE_ENUM_TEMPLATE(type) \ + inline std::unordered_map g_##type##_template = + +#define WINDOWPOS_CENTRED 0x2FFF0000 + +inline std::vector g_configDefinitions; + +CONFIG_DEFINE_ENUM_TEMPLATE(ELanguage) +{ + { "English", ELanguage::English }, + { "Japanese", ELanguage::Japanese }, + { "German", ELanguage::German }, + { "French", ELanguage::French }, + { "Spanish", ELanguage::Spanish }, + { "Italian", ELanguage::Italian } +}; + +enum class EUnleashGaugeBehaviour : uint32_t +{ + Original, + Revised +}; + +CONFIG_DEFINE_ENUM_TEMPLATE(EUnleashGaugeBehaviour) +{ + { "Original", EUnleashGaugeBehaviour::Original }, + { "Revised", EUnleashGaugeBehaviour::Revised } +}; + +enum class ETimeOfDayTransition : uint32_t +{ + Xbox, + PlayStation +}; + +CONFIG_DEFINE_ENUM_TEMPLATE(ETimeOfDayTransition) +{ + { "Xbox", ETimeOfDayTransition::Xbox }, + { "PlayStation", ETimeOfDayTransition::PlayStation } +}; + +enum class EControllerIcons : uint32_t +{ + Auto, + Xbox, + PlayStation +}; + +CONFIG_DEFINE_ENUM_TEMPLATE(EControllerIcons) +{ + { "Auto", EControllerIcons::Auto }, + { "Xbox", EControllerIcons::Xbox }, + { "PlayStation", EControllerIcons::PlayStation } +}; + +enum class EVoiceLanguage : uint32_t +{ + English, + Japanese +}; + +CONFIG_DEFINE_ENUM_TEMPLATE(EVoiceLanguage) +{ + { "English", EVoiceLanguage::English }, + { "Japanese", EVoiceLanguage::Japanese } +}; + +enum class EGraphicsAPI : uint32_t +{ +#ifdef SWA_D3D12 + D3D12, +#endif + Vulkan +}; + +CONFIG_DEFINE_ENUM_TEMPLATE(EGraphicsAPI) +{ +#ifdef SWA_D3D12 + { "D3D12", EGraphicsAPI::D3D12 }, +#endif + { "Vulkan", EGraphicsAPI::Vulkan } +}; + +enum class EWindowState : uint32_t +{ + Normal, + Maximised +}; + +CONFIG_DEFINE_ENUM_TEMPLATE(EWindowState) +{ + { "Normal", EWindowState::Normal }, + { "Maximised", EWindowState::Maximised }, + { "Maximized", EWindowState::Maximised } +}; + +enum class EAspectRatio : uint32_t +{ + Auto, + Square, + Widescreen +}; + +CONFIG_DEFINE_ENUM_TEMPLATE(EAspectRatio) +{ + { "Auto", EAspectRatio::Auto }, + { "4:3", EAspectRatio::Square }, + { "16:9", EAspectRatio::Widescreen } +}; + +enum class ETripleBuffering : uint32_t +{ + Auto, + On, + Off +}; + +CONFIG_DEFINE_ENUM_TEMPLATE(ETripleBuffering) +{ + { "Auto", ETripleBuffering::Auto }, + { "On", ETripleBuffering::On }, + { "Off", ETripleBuffering::Off } +}; + +enum class EAntiAliasing : uint32_t +{ + None = 0, + MSAA2x = 2, + MSAA4x = 4, + MSAA8x = 8 +}; + +CONFIG_DEFINE_ENUM_TEMPLATE(EAntiAliasing) +{ + { "None", EAntiAliasing::None }, + { "2x MSAA", EAntiAliasing::MSAA2x }, + { "4x MSAA", EAntiAliasing::MSAA4x }, + { "8x MSAA", EAntiAliasing::MSAA8x } +}; + +enum class EShadowResolution : int32_t +{ + Original = -1, + x512 = 512, + x1024 = 1024, + x2048 = 2048, + x4096 = 4096, + x8192 = 8192 +}; + +CONFIG_DEFINE_ENUM_TEMPLATE(EShadowResolution) +{ + { "Original", EShadowResolution::Original }, + { "512", EShadowResolution::x512 }, + { "1024", EShadowResolution::x1024 }, + { "2048", EShadowResolution::x2048 }, + { "4096", EShadowResolution::x4096 }, + { "8192", EShadowResolution::x8192 }, +}; + +enum class EGITextureFiltering : uint32_t +{ + Bilinear, + Bicubic +}; + +CONFIG_DEFINE_ENUM_TEMPLATE(EGITextureFiltering) +{ + { "Bilinear", EGITextureFiltering::Bilinear }, + { "Bicubic", EGITextureFiltering::Bicubic } +}; + +enum class EDepthOfFieldQuality : uint32_t +{ + Auto, + Low, + Medium, + High, + Ultra +}; + +CONFIG_DEFINE_ENUM_TEMPLATE(EDepthOfFieldQuality) +{ + { "Auto", EDepthOfFieldQuality::Auto }, + { "Low", EDepthOfFieldQuality::Low }, + { "Medium", EDepthOfFieldQuality::Medium }, + { "High", EDepthOfFieldQuality::High }, + { "Ultra", EDepthOfFieldQuality::Ultra } +}; + +enum class EMotionBlur : uint32_t +{ + Off, + Original, + Enhanced +}; + +CONFIG_DEFINE_ENUM_TEMPLATE(EMotionBlur) +{ + { "Off", EMotionBlur::Off }, + { "Original", EMotionBlur::Original }, + { "Enhanced", EMotionBlur::Enhanced } +}; + +enum class EMovieScaleMode : uint32_t +{ + Stretch, + Fit, + Fill +}; + +CONFIG_DEFINE_ENUM_TEMPLATE(EMovieScaleMode) +{ + { "Stretch", EMovieScaleMode::Stretch }, + { "Fit", EMovieScaleMode::Fit }, + { "Fill", EMovieScaleMode::Fill } +}; + +enum class EUIScaleMode : uint32_t +{ + Stretch, + Edge, + Centre +}; + +CONFIG_DEFINE_ENUM_TEMPLATE(EUIScaleMode) +{ + { "Stretch", EUIScaleMode::Stretch }, + { "Edge", EUIScaleMode::Edge }, + { "Centre", EUIScaleMode::Centre }, + { "Center", EUIScaleMode::Centre } +}; + +template +class ConfigDef final : public IConfigDef +{ +public: + std::string Section{}; + std::string Name{}; + CONFIG_LOCALE* Locale{}; + T DefaultValue{}; + T Value{ DefaultValue }; + std::unordered_map* EnumTemplate; + std::map EnumTemplateReverse{}; + CONFIG_ENUM_LOCALE(T)* EnumLocale{}; + std::function*)> Callback; + + // CONFIG_DEFINE + ConfigDef(std::string section, std::string name, T defaultValue) : Section(section), Name(name), DefaultValue(defaultValue) + { + g_configDefinitions.emplace_back(this); + } + + // CONFIG_DEFINE_LOCALISED + ConfigDef(std::string section, std::string name, CONFIG_LOCALE* nameLocale, T defaultValue) : Section(section), Name(name), Locale(nameLocale), DefaultValue(defaultValue) + { + g_configDefinitions.emplace_back(this); + } + + // CONFIG_DEFINE_ENUM + ConfigDef(std::string section, std::string name, T defaultValue, std::unordered_map* enumTemplate) : Section(section), Name(name), DefaultValue(defaultValue), EnumTemplate(enumTemplate) + { + for (const auto& pair : *EnumTemplate) + EnumTemplateReverse[pair.second] = pair.first; + + g_configDefinitions.emplace_back(this); + } + + // CONFIG_DEFINE_ENUM_LOCALISED + ConfigDef(std::string section, std::string name, CONFIG_LOCALE* nameLocale, T defaultValue, std::unordered_map* enumTemplate, CONFIG_ENUM_LOCALE(T)* enumLocale) : Section(section), Name(name), Locale(nameLocale), DefaultValue(defaultValue), EnumTemplate(enumTemplate), EnumLocale(enumLocale) + { + for (const auto& pair : *EnumTemplate) + EnumTemplateReverse[pair.second] = pair.first; + + g_configDefinitions.emplace_back(this); + } + + // CONFIG_DEFINE_CALLBACK + ConfigDef(std::string section, std::string name, T defaultValue, std::function*)> callback) : Section(section), Name(name), DefaultValue(defaultValue), Callback(callback) + { + g_configDefinitions.emplace_back(this); + } + + void ReadValue(toml::v3::ex::parse_result& toml) override + { + if (auto pSection = toml[Section].as_table()) + { + const auto& section = *pSection; + + if constexpr (std::is_same::value) + { + Value = section[Name].value_or(DefaultValue); + } + else if constexpr (std::is_enum_v) + { + std::string value = section[Name].value_or(std::string()); + auto it = EnumTemplate->find(value); + if (it != EnumTemplate->end()) + { + Value = it->second; + } + else + { + Value = DefaultValue; + } + } + else + { + Value = section[Name].value_or(DefaultValue); + } + + if (Callback) + Callback(this); + } + } + + void MakeDefault() override + { + Value = DefaultValue; + } + + std::string_view GetSection() const override + { + return Section; + } + + std::string_view GetName() const override + { + return Name; + } + + std::string GetNameLocalised(ELanguage language) const override + { + if (!Locale) + return Name; + + if (!Locale->count(language)) + { + if (Locale->count(ELanguage::English)) + { + return std::get<0>(Locale->at(ELanguage::English)); + } + else + { + return Name; + } + } + + return std::get<0>(Locale->at(language)); + } + + std::string GetDescription(ELanguage language) const override + { + if (!Locale) + return ""; + + if (!Locale->count(language)) + { + if (Locale->count(ELanguage::English)) + { + return std::get<1>(Locale->at(ELanguage::English)); + } + else + { + return ""; + } + } + + return std::get<1>(Locale->at(language)); + } + + bool IsDefaultValue() const override + { + return Value == DefaultValue; + } + + const void* GetValue() const override + { + return &Value; + } + + std::string GetValueLocalised(ELanguage language) const override + { + CONFIG_ENUM_LOCALE(T)* locale = nullptr; + + if constexpr (std::is_enum_v) + { + locale = EnumLocale; + } + else if constexpr (std::is_same_v) + { + return Value + ? Localise("Common_On") + : Localise("Common_Off"); + } + + if (!locale) + return ToString(false); + + if (!locale->count(language)) + { + if (locale->count(ELanguage::English)) + { + language = ELanguage::English; + } + else + { + return ToString(false); + } + } + + auto strings = locale->at(language); + + if (!strings.count(Value)) + return ToString(false); + + return std::get<0>(strings.at(Value)); + } + + std::string GetValueDescription(ELanguage language) const override + { + CONFIG_ENUM_LOCALE(T)* locale = nullptr; + + if constexpr (std::is_enum_v) + { + locale = EnumLocale; + } + else if constexpr (std::is_same_v) + { + return ""; + } + + if (!locale) + return ""; + + if (!locale->count(language)) + { + if (locale->count(ELanguage::English)) + { + language = ELanguage::English; + } + else + { + return ""; + } + } + + auto strings = locale->at(language); + + if (!strings.count(Value)) + return ""; + + return std::get<1>(strings.at(Value)); + } + + std::string GetDefinition(bool withSection = false) const override + { + std::string result; + + if (withSection) + result += "[" + Section + "]\n"; + + result += Name + " = " + ToString(); + + return result; + } + + std::string ToString(bool strWithQuotes = true) const override + { + std::string result = "N/A"; + + if constexpr (std::is_same_v) + { + result = fmt::format("{}", Value); + + if (strWithQuotes) + result = fmt::format("\"{}\"", result); + } + else if constexpr (std::is_enum_v) + { + auto it = EnumTemplateReverse.find(Value); + + if (it != EnumTemplateReverse.end()) + result = fmt::format("{}", it->second); + + if (strWithQuotes) + result = fmt::format("\"{}\"", result); + } + else + { + result = fmt::format("{}", Value); + } + + return result; + } + + void GetLocaleStrings(std::vector& localeStrings) const override + { + if (Locale != nullptr) + { + for (auto& [language, nameAndDesc] : *Locale) + { + localeStrings.push_back(std::get<0>(nameAndDesc)); + localeStrings.push_back(std::get<1>(nameAndDesc)); + } + } + + if (EnumLocale != nullptr) + { + for (auto& [language, locale] : *EnumLocale) + { + for (auto& [value, nameAndDesc] : locale) + { + localeStrings.push_back(std::get<0>(nameAndDesc)); + localeStrings.push_back(std::get<1>(nameAndDesc)); + } + } + } + } + + ConfigDef& operator=(const ConfigDef& other) + { + if (this != &other) + Value = other.Value; + + return *this; + } + + operator T() const + { + return Value; + } + + void operator=(const T& other) + { + Value = other; + } +}; + class Config { public: - static inline std::vector Definitions{}; - CONFIG_DEFINE_ENUM_LOCALISED("System", ELanguage, Language, ELanguage::English); CONFIG_DEFINE_LOCALISED("System", bool, Hints, true); CONFIG_DEFINE_LOCALISED("System", bool, ControlTutorial, true); @@ -28,7 +606,12 @@ public: CONFIG_DEFINE_LOCALISED("Audio", bool, MusicAttenuation, false); CONFIG_DEFINE_LOCALISED("Audio", bool, BattleTheme, true); +#ifdef SWA_D3D12 CONFIG_DEFINE_ENUM("Video", EGraphicsAPI, GraphicsAPI, EGraphicsAPI::D3D12); +#else + CONFIG_DEFINE_ENUM("Video", EGraphicsAPI, GraphicsAPI, EGraphicsAPI::Vulkan); +#endif + CONFIG_DEFINE("Video", int32_t, WindowX, WINDOWPOS_CENTRED); CONFIG_DEFINE("Video", int32_t, WindowY, WINDOWPOS_CENTRED); CONFIG_DEFINE("Video", int32_t, WindowWidth, 1280); diff --git a/UnleashedRecomp/user/config_detail.cpp b/UnleashedRecomp/user/config_detail.cpp deleted file mode 100644 index dc4cfb4c..00000000 --- a/UnleashedRecomp/user/config_detail.cpp +++ /dev/null @@ -1,193 +0,0 @@ -#include "config.h" -#include "config_detail.h" -#include - -// CONFIG_DEFINE -template -ConfigDef::ConfigDef(std::string section, std::string name, T defaultValue) : Section(section), Name(name), DefaultValue(defaultValue) -{ - Config::Definitions.emplace_back(this); -} - -// CONFIG_DEFINE_LOCALISED -template -ConfigDef::ConfigDef(std::string section, std::string name, CONFIG_LOCALE* locale, T defaultValue) - : Section(section), Name(name), Locale(locale), DefaultValue(defaultValue) -{ - Config::Definitions.emplace_back(this); -} - -// CONFIG_DEFINE_ENUM -template -ConfigDef::ConfigDef(std::string section, std::string name, T defaultValue, std::unordered_map* enumTemplate) - : Section(section), Name(name), DefaultValue(defaultValue), EnumTemplate(enumTemplate) -{ - for (const auto& pair : *EnumTemplate) - EnumTemplateReverse[pair.second] = pair.first; - - Config::Definitions.emplace_back(this); -} - -// CONFIG_DEFINE_ENUM_LOCALISED -template -ConfigDef::ConfigDef(std::string section, std::string name, CONFIG_LOCALE* locale, T defaultValue, std::unordered_map* enumTemplate, CONFIG_ENUM_LOCALE(T)* enumLocale) - : Section(section), Name(name), Locale(locale), DefaultValue(defaultValue), EnumTemplate(enumTemplate), EnumLocale(enumLocale) -{ - for (const auto& pair : *EnumTemplate) - EnumTemplateReverse[pair.second] = pair.first; - - Config::Definitions.emplace_back(this); -} - -// CONFIG_DEFINE_CALLBACK -template -ConfigDef::ConfigDef(std::string section, std::string name, T defaultValue, std::function*)> callback) - : Section(section), Name(name), DefaultValue(defaultValue), Callback(callback) -{ - Config::Definitions.emplace_back(this); -} - -template -std::string ConfigDef::GetNameLocalised() const -{ - if (!Locale) - return Name; - - if (!Locale->count(Config::Language)) - { - if (Locale->count(ELanguage::English)) - { - return std::get<0>(Locale->at(ELanguage::English)); - } - else - { - return Name; - } - } - - return std::get<0>(Locale->at(Config::Language)); -} - -template -std::string ConfigDef::GetDescription() const -{ - if (!Locale) - return ""; - - if (!Locale->count(Config::Language)) - { - if (Locale->count(ELanguage::English)) - { - return std::get<1>(Locale->at(ELanguage::English)); - } - else - { - return ""; - } - } - - return std::get<1>(Locale->at(Config::Language)); -} - -template -std::string ConfigDef::GetValueLocalised() const -{ - auto language = Config::Language; - CONFIG_ENUM_LOCALE(T)* locale = nullptr; - - if constexpr (std::is_enum_v) - { - locale = EnumLocale; - } - else if constexpr (std::is_same_v) - { - return Value - ? Localise("Common_On") - : Localise("Common_Off"); - } - - if (!locale) - return ToString(false); - - if (!locale->count(language)) - { - if (locale->count(ELanguage::English)) - { - language = ELanguage::English; - } - else - { - return ToString(false); - } - } - - auto strings = locale->at(language); - - if (!strings.count(Value)) - return ToString(false); - - return std::get<0>(strings.at(Value)); -} - -template -std::string ConfigDef::GetValueDescription() const -{ - auto language = Config::Language; - CONFIG_ENUM_LOCALE(T)* locale = nullptr; - - if constexpr (std::is_enum_v) - { - locale = EnumLocale; - } - else if constexpr (std::is_same_v) - { - return ""; - } - - if (!locale) - return ""; - - if (!locale->count(language)) - { - if (locale->count(ELanguage::English)) - { - language = ELanguage::English; - } - else - { - return ""; - } - } - - auto strings = locale->at(language); - - if (!strings.count(Value)) - return ""; - - return std::get<1>(strings.at(Value)); -} - -template -inline void ConfigDef::GetLocaleStrings(std::vector& localeStrings) const -{ - if (Locale != nullptr) - { - for (auto& [language, nameAndDesc] : *Locale) - { - localeStrings.push_back(std::get<0>(nameAndDesc)); - localeStrings.push_back(std::get<1>(nameAndDesc)); - } - } - - if (EnumLocale != nullptr) - { - for (auto& [language, locale] : *EnumLocale) - { - for (auto& [value, nameAndDesc] : locale) - { - localeStrings.push_back(std::get<0>(nameAndDesc)); - localeStrings.push_back(std::get<1>(nameAndDesc)); - } - } - } -} diff --git a/UnleashedRecomp/user/config_detail.h b/UnleashedRecomp/user/config_detail.h deleted file mode 100644 index 86d73d16..00000000 --- a/UnleashedRecomp/user/config_detail.h +++ /dev/null @@ -1,436 +0,0 @@ -#pragma once - -#define CONFIG_LOCALE std::unordered_map> -#define CONFIG_ENUM_LOCALE(type) std::unordered_map>> - -#define CONFIG_DEFINE(section, type, name, defaultValue) \ - static inline ConfigDef name{section, #name, defaultValue}; - -#define CONFIG_DEFINE_LOCALISED(section, type, name, defaultValue) \ - static CONFIG_LOCALE g_##name##_locale; \ - static inline ConfigDef name{section, #name, &g_##name##_locale, defaultValue}; - -#define CONFIG_DEFINE_ENUM(section, type, name, defaultValue) \ - static inline ConfigDef name{section, #name, defaultValue, &g_##type##_template}; - -#define CONFIG_DEFINE_ENUM_LOCALISED(section, type, name, defaultValue) \ - static CONFIG_LOCALE g_##name##_locale; \ - static CONFIG_ENUM_LOCALE(type) g_##type##_locale; \ - static inline ConfigDef name{section, #name, &g_##name##_locale, defaultValue, &g_##type##_template, &g_##type##_locale}; - -#define CONFIG_DEFINE_CALLBACK(section, type, name, defaultValue, readCallback) \ - static CONFIG_LOCALE g_##name##_locale; \ - static inline ConfigDef name{section, #name, defaultValue, [](ConfigDef* def) readCallback}; - -#define CONFIG_DEFINE_ENUM_TEMPLATE(type) \ - inline std::unordered_map g_##type##_template = - -#define WINDOWPOS_CENTRED 0x2FFF0000 - -enum class ELanguage : uint32_t -{ - English = 1, - Japanese, - German, - French, - Spanish, - Italian -}; - -CONFIG_DEFINE_ENUM_TEMPLATE(ELanguage) -{ - { "English", ELanguage::English }, - { "Japanese", ELanguage::Japanese }, - { "German", ELanguage::German }, - { "French", ELanguage::French }, - { "Spanish", ELanguage::Spanish }, - { "Italian", ELanguage::Italian } -}; - -enum class EUnleashGaugeBehaviour : uint32_t -{ - Original, - Revised -}; - -CONFIG_DEFINE_ENUM_TEMPLATE(EUnleashGaugeBehaviour) -{ - { "Original", EUnleashGaugeBehaviour::Original }, - { "Revised", EUnleashGaugeBehaviour::Revised } -}; - -enum class ETimeOfDayTransition : uint32_t -{ - Xbox, - PlayStation -}; - -CONFIG_DEFINE_ENUM_TEMPLATE(ETimeOfDayTransition) -{ - { "Xbox", ETimeOfDayTransition::Xbox }, - { "PlayStation", ETimeOfDayTransition::PlayStation } -}; - -enum class EControllerIcons : uint32_t -{ - Auto, - Xbox, - PlayStation -}; - -CONFIG_DEFINE_ENUM_TEMPLATE(EControllerIcons) -{ - { "Auto", EControllerIcons::Auto }, - { "Xbox", EControllerIcons::Xbox }, - { "PlayStation", EControllerIcons::PlayStation } -}; - -enum class EVoiceLanguage : uint32_t -{ - English, - Japanese -}; - -CONFIG_DEFINE_ENUM_TEMPLATE(EVoiceLanguage) -{ - { "English", EVoiceLanguage::English }, - { "Japanese", EVoiceLanguage::Japanese } -}; - -enum class EGraphicsAPI : uint32_t -{ - D3D12, - Vulkan -}; - -CONFIG_DEFINE_ENUM_TEMPLATE(EGraphicsAPI) -{ - { "D3D12", EGraphicsAPI::D3D12 }, - { "Vulkan", EGraphicsAPI::Vulkan } -}; - -enum class EWindowState : uint32_t -{ - Normal, - Maximised -}; - -CONFIG_DEFINE_ENUM_TEMPLATE(EWindowState) -{ - { "Normal", EWindowState::Normal }, - { "Maximised", EWindowState::Maximised }, - { "Maximized", EWindowState::Maximised } -}; - -enum class EAspectRatio : uint32_t -{ - Auto, - Square, - Widescreen -}; - -CONFIG_DEFINE_ENUM_TEMPLATE(EAspectRatio) -{ - { "Auto", EAspectRatio::Auto }, - { "4:3", EAspectRatio::Square }, - { "16:9", EAspectRatio::Widescreen } -}; - -enum class ETripleBuffering : uint32_t -{ - Auto, - On, - Off -}; - -CONFIG_DEFINE_ENUM_TEMPLATE(ETripleBuffering) -{ - { "Auto", ETripleBuffering::Auto }, - { "On", ETripleBuffering::On }, - { "Off", ETripleBuffering::Off } -}; - -enum class EAntiAliasing : uint32_t -{ - None = 0, - MSAA2x = 2, - MSAA4x = 4, - MSAA8x = 8 -}; - -CONFIG_DEFINE_ENUM_TEMPLATE(EAntiAliasing) -{ - { "None", EAntiAliasing::None }, - { "2x MSAA", EAntiAliasing::MSAA2x }, - { "4x MSAA", EAntiAliasing::MSAA4x }, - { "8x MSAA", EAntiAliasing::MSAA8x } -}; - -enum class EShadowResolution : int32_t -{ - Original = -1, - x512 = 512, - x1024 = 1024, - x2048 = 2048, - x4096 = 4096, - x8192 = 8192 -}; - -CONFIG_DEFINE_ENUM_TEMPLATE(EShadowResolution) -{ - { "Original", EShadowResolution::Original }, - { "512", EShadowResolution::x512 }, - { "1024", EShadowResolution::x1024 }, - { "2048", EShadowResolution::x2048 }, - { "4096", EShadowResolution::x4096 }, - { "8192", EShadowResolution::x8192 }, -}; - -enum class EGITextureFiltering : uint32_t -{ - Bilinear, - Bicubic -}; - -CONFIG_DEFINE_ENUM_TEMPLATE(EGITextureFiltering) -{ - { "Bilinear", EGITextureFiltering::Bilinear }, - { "Bicubic", EGITextureFiltering::Bicubic } -}; - -enum class EDepthOfFieldQuality : uint32_t -{ - Auto, - Low, - Medium, - High, - Ultra -}; - -CONFIG_DEFINE_ENUM_TEMPLATE(EDepthOfFieldQuality) -{ - { "Auto", EDepthOfFieldQuality::Auto }, - { "Low", EDepthOfFieldQuality::Low }, - { "Medium", EDepthOfFieldQuality::Medium }, - { "High", EDepthOfFieldQuality::High }, - { "Ultra", EDepthOfFieldQuality::Ultra } -}; - -enum class EMotionBlur : uint32_t -{ - Off, - Original, - Enhanced -}; - -CONFIG_DEFINE_ENUM_TEMPLATE(EMotionBlur) -{ - { "Off", EMotionBlur::Off }, - { "Original", EMotionBlur::Original }, - { "Enhanced", EMotionBlur::Enhanced } -}; - -enum class EMovieScaleMode : uint32_t -{ - Stretch, - Fit, - Fill -}; - -CONFIG_DEFINE_ENUM_TEMPLATE(EMovieScaleMode) -{ - { "Stretch", EMovieScaleMode::Stretch }, - { "Fit", EMovieScaleMode::Fit }, - { "Fill", EMovieScaleMode::Fill } -}; - -enum class EUIScaleMode : uint32_t -{ - Stretch, - Edge, - Centre -}; - -CONFIG_DEFINE_ENUM_TEMPLATE(EUIScaleMode) -{ - { "Stretch", EUIScaleMode::Stretch }, - { "Edge", EUIScaleMode::Edge }, - { "Centre", EUIScaleMode::Centre }, - { "Center", EUIScaleMode::Centre } -}; - -class IConfigDef -{ -public: - virtual ~IConfigDef() = default; - virtual void ReadValue(toml::v3::ex::parse_result& toml) = 0; - virtual void MakeDefault() = 0; - virtual std::string_view GetSection() const = 0; - virtual std::string_view GetName() const = 0; - virtual std::string GetNameLocalised() const = 0; - virtual std::string GetDescription() const = 0; - virtual bool IsDefaultValue() const = 0; - virtual const void* GetValue() const = 0; - virtual std::string GetValueLocalised() const = 0; - virtual std::string GetValueDescription() const = 0; - virtual std::string GetDefinition(bool withSection = false) const = 0; - virtual std::string ToString(bool strWithQuotes = true) const = 0; - virtual void GetLocaleStrings(std::vector& localeStrings) const = 0; -}; - -template -class ConfigDef final : public IConfigDef -{ -public: - std::string Section{}; - std::string Name{}; - CONFIG_LOCALE* Locale{}; - T DefaultValue{}; - T Value{ DefaultValue }; - std::unordered_map* EnumTemplate; - std::map EnumTemplateReverse{}; - CONFIG_ENUM_LOCALE(T)* EnumLocale{}; - std::function*)> Callback; - - // CONFIG_DEFINE - ConfigDef(std::string section, std::string name, T defaultValue); - - // CONFIG_DEFINE_LOCALISED - ConfigDef(std::string section, std::string name, CONFIG_LOCALE* nameLocale, T defaultValue); - - // CONFIG_DEFINE_ENUM - ConfigDef(std::string section, std::string name, T defaultValue, std::unordered_map* enumTemplate); - - // CONFIG_DEFINE_ENUM_LOCALISED - ConfigDef(std::string section, std::string name, CONFIG_LOCALE* nameLocale, T defaultValue, std::unordered_map* enumTemplate, CONFIG_ENUM_LOCALE(T)* enumLocale); - - // CONFIG_DEFINE_CALLBACK - ConfigDef(std::string section, std::string name, T defaultValue, std::function*)> callback); - - void ReadValue(toml::v3::ex::parse_result& toml) override - { - if (auto pSection = toml[Section].as_table()) - { - const auto& section = *pSection; - - if constexpr (std::is_same::value) - { - Value = section[Name].value_or(DefaultValue); - } - else if constexpr (std::is_enum_v) - { - std::string value = section[Name].value_or(std::string()); - auto it = EnumTemplate->find(value); - if (it != EnumTemplate->end()) - { - Value = it->second; - } - else - { - Value = DefaultValue; - } - } - else - { - Value = section[Name].value_or(DefaultValue); - } - - if (Callback) - Callback(this); - } - } - - void MakeDefault() override - { - Value = DefaultValue; - } - - std::string_view GetSection() const override - { - return Section; - } - - std::string_view GetName() const override - { - return Name; - } - - std::string GetNameLocalised() const override; - - std::string GetDescription() const override; - - bool IsDefaultValue() const override - { - return Value == DefaultValue; - } - - const void* GetValue() const override - { - return &Value; - } - - std::string GetValueLocalised() const override; - - std::string GetValueDescription() const override; - - std::string GetDefinition(bool withSection = false) const override - { - std::string result; - - if (withSection) - result += "[" + Section + "]\n"; - - result += Name + " = " + ToString(); - - return result; - } - - std::string ToString(bool strWithQuotes = true) const override - { - std::string result = "N/A"; - - if constexpr (std::is_same_v) - { - result = fmt::format("{}", Value); - - if (strWithQuotes) - result = fmt::format("\"{}\"", result); - } - else if constexpr (std::is_enum_v) - { - auto it = EnumTemplateReverse.find(Value); - - if (it != EnumTemplateReverse.end()) - result = fmt::format("{}", it->second); - - if (strWithQuotes) - result = fmt::format("\"{}\"", result); - } - else - { - result = fmt::format("{}", Value); - } - - return result; - } - - void GetLocaleStrings(std::vector& localeStrings) const override; - - ConfigDef& operator=(const ConfigDef& other) - { - if (this != &other) - Value = other.Value; - - return *this; - } - - operator T() const - { - return Value; - } - - void operator=(const T& other) - { - Value = other; - } -}; diff --git a/UnleashedRecomp/user/paths.h b/UnleashedRecomp/user/paths.h index 5af9d4ba..53a40e8b 100644 --- a/UnleashedRecomp/user/paths.h +++ b/UnleashedRecomp/user/paths.h @@ -2,24 +2,48 @@ #define USER_DIRECTORY "SWA" +#ifndef GAME_INSTALL_DIRECTORY +#define GAME_INSTALL_DIRECTORY "." +#endif + inline std::filesystem::path GetGamePath() { - return std::filesystem::current_path(); + return GAME_INSTALL_DIRECTORY; } inline std::filesystem::path GetUserPath() { - if (std::filesystem::exists("portable.txt")) - return std::filesystem::current_path(); + if (std::filesystem::exists(GAME_INSTALL_DIRECTORY "/portable.txt")) + return GAME_INSTALL_DIRECTORY; - std::filesystem::path userPath{}; + std::filesystem::path userPath; - // TODO: handle platform-specific paths. +#if defined(_WIN32) PWSTR knownPath = NULL; if (SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, NULL, &knownPath) == S_OK) userPath = std::filesystem::path{ knownPath } / USER_DIRECTORY; CoTaskMemFree(knownPath); +#elif defined(__linux__) + const char *homeDir = getenv("HOME"); + if (homeDir == nullptr) + { + homeDir = getpwuid(getuid())->pw_dir; + } + + if (homeDir != nullptr) + { + // Prefer to store in the .config directory if it exists. Use the home directory otherwise. + std::filesystem::path homePath = homeDir; + std::filesystem::path configPath = homePath / ".config"; + if (std::filesystem::exists(configPath)) + userPath = configPath / USER_DIRECTORY; + else + userPath = homePath / ("." USER_DIRECTORY); + } +#else + static_assert(false, "GetUserPath() not implemented for this platform."); +#endif return userPath; } diff --git a/UnleashedRecompLib/CMakeLists.txt b/UnleashedRecompLib/CMakeLists.txt index f0cff971..98d41dd1 100644 --- a/UnleashedRecompLib/CMakeLists.txt +++ b/UnleashedRecompLib/CMakeLists.txt @@ -1,11 +1,17 @@ project("UnleashedRecompLib") add_compile_options( - "/fp:strict" - "-march=sandybridge" - "-fno-strict-aliasing" + -march=sandybridge + -mlzcnt + -fno-strict-aliasing ) +if (WIN32) + add_compile_options(/fp:strict) +else() + add_compile_options(-ffp-model=strict) +endif() + target_compile_definitions(PowerRecomp PRIVATE CONFIG_FILE_PATH=\"${CMAKE_CURRENT_SOURCE_DIR}/config/SWA.toml\") set(SWA_PPC_RECOMPILED_SOURCES @@ -21,7 +27,7 @@ endforeach() add_custom_command( OUTPUT ${SWA_PPC_RECOMPILED_SOURCES} - COMMAND PowerRecomp + COMMAND $ DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/private/default.xex" "${CMAKE_CURRENT_SOURCE_DIR}/config/SWA.toml" ) @@ -41,7 +47,7 @@ file(GLOB SHADER_RECOMP_SOURCES add_custom_command( OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/shader/shader_cache.cpp" - COMMAND ShaderRecomp + COMMAND $ DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/private/shader.ar" ${SHADER_RECOMP_SOURCES} ${SHADER_RECOMP_INCLUDE} ) diff --git a/flatpak/README.md b/flatpak/README.md new file mode 100644 index 00000000..e191c794 --- /dev/null +++ b/flatpak/README.md @@ -0,0 +1,10 @@ +Build +```sh +flatpak-builder --force-clean --user --install-deps-from=flathub --repo=repo --install builddir io.github.hedge_dev.unleashedrecomp.json +``` + +Bundle +```sh +flatpak build-bundle repo io.github.hedge_dev.unleashedrecomp.flatpak io.github.hedge_dev.unleashedrecomp --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo +``` + diff --git a/flatpak/io.github.hedge_dev.unleashedrecomp.desktop b/flatpak/io.github.hedge_dev.unleashedrecomp.desktop new file mode 100644 index 00000000..8b4694ae --- /dev/null +++ b/flatpak/io.github.hedge_dev.unleashedrecomp.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Name=Unleashed Recompiled +Exec=/app/bin/SWA +Type=Application +Icon=io.github.hedge_dev.unleashedrecomp +Categories=Game; +Comment=Static recompilation of Sonic Unleashed. \ No newline at end of file diff --git a/flatpak/io.github.hedge_dev.unleashedrecomp.json b/flatpak/io.github.hedge_dev.unleashedrecomp.json new file mode 100644 index 00000000..51887c3f --- /dev/null +++ b/flatpak/io.github.hedge_dev.unleashedrecomp.json @@ -0,0 +1,56 @@ +{ + "id": "io.github.hedge_dev.unleashedrecomp", + "runtime": "org.freedesktop.Platform", + "runtime-version": "23.08", + "sdk": "org.freedesktop.Sdk", + "sdk-extensions" : [ "org.freedesktop.Sdk.Extension.llvm18" ], + "finish-args": [ + "--share=network", + "--socket=wayland", + "--socket=fallback-x11", + "--socket=pulseaudio", + "--device=all", + "--filesystem=host", + "--filesystem=/media", + "--filesystem=/run/media", + "--filesystem=/mnt" + ], + "modules": [ + { + "name": "UnleashedRecomp", + "buildsystem": "simple", + "build-commands": [ + "cmake --preset linux-release -DSWA_FLATPAK=ON -DSDL2MIXER_VORBIS=VORBISFILE", + "cmake --build out/build/linux-release", + "mkdir -p /app/bin", + "cp out/build/linux-release/UnleashedRecomp/SWA /app/bin/SWA", + "install -Dm644 UnleashedRecompResources/images/game_icon.png /app/share/icons/hicolor/128x128/apps/${FLATPAK_ID}.png", + "install -Dm644 flatpak/io.github.hedge_dev.unleashedrecomp.metainfo.xml /app/share/metainfo/${FLATPAK_ID}.metainfo.xml", + "install -Dm644 flatpak/io.github.hedge_dev.unleashedrecomp.desktop /app/share/applications/${FLATPAK_ID}.desktop" + ], + "sources": [ + { + "type": "dir", + "path": "../" + }, + { + "type": "file", + "path": "default.xex", + "dest": "UnleashedRecompLib/private" + }, + { + "type": "file", + "path": "shader.ar", + "dest": "UnleashedRecompLib/private" + } + ], + "build-options": { + "append-path": "/usr/lib/sdk/llvm18/bin", + "prepend-ld-library-path": "/usr/lib/sdk/llvm18/lib", + "build-args": [ + "--share=network" + ] + } + } + ] +} diff --git a/flatpak/io.github.hedge_dev.unleashedrecomp.metainfo.xml b/flatpak/io.github.hedge_dev.unleashedrecomp.metainfo.xml new file mode 100644 index 00000000..f268be3e --- /dev/null +++ b/flatpak/io.github.hedge_dev.unleashedrecomp.metainfo.xml @@ -0,0 +1,26 @@ + + + io.github.hedge_dev.unleashedrecomp + + Unleashed Recompiled + Static recompilation of Sonic Unleashed. + + CC0-1.0 + GPL-3.0+ + + + pointing + keyboard + touch + + + +

+ A native PC port of Sonic Unleashed for Xbox 360 achieved through static recompilation. + + https://github.com/hedge-dev/UnleashedRecomp +

+
+ + io.github.hedge_dev.unleashedrecomp.desktop +
\ No newline at end of file diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 914095fe..4ace5405 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -1,7 +1,22 @@ +cmake_policy(SET CMP0077 NEW) + set(MSDF_ATLAS_BUILD_STANDALONE OFF) set(MSDF_ATLAS_USE_SKIA OFF) set(MSDF_ATLAS_NO_ARTERY_FONT ON) set(MSDFGEN_DISABLE_PNG ON) -add_subdirectory(${SWA_THIRDPARTY_ROOT}/o1heap) -add_subdirectory(${SWA_THIRDPARTY_ROOT}/msdf-atlas-gen) +set(SDL2MIXER_DEPS_SHARED OFF) +set(SDL2MIXER_VENDORED ON) +set(SDL2MIXER_FLAC OFF) +set(SDL2MIXER_MOD OFF) +set(SDL2MIXER_MP3 OFF) +set(SDL2MIXER_MIDI OFF) +set(SDL2MIXER_OPUS OFF) +set(SDL2MIXER_VORBIS "VORBISFILE") +set(SDL2MIXER_WAVPACK OFF) + +add_subdirectory("${SWA_THIRDPARTY_ROOT}/msdf-atlas-gen") +add_subdirectory("${SWA_THIRDPARTY_ROOT}/nativefiledialog-extended") +add_subdirectory("${SWA_THIRDPARTY_ROOT}/o1heap") +add_subdirectory("${SWA_THIRDPARTY_ROOT}/SDL") +add_subdirectory("${SWA_THIRDPARTY_ROOT}/SDL_mixer") diff --git a/thirdparty/D3D12MemoryAllocator b/thirdparty/D3D12MemoryAllocator new file mode 160000 index 00000000..e00c4a7c --- /dev/null +++ b/thirdparty/D3D12MemoryAllocator @@ -0,0 +1 @@ +Subproject commit e00c4a7c85cf9c28c6f4a6cc75032736f416410f diff --git a/thirdparty/SDL b/thirdparty/SDL new file mode 160000 index 00000000..1edaad17 --- /dev/null +++ b/thirdparty/SDL @@ -0,0 +1 @@ +Subproject commit 1edaad17218d67b567c149badce9ef0fc67f65fa diff --git a/thirdparty/SDL_mixer b/thirdparty/SDL_mixer new file mode 160000 index 00000000..43799269 --- /dev/null +++ b/thirdparty/SDL_mixer @@ -0,0 +1 @@ +Subproject commit 437992692cf9300f2b2f04be35adc7445a9055bf diff --git a/thirdparty/Vulkan-Headers b/thirdparty/Vulkan-Headers new file mode 160000 index 00000000..14345dab --- /dev/null +++ b/thirdparty/Vulkan-Headers @@ -0,0 +1 @@ +Subproject commit 14345dab231912ee9601136e96ca67a6e1f632e7 diff --git a/thirdparty/VulkanMemoryAllocator b/thirdparty/VulkanMemoryAllocator new file mode 160000 index 00000000..1c35ba99 --- /dev/null +++ b/thirdparty/VulkanMemoryAllocator @@ -0,0 +1 @@ +Subproject commit 1c35ba99ce775f8342d87a83a3f0f696f99c2a39 diff --git a/thirdparty/concurrentqueue b/thirdparty/concurrentqueue new file mode 160000 index 00000000..6dd38b8a --- /dev/null +++ b/thirdparty/concurrentqueue @@ -0,0 +1 @@ +Subproject commit 6dd38b8a1dbaa7863aa907045f32308a56a6ff5d diff --git a/thirdparty/imgui b/thirdparty/imgui new file mode 160000 index 00000000..8199457a --- /dev/null +++ b/thirdparty/imgui @@ -0,0 +1 @@ +Subproject commit 8199457a7d9e453f8d3d9cadc14683fb54a858b5 diff --git a/thirdparty/magic_enum b/thirdparty/magic_enum new file mode 160000 index 00000000..1a1824df --- /dev/null +++ b/thirdparty/magic_enum @@ -0,0 +1 @@ +Subproject commit 1a1824df7ac798177a521eed952720681b0bf482 diff --git a/thirdparty/miniaudio b/thirdparty/miniaudio deleted file mode 160000 index 12a8d4e4..00000000 --- a/thirdparty/miniaudio +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 12a8d4e4911c5ab4f4c089b4d039433975ed8a66 diff --git a/thirdparty/nativefiledialog-extended b/thirdparty/nativefiledialog-extended new file mode 160000 index 00000000..388549a5 --- /dev/null +++ b/thirdparty/nativefiledialog-extended @@ -0,0 +1 @@ +Subproject commit 388549a5badaa7cbd138f5f189f50c67d5bf060c diff --git a/thirdparty/stb b/thirdparty/stb new file mode 160000 index 00000000..5c205738 --- /dev/null +++ b/thirdparty/stb @@ -0,0 +1 @@ +Subproject commit 5c205738c191bcb0abc65c4febfa9bd25ff35234 diff --git a/thirdparty/tiny-AES-c b/thirdparty/tiny-AES-c new file mode 160000 index 00000000..23856752 --- /dev/null +++ b/thirdparty/tiny-AES-c @@ -0,0 +1 @@ +Subproject commit 23856752fbd139da0b8ca6e471a13d5bcc99a08d diff --git a/thirdparty/unordered_dense b/thirdparty/unordered_dense new file mode 160000 index 00000000..d911053e --- /dev/null +++ b/thirdparty/unordered_dense @@ -0,0 +1 @@ +Subproject commit d911053e390816ecc5dedd5a9d6b4bb5ed92b4c9 diff --git a/thirdparty/vcpkg b/thirdparty/vcpkg new file mode 160000 index 00000000..b322364f --- /dev/null +++ b/thirdparty/vcpkg @@ -0,0 +1 @@ +Subproject commit b322364f06308bdd24823f9d8f03fe0cc86fd46f diff --git a/thirdparty/volk b/thirdparty/volk new file mode 160000 index 00000000..447e21b5 --- /dev/null +++ b/thirdparty/volk @@ -0,0 +1 @@ +Subproject commit 447e21b5d92ed8d5271b0d39b071f938fcfa875f diff --git a/toolchains/linux-clang.cmake b/toolchains/linux-clang.cmake new file mode 100644 index 00000000..5a2365c7 --- /dev/null +++ b/toolchains/linux-clang.cmake @@ -0,0 +1,86 @@ +# Copied and adapted from: +# https://github.com/microsoft/vcpkg/blob/7adc2e4d49e8d0efc07a369079faa6bc3dbb90f3/scripts/toolchains/linux.cmake +# +# The original seems to be written only for GCC. This version is changed to use clang and +# LLVM bin tools. + +if(NOT _VCPKG_LINUX_CLANG_TOOLCHAIN) + set(_VCPKG_LINUX_CLANG_TOOLCHAIN 1) + + if(POLICY CMP0056) + cmake_policy(SET CMP0056 NEW) + endif() + if(POLICY CMP0066) + cmake_policy(SET CMP0066 NEW) + endif() + if(POLICY CMP0067) + cmake_policy(SET CMP0067 NEW) + endif() + if(POLICY CMP0137) + cmake_policy(SET CMP0137 NEW) + endif() + list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES + VCPKG_CRT_LINKAGE VCPKG_TARGET_ARCHITECTURE + VCPKG_C_FLAGS VCPKG_CXX_FLAGS + VCPKG_C_FLAGS_DEBUG VCPKG_CXX_FLAGS_DEBUG + VCPKG_C_FLAGS_RELEASE VCPKG_CXX_FLAGS_RELEASE + VCPKG_LINKER_FLAGS VCPKG_LINKER_FLAGS_RELEASE VCPKG_LINKER_FLAGS_DEBUG + ) + + set(CMAKE_SYSTEM_NAME Linux CACHE STRING "") + + # Set compiler to clang + set(CMAKE_C_COMPILER clang) + set(CMAKE_CXX_COMPILER clang++) + SET(CMAKE_ASM_COMPILER clang) + + # Pick target architecture for clang + if(VCPKG_TARGET_ARCHITECTURE STREQUAL "x64") + set(CMAKE_SYSTEM_PROCESSOR x86_64 CACHE STRING "") + set(CLANG_TARGET x86_64-unknown-linux-gnu) + elseif(VCPKG_TARGET_ARCHITECTURE STREQUAL "x86") + set(CMAKE_SYSTEM_PROCESSOR i686 CACHE STRING "") + set(CLANG_TARGET i686-unknown-linux-gnu) + elseif(VCPKG_TARGET_ARCHITECTURE STREQUAL "arm") + set(CMAKE_SYSTEM_PROCESSOR armv7l CACHE STRING "") + set(CLANG_TARGET armv7-unknown-linux-gnueabihf) + elseif(VCPKG_TARGET_ARCHITECTURE STREQUAL "arm64") + set(CMAKE_SYSTEM_PROCESSOR aarch64 CACHE STRING "") + set(CLANG_TARGET aarch64-unknown-linux-gnu) + endif() + if(DEFINED CLANG_TARGET) + set(CMAKE_C_COMPILER_TARGET ${CLANG_TARGET}) + set(CMAKE_CXX_COMPILER_TARGET ${CLANG_TARGET}) + set(CMAKE_ASM_COMPILER_TARGET ${CLANG_TARGET}) + endif() + + if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR STREQUAL CMAKE_HOST_SYSTEM_PROCESSOR) + set(CMAKE_CROSSCOMPILING OFF CACHE BOOL "") + endif() + + string(APPEND CMAKE_C_FLAGS_INIT " -fPIC ${VCPKG_C_FLAGS} ") + string(APPEND CMAKE_CXX_FLAGS_INIT " -fPIC ${VCPKG_CXX_FLAGS} ") + string(APPEND CMAKE_C_FLAGS_DEBUG_INIT " ${VCPKG_C_FLAGS_DEBUG} ") + string(APPEND CMAKE_CXX_FLAGS_DEBUG_INIT " ${VCPKG_CXX_FLAGS_DEBUG} ") + string(APPEND CMAKE_C_FLAGS_RELEASE_INIT " ${VCPKG_C_FLAGS_RELEASE} ") + string(APPEND CMAKE_CXX_FLAGS_RELEASE_INIT " ${VCPKG_CXX_FLAGS_RELEASE} ") + + # Use LLVM's lld linker + set(CMAKE_LINKER_TYPE LLD) + + string(APPEND CMAKE_MODULE_LINKER_FLAGS_INIT " ${VCPKG_LINKER_FLAGS} ") + string(APPEND CMAKE_SHARED_LINKER_FLAGS_INIT " ${VCPKG_LINKER_FLAGS} ") + string(APPEND CMAKE_EXE_LINKER_FLAGS_INIT " ${VCPKG_LINKER_FLAGS} ") + if(VCPKG_CRT_LINKAGE STREQUAL "static") + string(APPEND CMAKE_MODULE_LINKER_FLAGS_INIT "-static ") + string(APPEND CMAKE_SHARED_LINKER_FLAGS_INIT "-static ") + string(APPEND CMAKE_EXE_LINKER_FLAGS_INIT "-static ") + endif() + string(APPEND CMAKE_MODULE_LINKER_FLAGS_DEBUG_INIT " ${VCPKG_LINKER_FLAGS_DEBUG} ") + string(APPEND CMAKE_SHARED_LINKER_FLAGS_DEBUG_INIT " ${VCPKG_LINKER_FLAGS_DEBUG} ") + string(APPEND CMAKE_EXE_LINKER_FLAGS_DEBUG_INIT " ${VCPKG_LINKER_FLAGS_DEBUG} ") + string(APPEND CMAKE_MODULE_LINKER_FLAGS_RELEASE_INIT " ${VCPKG_LINKER_FLAGS_RELEASE} ") + string(APPEND CMAKE_SHARED_LINKER_FLAGS_RELEASE_INIT " ${VCPKG_LINKER_FLAGS_RELEASE} ") + string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE_INIT " ${VCPKG_LINKER_FLAGS_RELEASE} ") + string(APPEND CMAKE_ASM_FLAGS_INIT " ${VCPKG_C_FLAGS} ") +endif() diff --git a/tools/PowerRecomp b/tools/PowerRecomp index 847842cd..ef687298 160000 --- a/tools/PowerRecomp +++ b/tools/PowerRecomp @@ -1 +1 @@ -Subproject commit 847842cd28a2427b9db520d2aaa7416e5dec3822 +Subproject commit ef6872984cdf0b8b4b08128223f513de4f3c913e diff --git a/tools/ShaderRecomp b/tools/ShaderRecomp index 12b38144..cf44a5e6 160000 --- a/tools/ShaderRecomp +++ b/tools/ShaderRecomp @@ -1 +1 @@ -Subproject commit 12b38144b9ff5d131e0d30af22bd38607d07d9fd +Subproject commit cf44a5e6fb7c9931888ee4e6506109787141f87e diff --git a/tools/bc_diff/CMakeLists.txt b/tools/bc_diff/CMakeLists.txt index 5bba3025..cfe70d44 100644 --- a/tools/bc_diff/CMakeLists.txt +++ b/tools/bc_diff/CMakeLists.txt @@ -6,5 +6,4 @@ set(CMAKE_CXX_STANDARD 17) add_executable(bc_diff "bc_diff.cpp") add_compile_definitions(bc_diff PRIVATE _CRT_SECURE_NO_WARNINGS) -find_package(xxHash CONFIG REQUIRED) target_link_libraries(bc_diff PRIVATE xxHash::xxhash) diff --git a/tools/bc_diff/bc_diff.cpp b/tools/bc_diff/bc_diff.cpp index f6e6753b..51ea7b28 100644 --- a/tools/bc_diff/bc_diff.cpp +++ b/tools/bc_diff/bc_diff.cpp @@ -1,5 +1,7 @@ #include "bc_diff.h" +#include #include +#include #include #include #include diff --git a/tools/file_to_c/CMakeLists.txt b/tools/file_to_c/CMakeLists.txt index 96bfb044..6fbafba6 100644 --- a/tools/file_to_c/CMakeLists.txt +++ b/tools/file_to_c/CMakeLists.txt @@ -7,5 +7,4 @@ set(CMAKE_CXX_STANDARD 17) add_executable(file_to_c "file_to_c.cpp") -find_package(zstd CONFIG REQUIRED) -target_link_libraries(file_to_c PRIVATE $,zstd::libzstd_static,zstd::libzstd>) +target_link_libraries(file_to_c PRIVATE $,libzstd_static,libzstd_shared>) diff --git a/tools/file_to_c/file_to_c.cpp b/tools/file_to_c/file_to_c.cpp index 4a9eb490..cbd6f272 100644 --- a/tools/file_to_c/file_to_c.cpp +++ b/tools/file_to_c/file_to_c.cpp @@ -22,6 +22,7 @@ SOFTWARE. */ +#include #include #include #include @@ -109,8 +110,8 @@ int main(int argc, const char** argv) { // Write decompressed size. if (!compressed_contents.empty()) { - output_c_file << "extern size_t " << array_name << "_uncompressed_size;\n"; - output_c_file << "size_t " << array_name << "_uncompressed_size = " << contents.size() << ";\n"; + output_c_file << "extern unsigned long long " << array_name << "_uncompressed_size;\n"; + output_c_file << "unsigned long long " << array_name << "_uncompressed_size = " << contents.size() << ";\n"; } } @@ -125,7 +126,7 @@ int main(int argc, const char** argv) { // Write decompressed size. if (!compressed_contents.empty()) { - output_h_file << "extern size_t " << array_name << "_uncompressed_size;\n"; + output_h_file << "extern unsigned long long " << array_name << "_uncompressed_size;\n"; } output_h_file << diff --git a/tools/fshasher/CMakeLists.txt b/tools/fshasher/CMakeLists.txt index 86221f8b..f30e4852 100644 --- a/tools/fshasher/CMakeLists.txt +++ b/tools/fshasher/CMakeLists.txt @@ -2,6 +2,4 @@ project("fshasher") add_executable(fshasher "fshasher.cpp") -find_package(xxhash CONFIG REQUIRED) - target_link_libraries(fshasher PRIVATE xxHash::xxhash) diff --git a/tools/fshasher/plainargs.h b/tools/fshasher/plainargs.h index 18ee421a..c19bd91b 100644 --- a/tools/fshasher/plainargs.h +++ b/tools/fshasher/plainargs.h @@ -25,8 +25,10 @@ // OTHER DEALINGS IN THE SOFTWARE. // +#include #include #include +#include namespace plainargs { class Result { @@ -144,4 +146,4 @@ namespace plainargs { Result parse(int argc, char *argv[]) { return Result(argc, argv); } -}; \ No newline at end of file +}; diff --git a/vcpkg.json b/vcpkg.json index b1e348a7..d930d54c 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,30 +1,15 @@ { - "builtin-baseline": "e63bd09dc0b7204467705c1c7c71d0e2a3f8860b", + "builtin-baseline": "b322364f06308bdd24823f9d8f03fe0cc86fd46f", "dependencies": [ - "directx-headers", - "directx12-agility", - "d3d12-memory-allocator", - "directx-dxc", - "sdl2", - "unordered-dense", - "volk", - "vulkan-headers", - "vulkan-memory-allocator", - "xxhash", - "pkgconf", - "tomlplusplus", - "zstd", - "stb", - "concurrentqueue", - "tiny-aes-c", { - "name": "imgui", - "features": [ "sdl2-binding" ] + "name": "directx-headers", + "platform": "windows" }, - "magic-enum", - "nativefiledialog-extended", - "freetype", - "libvorbis", - "fmt" + { + "name": "directx12-agility", + "platform": "windows" + }, + "directx-dxc", + "freetype" ] }