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 <dariosamo@gmail.com>

* 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 <dariosamo@gmail.com>
Co-authored-by: Hyper <34012267+hyperbx@users.noreply.github.com>
This commit is contained in:
Skyth (Asilkan) 2024-12-21 00:44:05 +03:00 committed by GitHub
parent f547c7ca6d
commit 67633917bf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
109 changed files with 3373 additions and 2850 deletions

45
.gitmodules vendored
View file

@ -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

View file

@ -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)

View file

@ -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
}
}
]
}

View file

@ -1,6 +1,14 @@
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)
@ -23,8 +31,8 @@ function(BIN2C)
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
COMMAND $<TARGET_FILE: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}"
BYPRODUCTS "${BIN2C_ARGS_DEST_FILE}.h"
COMMENT "Generating binary header for ${BIN2C_ARGS_SOURCE_FILE}..."
)
@ -34,7 +42,6 @@ function(BIN2C)
endfunction()
add_compile_options(
/fp:strict
-march=sandybridge
-fno-strict-aliasing
@ -45,8 +52,17 @@ add_compile_options(
-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
@ -85,6 +101,13 @@ if (WIN32)
"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
@ -97,10 +120,15 @@ set(SWA_GPU_CXX_SOURCES
"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"
)
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"
@ -109,7 +137,7 @@ set(SWA_APU_CXX_SOURCES
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")
list(APPEND SWA_APU_CXX_SOURCES "apu/driver/sdl2_driver.cpp")
endif()
set(SWA_HID_CXX_SOURCES
@ -142,7 +170,7 @@ set(SWA_UI_CXX_SOURCES
"ui/options_menu_thumbnails.cpp"
"ui/options_menu.cpp"
"ui/sdl_listener.cpp"
"ui/window.cpp"
"ui/game_window.cpp"
)
set(SWA_INSTALL_CXX_SOURCES
@ -161,25 +189,47 @@ set(SWA_INSTALL_CXX_SOURCES
"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_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"
@ -197,9 +247,8 @@ set(SWA_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}
${SWA_THIRDPARTY_SOURCES}
)
if (WIN32)
@ -213,98 +262,98 @@ 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)
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)
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 $<TARGET_PROPERTY:Microsoft::DirectX12-Core,IMPORTED_LOCATION_RELEASE> ${CMAKE_CURRENT_BINARY_DIR}/D3D12
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_PROPERTY:Microsoft::DirectX12-Layers,IMPORTED_LOCATION_DEBUG> ${CMAKE_CURRENT_BINARY_DIR}/D3D12
COMMAND_EXPAND_LISTS
)
if (SWA_D3D12)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/D3D12)
add_custom_command(TARGET UnleashedRecomp POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_PROPERTY:Microsoft::DirectX12-Core,IMPORTED_LOCATION_RELEASE> ${CMAKE_CURRENT_BINARY_DIR}/D3D12
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_PROPERTY:Microsoft::DirectX12-Layers,IMPORTED_LOCATION_DEBUG> ${CMAKE_CURRENT_BINARY_DIR}/D3D12
COMMAND_EXPAND_LISTS
)
file(COPY ${PACKAGE_PREFIX_DIR}/bin/dxil.dll DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
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
Microsoft::DirectX-Headers
Microsoft::DirectX-Guids
Microsoft::DirectX12-Agility
Microsoft::DirectXShaderCompiler
comctl32
dxgi
Vulkan::Headers
volk::volk
volk::volk_headers
GPUOpen::VulkanMemoryAllocator
ntdll
fmt::fmt
libzstd_static
msdf-atlas-gen::msdf-atlas-gen
nfd::nfd
o1heap
PowerUtils
SDL2::SDL2-static
SDL2_mixer
tomlplusplus::tomlplusplus
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}
"${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)
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}
)
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}.dxil.h ${FILE_PATH}.spirv.h)
target_sources(UnleashedRecomp PRIVATE ${FILE_PATH}.spirv.h)
endfunction()
function(compile_vertex_shader FILE_PATH)

View file

@ -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<void*>(0x82DFA0B0, in_Size, nullptr, 0, 0);
return GuestToHostFunction<void*>(sub_822C0988, in_Size);
}
inline static void __HH_FREE(const void* in_pData)
inline void __HH_FREE(const void* in_pData)
{
GuestToHostFunction<void>(0x82DF9E50, in_pData);
GuestToHostFunction<void>(sub_822C0270, in_pData);
}
namespace Hedgehog::Base

View file

@ -6,38 +6,16 @@ namespace Hedgehog::Base
{
struct SStringHolder
{
union
{
struct
{
be<uint16_t> Length;
be<uint16_t> RefCount;
};
be<uint32_t> RefCountAndLength;
};
char aStr[1u];
be<uint32_t> 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);
};
}

View file

@ -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<uint32_t> original, incremented;
do
{
originalValue = RefCountAndLength.value;
incrementedValue = ByteSwap(ByteSwap(originalValue) + 1);
} while (InterlockedCompareExchange(reinterpret_cast<LONG*>(&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<uint32_t> original, decremented;
do
{
originalValue = RefCountAndLength.value;
decrementedValue = ByteSwap(ByteSwap(originalValue) - 1);
} while (InterlockedCompareExchange(reinterpret_cast<LONG*>(&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;
}
}

View file

@ -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;
};
}

View file

@ -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<size_t>(-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<size_t>(-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<size_t>(-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<int>(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()];
}
}

View file

@ -28,22 +28,26 @@ namespace boost
void add_ref()
{
std::atomic_ref useCount(use_count_.value);
be<uint32_t> 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<uint32_t> 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<uint32_t> 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)
{

View file

@ -1,7 +1,7 @@
#include <app.h>
#include <install/installer.h>
#include <kernel/function.h>
#include <ui/window.h>
#include <ui/game_window.h>
#include <patches/audio_patches.h>
#include <user/config.h>
#include <os/process.h>
@ -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);

View file

@ -1,6 +1,6 @@
#pragma once
#include <user/config_detail.h>
#include <user/config.h>
class App
{

View file

@ -14,7 +14,7 @@
std::ofstream g_audioDumpStream;
#endif
uint32_t XAudioRegisterRenderDriverClient(XLPDWORD callback, XLPDWORD driver)
uint32_t XAudioRegisterRenderDriverClient(be<uint32_t>* callback, be<uint32_t>* 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;
}

View file

@ -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<uint32_t>* callback, be<uint32_t>* driver);
uint32_t XAudioUnregisterRenderDriverClient(uint32_t driver);
uint32_t XAudioSubmitRenderDriverFrame(uint32_t driver, void* samples);

View file

@ -1,53 +0,0 @@
#include "miniaudio_driver.h"
#include <cpu/code_cache.h>
#include <cpu/guest_thread.h>
#include <cpu/guest_code.h>
#include <kernel/heap.h>
static PPCFunc* g_clientCallback{};
static DWORD g_clientCallbackParam{}; // pointer in guest memory
static ma_device g_audioDevice{};
static std::unique_ptr<GuestThreadContext> 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<GuestThreadContext>(0);
g_audioCtx->ppcContext.r3.u64 = g_clientCallbackParam;
g_audioOutput = reinterpret_cast<uint32_t*>(pOutput);
(*g_clientCallback)(g_audioCtx->ppcContext, reinterpret_cast<uint8_t*>(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<uint32_t*>(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]);
}
}

View file

@ -0,0 +1,128 @@
#include "sdl2_driver.h"
#include <cpu/code_cache.h>
#include <cpu/guest_thread.h>
#include <cpu/guest_code.h>
#include <kernel/heap.h>
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<std::thread> 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<uint8_t*>(g_memory.base));
}
auto next = start + std::chrono::duration<double>(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<double>(std::chrono::steady_clock::now() - start).count() / INTERVAL + 1;
}
}
void XAudioRegisterClient(PPCFunc* callback, uint32_t param)
{
auto* pClientParam = static_cast<uint32_t*>(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<std::thread>(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<be<float>*>(samples);
std::array<float, 2 * XAUDIO_NUM_SAMPLES> 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<be<uint32_t>*>(samples);
std::array<uint32_t, XAUDIO_NUM_CHANNELS * XAUDIO_NUM_SAMPLES> 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));
}
}

View file

@ -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)

View file

@ -10,82 +10,6 @@
#include <res/sounds/sys_actstg_pausewinclose.ogg.h>
#include <res/sounds/sys_actstg_pausewinopen.ogg.h>
#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<std::unique_ptr<ma_sound>, SimultaneousLimit> sounds;
std::array<std::unique_ptr<ma_decoder>, SimultaneousLimit> decoders;
int oldestIndex = 0;
Mix_Chunk* chunk{};
};
static ma_engine g_audioEngine = {};
static std::array<EmbeddedSoundData, size_t(EmbeddedSound::Count)> g_embeddedSoundData = {};
static const std::unordered_map<std::string_view, EmbeddedSound> g_embeddedSoundMap =
{
@ -119,114 +39,61 @@ static const std::unordered_map<std::string_view, EmbeddedSound> 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<ma_decoder>();
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<ma_sound>();
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;
}
else if (ma_sound_at_end(data.sounds[i].get()))
{
// A sound has reached the end, pick it.
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;
}
data.chunk = Mix_LoadWAV_RW(SDL_RWFromConstMem(soundData, soundDataSize), 1);
}
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());
}
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;
}

View file

@ -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<uint64_t>(guest) * 2, sizeof(void*), MEM_COMMIT, PAGE_READWRITE);
*reinterpret_cast<const void**>(bucket + static_cast<uint64_t>(guest) * 2) = host;
#endif
*reinterpret_cast<PPCFunc**>(bucket + static_cast<uint64_t>(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);
}

View file

@ -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;
};

View file

@ -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<PPCFunc*>(g_codeCache.Find(params.function))(ctx.ppcContext, reinterpret_cast<uint8_t*>(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<GuestThreadParameter*>(pParameter);
const auto result = GuestThread::Start(*parameter);
delete parameter;
return result;
if constexpr (sizeof(id) == 4)
return *reinterpret_cast<const uint32_t*>(&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<GuestThreadHandle>(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);

View file

@ -1,12 +1,8 @@
#pragma once
struct PPCContext;
struct GuestThreadParameter
{
uint32_t function;
uint32_t value;
uint32_t flags;
};
#include <kernel/xdm.h>
#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<bool> 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
};

View file

@ -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;
}

View file

@ -4,7 +4,7 @@
#include <kernel/memory.h>
#include <cpu/guest_stack_var.h>
#include <ui/installer_wizard.h>
#include <ui/window.h>
#include <ui/game_window.h>
#include <api/boost/smart_ptr/shared_ptr.h>
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);
}

View file

@ -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());

View file

@ -196,7 +196,7 @@ void ImFontAtlasSnapshot::GenerateGlyphRanges()
{
std::vector<std::string_view> localeStrings;
for (auto& config : Config::Definitions)
for (auto& config : g_configDefinitions)
config->GetLocaleStrings(localeStrings);
std::set<ImWchar> glyphs;

View file

@ -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)) {

View file

@ -29,12 +29,18 @@
typedef struct _NSWindow NSWindow;
#endif
#ifdef SDL_VULKAN_ENABLED
#include <SDL_vulkan.h>
#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;

View file

@ -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<VkSemaphore> waitSemaphoresVector;
waitSemaphoresVector.clear();
for (uint32_t i = 0; i < waitSemaphoreCount; i++) {
@ -2139,6 +2152,15 @@ namespace plume {
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 = &currentPresentId;
presentId.swapchainCount = 1;
presentInfo.pNext = &presentId;
}
VkResult res;
{
const std::scoped_lock queueLock(*commandQueue->queue->mutex);
@ -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<VkExtensionProperties> availableExtensions(extensionCount);
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, availableExtensions.data());
std::unordered_set<std::string> missingRequiredExtensions = RequiredInstanceExtensions;
std::unordered_set<std::string> requiredExtensions = RequiredInstanceExtensions;
std::unordered_set<std::string> supportedOptionalExtensions;
# if DLSS_ENABLED
const std::unordered_set<std::string> 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<char *> 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<std::string> 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<const char *> 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<RenderInterface> CreateVulkanInterface(RenderWindow sdlWindow) {
std::unique_ptr<VulkanInterface> createdInterface = std::make_unique<VulkanInterface>(sdlWindow);
return createdInterface->isValid() ? std::move(createdInterface) : nullptr;
}
#else
std::unique_ptr<RenderInterface> CreateVulkanInterface() {
std::unique_ptr<VulkanInterface> createdInterface = std::make_unique<VulkanInterface>();
return createdInterface->isValid() ? std::move(createdInterface) : nullptr;
}
#endif
};

View file

@ -22,9 +22,18 @@
#define VK_USE_PLATFORM_XLIB_KHR
#endif
#include "volk.h"
#include <volk.h>
#include "vk_mem_alloc.h"
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnullability-completeness"
#endif
#include <vk_mem_alloc.h>
#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<VulkanTexture> 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<RenderDevice> createDevice() override;
const RenderInterfaceCapabilities &getCapabilities() const override;

View file

@ -26,56 +26,68 @@
#include <ui/message_window.h>
#include <ui/options_menu.h>
#include <ui/sdl_listener.h>
#include <ui/window.h>
#include <ui/game_window.h>
#include <user/config.h>
#include <xxHashMap.h>
#if defined(ASYNC_PSO_DEBUG) || defined(PSO_CACHING)
#include <magic_enum.hpp>
#include <magic_enum/magic_enum.hpp>
#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<RenderInterface> CreateD3D12Interface();
#endif
#ifdef SDL_VULKAN_ENABLED
extern std::unique_ptr<RenderInterface> CreateVulkanInterface(RenderWindow sdlWindow);
#else
extern std::unique_ptr<RenderInterface> CreateVulkanInterface();
#endif
}
#pragma pack(push, 1)
@ -165,7 +177,7 @@ struct DirtyStates
static DirtyStates g_dirtyStates(true);
template<typename T>
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<RenderInterface> g_interface;
static std::unique_ptr<RenderDevice> g_device;
@ -197,7 +214,6 @@ static std::unique_ptr<RenderCommandFence> g_copyCommandFence;
static std::unique_ptr<RenderSwapChain> 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<RenderTexture*, RenderTextureLayout> g_barrierMap;
@ -579,13 +595,18 @@ static std::unique_ptr<uint8_t[]> g_buttonBcDiff;
static void LoadEmbeddedResources()
{
const size_t decompressedSize = g_vulkan ? g_spirvCacheDecompressedSize : g_dxilCacheDecompressedSize;
g_shaderCache = std::make_unique<uint8_t[]>(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<uint8_t[]>(g_spirvCacheDecompressedSize);
ZSTD_decompress(g_shaderCache.get(), g_spirvCacheDecompressedSize, g_compressedSpirvCache, g_spirvCacheCompressedSize);
}
#ifdef SWA_D3D12
else
{
g_shaderCache = std::make_unique<uint8_t[]>(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<GuestRenderState, void*> g_setRenderStateFunctions[] =
static const std::pair<GuestRenderState, PPCFunc*> g_setRenderStateFunctions[] =
{
{ D3DRS_ZENABLE, HostToGuestFunction<SetRenderState<D3DRS_ZENABLE>> },
{ D3DRS_ZWRITEENABLE, HostToGuestFunction<SetRenderState<D3DRS_ZWRITEENABLE>> },
@ -1062,6 +1083,8 @@ static GuestShader* g_csdShader;
static std::unique_ptr<GuestShader> 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<GuestShader> 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<void*>(HostToGuestFunction<SetRenderStateUnimplemented>));
g_codeCache.Insert(functionOffset, HostToGuestFunction<SetRenderStateUnimplemented>);
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<uint32_t*>(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<int32_t>(g_dirtyStates.scissorRect, g_scissorRect.right, args.right);
}
static Mutex g_compiledSpecConstantLibraryBlobMutex;
static ankerl::unordered_dense::map<uint32_t, ComPtr<IDxcBlob>> 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<uint32_t, ComPtr<IDxcBlob>> g_compiledSpecConstantLibraryBlobs;
thread_local ComPtr<IDxcCompiler3> s_dxcCompiler;
thread_local ComPtr<IDxcLinker> s_dxcLinker;
thread_local ComPtr<IDxcUtils> 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<PipelineStateQueueItem> 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<uint8_t*>(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<GuestThreadContext> ctx;
while (true)
{
PipelineStateQueueItem queueItem;
g_pipelineStateQueue.wait_dequeue(queueItem);
ctx.ensureValid();
if (ctx == nullptr)
ctx = std::make_unique<GuestThreadContext>(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<boost::shared_ptr<Hedgehog::Database::CDatabaseData>> localPendingDataQueue;
MinimalGuestThreadContext ctx;
std::unique_ptr<GuestThreadContext> 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<GuestThreadContext>(0);
if (g_pendingPipelineStateCache)
{
@ -5673,6 +5714,8 @@ static void ModelConsumerThread()
if (allHandled)
localPendingDataQueue.clear();
std::this_thread::yield();
}
}

View file

@ -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<LONG*>(&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<LONG*>(&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<RenderShader> shader;
struct ShaderCacheEntry* shaderCacheEntry = nullptr;
ankerl::unordered_dense::map<uint32_t, std::unique_ptr<RenderShader>> linkedShaders;
#ifdef SWA_D3D12
std::vector<ComPtr<IDxcBlob>> shaderBlobs;
ComPtr<IDxcBlobEncoding> libraryBlob;
#endif
#ifdef ASYNC_PSO_DEBUG
const char* name = "<unknown>";
#endif
@ -390,7 +396,7 @@ enum GuestTextureAddress
D3DTADDRESS_BORDER = 6
};
extern bool g_needsResize;
inline bool g_needsResize;
extern std::unique_ptr<GuestTexture> LoadTexture(const uint8_t* data, size_t dataSize, RenderComponentMapping componentMapping = RenderComponentMapping());

View file

@ -2,7 +2,8 @@
#include <SDL.h>
#include <user/config.h>
#include <hid/hid_detail.h>
#include <ui/window.h>
#include <ui/game_window.h>
#include <kernel/xdm.h>
#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<Controller, 4> 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;

View file

@ -52,7 +52,7 @@ static std::unique_ptr<VirtualFileSystem> 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;
}
}

View file

@ -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
}

View file

@ -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

View file

@ -39,7 +39,7 @@ std::enable_if_t<(I < sizeof...(TArgs)), void> _tuple_for(std::tuple<TArgs...>&
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<XLPDWORD>(base + ctx.r1.u32 + 0x54 + ((arg - 8) * 8));
return *reinterpret_cast<be<uint32_t>*>(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<typename T>
FORCEINLINE constexpr static std::enable_if_t<!std::is_pointer_v<T>, T> GetValue(PPCContext& ctx, uint8_t* base, size_t idx) noexcept
constexpr static std::enable_if_t<!std::is_pointer_v<T>, T> GetValue(PPCContext& ctx, uint8_t* base, size_t idx) noexcept
{
if constexpr (is_precise_v<T>)
{
@ -142,7 +142,7 @@ struct ArgTranslator
}
template<typename T>
FORCEINLINE constexpr static std::enable_if_t<std::is_pointer_v<T>, T> GetValue(PPCContext& ctx, uint8_t* base, size_t idx) noexcept
constexpr static std::enable_if_t<std::is_pointer_v<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<typename T>
FORCEINLINE constexpr static std::enable_if_t<!std::is_pointer_v<T>, void> SetValue(PPCContext& ctx, uint8_t* base, size_t idx, T value) noexcept
constexpr static std::enable_if_t<!std::is_pointer_v<T>, void> SetValue(PPCContext& ctx, uint8_t* base, size_t idx, T value) noexcept
{
if constexpr (is_precise_v<T>)
{
@ -175,7 +175,7 @@ struct ArgTranslator
}
template<typename T>
FORCEINLINE constexpr static std::enable_if_t<std::is_pointer_v<T>, void> SetValue(PPCContext& ctx, uint8_t* base, size_t idx, T value) noexcept
constexpr static std::enable_if_t<std::is_pointer_v<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<auto Func, int I = 0, typename ...TArgs>
FORCEINLINE void _translate_args_to_host(PPCContext& ctx, uint8_t* base, std::tuple<TArgs...>&) noexcept
void _translate_args_to_host(PPCContext& ctx, uint8_t* base, std::tuple<TArgs...>&) noexcept
requires (I >= sizeof...(TArgs))
{
}
template <auto Func, int I = 0, typename ...TArgs>
FORCEINLINE std::enable_if_t<(I < sizeof...(TArgs)), void> _translate_args_to_host(PPCContext& ctx, uint8_t* base, std::tuple<TArgs...>& tpl) noexcept
std::enable_if_t<(I < sizeof...(TArgs)), void> _translate_args_to_host(PPCContext& ctx, uint8_t* base, std::tuple<TArgs...>& tpl) noexcept
{
using T = std::tuple_element_t<I, std::remove_reference_t<decltype(tpl)>>;
std::get<I>(tpl) = ArgTranslator::GetValue<T>(ctx, base, arg_ordinal_t<Func, I>::value);
@ -255,13 +255,13 @@ FORCEINLINE std::enable_if_t<(I < sizeof...(TArgs)), void> _translate_args_to_ho
}
template<int I = 0, typename ...TArgs>
FORCEINLINE void _translate_args_to_guest(PPCContext& ctx, uint8_t* base, std::tuple<TArgs...>&) noexcept
void _translate_args_to_guest(PPCContext& ctx, uint8_t* base, std::tuple<TArgs...>&) noexcept
requires (I >= sizeof...(TArgs))
{
}
template <int I = 0, typename ...TArgs>
FORCEINLINE std::enable_if_t<(I < sizeof...(TArgs)), void> _translate_args_to_guest(PPCContext& ctx, uint8_t* base, std::tuple<TArgs...>& tpl) noexcept
std::enable_if_t<(I < sizeof...(TArgs)), void> _translate_args_to_guest(PPCContext& ctx, uint8_t* base, std::tuple<TArgs...>& tpl) noexcept
{
using T = std::tuple_element_t<I, std::remove_reference_t<decltype(tpl)>>;
ArgTranslator::SetValue<T>(ctx, base, GatherFunctionArguments(std::tuple<TArgs...>{})[I].ordinal, std::get<I>(tpl));
@ -270,7 +270,7 @@ FORCEINLINE std::enable_if_t<(I < sizeof...(TArgs)), void> _translate_args_to_gu
}
template<auto Func>
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<ret_t>)
@ -308,7 +308,7 @@ FORCEINLINE PPC_FUNC(HostToGuestFunction)
}
template<typename T, typename TFunction, typename... TArgs>
FORCEINLINE T GuestToHostFunction(const TFunction& func, TArgs&&... argv)
T GuestToHostFunction(const TFunction& func, TArgs&&... argv)
{
auto args = std::make_tuple(std::forward<TArgs>(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<T>)
{
return;
}
else if constexpr (std::is_pointer_v<T>)
if constexpr (std::is_pointer_v<T>)
{
return reinterpret_cast<T>((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<T>, "Unsupported return type.");
}
}

View file

@ -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);

View file

@ -19,7 +19,7 @@ struct Heap
size_t Size(void* ptr);
template<typename T, typename... Args>
T* Alloc(Args... args)
T* Alloc(Args&&... args)
{
T* obj = (T*)Alloc(sizeof(T));
new (obj) T(std::forward<Args>(args)...);
@ -27,7 +27,7 @@ struct Heap
}
template<typename T, typename... Args>
T* AllocPhysical(Args... args)
T* AllocPhysical(Args&&... args)
{
T* obj = (T*)AllocPhysical(sizeof(T), alignof(T));
new (obj) T(std::forward<Args>(args)...);

View file

@ -10,11 +10,129 @@
#include <memory>
#include "xam.h"
#include "xdm.h"
#include <timeapi.h>
#include <user/config.h>
#include <os/logger.h>
#ifdef _WIN32
#include <ntstatus.h>
#endif
struct Event final : KernelObject, HostObject<XKEVENT>
{
bool manualReset;
std::atomic<bool> 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<uint32_t> g_keSetEventGeneration;
struct Semaphore final : KernelObject, HostObject<XKSEMAPHORE>
{
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<int64_t>* 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<uint32_t>* isCreator, be<uint64_t>* 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<uint32_t>* 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<uint32_t>* 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<int64_t>* 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<uint32_t>* 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<uint32_t>* handle, void* objAttributes, uint32_t eventType, uint32_t initialState)
{
*handle = ByteSwap((uint32_t)CreateEventA(nullptr, !eventType, !!initialState, nullptr));
*handle = GetKernelHandle(CreateKernelObject<Event>(!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<uint32_t>* BytesInMultiByteString, const be<uint16_t>* 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<int64_t>* 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<uint32_t>* 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<uint32_t>* lpPreviousAffinity)
{
if (lpPreviousAffinity)
*lpPreviousAffinity = 2;
@ -531,38 +649,6 @@ uint32_t KeSetAffinityThread(DWORD Thread, DWORD Affinity, XLPDWORD lpPreviousAf
return 0;
}
struct Event : HostObject<XKEVENT>
{
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<XKSEMAPHORE>
{
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<Event>(*pEvent)->Set();
bool result = QueryKernelObject<Event>(*pEvent)->Set();
++g_keSetEventGeneration;
g_keSetEventGeneration.notify_all();
return result;
}
bool KeResetEvent(XKEVENT* pEvent)
{
return ObQueryObject<Event>(*pEvent)->Reset();
return QueryKernelObject<Event>(*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<int64_t>* 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<Event>(*Object)->handle;
QueryKernelObject<Event>(*Object)->Wait(timeout);
break;
case 5:
handle = ObQueryObject<Semaphore>(*Object)->handle;
QueryKernelObject<Semaphore>(*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<size_t> 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<uint32_t> 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<Mutex> 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<Mutex> lock(g_tlsAllocationMutex);
g_tlsFreeIndices.push_back(dwTlsIndex);
return TRUE;
}
uint32_t XMsgInProcessCall(uint32_t app, uint32_t message, be<uint32_t>* param1, be<uint32_t>* param2)
{
if (message == 0x7001B)
{
@ -939,7 +1076,7 @@ void XamUserReadProfileSettings
uint64_t* xuids,
uint32_t settingCount,
uint32_t* settingIds,
XDWORD* bufferSize,
be<uint32_t>* 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<uint16_t>* UnicodeString, uint32_t MaxBytesInUnicodeString, be<uint32_t>* 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<uint32_t>* Handle, XOBJECT_ATTRIBUTES* ObjectAttributes, uint32_t InitialCount, uint32_t MaximumCount)
{
*Handle = (uint32_t)CreateSemaphoreA(nullptr, InitialCount, MaximumCount, nullptr);
*Handle = GetKernelHandle(CreateKernelObject<Semaphore>(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<uint64_t>* 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<std::chrono::duration<int64_t, std::ratio<1, 10000000>>>(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<uint32_t>* handle, uint32_t stackSize, be<uint32_t>* 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<XDISPATCHER_HEADER>* Objects, DWORD WaitType, DWORD WaitReason, DWORD WaitMode, DWORD Alertable, XLPQWORD Timeout)
uint32_t KeWaitForMultipleObjects(uint32_t Count, xpointer<XDISPATCHER_HEADER>* Objects, uint32_t WaitType, uint32_t WaitReason, uint32_t WaitMode, uint32_t Alertable, be<int64_t>* 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<HANDLE> 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<Event>(*Objects[i].get())->handle;
for (size_t i = 0; i < Count; i++)
QueryKernelObject<Event>(*Objects[i])->Wait(timeout);
}
else
{
thread_local std::vector<Event*> s_events;
s_events.resize(Count);
for (size_t i = 0; i < Count; i++)
s_events[i] = QueryKernelObject<Event>(*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>(semaphore->Header);
return ReleaseSemaphore(object->handle, adjustment, nullptr) ? 0 : 0xFFFFFFFF;
auto* object = QueryKernelObject<Semaphore>(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<uint32_t>* 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>(semaphore->Header);
auto* object = QueryKernelObject<Semaphore>(semaphore->Header);
}
void XMAReleaseContext()
@ -1395,7 +1564,7 @@ void XMACreateContext()
LOG_UTILITY("!!! STUB !!!");
}
// uint32_t XAudioRegisterRenderDriverClient(XLPDWORD callback, XLPDWORD driver)
// uint32_t XAudioRegisterRenderDriverClient(be<uint32_t>* callback, be<uint32_t>* driver)
// {
// //printf("XAudioRegisterRenderDriverClient(): %x %x\n");
//

View file

@ -6,78 +6,140 @@
#include <cpu/guest_thread.h>
#include <os/logger.h>
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 *fileHandle = CreateKernelObject<FileHandle>();
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<uint32_t>* 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);
}
if (lpFileSizeHigh != nullptr)
*lpFileSizeHigh = ByteSwap(*lpFileSizeHigh);
return (uint32_t)(fileSize);
}
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<uint32_t>* 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<int32_t>* 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<FindHandle>();
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<FindHandle>();
return GUEST_HANDLE(ObInsertObject(handle, FindHandleCloser));
FindHandle *findHandle = CreateKernelObject<FindHandle>();
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<uint32_t>* 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)

View file

@ -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)

View file

@ -1,5 +1,10 @@
#pragma once
#ifndef _WIN32
#define MEM_COMMIT 0x00001000
#define MEM_RESERVE 0x00002000
#endif
class Memory
{
public:

View file

@ -2,16 +2,92 @@
#include "xam.h"
#include "xdm.h"
#include <hid/hid.h>
#include <ui/window.h>
#include <ui/game_window.h>
#include <cpu/guest_thread.h>
#include <ranges>
#include <unordered_set>
#include <CommCtrl.h>
#include "xxHashMap.h"
#include <user/paths.h>
#include <SDL.h>
#ifdef _WIN32
#include <CommCtrl.h>
// 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<std::tuple<uint32_t, uint32_t>> notifications;
XamListener(const XamListener&) = delete;
XamListener& operator=(const XamListener&) = delete;
XamListener();
~XamListener();
};
struct XamEnumeratorBase : KernelObject
{
virtual uint32_t Next(void* buffer)
{
return -1;
}
};
template<typename TIterator = std::vector<XHOSTCONTENT_DATA>::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<xxHashMap<XHOSTCONTENT_DATA>, 3> gContentRegistry{};
std::unordered_set<XamListener*> 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<XamListener>(handle);
auto* listener = CreateKernelObject<XamListener>();
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<uint32_t>* pdwId, be<uint32_t>* pParam)
{
auto& listener = *ObTryQueryObject<XamListener>(HOST_HANDLE(hNotification));
auto& listener = *GetKernelObject<XamListener>(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<XWORD>* pwszButtons, DWORD dwFocusButton, DWORD dwFlags, XLPDWORD pResult, XXOVERLAPPED* pOverlapped)
SWA_API uint32_t XamShowMessageBoxUI(uint32_t dwUserIndex, be<uint16_t>* wszTitle, be<uint16_t>* wszText, uint32_t cButtons,
xpointer<be<uint16_t>>* pwszButtons, uint32_t dwFocusButton, uint32_t dwFlags, be<uint32_t>* pResult, XXOVERLAPPED* pOverlapped)
{
int button{};
#ifdef _WIN32
std::vector<std::wstring> texts{};
std::vector<TASKDIALOG_BUTTON> 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<uint32_t>* pcbBuffer, be<uint32_t>* 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<XamEnumerator<decltype(values.begin())>>(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<uint32_t>* pcItemsReturned, XXOVERLAPPED* pOverlapped)
{
auto* enumerator = ObTryQueryObject<XamEnumeratorBase>(HOST_HANDLE(hEnum));
auto* enumerator = GetKernelObject<XamEnumeratorBase>(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<uint32_t>* pdwDisposition, be<uint32_t>* 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)
if (keyboardState[SDL_SCANCODE_RETURN])
state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_START;
if (GetAsyncKeyState(VK_BACK) & 0x8000)
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)

View file

@ -1,108 +1,32 @@
#pragma once
#include <xbox.h>
#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<std::tuple<DWORD, DWORD>> 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<typename TIterator = std::vector<XHOSTCONTENT_DATA>::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<uint32_t>* pdwId, be<uint32_t>* pParam);
SWA_API uint32_t XamShowMessageBoxUI(DWORD dwUserIndex, XWORD* wszTitle, XWORD* wszText, DWORD cButtons,
xpointer<XWORD>* pwszButtons, DWORD dwFocusButton, DWORD dwFlags, XLPDWORD pResult, XXOVERLAPPED* pOverlapped);
SWA_API uint32_t XamShowMessageBoxUI(uint32_t dwUserIndex, be<uint16_t>* wszTitle, be<uint16_t>* wszText, uint32_t cButtons,
xpointer<be<uint16_t>>* pwszButtons, uint32_t dwFocusButton, uint32_t dwFlags, be<uint32_t>* 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<uint32_t>* pcbBuffer, be<uint32_t>* phEnum);
SWA_API uint32_t XamEnumerate(uint32_t hEnum, uint32_t dwFlags, void* pvBuffer, uint32_t cbBuffer, be<uint32_t>* 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<uint32_t>* pdwDisposition, be<uint32_t>* 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);

View file

@ -2,47 +2,36 @@
#include "xdm.h"
#include "freelist.h"
FreeList<std::tuple<std::unique_ptr<char>, 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<char*>(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();
}

View file

@ -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<typename T, typename... Args>
inline T* CreateKernelObject(Args&&... args)
{
static_assert(std::is_base_of_v<KernelObject, T>);
return g_userHeap.AllocPhysical<T>(std::forward<Args>(args)...);
}
template<typename T = KernelObject>
inline T* GetKernelObject(uint32_t handle)
{
assert(handle != GUEST_INVALID_HANDLE_VALUE);
return reinterpret_cast<T*>(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<typename T = void>
inline T* GetInvalidKernelObject()
{
return reinterpret_cast<T*>(g_memory.Translate(GUEST_INVALID_HANDLE_VALUE));
}
extern Mutex g_kernelLock;
template<typename T>
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<typename T::guest_type*>(&header));
header.WaitListHead.Blink = ObInsertObject(obj, DestroyObject<T>);
auto* obj = CreateKernelObject<T>(reinterpret_cast<typename T::guest_type*>(&header));
header.WaitListHead.Blink = g_memory.MapVirtual(obj);
return obj;
}
return static_cast<T*>(ObQueryObject(header.WaitListHead.Blink.get()));
}
template<typename T>
size_t ObInsertObject(T* object)
{
return ObInsertObject(object, DestroyObject<T>);
}
template<typename T>
T* ObCreateObject(int& handle)
{
auto* obj = new T();
handle = ::ObInsertObject(obj, DestroyObject<T>);
return obj;
return static_cast<T*>(g_memory.Translate(header.WaitListHead.Blink.get()));
}
// Get object without initialisation
template<typename T>
T* ObTryQueryObject(XDISPATCHER_HEADER& header)
inline T* TryQueryKernelObject(XDISPATCHER_HEADER& header)
{
if (header.WaitListHead.Flink != OBJECT_SIGNATURE)
return nullptr;
return static_cast<T*>(ObQueryObject(header.WaitListHead.Blink));
}
template<typename T>
T* ObTryQueryObject(int handle)
{
return static_cast<T*>(ObQueryObject(handle));
return static_cast<T*>(g_memory.Translate(header.WaitListHead.Blink.get()));
}

View file

@ -1,5 +1,4 @@
#include <user/config.h>
#include <user/config_detail.h>
#define CONFIG_DEFINE_LOCALE(name) \
CONFIG_LOCALE Config::g_##name##_locale =

View file

@ -1,3 +1,4 @@
#include <user/config.h>
#include <locale/locale.h>
std::unordered_map<std::string, std::unordered_map<ELanguage, std::string>> g_locale =

View file

@ -1,6 +1,14 @@
#pragma once
#include <user/config.h>
enum class ELanguage : uint32_t
{
English = 1,
Japanese,
German,
French,
Spanish,
Italian
};
inline std::string g_localeMissing = "<missing string>";

View file

@ -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;
}

View file

@ -1,18 +1,23 @@
#include "stdafx.h"
#include <kernel/function.h>
#include <kernel/xdm.h>
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::milliseconds>(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);

View file

@ -1,5 +1,7 @@
#pragma once
#ifdef _WIN32
struct Mutex : CRITICAL_SECTION
{
Mutex()
@ -21,3 +23,9 @@ struct Mutex : CRITICAL_SECTION
LeaveCriticalSection(this);
}
};
#else
using Mutex = std::mutex;
#endif

View file

@ -0,0 +1,17 @@
#include <os/logger_detail.h>
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);
}
}

View file

@ -0,0 +1,7 @@
#include <os/media_detail.h>
bool os::media::detail::IsExternalMediaPlaying()
{
// This functionality is not supported in Linux.
return false;
}

View file

@ -0,0 +1,57 @@
#include <os/process_detail.h>
#include <signal.h>
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<std::string> 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<char*> 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;
}

View file

@ -0,0 +1,7 @@
#include <os/version_detail.h>
os::version::detail::OSVersion os::version::detail::GetOSVersion()
{
assert(false && "Unimplemented.");
return os::version::detail::OSVersion();
}

View file

@ -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);

View file

@ -1,6 +1,6 @@
#include <cpu/guest_code.h>
#include <api/SWA.h>
#include <ui/window.h>
#include <ui/game_window.h>
#include <user/config.h>
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;
}

View file

@ -1,11 +1,11 @@
#include <cpu/guest_code.h>
#include <api/SWA.h>
#include <ui/window.h>
#include <ui/game_window.h>
#include <user/config.h>
#include <app.h>
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)
{

View file

@ -1,6 +1,6 @@
#include <cpu/guest_code.h>
#include <api/SWA.h>
#include <ui/window.h>
#include <ui/game_window.h>
#include <user/achievement_data.h>
#include <user/config.h>

View file

@ -1,6 +1,6 @@
#include <cpu/guest_code.h>
#include <api/SWA.h>
#include <ui/window.h>
#include <ui/game_window.h>
#include <ui/window_events.h>
#include <user/config.h>
#include <os/logger.h>
@ -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);
}

View file

@ -1,7 +1,7 @@
#include <cpu/guest_code.h>
#include <user/config.h>
#include <api/SWA.h>
#include <ui/window.h>
#include <ui/game_window.h>
// 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)
{

View file

@ -1,10 +1,4 @@
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#define MINIAUDIO_IMPLEMENTATION
#include <miniaudio.h>
#define ma_offset_pcm_frames_ptr (char*)ma_offset_pcm_frames_ptr
#include <extras/miniaudio_libvorbis.h>
#include "stdafx.h"

View file

@ -2,9 +2,21 @@
#define NOMINMAX
#if defined(_WIN32)
#include <windows.h>
#include <dxcapi.h>
#include <ShlObj_core.h>
#include <wrl/client.h>
using Microsoft::WRL::ComPtr;
#elif defined(__linux__)
#include <unistd.h>
#include <pwd.h>
#endif
#ifdef SWA_D3D12
#include <dxcapi.h>
#endif
#include <algorithm>
#include <mutex>
#include <filesystem>
@ -22,21 +34,23 @@
#include <toml++/toml.hpp>
#include <zstd.h>
#include <stb_image.h>
#include <concurrentqueue/blockingconcurrentqueue.h>
#include <blockingconcurrentqueue.h>
#include <SDL.h>
#include <SDL_mixer.h>
#include <imgui.h>
#include <imgui_internal.h>
#include <imgui_impl_sdl2.h>
#include <backends/imgui_impl_sdl2.h>
#include <o1heap.h>
#include <cstddef>
#include <wrl/client.h>
#include <smolv.h>
#include <set>
#include <miniaudio.h>
#include <extras/miniaudio_libvorbis.h>
#include <fmt/core.h>
using Microsoft::WRL::ComPtr;
#include <list>
#include <semaphore>
#include "framework.h"
#include "mutex.h"
#ifndef _WIN32
#include <sys/mman.h>
#endif

View file

@ -281,9 +281,14 @@ static void DrawAchievement(int rowIndex, float yOffset, Achievement& achievemen
return;
char buffer[32];
struct tm time;
localtime_s(&time, &timestamp);
strftime(buffer, sizeof(buffer), "%Y/%m/%d %H:%M", &time);
#ifdef _WIN32
tm timeStruct;
tm *timePtr = &timeStruct;
localtime_s(timePtr, &timestamp);
#else
tm *timePtr = localtime(&timestamp);
#endif
strftime(buffer, sizeof(buffer), "%Y/%m/%d %H:%M", timePtr);
fontSize = Scale(12);
textSize = g_fntNewRodinDB->CalcTextSizeA(fontSize, FLT_MAX, 0, buffer);

View file

@ -1,4 +1,4 @@
#include "window.h"
#include "game_window.h"
#include "sdl_listener.h"
#include <user/config.h>
#include <SDL_syswm.h>
@ -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)

View file

@ -6,6 +6,7 @@
#include <os/version.h>
#include <ui/window_events.h>
#include <user/config.h>
#include <gpu/rhi/plume_render_interface_types.h>
#if _WIN32
#include <dwmapi.h>
@ -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();
};

View file

@ -13,7 +13,7 @@
#include <ui/button_guide.h>
#include <ui/message_window.h>
#include <ui/sdl_listener.h>
#include <ui/window.h>
#include <ui/game_window.h>
#include <decompressor.h>
#include <res/images/installer/install_001.dds.h>
@ -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<std::filesystem::path, int(DLC::Count)> 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<std::filesystem::path> g_currentPickerResults;
static std::atomic<bool> g_currentPickerResultsReady = false;
static std::string g_currentPickerErrorMessage;
static std::unique_ptr<std::thread> 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 &currentRect = 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<std::filesyste
for (nfdpathsetsize_t i = 0; i < pathSetCount; i++)
{
char *pathSetPath = nullptr;
if (NFD_PathSet_GetPathU8(pathSet, i, &pathSetPath) != NFD_OKAY)
nfdnchar_t *pathSetPath = nullptr;
if (NFD_PathSet_GetPathN(pathSet, i, &pathSetPath) != NFD_OKAY)
{
filePaths.clear();
return false;
}
filePaths.emplace_back(std::filesystem::path(std::u8string_view((const char8_t *)(pathSetPath))));
NFD_PathSet_FreePathU8(pathSetPath);
filePaths.emplace_back(std::filesystem::path(pathSetPath));
NFD_PathSet_FreePathN(pathSetPath);
}
return true;
}
static bool ShowFilesPicker(std::list<std::filesystem::path> &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<std::filesystem::path> &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<std::thread>(PickerThreadProcess);
}
static void ParseSourcePaths(std::list<std::filesystem::path> &paths)
@ -973,7 +985,8 @@ static void ParseSourcePaths(std::list<std::filesystem::path> &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<std::filesystem::path> 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();

View file

@ -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);
};

View file

@ -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 <api/SWA/System/InputState.h>
@ -444,7 +444,7 @@ static void DrawConfigOption(int32_t rowIndex, float yOffset, ConfigDef<T>* 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<T>* 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);

View file

@ -1,7 +1,7 @@
#pragma once
#include <SDL.h>
#include "ui/window.h"
#include "ui/game_window.h"
#define SDL_USER_EVILSONIC (SDL_USEREVENT + 1)

View file

@ -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();

View file

@ -1,14 +1,592 @@
#pragma once
#include <user/config_detail.h>
#include <locale/locale.h>
#include <user/paths.h>
#include <exports.h>
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<std::string_view>& localeStrings) const = 0;
};
#define CONFIG_LOCALE std::unordered_map<ELanguage, std::tuple<std::string, std::string>>
#define CONFIG_ENUM_LOCALE(type) std::unordered_map<ELanguage, std::unordered_map<type, std::tuple<std::string, std::string>>>
#define CONFIG_DEFINE(section, type, name, defaultValue) \
static inline ConfigDef<type> name{section, #name, defaultValue};
#define CONFIG_DEFINE_LOCALISED(section, type, name, defaultValue) \
static CONFIG_LOCALE g_##name##_locale; \
static inline ConfigDef<type> name{section, #name, &g_##name##_locale, defaultValue};
#define CONFIG_DEFINE_ENUM(section, type, name, defaultValue) \
static inline ConfigDef<type> 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<type> 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<type> name{section, #name, defaultValue, [](ConfigDef<type>* def) readCallback};
#define CONFIG_DEFINE_ENUM_TEMPLATE(type) \
inline std::unordered_map<std::string, type> g_##type##_template =
#define WINDOWPOS_CENTRED 0x2FFF0000
inline std::vector<IConfigDef*> 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<typename T>
class ConfigDef final : public IConfigDef
{
public:
std::string Section{};
std::string Name{};
CONFIG_LOCALE* Locale{};
T DefaultValue{};
T Value{ DefaultValue };
std::unordered_map<std::string, T>* EnumTemplate;
std::map<T, std::string> EnumTemplateReverse{};
CONFIG_ENUM_LOCALE(T)* EnumLocale{};
std::function<void(ConfigDef<T>*)> 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<std::string, T>* 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<std::string, T>* 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<void(ConfigDef<T>*)> 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<T, std::string>::value)
{
Value = section[Name].value_or<std::string>(DefaultValue);
}
else if constexpr (std::is_enum_v<T>)
{
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<T>)
{
locale = EnumLocale;
}
else if constexpr (std::is_same_v<T, bool>)
{
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<T>)
{
locale = EnumLocale;
}
else if constexpr (std::is_same_v<T, bool>)
{
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<T, std::string>)
{
result = fmt::format("{}", Value);
if (strWithQuotes)
result = fmt::format("\"{}\"", result);
}
else if constexpr (std::is_enum_v<T>)
{
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<std::string_view>& 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<IConfigDef*> 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);

View file

@ -1,193 +0,0 @@
#include "config.h"
#include "config_detail.h"
#include <locale/locale.h>
// CONFIG_DEFINE
template<typename T>
ConfigDef<T>::ConfigDef(std::string section, std::string name, T defaultValue) : Section(section), Name(name), DefaultValue(defaultValue)
{
Config::Definitions.emplace_back(this);
}
// CONFIG_DEFINE_LOCALISED
template<typename T>
ConfigDef<T>::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<typename T>
ConfigDef<T>::ConfigDef(std::string section, std::string name, T defaultValue, std::unordered_map<std::string, T>* 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<typename T>
ConfigDef<T>::ConfigDef(std::string section, std::string name, CONFIG_LOCALE* locale, T defaultValue, std::unordered_map<std::string, T>* 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<typename T>
ConfigDef<T>::ConfigDef(std::string section, std::string name, T defaultValue, std::function<void(ConfigDef<T>*)> callback)
: Section(section), Name(name), DefaultValue(defaultValue), Callback(callback)
{
Config::Definitions.emplace_back(this);
}
template<typename T>
std::string ConfigDef<T>::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<typename T>
std::string ConfigDef<T>::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<typename T>
std::string ConfigDef<T>::GetValueLocalised() const
{
auto language = Config::Language;
CONFIG_ENUM_LOCALE(T)* locale = nullptr;
if constexpr (std::is_enum_v<T>)
{
locale = EnumLocale;
}
else if constexpr (std::is_same_v<T, bool>)
{
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<typename T>
std::string ConfigDef<T>::GetValueDescription() const
{
auto language = Config::Language;
CONFIG_ENUM_LOCALE(T)* locale = nullptr;
if constexpr (std::is_enum_v<T>)
{
locale = EnumLocale;
}
else if constexpr (std::is_same_v<T, bool>)
{
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<typename T>
inline void ConfigDef<T>::GetLocaleStrings(std::vector<std::string_view>& 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));
}
}
}
}

View file

@ -1,436 +0,0 @@
#pragma once
#define CONFIG_LOCALE std::unordered_map<ELanguage, std::tuple<std::string, std::string>>
#define CONFIG_ENUM_LOCALE(type) std::unordered_map<ELanguage, std::unordered_map<type, std::tuple<std::string, std::string>>>
#define CONFIG_DEFINE(section, type, name, defaultValue) \
static inline ConfigDef<type> name{section, #name, defaultValue};
#define CONFIG_DEFINE_LOCALISED(section, type, name, defaultValue) \
static CONFIG_LOCALE g_##name##_locale; \
static inline ConfigDef<type> name{section, #name, &g_##name##_locale, defaultValue};
#define CONFIG_DEFINE_ENUM(section, type, name, defaultValue) \
static inline ConfigDef<type> 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<type> 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<type> name{section, #name, defaultValue, [](ConfigDef<type>* def) readCallback};
#define CONFIG_DEFINE_ENUM_TEMPLATE(type) \
inline std::unordered_map<std::string, type> 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<std::string_view>& localeStrings) const = 0;
};
template<typename T>
class ConfigDef final : public IConfigDef
{
public:
std::string Section{};
std::string Name{};
CONFIG_LOCALE* Locale{};
T DefaultValue{};
T Value{ DefaultValue };
std::unordered_map<std::string, T>* EnumTemplate;
std::map<T, std::string> EnumTemplateReverse{};
CONFIG_ENUM_LOCALE(T)* EnumLocale{};
std::function<void(ConfigDef<T>*)> 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<std::string, T>* enumTemplate);
// CONFIG_DEFINE_ENUM_LOCALISED
ConfigDef(std::string section, std::string name, CONFIG_LOCALE* nameLocale, T defaultValue, std::unordered_map<std::string, T>* enumTemplate, CONFIG_ENUM_LOCALE(T)* enumLocale);
// CONFIG_DEFINE_CALLBACK
ConfigDef(std::string section, std::string name, T defaultValue, std::function<void(ConfigDef<T>*)> 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<T, std::string>::value)
{
Value = section[Name].value_or<std::string>(DefaultValue);
}
else if constexpr (std::is_enum_v<T>)
{
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<T, std::string>)
{
result = fmt::format("{}", Value);
if (strWithQuotes)
result = fmt::format("\"{}\"", result);
}
else if constexpr (std::is_enum_v<T>)
{
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<std::string_view>& 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;
}
};

View file

@ -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;
}

View file

@ -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 $<TARGET_FILE:PowerRecomp>
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 $<TARGET_FILE:ShaderRecomp>
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/private/shader.ar" ${SHADER_RECOMP_SOURCES} ${SHADER_RECOMP_INCLUDE}
)

10
flatpak/README.md Normal file
View file

@ -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
```

View file

@ -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.

View file

@ -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"
]
}
}
]
}

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>io.github.hedge_dev.unleashedrecomp</id>
<name>Unleashed Recompiled</name>
<summary>Static recompilation of Sonic Unleashed.</summary>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0+</project_license>
<supports>
<control>pointing</control>
<control>keyboard</control>
<control>touch</control>
</supports>
<description>
<p>
A native PC port of Sonic Unleashed for Xbox 360 achieved through static recompilation.
https://github.com/hedge-dev/UnleashedRecomp
</p>
</description>
<launchable type="desktop-id">io.github.hedge_dev.unleashedrecomp.desktop</launchable>
</component>

View file

@ -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")

1
thirdparty/D3D12MemoryAllocator vendored Submodule

@ -0,0 +1 @@
Subproject commit e00c4a7c85cf9c28c6f4a6cc75032736f416410f

1
thirdparty/SDL vendored Submodule

@ -0,0 +1 @@
Subproject commit 1edaad17218d67b567c149badce9ef0fc67f65fa

1
thirdparty/SDL_mixer vendored Submodule

@ -0,0 +1 @@
Subproject commit 437992692cf9300f2b2f04be35adc7445a9055bf

1
thirdparty/Vulkan-Headers vendored Submodule

@ -0,0 +1 @@
Subproject commit 14345dab231912ee9601136e96ca67a6e1f632e7

1
thirdparty/VulkanMemoryAllocator vendored Submodule

@ -0,0 +1 @@
Subproject commit 1c35ba99ce775f8342d87a83a3f0f696f99c2a39

1
thirdparty/concurrentqueue vendored Submodule

@ -0,0 +1 @@
Subproject commit 6dd38b8a1dbaa7863aa907045f32308a56a6ff5d

1
thirdparty/imgui vendored Submodule

@ -0,0 +1 @@
Subproject commit 8199457a7d9e453f8d3d9cadc14683fb54a858b5

1
thirdparty/magic_enum vendored Submodule

@ -0,0 +1 @@
Subproject commit 1a1824df7ac798177a521eed952720681b0bf482

@ -1 +0,0 @@
Subproject commit 12a8d4e4911c5ab4f4c089b4d039433975ed8a66

@ -0,0 +1 @@
Subproject commit 388549a5badaa7cbd138f5f189f50c67d5bf060c

1
thirdparty/stb vendored Submodule

@ -0,0 +1 @@
Subproject commit 5c205738c191bcb0abc65c4febfa9bd25ff35234

1
thirdparty/tiny-AES-c vendored Submodule

@ -0,0 +1 @@
Subproject commit 23856752fbd139da0b8ca6e471a13d5bcc99a08d

1
thirdparty/unordered_dense vendored Submodule

@ -0,0 +1 @@
Subproject commit d911053e390816ecc5dedd5a9d6b4bb5ed92b4c9

1
thirdparty/vcpkg vendored Submodule

@ -0,0 +1 @@
Subproject commit b322364f06308bdd24823f9d8f03fe0cc86fd46f

1
thirdparty/volk vendored Submodule

@ -0,0 +1 @@
Subproject commit 447e21b5d92ed8d5271b0d39b071f938fcfa875f

View file

@ -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()

Some files were not shown because too many files have changed in this diff Show more