Merge branch 'main' into Feature/initial-ps4-ime-keyboard

This commit is contained in:
Valdis Bogdāns 2025-07-12 22:44:07 +02:00 committed by GitHub
commit 304fc80231
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
252 changed files with 18495 additions and 8065 deletions

View File

@ -35,7 +35,7 @@ body:
required: true required: true
- label: I have disabled all patches and cheats and the issue is still present. - label: I have disabled all patches and cheats and the issue is still present.
required: true required: true
- label: I have all the required [system modules](https://github.com/shadps4-emu/shadps4-game-compatibility?tab=readme-ov-file#informations) installed. - label: I have all the required [system modules](https://github.com/shadps4-emu/shadPS4/wiki/I.-Quick-start-%5BUsers%5D#4-adding-modules) installed.
required: true required: true
- type: textarea - type: textarea
id: desc id: desc

View File

@ -76,18 +76,13 @@ jobs:
${{ env.cache-name }}- ${{ env.cache-name }}-
- name: Cache CMake Build - name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.17 uses: hendrikmuhs/ccache-action@v1.2.18
env: env:
cache-name: ${{ runner.os }}-sdl-cache-cmake-build cache-name: ${{ runner.os }}-sdl-cache-cmake-build
with: with:
append-timestamp: false append-timestamp: false
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
- name: Setup VS Environment
uses: ilammy/msvc-dev-cmd@v1.13.0
with:
arch: amd64
- name: Configure CMake - name: Configure CMake
run: cmake --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache run: cmake --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
@ -111,7 +106,7 @@ jobs:
- name: Setup Qt - name: Setup Qt
uses: jurplel/install-qt-action@v4 uses: jurplel/install-qt-action@v4
with: with:
version: 6.9.0 version: 6.9.1
host: windows host: windows
target: desktop target: desktop
arch: win64_msvc2022_64 arch: win64_msvc2022_64
@ -130,18 +125,13 @@ jobs:
${{ env.cache-name }}- ${{ env.cache-name }}-
- name: Cache CMake Build - name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.17 uses: hendrikmuhs/ccache-action@v1.2.18
env: env:
cache-name: ${{ runner.os }}-qt-cache-cmake-build cache-name: ${{ runner.os }}-qt-cache-cmake-build
with: with:
append-timestamp: false append-timestamp: false
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
- name: Setup VS Environment
uses: ilammy/msvc-dev-cmd@v1.13.0
with:
arch: amd64
- name: Configure CMake - name: Configure CMake
run: cmake --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache run: cmake --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
@ -186,7 +176,7 @@ jobs:
${{ env.cache-name }}- ${{ env.cache-name }}-
- name: Cache CMake Build - name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.17 uses: hendrikmuhs/ccache-action@v1.2.18
env: env:
cache-name: ${{runner.os}}-sdl-cache-cmake-build cache-name: ${{runner.os}}-sdl-cache-cmake-build
with: with:
@ -228,13 +218,16 @@ jobs:
- name: Setup Qt - name: Setup Qt
uses: jurplel/install-qt-action@v4 uses: jurplel/install-qt-action@v4
with: with:
version: 6.9.0 version: 6.9.1
host: mac host: mac
target: desktop target: desktop
arch: clang_64 arch: clang_64
archives: qtbase qttools archives: qtbase qttools
modules: qtmultimedia modules: qtmultimedia
- name: Workaround Qt <=6.9.1 issue
run: sed -i '' '/target_link_libraries(WrapOpenGL::WrapOpenGL INTERFACE ${__opengl_agl_fw_path})/d' ${{env.QT_ROOT_DIR}}/lib/cmake/Qt6/FindWrapOpenGL.cmake
- name: Cache CMake Configuration - name: Cache CMake Configuration
uses: actions/cache@v4 uses: actions/cache@v4
env: env:
@ -247,7 +240,7 @@ jobs:
${{ env.cache-name }}- ${{ env.cache-name }}-
- name: Cache CMake Build - name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.17 uses: hendrikmuhs/ccache-action@v1.2.18
env: env:
cache-name: ${{runner.os}}-qt-cache-cmake-build cache-name: ${{runner.os}}-qt-cache-cmake-build
with: with:
@ -301,7 +294,7 @@ jobs:
${{ env.cache-name }}- ${{ env.cache-name }}-
- name: Cache CMake Build - name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.17 uses: hendrikmuhs/ccache-action@v1.2.18
env: env:
cache-name: ${{ runner.os }}-sdl-cache-cmake-build cache-name: ${{ runner.os }}-sdl-cache-cmake-build
with: with:
@ -362,7 +355,7 @@ jobs:
${{ env.cache-name }}- ${{ env.cache-name }}-
- name: Cache CMake Build - name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.17 uses: hendrikmuhs/ccache-action@v1.2.18
env: env:
cache-name: ${{ runner.os }}-qt-cache-cmake-build cache-name: ${{ runner.os }}-qt-cache-cmake-build
with: with:
@ -409,7 +402,7 @@ jobs:
${{ env.cache-name }}- ${{ env.cache-name }}-
- name: Cache CMake Build - name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.17 uses: hendrikmuhs/ccache-action@v1.2.18
env: env:
cache-name: ${{ runner.os }}-sdl-gcc-cache-cmake-build cache-name: ${{ runner.os }}-sdl-gcc-cache-cmake-build
with: with:
@ -445,7 +438,7 @@ jobs:
${{ env.cache-name }}- ${{ env.cache-name }}-
- name: Cache CMake Build - name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.17 uses: hendrikmuhs/ccache-action@v1.2.18
env: env:
cache-name: ${{ runner.os }}-qt-gcc-cache-cmake-build cache-name: ${{ runner.os }}-qt-gcc-cache-cmake-build
with: with:
@ -494,7 +487,7 @@ jobs:
with: with:
token: ${{ secrets.SHADPS4_TOKEN_REPO }} token: ${{ secrets.SHADPS4_TOKEN_REPO }}
name: "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}" name: "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}"
tag: "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}" tag: "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.fullhash }}"
draft: false draft: false
prerelease: true prerelease: true
body: "Full Changelog: [${{ env.last_release_tag }}...${{ needs.get-info.outputs.shorthash }}](https://github.com/shadps4-emu/shadPS4/compare/${{ env.last_release_tag }}...${{ needs.get-info.outputs.fullhash }})" body: "Full Changelog: [${{ env.last_release_tag }}...${{ needs.get-info.outputs.shorthash }}](https://github.com/shadps4-emu/shadPS4/compare/${{ env.last_release_tag }}...${{ needs.get-info.outputs.fullhash }})"
@ -530,14 +523,14 @@ jobs:
# Check if release already exists and get ID # Check if release already exists and get ID
release_id=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \ release_id=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/$REPO/releases/tags/Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}" | jq -r '.id') "https://api.github.com/repos/$REPO/releases/tags/Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.fullhash }}" | jq -r '.id')
if [[ "$release_id" == "null" ]]; then if [[ "$release_id" == "null" ]]; then
echo "Creating release in $REPO for $filename" echo "Creating release in $REPO for $filename"
release_id=$(curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" \ release_id=$(curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \ -H "Accept: application/vnd.github.v3+json" \
-d '{ -d '{
"tag_name": "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}", "tag_name": "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.fullhash }}",
"name": "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}", "name": "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}",
"draft": false, "draft": false,
"prerelease": true, "prerelease": true,

21
CMakeLinuxPresets.json Normal file
View File

@ -0,0 +1,21 @@
{
"version": 9,
"cmakeMinimumRequired": {
"major": 3,
"minor": 30,
"patch": 0
},
"configurePresets": [
{
"name": "x64-Clang-Base",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/Build/${presetName}",
"cacheVariables": {
"CMAKE_C_COMPILER": "clang",
"CMAKE_CXX_COMPILER": "clang++",
"CMAKE_INSTALL_PREFIX": "${sourceDir}/Build/${presetName}"
}
}
]
}

View File

@ -203,7 +203,7 @@ execute_process(
# Set Version # Set Version
set(EMULATOR_VERSION_MAJOR "0") set(EMULATOR_VERSION_MAJOR "0")
set(EMULATOR_VERSION_MINOR "9") set(EMULATOR_VERSION_MINOR "10")
set(EMULATOR_VERSION_PATCH "1") set(EMULATOR_VERSION_PATCH "1")
set_source_files_properties(src/shadps4.rc PROPERTIES COMPILE_DEFINITIONS "EMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR};EMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR};EMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}") set_source_files_properties(src/shadps4.rc PROPERTIES COMPILE_DEFINITIONS "EMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR};EMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR};EMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}")
@ -214,6 +214,10 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp.in" "${CMAKE_
message("end git things, remote: ${GIT_REMOTE_NAME}, branch: ${GIT_BRANCH}") message("end git things, remote: ${GIT_REMOTE_NAME}, branch: ${GIT_BRANCH}")
if(WIN32 AND ENABLE_QT_GUI AND NOT CMAKE_PREFIX_PATH)
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/DetectQtInstallation.cmake")
endif ()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
find_package(Boost 1.84.0 CONFIG) find_package(Boost 1.84.0 CONFIG)
find_package(FFmpeg 5.1.2 MODULE) find_package(FFmpeg 5.1.2 MODULE)
@ -296,6 +300,8 @@ set(AJM_LIB src/core/libraries/ajm/ajm.cpp
set(AUDIO_LIB src/core/libraries/audio/audioin.cpp set(AUDIO_LIB src/core/libraries/audio/audioin.cpp
src/core/libraries/audio/audioin.h src/core/libraries/audio/audioin.h
src/core/libraries/voice/voice.cpp
src/core/libraries/voice/voice.h
src/core/libraries/audio/audioout.cpp src/core/libraries/audio/audioout.cpp
src/core/libraries/audio/audioout.h src/core/libraries/audio/audioout.h
src/core/libraries/audio/audioout_backend.h src/core/libraries/audio/audioout_backend.h
@ -607,6 +613,8 @@ set(CAMERA_LIBS src/core/libraries/camera/camera.cpp
set(COMPANION_LIBS src/core/libraries/companion/companion_httpd.cpp set(COMPANION_LIBS src/core/libraries/companion/companion_httpd.cpp
src/core/libraries/companion/companion_httpd.h src/core/libraries/companion/companion_httpd.h
src/core/libraries/companion/companion_util.cpp
src/core/libraries/companion/companion_util.h
src/core/libraries/companion/companion_error.h src/core/libraries/companion/companion_error.h
) )
set(DEV_TOOLS src/core/devtools/layer.cpp set(DEV_TOOLS src/core/devtools/layer.cpp
@ -653,6 +661,7 @@ set(COMMON src/common/logging/backend.cpp
src/common/arch.h src/common/arch.h
src/common/assert.cpp src/common/assert.cpp
src/common/assert.h src/common/assert.h
src/common/bit_array.h
src/common/bit_field.h src/common/bit_field.h
src/common/bounded_threadsafe_queue.h src/common/bounded_threadsafe_queue.h
src/common/concepts.h src/common/concepts.h
@ -678,11 +687,13 @@ set(COMMON src/common/logging/backend.cpp
src/common/path_util.h src/common/path_util.h
src/common/object_pool.h src/common/object_pool.h
src/common/polyfill_thread.h src/common/polyfill_thread.h
src/common/range_lock.h
src/common/rdtsc.cpp src/common/rdtsc.cpp
src/common/rdtsc.h src/common/rdtsc.h
src/common/recursive_lock.cpp src/common/recursive_lock.cpp
src/common/recursive_lock.h src/common/recursive_lock.h
src/common/sha1.h src/common/sha1.h
src/common/shared_first_mutex.h
src/common/signal_context.h src/common/signal_context.h
src/common/signal_context.cpp src/common/signal_context.cpp
src/common/singleton.h src/common/singleton.h
@ -870,6 +881,7 @@ set(SHADER_RECOMPILER src/shader_recompiler/exception.h
src/shader_recompiler/ir/passes/ring_access_elimination.cpp src/shader_recompiler/ir/passes/ring_access_elimination.cpp
src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp
src/shader_recompiler/ir/passes/shared_memory_barrier_pass.cpp src/shader_recompiler/ir/passes/shared_memory_barrier_pass.cpp
src/shader_recompiler/ir/passes/shared_memory_simplify_pass.cpp
src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp
src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp
src/shader_recompiler/ir/abstract_syntax_list.cpp src/shader_recompiler/ir/abstract_syntax_list.cpp
@ -912,9 +924,10 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp
src/video_core/buffer_cache/buffer.h src/video_core/buffer_cache/buffer.h
src/video_core/buffer_cache/buffer_cache.cpp src/video_core/buffer_cache/buffer_cache.cpp
src/video_core/buffer_cache/buffer_cache.h src/video_core/buffer_cache/buffer_cache.h
src/video_core/buffer_cache/memory_tracker_base.h src/video_core/buffer_cache/memory_tracker.h
src/video_core/buffer_cache/range_set.h src/video_core/buffer_cache/range_set.h
src/video_core/buffer_cache/word_manager.h src/video_core/buffer_cache/region_definitions.h
src/video_core/buffer_cache/region_manager.h
src/video_core/renderer_vulkan/liverpool_to_vk.cpp src/video_core/renderer_vulkan/liverpool_to_vk.cpp
src/video_core/renderer_vulkan/liverpool_to_vk.h src/video_core/renderer_vulkan/liverpool_to_vk.h
src/video_core/renderer_vulkan/vk_common.cpp src/video_core/renderer_vulkan/vk_common.cpp
@ -951,6 +964,10 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp
src/video_core/renderer_vulkan/host_passes/fsr_pass.h src/video_core/renderer_vulkan/host_passes/fsr_pass.h
src/video_core/renderer_vulkan/host_passes/pp_pass.cpp src/video_core/renderer_vulkan/host_passes/pp_pass.cpp
src/video_core/renderer_vulkan/host_passes/pp_pass.h src/video_core/renderer_vulkan/host_passes/pp_pass.h
src/video_core/texture_cache/blit_helper.cpp
src/video_core/texture_cache/blit_helper.h
src/video_core/texture_cache/host_compatibility.cpp
src/video_core/texture_cache/host_compatibility.h
src/video_core/texture_cache/image.cpp src/video_core/texture_cache/image.cpp
src/video_core/texture_cache/image.h src/video_core/texture_cache/image.h
src/video_core/texture_cache/image_info.cpp src/video_core/texture_cache/image_info.cpp
@ -964,7 +981,6 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp
src/video_core/texture_cache/tile_manager.cpp src/video_core/texture_cache/tile_manager.cpp
src/video_core/texture_cache/tile_manager.h src/video_core/texture_cache/tile_manager.h
src/video_core/texture_cache/types.h src/video_core/texture_cache/types.h
src/video_core/texture_cache/host_compatibility.h
src/video_core/page_manager.cpp src/video_core/page_manager.cpp
src/video_core/page_manager.h src/video_core/page_manager.h
src/video_core/multi_level_page_table.h src/video_core/multi_level_page_table.h
@ -1053,6 +1069,12 @@ set(QT_GUI src/qt_gui/about_dialog.cpp
src/qt_gui/settings_dialog.h src/qt_gui/settings_dialog.h
src/qt_gui/settings_dialog.ui src/qt_gui/settings_dialog.ui
src/qt_gui/main.cpp src/qt_gui/main.cpp
src/qt_gui/gui_settings.cpp
src/qt_gui/gui_settings.h
src/qt_gui/settings.cpp
src/qt_gui/settings.h
src/qt_gui/sdl_event_wrapper.cpp
src/qt_gui/sdl_event_wrapper.h
${EMULATOR} ${EMULATOR}
${RESOURCE_FILES} ${RESOURCE_FILES}
${TRANSLATIONS} ${TRANSLATIONS}
@ -1118,6 +1140,10 @@ if (APPLE)
set(MVK_BUNDLE_PATH "Resources/vulkan/icd.d") set(MVK_BUNDLE_PATH "Resources/vulkan/icd.d")
set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path/../${MVK_BUNDLE_PATH}") set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path/../${MVK_BUNDLE_PATH}")
set(MVK_DST ${CMAKE_CURRENT_BINARY_DIR}/shadps4.app/Contents/${MVK_BUNDLE_PATH}) set(MVK_DST ${CMAKE_CURRENT_BINARY_DIR}/shadps4.app/Contents/${MVK_BUNDLE_PATH})
add_custom_command(
OUTPUT ${MVK_DST}
COMMAND ${CMAKE_COMMAND} -E make_directory ${MVK_DST})
else() else()
set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path") set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path")
set(MVK_DST ${CMAKE_CURRENT_BINARY_DIR}) set(MVK_DST ${CMAKE_CURRENT_BINARY_DIR})
@ -1128,9 +1154,6 @@ if (APPLE)
set(MVK_ICD_SRC ${CMAKE_CURRENT_SOURCE_DIR}/externals/MoltenVK/MoltenVK/MoltenVK/icd/MoltenVK_icd.json) set(MVK_ICD_SRC ${CMAKE_CURRENT_SOURCE_DIR}/externals/MoltenVK/MoltenVK/MoltenVK/icd/MoltenVK_icd.json)
set(MVK_ICD_DST ${MVK_DST}/MoltenVK_icd.json) set(MVK_ICD_DST ${MVK_DST}/MoltenVK_icd.json)
add_custom_command(
OUTPUT ${MVK_DST}
COMMAND ${CMAKE_COMMAND} -E make_directory ${MVK_DST})
add_custom_command( add_custom_command(
OUTPUT ${MVK_ICD_DST} OUTPUT ${MVK_ICD_DST}
DEPENDS ${MVK_ICD_SRC} ${MVK_DST} DEPENDS ${MVK_ICD_SRC} ${MVK_DST}
@ -1145,17 +1168,13 @@ if (APPLE)
if (ARCHITECTURE STREQUAL "x86_64") if (ARCHITECTURE STREQUAL "x86_64")
# Reserve system-managed memory space. # Reserve system-managed memory space.
target_link_options(shadps4 PRIVATE -Wl,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x4000,-segaddr,TCB_SPACE,0x4000,-segaddr,SYSTEM_MANAGED,0x400000,-segaddr,SYSTEM_RESERVED,0x7FFFFC000,-image_base,0x20000000000) target_link_options(shadps4 PRIVATE -Wl,-ld_classic,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x4000,-segaddr,TCB_SPACE,0x4000,-segaddr,SYSTEM_MANAGED,0x400000,-segaddr,SYSTEM_RESERVED,0x7FFFFC000,-image_base,0x20000000000)
endif() endif()
# Replacement for std::chrono::time_zone # Replacement for std::chrono::time_zone
target_link_libraries(shadps4 PRIVATE date::date-tz) target_link_libraries(shadps4 PRIVATE date::date-tz)
endif() endif()
if (NOT ENABLE_QT_GUI)
target_link_libraries(shadps4 PRIVATE SDL3::SDL3)
endif()
if (ENABLE_QT_GUI) if (ENABLE_QT_GUI)
target_link_libraries(shadps4 PRIVATE Qt6::Widgets Qt6::Concurrent Qt6::Network Qt6::Multimedia) target_link_libraries(shadps4 PRIVATE Qt6::Widgets Qt6::Concurrent Qt6::Network Qt6::Multimedia)
add_definitions(-DENABLE_QT_GUI) add_definitions(-DENABLE_QT_GUI)

62
CMakePresets.json Normal file
View File

@ -0,0 +1,62 @@
{
"version": 9,
"cmakeMinimumRequired": {
"major": 3,
"minor": 30,
"patch": 0
},
"include": ["CMake${hostSystemName}Presets.json"],
"configurePresets": [
{
"name": "x64-Clang-Debug",
"displayName": "Clang x64 Debug",
"inherits": ["x64-Clang-Base"],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "x64-Clang-Debug-Qt",
"displayName": "Clang x64 Debug with Qt",
"inherits": ["x64-Clang-Base"],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"ENABLE_QT_GUI": "ON"
}
},
{
"name": "x64-Clang-Release",
"displayName": "Clang x64 Release",
"inherits": ["x64-Clang-Base"],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "x64-Clang-Release-Qt",
"displayName": "Clang x64 Release with Qt",
"inherits": ["x64-Clang-Base"],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"ENABLE_QT_GUI": "ON"
}
},
{
"name": "x64-Clang-RelWithDebInfo",
"displayName": "Clang x64 RelWithDebInfo",
"inherits": ["x64-Clang-Base"],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
}
},
{
"name": "x64-Clang-RelWithDebInfo-Qt",
"displayName": "Clang x64 RelWithDebInfo with Qt",
"inherits": ["x64-Clang-Base"],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
"ENABLE_QT_GUI": "ON"
}
}
]
}

View File

@ -12,6 +12,18 @@
"inheritEnvironments": [ "clang_cl_x64_x64" ], "inheritEnvironments": [ "clang_cl_x64_x64" ],
"intelliSenseMode": "windows-clang-x64" "intelliSenseMode": "windows-clang-x64"
}, },
{
"name": "x64-Clang-Release-Qt",
"generator": "Ninja",
"configurationType": "Release",
"buildRoot": "${projectDir}\\Build\\${name}",
"installRoot": "${projectDir}\\Install\\${name}",
"cmakeCommandArgs": "-DENABLE_QT_GUI=ON",
"buildCommandArgs": "",
"ctestCommandArgs": "",
"inheritEnvironments": [ "clang_cl_x64_x64" ],
"intelliSenseMode": "windows-clang-x64"
},
{ {
"name": "x64-Clang-Debug", "name": "x64-Clang-Debug",
"generator": "Ninja", "generator": "Ninja",
@ -24,6 +36,18 @@
"inheritEnvironments": [ "clang_cl_x64_x64" ], "inheritEnvironments": [ "clang_cl_x64_x64" ],
"intelliSenseMode": "windows-clang-x64" "intelliSenseMode": "windows-clang-x64"
}, },
{
"name": "x64-Clang-Debug-Qt",
"generator": "Ninja",
"configurationType": "Debug",
"buildRoot": "${projectDir}\\Build\\${name}",
"installRoot": "${projectDir}\\Install\\${name}",
"cmakeCommandArgs": "-DENABLE_QT_GUI=ON",
"buildCommandArgs": "",
"ctestCommandArgs": "",
"inheritEnvironments": [ "clang_cl_x64_x64" ],
"intelliSenseMode": "windows-clang-x64"
},
{ {
"name": "x64-Clang-RelWithDebInfo", "name": "x64-Clang-RelWithDebInfo",
"generator": "Ninja", "generator": "Ninja",
@ -35,6 +59,18 @@
"ctestCommandArgs": "", "ctestCommandArgs": "",
"inheritEnvironments": [ "clang_cl_x64_x64" ], "inheritEnvironments": [ "clang_cl_x64_x64" ],
"intelliSenseMode": "windows-clang-x64" "intelliSenseMode": "windows-clang-x64"
},
{
"name": "x64-Clang-RelWithDebInfo-Qt",
"generator": "Ninja",
"configurationType": "RelWithDebInfo",
"buildRoot": "${projectDir}\\Build\\${name}",
"installRoot": "${projectDir}\\Install\\${name}",
"cmakeCommandArgs": "-DENABLE_QT_GUI=ON",
"buildCommandArgs": "",
"ctestCommandArgs": "",
"inheritEnvironments": [ "clang_cl_x64_x64" ],
"intelliSenseMode": "windows-clang-x64"
} }
] ]
} }

26
CMakeWindowsPresets.json Normal file
View File

@ -0,0 +1,26 @@
{
"version": 9,
"cmakeMinimumRequired": {
"major": 3,
"minor": 30,
"patch": 0
},
"configurePresets": [
{
"name": "x64-Clang-Base",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/Build/${presetName}",
"cacheVariables": {
"CMAKE_C_COMPILER": "clang-cl",
"CMAKE_CXX_COMPILER": "clang-cl",
"CMAKE_INSTALL_PREFIX": "${sourceDir}/Build/${presetName}"
},
"vendor": {
"microsoft.com/VisualStudioSettings/CMake/1.0": {
"intelliSenseMode": "windows-clang-x64"
}
}
}
]
}

View File

@ -36,7 +36,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
**shadPS4** is an early **PlayStation 4** emulator for **Windows**, **Linux** and **macOS** written in C++. **shadPS4** is an early **PlayStation 4** emulator for **Windows**, **Linux** and **macOS** written in C++.
If you encounter problems or have doubts, do not hesitate to look at the [**Quickstart**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Quickstart/Quickstart.md).\ If you encounter problems or have doubts, do not hesitate to look at the [**Quickstart**](https://github.com/shadps4-emu/shadPS4/wiki/I.-Quick-start-%5BUsers%5D).\
To verify that a game works, you can look at [**shadPS4 Game Compatibility**](https://github.com/shadps4-emu/shadps4-game-compatibility).\ To verify that a game works, you can look at [**shadPS4 Game Compatibility**](https://github.com/shadps4-emu/shadps4-game-compatibility).\
To discuss shadPS4 development, suggest ideas or to ask for help, join our [**Discord server**](https://discord.gg/bFJxfftGW6).\ To discuss shadPS4 development, suggest ideas or to ask for help, join our [**Discord server**](https://discord.gg/bFJxfftGW6).\
To get the latest news, go to our [**X (Twitter)**](https://x.com/shadps4) or our [**website**](https://shadps4.net/).\ To get the latest news, go to our [**X (Twitter)**](https://x.com/shadps4) or our [**website**](https://shadps4.net/).\
@ -124,8 +124,8 @@ Keyboard and mouse inputs can be customized in the settings menu by clicking the
# Firmware files # Firmware files
shadPS4 can load some PlayStation 4 firmware files, these must be dumped from your legally owned PlayStation 4 console.\ shadPS4 can load some PlayStation 4 firmware files, these must be dumped from your legally owned PlayStation 4 console.
The following firmware modules are supported and must be placed in shadPS4's `user/sys_modules` folder. The following firmware modules are supported and must be placed in shadPS4's `sys_modules` folder.
<div align="center"> <div align="center">
@ -138,8 +138,7 @@ The following firmware modules are supported and must be placed in shadPS4's `us
</div> </div>
> [!Caution] > [!Caution]
> The above modules are required to run the games properly and must be extracted from your PlayStation 4.\ > The above modules are required to run the games properly and must be extracted from your PlayStation 4.
> **We do not provide any information or support on how to do this**.
@ -148,7 +147,7 @@ The following firmware modules are supported and must be placed in shadPS4's `us
- [**georgemoralis**](https://github.com/georgemoralis) - [**georgemoralis**](https://github.com/georgemoralis)
- [**psucien**](https://github.com/psucien) - [**psucien**](https://github.com/psucien)
- [**viniciuslrangel**](https://github.com/viniciuslrangel) - [**viniciuslrangel**](https://github.com/viniciuslrangel)
- [**roamic**](https://github.com/vladmikhalin) - [**roamic**](https://github.com/roamic)
- [**squidbus**](https://github.com/squidbus) - [**squidbus**](https://github.com/squidbus)
- [**frodo**](https://github.com/baggins183) - [**frodo**](https://github.com/baggins183)
- [**Stephen Miller**](https://github.com/StevenMiller123) - [**Stephen Miller**](https://github.com/StevenMiller123)
@ -158,7 +157,7 @@ Logo is done by [**Xphalnos**](https://github.com/Xphalnos)
# Contributing # Contributing
If you want to contribute, please look the [**CONTRIBUTING.md**](https://github.com/shadps4-emu/shadPS4/blob/main/CONTRIBUTING.md) file.\ If you want to contribute, please read the [**CONTRIBUTING.md**](https://github.com/shadps4-emu/shadPS4/blob/main/CONTRIBUTING.md) file.\
Open a PR and we'll check it :) Open a PR and we'll check it :)
# Translations # Translations

View File

@ -5,10 +5,13 @@ path = [
"REUSE.toml", "REUSE.toml",
"crowdin.yml", "crowdin.yml",
"CMakeSettings.json", "CMakeSettings.json",
"CMakeLinuxPresets.json",
"CMakeWindowsPresets.json",
"CMakePresets.json",
".github/FUNDING.yml", ".github/FUNDING.yml",
".github/shadps4.png", ".github/shadps4.png",
".github/workflows/scripts/update_translation.sh", ".github/workflows/scripts/update_translation.sh",
".github/workflows/update_translation.yml", ".github/workflows/update_translation.yml",
".gitmodules", ".gitmodules",
"dist/MacOSBundleInfo.plist.in", "dist/MacOSBundleInfo.plist.in",
"dist/net.shadps4.shadPS4.desktop", "dist/net.shadps4.shadPS4.desktop",
@ -29,6 +32,7 @@ path = [
"src/images/discord.png", "src/images/discord.png",
"src/images/dump_icon.png", "src/images/dump_icon.png",
"src/images/exit_icon.png", "src/images/exit_icon.png",
"src/images/favorite_icon.png",
"src/images/file_icon.png", "src/images/file_icon.png",
"src/images/trophy_icon.png", "src/images/trophy_icon.png",
"src/images/flag_china.png", "src/images/flag_china.png",
@ -71,7 +75,7 @@ path = [
"src/images/youtube.svg", "src/images/youtube.svg",
"src/shadps4.qrc", "src/shadps4.qrc",
"src/shadps4.rc", "src/shadps4.rc",
"src/qt_gui/translations/update_translation.sh", "src/qt_gui/translations/update_translation.sh",
] ]
precedence = "aggregate" precedence = "aggregate"
SPDX-FileCopyrightText = "shadPS4 Emulator Project" SPDX-FileCopyrightText = "shadPS4 Emulator Project"

View File

@ -0,0 +1,28 @@
# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
set(highest_version "0")
set(CANDIDATE_DRIVES A B C D E F G H I J K L M N O P Q R S T U V W X Y Z)
foreach(drive ${CANDIDATE_DRIVES})
file(GLOB kits LIST_DIRECTORIES true CONFIGURE_DEPENDS "${drive}:/Qt/*/msvc*_64")
foreach(kit IN LISTS kits)
get_filename_component(version_dir "${kit}" DIRECTORY)
get_filename_component(kit_version "${version_dir}" NAME)
message(STATUS "DetectQtInstallation.cmake: Detected Qt: ${kit}")
if (kit_version VERSION_GREATER highest_version)
set(highest_version "${kit_version}")
set(QT_PREFIX "${kit}")
endif()
endforeach()
endforeach()
if(QT_PREFIX)
set(CMAKE_PREFIX_PATH "${QT_PREFIX}" CACHE PATH "Qt prefix autodetected" FORCE)
message(STATUS "DetectQtInstallation.cmake: Choose newest Qt: ${QT_PREFIX}")
else()
message(STATUS "DetectQtInstallation.cmake: No QtDirectory found in <drive>:/Qt please set CMAKE_PREFIX_PATH manually")
endif()

View File

@ -37,6 +37,9 @@
<category translate="no">Game</category> <category translate="no">Game</category>
</categories> </categories>
<releases> <releases>
<release version="0.10.0" date="2025-07-06">
<url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.10.0</url>
</release>
<release version="0.9.0" date="2025-05-22"> <release version="0.9.0" date="2025-05-22">
<url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.9.0</url> <url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.9.0</url>
</release> </release>

View File

@ -25,11 +25,11 @@ sudo apt install build-essential clang git cmake libasound2-dev \
```bash ```bash
sudo dnf install clang git cmake libatomic alsa-lib-devel \ sudo dnf install clang git cmake libatomic alsa-lib-devel \
pipewire-jack-audio-connection-kit-devel openal-devel \ pipewire-jack-audio-connection-kit-devel openal-soft-devel \
openssl-devel libevdev-devel libudev-devel libXext-devel \ openssl-devel libevdev-devel libudev-devel libXext-devel \
qt6-qtbase-devel qt6-qtbase-private-devel \ qt6-qtbase-devel qt6-qtbase-private-devel \
qt6-qtmultimedia-devel qt6-qtsvg-devel qt6-qttools-devel \ qt6-qtmultimedia-devel qt6-qtsvg-devel qt6-qttools-devel \
vulkan-devel vulkan-validation-layers libpng-devel vulkan-devel vulkan-validation-layers libpng-devel libuuid-devel
``` ```
#### Arch Linux #### Arch Linux
@ -74,6 +74,7 @@ and install the dependencies on that container as cited above.
This option is **highly recommended** for distributions with immutable/atomic filesystems (example: Fedora Kinoite, SteamOS). This option is **highly recommended** for distributions with immutable/atomic filesystems (example: Fedora Kinoite, SteamOS).
### Cloning ### Cloning
The project uses submodules to manage dependencies, and they need to be initialized before you can build the project. To achieve this, make sure you've cloned the repository with the --recursive flag
```bash ```bash
git clone --recursive https://github.com/shadps4-emu/shadPS4.git git clone --recursive https://github.com/shadps4-emu/shadPS4.git

@ -1 +1 @@
Subproject commit 3a0b07a24a4a681ffe70b461b1f4333b2729e2ef Subproject commit 00abd384ce01cbd439045905d2fa6cf799dfa2f6

@ -1 +1 @@
Subproject commit 969e75f7cc0718774231d029f9d52fa87d4ae1b2 Subproject commit 1a69a919fa302e92b337594bd0a8aaea61037d91

2
externals/sirit vendored

@ -1 +1 @@
Subproject commit 6b450704f6fedb9413d0c89a9eb59d028eb1e6c0 Subproject commit b4eccb336f1b1169af48dac1e04015985af86e3e

View File

@ -18,6 +18,9 @@ public:
void unlock() { void unlock() {
pthread_mutex_unlock(&mutex); pthread_mutex_unlock(&mutex);
} }
[[nodiscard]] bool try_lock() {
return pthread_mutex_trylock(&mutex) == 0;
}
private: private:
pthread_mutex_t mutex = PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP; pthread_mutex_t mutex = PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP;

406
src/common/bit_array.h Normal file
View File

@ -0,0 +1,406 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <cstddef>
#include "common/types.h"
#ifdef __AVX2__
#define BIT_ARRAY_USE_AVX
#include <immintrin.h>
#endif
namespace Common {
template <size_t N>
class BitArray {
static_assert(N % 64 == 0, "BitArray size must be a multiple of 64 bits.");
static constexpr size_t BITS_PER_WORD = 64;
static constexpr size_t WORD_COUNT = N / BITS_PER_WORD;
static constexpr size_t WORDS_PER_AVX = 4;
static constexpr size_t AVX_WORD_COUNT = WORD_COUNT / WORDS_PER_AVX;
public:
using Range = std::pair<size_t, size_t>;
class Iterator {
public:
explicit Iterator(const BitArray& bit_array_, u64 start) : bit_array(bit_array_) {
range = bit_array.FirstRangeFrom(start);
}
Iterator& operator++() {
range = bit_array.FirstRangeFrom(range.second);
return *this;
}
bool operator==(const Iterator& other) const {
return range == other.range;
}
bool operator!=(const Iterator& other) const {
return !(*this == other);
}
const Range& operator*() const {
return range;
}
const Range* operator->() const {
return &range;
}
private:
const BitArray& bit_array;
Range range;
};
using const_iterator = Iterator;
using iterator_category = std::forward_iterator_tag;
using value_type = Range;
using difference_type = std::ptrdiff_t;
using pointer = const Range*;
using reference = const Range&;
BitArray() = default;
BitArray(const BitArray& other) = default;
BitArray& operator=(const BitArray& other) = default;
BitArray(BitArray&& other) noexcept = default;
BitArray& operator=(BitArray&& other) noexcept = default;
~BitArray() = default;
BitArray(const BitArray& other, size_t start, size_t end) {
if (start >= end || end > N) {
return;
}
const size_t first_word = start / BITS_PER_WORD;
const size_t last_word = (end - 1) / BITS_PER_WORD;
const size_t start_bit = start % BITS_PER_WORD;
const size_t end_bit = (end - 1) % BITS_PER_WORD;
const u64 start_mask = ~((1ULL << start_bit) - 1);
const u64 end_mask = end_bit == BITS_PER_WORD - 1 ? ~0ULL : (1ULL << (end_bit + 1)) - 1;
if (first_word == last_word) {
data[first_word] = other.data[first_word] & (start_mask & end_mask);
} else {
data[first_word] = other.data[first_word] & start_mask;
size_t i = first_word + 1;
#ifdef BIT_ARRAY_USE_AVX
for (; i + WORDS_PER_AVX <= last_word; i += WORDS_PER_AVX) {
const __m256i current =
_mm256_loadu_si256(reinterpret_cast<const __m256i*>(&other.data[i]));
_mm256_storeu_si256(reinterpret_cast<__m256i*>(&data[i]), current);
}
#endif
for (; i < last_word; ++i) {
data[i] = other.data[i];
}
data[last_word] = other.data[last_word] & end_mask;
}
}
BitArray(const BitArray& other, const Range& range)
: BitArray(other, range.first, range.second) {}
const_iterator begin() const {
return Iterator(*this, 0);
}
const_iterator end() const {
return Iterator(*this, N);
}
inline constexpr void Set(size_t idx) {
data[idx / BITS_PER_WORD] |= (1ULL << (idx % BITS_PER_WORD));
}
inline constexpr void Unset(size_t idx) {
data[idx / BITS_PER_WORD] &= ~(1ULL << (idx % BITS_PER_WORD));
}
inline constexpr bool Get(size_t idx) const {
return (data[idx / BITS_PER_WORD] & (1ULL << (idx % BITS_PER_WORD))) != 0;
}
inline void SetRange(size_t start, size_t end) {
if (start >= end || end > N) {
return;
}
const size_t first_word = start / BITS_PER_WORD;
const size_t last_word = (end - 1) / BITS_PER_WORD;
const size_t start_bit = start % BITS_PER_WORD;
const size_t end_bit = (end - 1) % BITS_PER_WORD;
const u64 start_mask = ~((1ULL << start_bit) - 1);
const u64 end_mask = end_bit == BITS_PER_WORD - 1 ? ~0ULL : (1ULL << (end_bit + 1)) - 1;
if (first_word == last_word) {
data[first_word] |= start_mask & end_mask;
} else {
data[first_word] |= start_mask;
size_t i = first_word + 1;
#ifdef BIT_ARRAY_USE_AVX
const __m256i value = _mm256_set1_epi64x(-1);
for (; i + WORDS_PER_AVX <= last_word; i += WORDS_PER_AVX) {
_mm256_storeu_si256(reinterpret_cast<__m256i*>(&data[i]), value);
}
#endif
for (; i < last_word; ++i) {
data[i] = ~0ULL;
}
data[last_word] |= end_mask;
}
}
inline void UnsetRange(size_t start, size_t end) {
if (start >= end || end > N) {
return;
}
size_t first_word = start / BITS_PER_WORD;
const size_t last_word = (end - 1) / BITS_PER_WORD;
const size_t start_bit = start % BITS_PER_WORD;
const size_t end_bit = (end - 1) % BITS_PER_WORD;
const u64 start_mask = (1ULL << start_bit) - 1;
const u64 end_mask = end_bit == BITS_PER_WORD - 1 ? 0ULL : ~((1ULL << (end_bit + 1)) - 1);
if (first_word == last_word) {
data[first_word] &= start_mask | end_mask;
} else {
data[first_word] &= start_mask;
size_t i = first_word + 1;
#ifdef BIT_ARRAY_USE_AVX
const __m256i value = _mm256_setzero_si256();
for (; i + WORDS_PER_AVX <= last_word; i += WORDS_PER_AVX) {
_mm256_storeu_si256(reinterpret_cast<__m256i*>(&data[i]), value);
}
#endif
for (; i < last_word; ++i) {
data[i] = 0ULL;
}
data[last_word] &= end_mask;
}
}
inline constexpr void SetRange(const Range& range) {
SetRange(range.first, range.second);
}
inline constexpr void UnsetRange(const Range& range) {
UnsetRange(range.first, range.second);
}
inline constexpr void Clear() {
data.fill(0);
}
inline constexpr void Fill() {
data.fill(~0ULL);
}
inline constexpr bool None() const {
u64 result = 0;
for (const auto& word : data) {
result |= word;
}
return result == 0;
}
inline constexpr bool Any() const {
return !None();
}
Range FirstRangeFrom(size_t start) const {
if (start >= N) {
return {N, N};
}
const auto find_end_bit = [&](size_t word) {
#ifdef BIT_ARRAY_USE_AVX
const __m256i all_one = _mm256_set1_epi64x(-1);
for (; word + WORDS_PER_AVX <= WORD_COUNT; word += WORDS_PER_AVX) {
const __m256i current =
_mm256_loadu_si256(reinterpret_cast<const __m256i*>(&data[word]));
const __m256i cmp = _mm256_cmpeq_epi64(current, all_one);
if (_mm256_movemask_epi8(cmp) != 0xFFFFFFFF) {
break;
}
}
#endif
for (; word < WORD_COUNT; ++word) {
if (data[word] != ~0ULL) {
return (word * BITS_PER_WORD) + std::countr_one(data[word]);
}
}
return N;
};
const auto word_bits = [&](size_t index, u64 word) {
const int empty_bits = std::countr_zero(word);
const int ones_count = std::countr_one(word >> empty_bits);
const size_t start_bit = index * BITS_PER_WORD + empty_bits;
if (ones_count + empty_bits < BITS_PER_WORD) {
return Range{start_bit, start_bit + ones_count};
}
return Range{start_bit, find_end_bit(index + 1)};
};
const size_t start_word = start / BITS_PER_WORD;
const size_t start_bit = start % BITS_PER_WORD;
const u64 masked_first = data[start_word] & (~((1ULL << start_bit) - 1));
if (masked_first) {
return word_bits(start_word, masked_first);
}
size_t word = start_word + 1;
#ifdef BIT_ARRAY_USE_AVX
for (; word + WORDS_PER_AVX <= WORD_COUNT; word += WORDS_PER_AVX) {
const __m256i current =
_mm256_loadu_si256(reinterpret_cast<const __m256i*>(&data[word]));
if (!_mm256_testz_si256(current, current)) {
break;
}
}
#endif
for (; word < WORD_COUNT; ++word) {
if (data[word] != 0) {
return word_bits(word, data[word]);
}
}
return {N, N};
}
inline constexpr Range FirstRange() const {
return FirstRangeFrom(0);
}
Range LastRangeFrom(size_t end) const {
if (end == 0) {
return {0, 0};
}
if (end > N) {
end = N;
}
const auto find_start_bit = [&](size_t word) {
#ifdef BIT_ARRAY_USE_AVX
const __m256i all_zero = _mm256_setzero_si256();
for (; word >= WORDS_PER_AVX; word -= WORDS_PER_AVX) {
const __m256i current = _mm256_loadu_si256(
reinterpret_cast<const __m256i*>(&data[word - WORDS_PER_AVX]));
const __m256i cmp = _mm256_cmpeq_epi64(current, all_zero);
if (_mm256_movemask_epi8(cmp) != 0xFFFFFFFF) {
break;
}
}
#endif
for (; word > 0; --word) {
if (data[word - 1] != ~0ULL) {
return word * BITS_PER_WORD - std::countl_one(data[word - 1]);
}
}
return size_t(0);
};
const auto word_bits = [&](size_t index, u64 word) {
const int empty_bits = std::countl_zero(word);
const int ones_count = std::countl_one(word << empty_bits);
const size_t end_bit = index * BITS_PER_WORD - empty_bits;
if (empty_bits + ones_count < BITS_PER_WORD) {
return Range{end_bit - ones_count, end_bit};
}
return Range{find_start_bit(index - 1), end_bit};
};
const size_t end_word = ((end - 1) / BITS_PER_WORD) + 1;
const size_t end_bit = (end - 1) % BITS_PER_WORD;
u64 masked_last = data[end_word - 1];
if (end_bit < BITS_PER_WORD - 1) {
masked_last &= (1ULL << (end_bit + 1)) - 1;
}
if (masked_last) {
return word_bits(end_word, masked_last);
}
size_t word = end_word - 1;
#ifdef BIT_ARRAY_USE_AVX
for (; word >= WORDS_PER_AVX; word -= WORDS_PER_AVX) {
const __m256i current =
_mm256_loadu_si256(reinterpret_cast<const __m256i*>(&data[word - WORDS_PER_AVX]));
if (!_mm256_testz_si256(current, current)) {
break;
}
}
#endif
for (; word > 0; --word) {
if (data[word - 1] != 0) {
return word_bits(word, data[word - 1]);
}
}
return {0, 0};
}
inline constexpr Range LastRange() const {
return LastRangeFrom(N);
}
inline constexpr size_t Size() const {
return N;
}
inline constexpr BitArray& operator|=(const BitArray& other) {
for (size_t i = 0; i < WORD_COUNT; ++i) {
data[i] |= other.data[i];
}
return *this;
}
inline constexpr BitArray& operator&=(const BitArray& other) {
for (size_t i = 0; i < WORD_COUNT; ++i) {
data[i] &= other.data[i];
}
return *this;
}
inline constexpr BitArray& operator^=(const BitArray& other) {
for (size_t i = 0; i < WORD_COUNT; ++i) {
data[i] ^= other.data[i];
}
return *this;
}
inline constexpr BitArray operator|(const BitArray& other) const {
BitArray result = *this;
result |= other;
return result;
}
inline constexpr BitArray operator&(const BitArray& other) const {
BitArray result = *this;
result &= other;
return result;
}
inline constexpr BitArray operator^(const BitArray& other) const {
BitArray result = *this;
result ^= other;
return result;
}
inline constexpr BitArray operator~() const {
BitArray result = *this;
for (size_t i = 0; i < WORD_COUNT; ++i) {
result.data[i] = ~result.data[i];
}
return result;
}
inline constexpr bool operator==(const BitArray& other) const {
u64 result = 0;
for (size_t i = 0; i < WORD_COUNT; ++i) {
result |= data[i] ^ other.data[i];
}
return result == 0;
}
inline constexpr bool operator!=(const BitArray& other) const {
return !(*this == other);
}
private:
std::array<u64, WORD_COUNT> data{};
};
} // namespace Common

View File

@ -31,35 +31,53 @@ std::filesystem::path find_fs_path_or(const basic_value<TC>& v, const K& ky,
namespace Config { namespace Config {
// General
static bool isNeo = false; static bool isNeo = false;
static bool isDevKit = false; static bool isDevKit = false;
static bool playBGM = false; static bool isPSNSignedIn = false;
static bool isTrophyPopupDisabled = false; static bool isTrophyPopupDisabled = false;
static int BGMvolume = 50; static double trophyNotificationDuration = 6.0;
static bool enableDiscordRPC = false; static bool enableDiscordRPC = false;
static u32 screenWidth = 1280; static std::string logFilter = "";
static u32 screenHeight = 720;
static s32 gpuId = -1; // Vulkan physical device index. Set to negative for auto select
static std::string logFilter;
static std::string logType = "sync"; static std::string logType = "sync";
static std::string userName = "shadPS4"; static std::string userName = "shadPS4";
static std::string updateChannel; static std::string chooseHomeTab = "General";
static std::string chooseHomeTab; static bool isShowSplash = false;
static std::string backButtonBehavior = "left"; static std::string isSideTrophy = "right";
static bool compatibilityData = false;
static bool checkCompatibilityOnStartup = false;
// Input
static int cursorState = HideCursorState::Idle;
static int cursorHideTimeout = 5; // 5 seconds (default)
static bool useSpecialPad = false; static bool useSpecialPad = false;
static int specialPadClass = 1; static int specialPadClass = 1;
static bool isMotionControlsEnabled = true; static bool isMotionControlsEnabled = true;
static bool isDebugDump = false; static bool useUnifiedInputConfig = true;
static bool isShaderDebug = false;
static bool isShowSplash = false; // These two entries aren't stored in the config
static bool isAutoUpdate = false; static bool overrideControllerColor = false;
static bool isAlwaysShowChangelog = false; static int controllerCustomColorRGB[3] = {0, 0, 255};
static std::string isSideTrophy = "right";
// GPU
static u32 windowWidth = 1280;
static u32 windowHeight = 720;
static u32 internalScreenWidth = 1280;
static u32 internalScreenHeight = 720;
static bool isNullGpu = false; static bool isNullGpu = false;
static bool shouldCopyGPUBuffers = false; static bool shouldCopyGPUBuffers = false;
static bool readbacksEnabled = false;
static bool readbackLinearImagesEnabled = false;
static bool directMemoryAccessEnabled = false;
static bool shouldDumpShaders = false; static bool shouldDumpShaders = false;
static bool shouldPatchShaders = true; static bool shouldPatchShaders = false;
static u32 vblankDivider = 1; static u32 vblankDivider = 1;
static bool isFullscreen = false;
static std::string fullscreenMode = "Windowed";
static bool isHDRAllowed = false;
// Vulkan
static s32 gpuId = -1;
static bool vkValidation = false; static bool vkValidation = false;
static bool vkValidationSync = false; static bool vkValidationSync = false;
static bool vkValidationGpu = false; static bool vkValidationGpu = false;
@ -67,49 +85,29 @@ static bool vkCrashDiagnostic = false;
static bool vkHostMarkers = false; static bool vkHostMarkers = false;
static bool vkGuestMarkers = false; static bool vkGuestMarkers = false;
static bool rdocEnable = false; static bool rdocEnable = false;
static bool isFpsColor = true;
static bool isSeparateLogFilesEnabled = false;
static s16 cursorState = HideCursorState::Idle;
static int cursorHideTimeout = 5; // 5 seconds (default)
static double trophyNotificationDuration = 6.0;
static bool useUnifiedInputConfig = true;
static bool overrideControllerColor = false;
static int controllerCustomColorRGB[3] = {0, 0, 255};
static bool compatibilityData = false;
static bool checkCompatibilityOnStartup = false;
static std::string trophyKey;
// Gui // Debug
static bool isDebugDump = false;
static bool isShaderDebug = false;
static bool isSeparateLogFilesEnabled = false;
static bool isFpsColor = true;
// GUI
static bool load_game_size = true; static bool load_game_size = true;
static std::vector<GameInstallDir> settings_install_dirs = {}; static std::vector<GameInstallDir> settings_install_dirs = {};
std::vector<bool> install_dirs_enabled = {}; std::vector<bool> install_dirs_enabled = {};
std::filesystem::path settings_addon_install_dir = {}; std::filesystem::path settings_addon_install_dir = {};
std::filesystem::path save_data_path = {}; std::filesystem::path save_data_path = {};
u32 main_window_geometry_x = 400;
u32 main_window_geometry_y = 400;
u32 main_window_geometry_w = 1280;
u32 main_window_geometry_h = 720;
u32 mw_themes = 0;
u32 m_icon_size = 36;
u32 m_icon_size_grid = 69;
u32 m_slider_pos = 0;
u32 m_slider_pos_grid = 0;
u32 m_table_mode = 0;
u32 m_window_size_W = 1280;
u32 m_window_size_H = 720;
std::vector<std::string> m_elf_viewer;
std::vector<std::string> m_recent_files;
std::string emulator_language = "en_US";
static int backgroundImageOpacity = 50;
static bool showBackgroundImage = true;
static bool isFullscreen = false;
static std::string fullscreenMode = "Windowed";
static bool isHDRAllowed = false;
static bool showLabelsUnderIcons = true;
// Language // Settings
u32 m_language = 1; // english u32 m_language = 1; // english
// Keys
static std::string trophyKey = "";
// Expected number of items in the config file
static constexpr u64 total_entries = 54;
bool allowHDR() { bool allowHDR() {
return isHDRAllowed; return isHDRAllowed;
} }
@ -175,14 +173,6 @@ bool getIsFullscreen() {
return isFullscreen; return isFullscreen;
} }
bool getShowLabelsUnderIcons() {
return showLabelsUnderIcons;
}
bool setShowLabelsUnderIcons() {
return false;
}
std::string getFullscreenMode() { std::string getFullscreenMode() {
return fullscreenMode; return fullscreenMode;
} }
@ -191,14 +181,6 @@ bool getisTrophyPopupDisabled() {
return isTrophyPopupDisabled; return isTrophyPopupDisabled;
} }
bool getPlayBGM() {
return playBGM;
}
int getBGMvolume() {
return BGMvolume;
}
bool getEnableDiscordRPC() { bool getEnableDiscordRPC() {
return enableDiscordRPC; return enableDiscordRPC;
} }
@ -215,12 +197,20 @@ double getTrophyNotificationDuration() {
return trophyNotificationDuration; return trophyNotificationDuration;
} }
u32 getScreenWidth() { u32 getWindowWidth() {
return screenWidth; return windowWidth;
} }
u32 getScreenHeight() { u32 getWindowHeight() {
return screenHeight; return windowHeight;
}
u32 getInternalScreenWidth() {
return internalScreenHeight;
}
u32 getInternalScreenHeight() {
return internalScreenHeight;
} }
s32 getGpuId() { s32 getGpuId() {
@ -239,18 +229,10 @@ std::string getUserName() {
return userName; return userName;
} }
std::string getUpdateChannel() {
return updateChannel;
}
std::string getChooseHomeTab() { std::string getChooseHomeTab() {
return chooseHomeTab; return chooseHomeTab;
} }
std::string getBackButtonBehavior() {
return backButtonBehavior;
}
bool getUseSpecialPad() { bool getUseSpecialPad() {
return useSpecialPad; return useSpecialPad;
} }
@ -275,14 +257,6 @@ bool showSplash() {
return isShowSplash; return isShowSplash;
} }
bool autoUpdate() {
return isAutoUpdate;
}
bool alwaysShowChangelog() {
return isAlwaysShowChangelog;
}
std::string sideTrophy() { std::string sideTrophy() {
return isSideTrophy; return isSideTrophy;
} }
@ -295,6 +269,18 @@ bool copyGPUCmdBuffers() {
return shouldCopyGPUBuffers; return shouldCopyGPUBuffers;
} }
bool readbacks() {
return readbacksEnabled;
}
bool readbackLinearImages() {
return readbackLinearImagesEnabled;
}
bool directMemoryAccess() {
return directMemoryAccessEnabled;
}
bool dumpShaders() { bool dumpShaders() {
return shouldDumpShaders; return shouldDumpShaders;
} }
@ -363,12 +349,20 @@ void setGpuId(s32 selectedGpuId) {
gpuId = selectedGpuId; gpuId = selectedGpuId;
} }
void setScreenWidth(u32 width) { void setWindowWidth(u32 width) {
screenWidth = width; windowWidth = width;
} }
void setScreenHeight(u32 height) { void setWindowHeight(u32 height) {
screenHeight = height; windowHeight = height;
}
void setInternalScreenWidth(u32 width) {
internalScreenWidth = width;
}
void setInternalScreenHeight(u32 height) {
internalScreenHeight = height;
} }
void setDebugDump(bool enable) { void setDebugDump(bool enable) {
@ -383,14 +377,6 @@ void setShowSplash(bool enable) {
isShowSplash = enable; isShowSplash = enable;
} }
void setAutoUpdate(bool enable) {
isAutoUpdate = enable;
}
void setAlwaysShowChangelog(bool enable) {
isAlwaysShowChangelog = enable;
}
void setSideTrophy(std::string side) { void setSideTrophy(std::string side) {
isSideTrophy = side; isSideTrophy = side;
} }
@ -407,6 +393,14 @@ void setCopyGPUCmdBuffers(bool enable) {
shouldCopyGPUBuffers = enable; shouldCopyGPUBuffers = enable;
} }
void setReadbacks(bool enable) {
readbacksEnabled = enable;
}
void setDirectMemoryAccess(bool enable) {
directMemoryAccessEnabled = enable;
}
void setDumpShaders(bool enable) { void setDumpShaders(bool enable) {
shouldDumpShaders = enable; shouldDumpShaders = enable;
} }
@ -430,9 +424,6 @@ void setVblankDiv(u32 value) {
void setIsFullscreen(bool enable) { void setIsFullscreen(bool enable) {
isFullscreen = enable; isFullscreen = enable;
} }
static void setShowLabelsUnderIcons(bool enable) {
showLabelsUnderIcons = enable;
}
void setFullscreenMode(std::string mode) { void setFullscreenMode(std::string mode) {
fullscreenMode = mode; fullscreenMode = mode;
@ -442,14 +433,6 @@ void setisTrophyPopupDisabled(bool disable) {
isTrophyPopupDisabled = disable; isTrophyPopupDisabled = disable;
} }
void setPlayBGM(bool enable) {
playBGM = enable;
}
void setBGMvolume(int volume) {
BGMvolume = volume;
}
void setEnableDiscordRPC(bool enable) { void setEnableDiscordRPC(bool enable) {
enableDiscordRPC = enable; enableDiscordRPC = enable;
} }
@ -461,6 +444,7 @@ void setCursorState(s16 newCursorState) {
void setCursorHideTimeout(int newcursorHideTimeout) { void setCursorHideTimeout(int newcursorHideTimeout) {
cursorHideTimeout = newcursorHideTimeout; cursorHideTimeout = newcursorHideTimeout;
} }
void setTrophyNotificationDuration(double newTrophyNotificationDuration) { void setTrophyNotificationDuration(double newTrophyNotificationDuration) {
trophyNotificationDuration = newTrophyNotificationDuration; trophyNotificationDuration = newTrophyNotificationDuration;
} }
@ -489,17 +473,10 @@ void setUserName(const std::string& type) {
userName = type; userName = type;
} }
void setUpdateChannel(const std::string& type) {
updateChannel = type;
}
void setChooseHomeTab(const std::string& type) { void setChooseHomeTab(const std::string& type) {
chooseHomeTab = type; chooseHomeTab = type;
} }
void setBackButtonBehavior(const std::string& type) {
backButtonBehavior = type;
}
void setUseSpecialPad(bool use) { void setUseSpecialPad(bool use) {
useSpecialPad = use; useSpecialPad = use;
} }
@ -520,13 +497,6 @@ void setCheckCompatibilityOnStartup(bool use) {
checkCompatibilityOnStartup = use; checkCompatibilityOnStartup = use;
} }
void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) {
main_window_geometry_x = x;
main_window_geometry_y = y;
main_window_geometry_w = w;
main_window_geometry_h = h;
}
bool addGameInstallDir(const std::filesystem::path& dir, bool enabled) { bool addGameInstallDir(const std::filesystem::path& dir, bool enabled) {
for (const auto& install_dir : settings_install_dirs) { for (const auto& install_dir : settings_install_dirs) {
if (install_dir.path == dir) { if (install_dir.path == dir) {
@ -559,52 +529,6 @@ void setAddonInstallDir(const std::filesystem::path& dir) {
settings_addon_install_dir = dir; settings_addon_install_dir = dir;
} }
void setMainWindowTheme(u32 theme) {
mw_themes = theme;
}
void setIconSize(u32 size) {
m_icon_size = size;
}
void setIconSizeGrid(u32 size) {
m_icon_size_grid = size;
}
void setSliderPosition(u32 pos) {
m_slider_pos = pos;
}
void setSliderPositionGrid(u32 pos) {
m_slider_pos_grid = pos;
}
void setTableMode(u32 mode) {
m_table_mode = mode;
}
void setMainWindowWidth(u32 width) {
m_window_size_W = width;
}
void setMainWindowHeight(u32 height) {
m_window_size_H = height;
}
void setElfViewer(const std::vector<std::string>& elfList) {
m_elf_viewer.resize(elfList.size());
m_elf_viewer = elfList;
}
void setRecentFiles(const std::vector<std::string>& recentFiles) {
m_recent_files.resize(recentFiles.size());
m_recent_files = recentFiles;
}
void setEmulatorLanguage(std::string language) {
emulator_language = language;
}
void setGameInstallDirs(const std::vector<std::filesystem::path>& dirs_config) { void setGameInstallDirs(const std::vector<std::filesystem::path>& dirs_config) {
settings_install_dirs.clear(); settings_install_dirs.clear();
for (const auto& dir : dirs_config) { for (const auto& dir : dirs_config) {
@ -620,22 +544,6 @@ void setSaveDataPath(const std::filesystem::path& path) {
save_data_path = path; save_data_path = path;
} }
u32 getMainWindowGeometryX() {
return main_window_geometry_x;
}
u32 getMainWindowGeometryY() {
return main_window_geometry_y;
}
u32 getMainWindowGeometryW() {
return main_window_geometry_w;
}
u32 getMainWindowGeometryH() {
return main_window_geometry_h;
}
const std::vector<std::filesystem::path> getGameInstallDirs() { const std::vector<std::filesystem::path> getGameInstallDirs() {
std::vector<std::filesystem::path> enabled_dirs; std::vector<std::filesystem::path> enabled_dirs;
for (const auto& dir : settings_install_dirs) { for (const auto& dir : settings_install_dirs) {
@ -662,50 +570,6 @@ std::filesystem::path getAddonInstallDir() {
return settings_addon_install_dir; return settings_addon_install_dir;
} }
u32 getMainWindowTheme() {
return mw_themes;
}
u32 getIconSize() {
return m_icon_size;
}
u32 getIconSizeGrid() {
return m_icon_size_grid;
}
u32 getSliderPosition() {
return m_slider_pos;
}
u32 getSliderPositionGrid() {
return m_slider_pos_grid;
}
u32 getTableMode() {
return m_table_mode;
}
u32 getMainWindowWidth() {
return m_window_size_W;
}
u32 getMainWindowHeight() {
return m_window_size_H;
}
std::vector<std::string> getElfViewer() {
return m_elf_viewer;
}
std::vector<std::string> getRecentFiles() {
return m_recent_files;
}
std::string getEmulatorLanguage() {
return emulator_language;
}
u32 GetLanguage() { u32 GetLanguage() {
return m_language; return m_language;
} }
@ -714,20 +578,12 @@ bool getSeparateLogFilesEnabled() {
return isSeparateLogFilesEnabled; return isSeparateLogFilesEnabled;
} }
int getBackgroundImageOpacity() { bool getPSNSignedIn() {
return backgroundImageOpacity; return isPSNSignedIn;
} }
void setBackgroundImageOpacity(int opacity) { void setPSNSignedIn(bool sign) {
backgroundImageOpacity = std::clamp(opacity, 0, 100); isPSNSignedIn = sign;
}
bool getShowBackgroundImage() {
return showBackgroundImage;
}
void setShowBackgroundImage(bool show) {
showBackgroundImage = show;
} }
void load(const std::filesystem::path& path) { void load(const std::filesystem::path& path) {
@ -749,95 +605,104 @@ void load(const std::filesystem::path& path) {
fmt::print("Got exception trying to load config file. Exception: {}\n", ex.what()); fmt::print("Got exception trying to load config file. Exception: {}\n", ex.what());
return; return;
} }
u64 entry_count = 0;
if (data.contains("General")) { if (data.contains("General")) {
const toml::value& general = data.at("General"); const toml::value& general = data.at("General");
isNeo = toml::find_or<bool>(general, "isPS4Pro", false); isNeo = toml::find_or<bool>(general, "isPS4Pro", isNeo);
isDevKit = toml::find_or<bool>(general, "isDevKit", false); isDevKit = toml::find_or<bool>(general, "isDevKit", isDevKit);
playBGM = toml::find_or<bool>(general, "playBGM", false); isPSNSignedIn = toml::find_or<bool>(general, "isPSNSignedIn", isPSNSignedIn);
isTrophyPopupDisabled = toml::find_or<bool>(general, "isTrophyPopupDisabled", false); isTrophyPopupDisabled =
trophyNotificationDuration = toml::find_or<bool>(general, "isTrophyPopupDisabled", isTrophyPopupDisabled);
toml::find_or<double>(general, "trophyNotificationDuration", 5.0); trophyNotificationDuration = toml::find_or<double>(general, "trophyNotificationDuration",
BGMvolume = toml::find_or<int>(general, "BGMvolume", 50); trophyNotificationDuration);
enableDiscordRPC = toml::find_or<bool>(general, "enableDiscordRPC", true); enableDiscordRPC = toml::find_or<bool>(general, "enableDiscordRPC", enableDiscordRPC);
logFilter = toml::find_or<std::string>(general, "logFilter", ""); logFilter = toml::find_or<std::string>(general, "logFilter", logFilter);
logType = toml::find_or<std::string>(general, "logType", "sync"); logType = toml::find_or<std::string>(general, "logType", logType);
userName = toml::find_or<std::string>(general, "userName", "shadPS4"); userName = toml::find_or<std::string>(general, "userName", userName);
if (Common::g_is_release) { isShowSplash = toml::find_or<bool>(general, "showSplash", isShowSplash);
updateChannel = toml::find_or<std::string>(general, "updateChannel", "Release"); isSideTrophy = toml::find_or<std::string>(general, "sideTrophy", isSideTrophy);
} else { compatibilityData = toml::find_or<bool>(general, "compatibilityEnabled", compatibilityData);
updateChannel = toml::find_or<std::string>(general, "updateChannel", "Nightly"); checkCompatibilityOnStartup = toml::find_or<bool>(general, "checkCompatibilityOnStartup",
} checkCompatibilityOnStartup);
isShowSplash = toml::find_or<bool>(general, "showSplash", true); chooseHomeTab = toml::find_or<std::string>(general, "chooseHomeTab", chooseHomeTab);
isAutoUpdate = toml::find_or<bool>(general, "autoUpdate", false);
isAlwaysShowChangelog = toml::find_or<bool>(general, "alwaysShowChangelog", false); entry_count += general.size();
isSideTrophy = toml::find_or<std::string>(general, "sideTrophy", "right");
compatibilityData = toml::find_or<bool>(general, "compatibilityEnabled", false);
checkCompatibilityOnStartup =
toml::find_or<bool>(general, "checkCompatibilityOnStartup", false);
chooseHomeTab = toml::find_or<std::string>(general, "chooseHomeTab", "Release");
} }
if (data.contains("Input")) { if (data.contains("Input")) {
const toml::value& input = data.at("Input"); const toml::value& input = data.at("Input");
cursorState = toml::find_or<int>(input, "cursorState", HideCursorState::Idle); cursorState = toml::find_or<int>(input, "cursorState", cursorState);
cursorHideTimeout = toml::find_or<int>(input, "cursorHideTimeout", 5); cursorHideTimeout = toml::find_or<int>(input, "cursorHideTimeout", cursorHideTimeout);
backButtonBehavior = toml::find_or<std::string>(input, "backButtonBehavior", "left"); useSpecialPad = toml::find_or<bool>(input, "useSpecialPad", useSpecialPad);
useSpecialPad = toml::find_or<bool>(input, "useSpecialPad", false); specialPadClass = toml::find_or<int>(input, "specialPadClass", specialPadClass);
specialPadClass = toml::find_or<int>(input, "specialPadClass", 1); isMotionControlsEnabled =
isMotionControlsEnabled = toml::find_or<bool>(input, "isMotionControlsEnabled", true); toml::find_or<bool>(input, "isMotionControlsEnabled", isMotionControlsEnabled);
useUnifiedInputConfig = toml::find_or<bool>(input, "useUnifiedInputConfig", true); useUnifiedInputConfig =
toml::find_or<bool>(input, "useUnifiedInputConfig", useUnifiedInputConfig);
entry_count += input.size();
} }
if (data.contains("GPU")) { if (data.contains("GPU")) {
const toml::value& gpu = data.at("GPU"); const toml::value& gpu = data.at("GPU");
screenWidth = toml::find_or<int>(gpu, "screenWidth", screenWidth); windowWidth = toml::find_or<int>(gpu, "screenWidth", windowWidth);
screenHeight = toml::find_or<int>(gpu, "screenHeight", screenHeight); windowHeight = toml::find_or<int>(gpu, "screenHeight", windowHeight);
isNullGpu = toml::find_or<bool>(gpu, "nullGpu", false); internalScreenWidth = toml::find_or<int>(gpu, "internalScreenWidth", internalScreenWidth);
shouldCopyGPUBuffers = toml::find_or<bool>(gpu, "copyGPUBuffers", false); internalScreenHeight =
shouldDumpShaders = toml::find_or<bool>(gpu, "dumpShaders", false); toml::find_or<int>(gpu, "internalScreenHeight", internalScreenHeight);
shouldPatchShaders = toml::find_or<bool>(gpu, "patchShaders", true); isNullGpu = toml::find_or<bool>(gpu, "nullGpu", isNullGpu);
vblankDivider = toml::find_or<int>(gpu, "vblankDivider", 1); shouldCopyGPUBuffers = toml::find_or<bool>(gpu, "copyGPUBuffers", shouldCopyGPUBuffers);
isFullscreen = toml::find_or<bool>(gpu, "Fullscreen", false); readbacksEnabled = toml::find_or<bool>(gpu, "readbacks", readbacksEnabled);
fullscreenMode = toml::find_or<std::string>(gpu, "FullscreenMode", "Windowed"); readbackLinearImagesEnabled =
isHDRAllowed = toml::find_or<bool>(gpu, "allowHDR", false); toml::find_or<bool>(gpu, "readbackLinearImages", readbackLinearImagesEnabled);
directMemoryAccessEnabled =
toml::find_or<bool>(gpu, "directMemoryAccess", directMemoryAccessEnabled);
shouldDumpShaders = toml::find_or<bool>(gpu, "dumpShaders", shouldDumpShaders);
shouldPatchShaders = toml::find_or<bool>(gpu, "patchShaders", shouldPatchShaders);
vblankDivider = toml::find_or<int>(gpu, "vblankDivider", vblankDivider);
isFullscreen = toml::find_or<bool>(gpu, "Fullscreen", isFullscreen);
fullscreenMode = toml::find_or<std::string>(gpu, "FullscreenMode", fullscreenMode);
isHDRAllowed = toml::find_or<bool>(gpu, "allowHDR", isHDRAllowed);
entry_count += gpu.size();
} }
if (data.contains("Vulkan")) { if (data.contains("Vulkan")) {
const toml::value& vk = data.at("Vulkan"); const toml::value& vk = data.at("Vulkan");
gpuId = toml::find_or<int>(vk, "gpuId", -1); gpuId = toml::find_or<int>(vk, "gpuId", gpuId);
vkValidation = toml::find_or<bool>(vk, "validation", false); vkValidation = toml::find_or<bool>(vk, "validation", vkValidation);
vkValidationSync = toml::find_or<bool>(vk, "validation_sync", false); vkValidationSync = toml::find_or<bool>(vk, "validation_sync", vkValidationSync);
vkValidationGpu = toml::find_or<bool>(vk, "validation_gpu", true); vkValidationGpu = toml::find_or<bool>(vk, "validation_gpu", vkValidationGpu);
vkCrashDiagnostic = toml::find_or<bool>(vk, "crashDiagnostic", false); vkCrashDiagnostic = toml::find_or<bool>(vk, "crashDiagnostic", vkCrashDiagnostic);
vkHostMarkers = toml::find_or<bool>(vk, "hostMarkers", false); vkHostMarkers = toml::find_or<bool>(vk, "hostMarkers", vkHostMarkers);
vkGuestMarkers = toml::find_or<bool>(vk, "guestMarkers", false); vkGuestMarkers = toml::find_or<bool>(vk, "guestMarkers", vkGuestMarkers);
rdocEnable = toml::find_or<bool>(vk, "rdocEnable", false); rdocEnable = toml::find_or<bool>(vk, "rdocEnable", rdocEnable);
entry_count += vk.size();
} }
if (data.contains("Debug")) { if (data.contains("Debug")) {
const toml::value& debug = data.at("Debug"); const toml::value& debug = data.at("Debug");
isDebugDump = toml::find_or<bool>(debug, "DebugDump", false); isDebugDump = toml::find_or<bool>(debug, "DebugDump", isDebugDump);
isSeparateLogFilesEnabled = toml::find_or<bool>(debug, "isSeparateLogFilesEnabled", false); isSeparateLogFilesEnabled =
isShaderDebug = toml::find_or<bool>(debug, "CollectShader", false); toml::find_or<bool>(debug, "isSeparateLogFilesEnabled", isSeparateLogFilesEnabled);
isFpsColor = toml::find_or<bool>(debug, "FPSColor", true); isShaderDebug = toml::find_or<bool>(debug, "CollectShader", isShaderDebug);
isFpsColor = toml::find_or<bool>(debug, "FPSColor", isFpsColor);
entry_count += debug.size();
} }
if (data.contains("GUI")) { if (data.contains("GUI")) {
const toml::value& gui = data.at("GUI"); const toml::value& gui = data.at("GUI");
load_game_size = toml::find_or<bool>(gui, "loadGameSizeEnabled", true); load_game_size = toml::find_or<bool>(gui, "loadGameSizeEnabled", load_game_size);
m_icon_size = toml::find_or<int>(gui, "iconSize", 0);
m_icon_size_grid = toml::find_or<int>(gui, "iconSizeGrid", 0);
m_slider_pos = toml::find_or<int>(gui, "sliderPos", 0);
m_slider_pos_grid = toml::find_or<int>(gui, "sliderPosGrid", 0);
mw_themes = toml::find_or<int>(gui, "theme", 0);
m_window_size_W = toml::find_or<int>(gui, "mw_width", 0);
m_window_size_H = toml::find_or<int>(gui, "mw_height", 0);
const auto install_dir_array = const auto install_dir_array =
toml::find_or<std::vector<std::u8string>>(gui, "installDirs", {}); toml::find_or<std::vector<std::u8string>>(gui, "installDirs", {});
@ -859,41 +724,31 @@ void load(const std::filesystem::path& path) {
{std::filesystem::path{install_dir_array[i]}, install_dirs_enabled[i]}); {std::filesystem::path{install_dir_array[i]}, install_dirs_enabled[i]});
} }
save_data_path = toml::find_fs_path_or(gui, "saveDataPath", {}); save_data_path = toml::find_fs_path_or(gui, "saveDataPath", save_data_path);
settings_addon_install_dir = toml::find_fs_path_or(gui, "addonInstallDir", {}); settings_addon_install_dir =
main_window_geometry_x = toml::find_or<int>(gui, "geometry_x", 0); toml::find_fs_path_or(gui, "addonInstallDir", settings_addon_install_dir);
main_window_geometry_y = toml::find_or<int>(gui, "geometry_y", 0);
main_window_geometry_w = toml::find_or<int>(gui, "geometry_w", 0); entry_count += gui.size();
main_window_geometry_h = toml::find_or<int>(gui, "geometry_h", 0);
m_elf_viewer = toml::find_or<std::vector<std::string>>(gui, "elfDirs", {});
m_recent_files = toml::find_or<std::vector<std::string>>(gui, "recentFiles", {});
m_table_mode = toml::find_or<int>(gui, "gameTableMode", 0);
emulator_language = toml::find_or<std::string>(gui, "emulatorLanguage", "en_US");
backgroundImageOpacity = toml::find_or<int>(gui, "backgroundImageOpacity", 50);
showBackgroundImage = toml::find_or<bool>(gui, "showBackgroundImage", true);
} }
if (data.contains("Settings")) { if (data.contains("Settings")) {
const toml::value& settings = data.at("Settings"); const toml::value& settings = data.at("Settings");
m_language = toml::find_or<int>(settings, "consoleLanguage", m_language);
m_language = toml::find_or<int>(settings, "consoleLanguage", 1); entry_count += settings.size();
} }
if (data.contains("Keys")) { if (data.contains("Keys")) {
const toml::value& keys = data.at("Keys"); const toml::value& keys = data.at("Keys");
trophyKey = toml::find_or<std::string>(keys, "TrophyKey", ""); trophyKey = toml::find_or<std::string>(keys, "TrophyKey", trophyKey);
entry_count += keys.size();
} }
// Check if the loaded language is in the allowed list // Run save after loading to generate any missing fields with default values.
const std::vector<std::string> allowed_languages = { if (entry_count != total_entries) {
"ar_SA", "da_DK", "de_DE", "el_GR", "en_US", "es_ES", "fa_IR", "fi_FI", "fr_FR", "hu_HU", fmt::print("Outdated config detected, updating config file.\n");
"id_ID", "it_IT", "ja_JP", "ko_KR", "lt_LT", "nb_NO", "nl_NL", "pl_PL", "pt_BR", "pt_PT",
"ro_RO", "ru_RU", "sq_AL", "sv_SE", "tr_TR", "uk_UA", "vi_VN", "zh_CN", "zh_TW"};
if (std::find(allowed_languages.begin(), allowed_languages.end(), emulator_language) ==
allowed_languages.end()) {
emulator_language = "en_US"; // Default to en_US if not in the list
save(path); save(path);
} }
} }
@ -953,33 +808,33 @@ void save(const std::filesystem::path& path) {
data["General"]["isPS4Pro"] = isNeo; data["General"]["isPS4Pro"] = isNeo;
data["General"]["isDevKit"] = isDevKit; data["General"]["isDevKit"] = isDevKit;
data["General"]["isPSNSignedIn"] = isPSNSignedIn;
data["General"]["isTrophyPopupDisabled"] = isTrophyPopupDisabled; data["General"]["isTrophyPopupDisabled"] = isTrophyPopupDisabled;
data["General"]["trophyNotificationDuration"] = trophyNotificationDuration; data["General"]["trophyNotificationDuration"] = trophyNotificationDuration;
data["General"]["playBGM"] = playBGM;
data["General"]["BGMvolume"] = BGMvolume;
data["General"]["enableDiscordRPC"] = enableDiscordRPC; data["General"]["enableDiscordRPC"] = enableDiscordRPC;
data["General"]["logFilter"] = logFilter; data["General"]["logFilter"] = logFilter;
data["General"]["logType"] = logType; data["General"]["logType"] = logType;
data["General"]["userName"] = userName; data["General"]["userName"] = userName;
data["General"]["updateChannel"] = updateChannel;
data["General"]["chooseHomeTab"] = chooseHomeTab; data["General"]["chooseHomeTab"] = chooseHomeTab;
data["General"]["showSplash"] = isShowSplash; data["General"]["showSplash"] = isShowSplash;
data["General"]["autoUpdate"] = isAutoUpdate;
data["General"]["alwaysShowChangelog"] = isAlwaysShowChangelog;
data["General"]["sideTrophy"] = isSideTrophy; data["General"]["sideTrophy"] = isSideTrophy;
data["General"]["compatibilityEnabled"] = compatibilityData; data["General"]["compatibilityEnabled"] = compatibilityData;
data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup; data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup;
data["Input"]["cursorState"] = cursorState; data["Input"]["cursorState"] = cursorState;
data["Input"]["cursorHideTimeout"] = cursorHideTimeout; data["Input"]["cursorHideTimeout"] = cursorHideTimeout;
data["Input"]["backButtonBehavior"] = backButtonBehavior;
data["Input"]["useSpecialPad"] = useSpecialPad; data["Input"]["useSpecialPad"] = useSpecialPad;
data["Input"]["specialPadClass"] = specialPadClass; data["Input"]["specialPadClass"] = specialPadClass;
data["Input"]["isMotionControlsEnabled"] = isMotionControlsEnabled; data["Input"]["isMotionControlsEnabled"] = isMotionControlsEnabled;
data["Input"]["useUnifiedInputConfig"] = useUnifiedInputConfig; data["Input"]["useUnifiedInputConfig"] = useUnifiedInputConfig;
data["GPU"]["screenWidth"] = screenWidth; data["GPU"]["screenWidth"] = windowWidth;
data["GPU"]["screenHeight"] = screenHeight; data["GPU"]["screenHeight"] = windowHeight;
data["GPU"]["internalScreenWidth"] = internalScreenWidth;
data["GPU"]["internalScreenHeight"] = internalScreenHeight;
data["GPU"]["nullGpu"] = isNullGpu; data["GPU"]["nullGpu"] = isNullGpu;
data["GPU"]["copyGPUBuffers"] = shouldCopyGPUBuffers; data["GPU"]["copyGPUBuffers"] = shouldCopyGPUBuffers;
data["GPU"]["readbacks"] = readbacksEnabled;
data["GPU"]["readbackLinearImages"] = readbackLinearImagesEnabled;
data["GPU"]["directMemoryAccess"] = directMemoryAccessEnabled;
data["GPU"]["dumpShaders"] = shouldDumpShaders; data["GPU"]["dumpShaders"] = shouldDumpShaders;
data["GPU"]["patchShaders"] = shouldPatchShaders; data["GPU"]["patchShaders"] = shouldPatchShaders;
data["GPU"]["vblankDivider"] = vblankDivider; data["GPU"]["vblankDivider"] = vblankDivider;
@ -1034,9 +889,6 @@ void save(const std::filesystem::path& path) {
data["GUI"]["addonInstallDir"] = data["GUI"]["addonInstallDir"] =
std::string{fmt::UTF(settings_addon_install_dir.u8string()).data}; std::string{fmt::UTF(settings_addon_install_dir.u8string()).data};
data["GUI"]["emulatorLanguage"] = emulator_language;
data["GUI"]["backgroundImageOpacity"] = backgroundImageOpacity;
data["GUI"]["showBackgroundImage"] = showBackgroundImage;
data["Settings"]["consoleLanguage"] = m_language; data["Settings"]["consoleLanguage"] = m_language;
// Sorting of TOML sections // Sorting of TOML sections
@ -1045,90 +897,56 @@ void save(const std::filesystem::path& path) {
std::ofstream file(path, std::ios::binary); std::ofstream file(path, std::ios::binary);
file << data; file << data;
file.close(); file.close();
saveMainWindow(path);
}
void saveMainWindow(const std::filesystem::path& path) {
toml::ordered_value data;
std::error_code error;
if (std::filesystem::exists(path, error)) {
try {
std::ifstream ifs;
ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit);
ifs.open(path, std::ios_base::binary);
data = toml::parse<toml::ordered_type_config>(
ifs, std::string{fmt::UTF(path.filename().u8string()).data});
} catch (const std::exception& ex) {
fmt::print("Exception trying to parse config file. Exception: {}\n", ex.what());
return;
}
} else {
if (error) {
fmt::print("Filesystem error: {}\n", error.message());
}
fmt::print("Saving new configuration file {}\n", fmt::UTF(path.u8string()));
}
data["GUI"]["mw_width"] = m_window_size_W;
data["GUI"]["mw_height"] = m_window_size_H;
data["GUI"]["theme"] = mw_themes;
data["GUI"]["iconSize"] = m_icon_size;
data["GUI"]["sliderPos"] = m_slider_pos;
data["GUI"]["iconSizeGrid"] = m_icon_size_grid;
data["GUI"]["sliderPosGrid"] = m_slider_pos_grid;
data["GUI"]["gameTableMode"] = m_table_mode;
data["GUI"]["geometry_x"] = main_window_geometry_x;
data["GUI"]["geometry_y"] = main_window_geometry_y;
data["GUI"]["geometry_w"] = main_window_geometry_w;
data["GUI"]["geometry_h"] = main_window_geometry_h;
data["GUI"]["elfDirs"] = m_elf_viewer;
data["GUI"]["recentFiles"] = m_recent_files;
// Sorting of TOML sections
sortTomlSections(data);
std::ofstream file(path, std::ios::binary);
file << data;
file.close();
} }
void setDefaultValues() { void setDefaultValues() {
isHDRAllowed = false; // General
isNeo = false; isNeo = false;
isDevKit = false; isDevKit = false;
isFullscreen = false; isPSNSignedIn = false;
isTrophyPopupDisabled = false; isTrophyPopupDisabled = false;
playBGM = false; trophyNotificationDuration = 6.0;
BGMvolume = 50; enableDiscordRPC = false;
enableDiscordRPC = true;
screenWidth = 1280;
screenHeight = 720;
logFilter = ""; logFilter = "";
logType = "sync"; logType = "sync";
userName = "shadPS4"; userName = "shadPS4";
if (Common::g_is_release) {
updateChannel = "Release";
} else {
updateChannel = "Nightly";
}
chooseHomeTab = "General"; chooseHomeTab = "General";
isShowSplash = false;
isSideTrophy = "right";
compatibilityData = false;
checkCompatibilityOnStartup = false;
// Input
cursorState = HideCursorState::Idle; cursorState = HideCursorState::Idle;
cursorHideTimeout = 5; cursorHideTimeout = 5;
trophyNotificationDuration = 6.0;
backButtonBehavior = "left";
useSpecialPad = false; useSpecialPad = false;
specialPadClass = 1; specialPadClass = 1;
isDebugDump = false; isMotionControlsEnabled = true;
isShaderDebug = false; useUnifiedInputConfig = true;
isShowSplash = false; overrideControllerColor = false;
isAutoUpdate = false; controllerCustomColorRGB[0] = 0;
isAlwaysShowChangelog = false; controllerCustomColorRGB[1] = 0;
isSideTrophy = "right"; controllerCustomColorRGB[2] = 255;
// GPU
windowWidth = 1280;
windowHeight = 720;
internalScreenWidth = 1280;
internalScreenHeight = 720;
isNullGpu = false; isNullGpu = false;
shouldCopyGPUBuffers = false;
readbacksEnabled = false;
readbackLinearImagesEnabled = false;
directMemoryAccessEnabled = false;
shouldDumpShaders = false; shouldDumpShaders = false;
shouldPatchShaders = false;
vblankDivider = 1; vblankDivider = 1;
isFullscreen = false;
fullscreenMode = "Windowed";
isHDRAllowed = false;
// Vulkan
gpuId = -1;
vkValidation = false; vkValidation = false;
vkValidationSync = false; vkValidationSync = false;
vkValidationGpu = false; vkValidationGpu = false;
@ -1136,13 +954,18 @@ void setDefaultValues() {
vkHostMarkers = false; vkHostMarkers = false;
vkGuestMarkers = false; vkGuestMarkers = false;
rdocEnable = false; rdocEnable = false;
emulator_language = "en_US";
// Debug
isDebugDump = false;
isShaderDebug = false;
isSeparateLogFilesEnabled = false;
isFpsColor = true;
// GUI
load_game_size = true;
// Settings
m_language = 1; m_language = 1;
gpuId = -1;
compatibilityData = false;
checkCompatibilityOnStartup = false;
backgroundImageOpacity = 50;
showBackgroundImage = true;
} }
constexpr std::string_view GetDefaultKeyboardConfig() { constexpr std::string_view GetDefaultKeyboardConfig() {
@ -1168,7 +991,7 @@ l3 = x
r3 = m r3 = m
options = enter options = enter
touchpad = space touchpad_center = space
pad_up = up pad_up = up
pad_down = down pad_down = down
@ -1200,7 +1023,7 @@ r2 = r2
r3 = r3 r3 = r3
options = options options = options
touchpad = back touchpad_center = back
pad_up = pad_up pad_up = pad_up
pad_down = pad_down pad_down = pad_down

View File

@ -14,172 +14,129 @@ struct GameInstallDir {
bool enabled; bool enabled;
}; };
enum HideCursorState : s16 { Never, Idle, Always }; enum HideCursorState : int { Never, Idle, Always };
void load(const std::filesystem::path& path); void load(const std::filesystem::path& path);
void save(const std::filesystem::path& path); void save(const std::filesystem::path& path);
void saveMainWindow(const std::filesystem::path& path);
std::string getTrophyKey(); std::string getTrophyKey();
void setTrophyKey(std::string key); void setTrophyKey(std::string key);
bool getIsFullscreen();
void setIsFullscreen(bool enable);
std::string getFullscreenMode();
void setFullscreenMode(std::string mode);
u32 getWindowWidth();
u32 getWindowHeight();
void setWindowWidth(u32 width);
void setWindowHeight(u32 height);
u32 getInternalScreenWidth();
u32 getInternalScreenHeight();
void setInternalScreenWidth(u32 width);
void setInternalScreenHeight(u32 height);
bool debugDump();
void setDebugDump(bool enable);
s32 getGpuId();
void setGpuId(s32 selectedGpuId);
bool allowHDR();
void setAllowHDR(bool enable);
bool collectShadersForDebug();
void setCollectShaderForDebug(bool enable);
bool showSplash();
void setShowSplash(bool enable);
std::string sideTrophy();
void setSideTrophy(std::string side);
bool nullGpu();
void setNullGpu(bool enable);
bool copyGPUCmdBuffers();
void setCopyGPUCmdBuffers(bool enable);
bool readbacks();
void setReadbacks(bool enable);
bool readbackLinearImages();
bool directMemoryAccess();
void setDirectMemoryAccess(bool enable);
bool dumpShaders();
void setDumpShaders(bool enable);
u32 vblankDiv();
void setVblankDiv(u32 value);
bool getisTrophyPopupDisabled();
void setisTrophyPopupDisabled(bool disable);
s16 getCursorState();
void setCursorState(s16 cursorState);
bool vkValidationEnabled();
void setVkValidation(bool enable);
bool vkValidationSyncEnabled();
void setVkSyncValidation(bool enable);
bool getVkCrashDiagnosticEnabled();
void setVkCrashDiagnosticEnabled(bool enable);
bool getVkHostMarkersEnabled();
void setVkHostMarkersEnabled(bool enable);
bool getVkGuestMarkersEnabled();
void setVkGuestMarkersEnabled(bool enable);
bool getEnableDiscordRPC();
void setEnableDiscordRPC(bool enable);
bool isRdocEnabled();
void setRdocEnabled(bool enable);
std::string getLogType();
void setLogType(const std::string& type);
std::string getLogFilter();
void setLogFilter(const std::string& type);
double getTrophyNotificationDuration();
void setTrophyNotificationDuration(double newTrophyNotificationDuration);
int getCursorHideTimeout();
void setCursorHideTimeout(int newcursorHideTimeout);
void setSeparateLogFilesEnabled(bool enabled);
bool getSeparateLogFilesEnabled();
u32 GetLanguage();
void setLanguage(u32 language);
void setUseSpecialPad(bool use);
bool getUseSpecialPad();
void setSpecialPadClass(int type);
int getSpecialPadClass();
bool getPSNSignedIn();
void setPSNSignedIn(bool sign); // no ui setting
bool patchShaders(); // no set
bool fpsColor(); // no set
bool isNeoModeConsole();
void setNeoMode(bool enable); // no ui setting
bool isDevKitConsole(); // no set
bool vkValidationGpuEnabled(); // no set
bool getIsMotionControlsEnabled();
void setIsMotionControlsEnabled(bool use);
// TODO
bool GetLoadGameSizeEnabled(); bool GetLoadGameSizeEnabled();
std::filesystem::path GetSaveDataPath(); std::filesystem::path GetSaveDataPath();
void setLoadGameSizeEnabled(bool enable); void setLoadGameSizeEnabled(bool enable);
bool getIsFullscreen();
bool getShowLabelsUnderIcons();
bool setShowLabelsUnderIcons();
std::string getFullscreenMode();
bool isNeoModeConsole();
bool isDevKitConsole();
bool getPlayBGM();
int getBGMvolume();
bool getisTrophyPopupDisabled();
bool getEnableDiscordRPC();
bool getCompatibilityEnabled(); bool getCompatibilityEnabled();
bool getCheckCompatibilityOnStartup(); bool getCheckCompatibilityOnStartup();
int getBackgroundImageOpacity();
bool getShowBackgroundImage();
std::string getLogFilter();
std::string getLogType();
std::string getUserName(); std::string getUserName();
std::string getUpdateChannel();
std::string getChooseHomeTab(); std::string getChooseHomeTab();
s16 getCursorState();
int getCursorHideTimeout();
double getTrophyNotificationDuration();
std::string getBackButtonBehavior();
bool getUseSpecialPad();
int getSpecialPadClass();
bool getIsMotionControlsEnabled();
bool GetUseUnifiedInputConfig(); bool GetUseUnifiedInputConfig();
void SetUseUnifiedInputConfig(bool use); void SetUseUnifiedInputConfig(bool use);
bool GetOverrideControllerColor(); bool GetOverrideControllerColor();
void SetOverrideControllerColor(bool enable); void SetOverrideControllerColor(bool enable);
int* GetControllerCustomColor(); int* GetControllerCustomColor();
void SetControllerCustomColor(int r, int b, int g); void SetControllerCustomColor(int r, int b, int g);
u32 getScreenWidth();
u32 getScreenHeight();
s32 getGpuId();
bool allowHDR();
bool debugDump();
bool collectShadersForDebug();
bool showSplash();
bool autoUpdate();
bool alwaysShowChangelog();
std::string sideTrophy();
bool nullGpu();
bool copyGPUCmdBuffers();
bool dumpShaders();
bool patchShaders();
bool isRdocEnabled();
bool fpsColor();
u32 vblankDiv();
void setDebugDump(bool enable);
void setCollectShaderForDebug(bool enable);
void setShowSplash(bool enable);
void setAutoUpdate(bool enable);
void setAlwaysShowChangelog(bool enable);
void setSideTrophy(std::string side);
void setNullGpu(bool enable);
void setAllowHDR(bool enable);
void setCopyGPUCmdBuffers(bool enable);
void setDumpShaders(bool enable);
void setVblankDiv(u32 value);
void setGpuId(s32 selectedGpuId);
void setScreenWidth(u32 width);
void setScreenHeight(u32 height);
void setIsFullscreen(bool enable);
void setFullscreenMode(std::string mode);
void setisTrophyPopupDisabled(bool disable);
void setPlayBGM(bool enable);
void setBGMvolume(int volume);
void setEnableDiscordRPC(bool enable);
void setLanguage(u32 language);
void setNeoMode(bool enable);
void setUserName(const std::string& type); void setUserName(const std::string& type);
void setUpdateChannel(const std::string& type);
void setChooseHomeTab(const std::string& type); void setChooseHomeTab(const std::string& type);
void setGameInstallDirs(const std::vector<std::filesystem::path>& dirs_config); void setGameInstallDirs(const std::vector<std::filesystem::path>& dirs_config);
void setAllGameInstallDirs(const std::vector<GameInstallDir>& dirs_config); void setAllGameInstallDirs(const std::vector<GameInstallDir>& dirs_config);
void setSaveDataPath(const std::filesystem::path& path); void setSaveDataPath(const std::filesystem::path& path);
void setCompatibilityEnabled(bool use); void setCompatibilityEnabled(bool use);
void setCheckCompatibilityOnStartup(bool use); void setCheckCompatibilityOnStartup(bool use);
void setBackgroundImageOpacity(int opacity);
void setShowBackgroundImage(bool show);
void setCursorState(s16 cursorState);
void setCursorHideTimeout(int newcursorHideTimeout);
void setTrophyNotificationDuration(double newTrophyNotificationDuration);
void setBackButtonBehavior(const std::string& type);
void setUseSpecialPad(bool use);
void setSpecialPadClass(int type);
void setIsMotionControlsEnabled(bool use);
void setLogType(const std::string& type);
void setLogFilter(const std::string& type);
void setSeparateLogFilesEnabled(bool enabled);
bool getSeparateLogFilesEnabled();
void setVkValidation(bool enable);
void setVkSyncValidation(bool enable);
void setRdocEnabled(bool enable);
bool vkValidationEnabled();
bool vkValidationSyncEnabled();
bool vkValidationGpuEnabled();
bool getVkCrashDiagnosticEnabled();
bool getVkHostMarkersEnabled();
bool getVkGuestMarkersEnabled();
void setVkCrashDiagnosticEnabled(bool enable);
void setVkHostMarkersEnabled(bool enable);
void setVkGuestMarkersEnabled(bool enable);
// Gui // Gui
void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h);
bool addGameInstallDir(const std::filesystem::path& dir, bool enabled = true); bool addGameInstallDir(const std::filesystem::path& dir, bool enabled = true);
void removeGameInstallDir(const std::filesystem::path& dir); void removeGameInstallDir(const std::filesystem::path& dir);
void setGameInstallDirEnabled(const std::filesystem::path& dir, bool enabled); void setGameInstallDirEnabled(const std::filesystem::path& dir, bool enabled);
void setAddonInstallDir(const std::filesystem::path& dir); void setAddonInstallDir(const std::filesystem::path& dir);
void setMainWindowTheme(u32 theme);
void setIconSize(u32 size);
void setIconSizeGrid(u32 size);
void setSliderPosition(u32 pos);
void setSliderPositionGrid(u32 pos);
void setTableMode(u32 mode);
void setMainWindowWidth(u32 width);
void setMainWindowHeight(u32 height);
void setElfViewer(const std::vector<std::string>& elfList);
void setRecentFiles(const std::vector<std::string>& recentFiles);
void setEmulatorLanguage(std::string language);
u32 getMainWindowGeometryX();
u32 getMainWindowGeometryY();
u32 getMainWindowGeometryW();
u32 getMainWindowGeometryH();
const std::vector<std::filesystem::path> getGameInstallDirs(); const std::vector<std::filesystem::path> getGameInstallDirs();
const std::vector<bool> getGameInstallDirsEnabled(); const std::vector<bool> getGameInstallDirsEnabled();
std::filesystem::path getAddonInstallDir(); std::filesystem::path getAddonInstallDir();
u32 getMainWindowTheme();
u32 getIconSize();
u32 getIconSizeGrid();
u32 getSliderPosition();
u32 getSliderPositionGrid();
u32 getTableMode();
u32 getMainWindowWidth();
u32 getMainWindowHeight();
std::vector<std::string> getElfViewer();
std::vector<std::string> getRecentFiles();
std::string getEmulatorLanguage();
void setDefaultValues(); void setDefaultValues();
// todo: name and function location pending // todo: name and function location pending
std::filesystem::path GetFoolproofKbmConfigFile(const std::string& game_id = ""); std::filesystem::path GetFoolproofKbmConfigFile(const std::string& game_id = "");
// settings
u32 GetLanguage();
}; // namespace Config }; // namespace Config

View File

@ -186,7 +186,9 @@ public:
template <typename T> template <typename T>
size_t WriteRaw(const void* data, size_t size) const { size_t WriteRaw(const void* data, size_t size) const {
return std::fwrite(data, sizeof(T), size, file); auto bytes = std::fwrite(data, sizeof(T), size, file);
std::fflush(file);
return bytes;
} }
template <typename T> template <typename T>

View File

@ -140,6 +140,8 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
SUB(Lib, SigninDialog) \ SUB(Lib, SigninDialog) \
SUB(Lib, Camera) \ SUB(Lib, Camera) \
SUB(Lib, CompanionHttpd) \ SUB(Lib, CompanionHttpd) \
SUB(Lib, CompanionUtil) \
SUB(Lib, Voice) \
CLS(Frontend) \ CLS(Frontend) \
CLS(Render) \ CLS(Render) \
SUB(Render, Vulkan) \ SUB(Render, Vulkan) \

View File

@ -98,6 +98,7 @@ enum class Class : u8 {
Lib_Fiber, ///< The LibSceFiber implementation. Lib_Fiber, ///< The LibSceFiber implementation.
Lib_Vdec2, ///< The LibSceVideodec2 implementation. Lib_Vdec2, ///< The LibSceVideodec2 implementation.
Lib_Videodec, ///< The LibSceVideodec implementation. Lib_Videodec, ///< The LibSceVideodec implementation.
Lib_Voice, ///< The LibSceVoice implementation.
Lib_RazorCpu, ///< The LibRazorCpu implementation. Lib_RazorCpu, ///< The LibRazorCpu implementation.
Lib_Mouse, ///< The LibSceMouse implementation Lib_Mouse, ///< The LibSceMouse implementation
Lib_WebBrowserDialog, ///< The LibSceWebBrowserDialog implementation Lib_WebBrowserDialog, ///< The LibSceWebBrowserDialog implementation
@ -107,6 +108,7 @@ enum class Class : u8 {
Lib_SigninDialog, ///< The LibSigninDialog implementation. Lib_SigninDialog, ///< The LibSigninDialog implementation.
Lib_Camera, ///< The LibCamera implementation. Lib_Camera, ///< The LibCamera implementation.
Lib_CompanionHttpd, ///< The LibCompanionHttpd implementation. Lib_CompanionHttpd, ///< The LibCompanionHttpd implementation.
Lib_CompanionUtil, ///< The LibCompanionUtil implementation.
Frontend, ///< Emulator UI Frontend, ///< Emulator UI
Render, ///< Video Core Render, ///< Video Core
Render_Vulkan, ///< Vulkan backend Render_Vulkan, ///< Vulkan backend

101
src/common/range_lock.h Normal file
View File

@ -0,0 +1,101 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <iterator>
#include <mutex>
namespace Common {
// From boost thread locking
template <typename Iterator>
struct RangeLockGuard {
Iterator begin;
Iterator end;
RangeLockGuard(Iterator begin_, Iterator end_) : begin(begin_), end(end_) {
LockRange(begin, end);
}
void release() {
begin = end;
}
~RangeLockGuard() {
for (; begin != end; ++begin) {
begin->unlock();
}
}
};
template <typename Iterator>
Iterator TryLockRange(Iterator begin, Iterator end) {
using LockType = typename std::iterator_traits<Iterator>::value_type;
if (begin == end) {
return end;
}
std::unique_lock<LockType> guard(*begin, std::try_to_lock);
if (!guard.owns_lock()) {
return begin;
}
Iterator failed = TryLockRange(++begin, end);
if (failed == end) {
guard.release();
}
return failed;
}
template <typename Iterator>
void LockRange(Iterator begin, Iterator end) {
using LockType = typename std::iterator_traits<Iterator>::value_type;
if (begin == end) {
return;
}
bool start_with_begin = true;
Iterator second = begin;
++second;
Iterator next = second;
while (true) {
std::unique_lock<LockType> begin_lock(*begin, std::defer_lock);
if (start_with_begin) {
begin_lock.lock();
const Iterator failed_lock = TryLockRange(next, end);
if (failed_lock == end) {
begin_lock.release();
return;
}
start_with_begin = false;
next = failed_lock;
} else {
RangeLockGuard<Iterator> guard(next, end);
if (begin_lock.try_lock()) {
const Iterator failed_lock = TryLockRange(second, next);
if (failed_lock == next) {
begin_lock.release();
guard.release();
return;
}
start_with_begin = false;
next = failed_lock;
} else {
start_with_begin = true;
next = second;
}
}
}
}
} // namespace Common

View File

@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <string>
#include "common/scm_rev.h" #include "common/scm_rev.h"
namespace Common { namespace Common {
@ -15,5 +17,26 @@ constexpr char g_scm_remote_name[] = "@GIT_REMOTE_NAME@";
constexpr char g_scm_remote_url[] = "@GIT_REMOTE_URL@"; constexpr char g_scm_remote_url[] = "@GIT_REMOTE_URL@";
constexpr char g_scm_date[] = "@BUILD_DATE@"; constexpr char g_scm_date[] = "@BUILD_DATE@";
const std::string GetRemoteNameFromLink() {
std::string remote_url(Common::g_scm_remote_url);
std::string remote_host;
try {
if (remote_url.starts_with("http")) {
if (*remote_url.rbegin() == '/') {
remote_url.pop_back();
}
remote_host = remote_url.substr(19, remote_url.rfind('/') - 19);
} else if (remote_url.starts_with("git@")) {
auto after_comma_pos = remote_url.find(':') + 1, slash_pos = remote_url.find('/');
remote_host = remote_url.substr(after_comma_pos, slash_pos - after_comma_pos);
} else {
remote_host = "unknown";
}
} catch (...) {
remote_host = "unknown";
}
return remote_host;
}
} // namespace } // namespace

View File

@ -3,6 +3,8 @@
#pragma once #pragma once
#include <string>
namespace Common { namespace Common {
extern const char g_version[]; extern const char g_version[];
@ -15,4 +17,6 @@ extern const char g_scm_remote_name[];
extern const char g_scm_remote_url[]; extern const char g_scm_remote_url[];
extern const char g_scm_date[]; extern const char g_scm_date[];
const std::string GetRemoteNameFromLink();
} // namespace Common } // namespace Common

View File

@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <condition_variable>
#include <mutex>
namespace Common {
// Like std::shared_mutex, but reader has priority over writer.
class SharedFirstMutex {
public:
void lock() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [this]() { return !writer_active && readers == 0; });
writer_active = true;
}
void unlock() {
std::lock_guard<std::mutex> lock(mtx);
writer_active = false;
cv.notify_all();
}
void lock_shared() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [this]() { return !writer_active; });
++readers;
}
void unlock_shared() {
std::lock_guard<std::mutex> lock(mtx);
if (--readers == 0) {
cv.notify_all();
}
}
private:
std::mutex mtx;
std::condition_variable cv;
int readers = 0;
bool writer_active = false;
};
} // namespace Common

View File

@ -302,14 +302,15 @@ struct AddressSpace::Impl {
new_flags = PAGE_READWRITE; new_flags = PAGE_READWRITE;
} else if (read && !write) { } else if (read && !write) {
new_flags = PAGE_READONLY; new_flags = PAGE_READONLY;
} else if (execute && !read && not write) { } else if (execute && !read && !write) {
new_flags = PAGE_EXECUTE; new_flags = PAGE_EXECUTE;
} else if (!read && !write && !execute) { } else if (!read && !write && !execute) {
new_flags = PAGE_NOACCESS; new_flags = PAGE_NOACCESS;
} else { } else {
LOG_CRITICAL(Common_Memory, LOG_CRITICAL(Common_Memory,
"Unsupported protection flag combination for address {:#x}, size {}", "Unsupported protection flag combination for address {:#x}, size {}, "
virtual_addr, size); "read={}, write={}, execute={}",
virtual_addr, size, read, write, execute);
return; return;
} }
@ -357,9 +358,17 @@ enum PosixPageProtection {
[[nodiscard]] constexpr PosixPageProtection ToPosixProt(Core::MemoryProt prot) { [[nodiscard]] constexpr PosixPageProtection ToPosixProt(Core::MemoryProt prot) {
if (True(prot & Core::MemoryProt::CpuReadWrite) || if (True(prot & Core::MemoryProt::CpuReadWrite) ||
True(prot & Core::MemoryProt::GpuReadWrite)) { True(prot & Core::MemoryProt::GpuReadWrite)) {
return PAGE_READWRITE; if (True(prot & Core::MemoryProt::CpuExec)) {
return PAGE_EXECUTE_READWRITE;
} else {
return PAGE_READWRITE;
}
} else if (True(prot & Core::MemoryProt::CpuRead) || True(prot & Core::MemoryProt::GpuRead)) { } else if (True(prot & Core::MemoryProt::CpuRead) || True(prot & Core::MemoryProt::GpuRead)) {
return PAGE_READONLY; if (True(prot & Core::MemoryProt::CpuExec)) {
return PAGE_EXECUTE_READ;
} else {
return PAGE_READONLY;
}
} else { } else {
return PAGE_NOACCESS; return PAGE_NOACCESS;
} }

View File

@ -11,6 +11,7 @@
namespace Core { namespace Core {
enum class MemoryPermission : u32 { enum class MemoryPermission : u32 {
None = 0,
Read = 1 << 0, Read = 1 << 0,
Write = 1 << 1, Write = 1 << 1,
ReadWrite = Read | Write, ReadWrite = Read | Write,

View File

@ -88,7 +88,8 @@ static bool FilterTcbAccess(const ZydisDecodedOperand* operands) {
dst_op.reg.value <= ZYDIS_REGISTER_R15; dst_op.reg.value <= ZYDIS_REGISTER_R15;
} }
static void GenerateTcbAccess(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { static void GenerateTcbAccess(void* /* address */, const ZydisDecodedOperand* operands,
Xbyak::CodeGenerator& c) {
const auto dst = ZydisToXbyakRegisterOperand(operands[0]); const auto dst = ZydisToXbyakRegisterOperand(operands[0]);
#if defined(_WIN32) #if defined(_WIN32)
@ -126,7 +127,8 @@ static bool FilterNoSSE4a(const ZydisDecodedOperand*) {
return !cpu.has(Cpu::tSSE4a); return !cpu.has(Cpu::tSSE4a);
} }
static void GenerateEXTRQ(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { static void GenerateEXTRQ(void* /* address */, const ZydisDecodedOperand* operands,
Xbyak::CodeGenerator& c) {
bool immediateForm = operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE && bool immediateForm = operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE &&
operands[2].type == ZYDIS_OPERAND_TYPE_IMMEDIATE; operands[2].type == ZYDIS_OPERAND_TYPE_IMMEDIATE;
@ -161,7 +163,9 @@ static void GenerateEXTRQ(const ZydisDecodedOperand* operands, Xbyak::CodeGenera
mask = (1ULL << length) - 1; mask = (1ULL << length) - 1;
} }
ASSERT_MSG(length + index <= 64, "length + index must be less than or equal to 64."); if (length + index > 64) {
mask = 0xFFFF'FFFF'FFFF'FFFF;
}
// Get lower qword from xmm register // Get lower qword from xmm register
c.vmovq(scratch1, xmm_dst); c.vmovq(scratch1, xmm_dst);
@ -175,8 +179,8 @@ static void GenerateEXTRQ(const ZydisDecodedOperand* operands, Xbyak::CodeGenera
c.mov(scratch2, mask); c.mov(scratch2, mask);
c.and_(scratch1, scratch2); c.and_(scratch1, scratch2);
// Writeback to xmm register, extrq instruction says top 64-bits are undefined so we don't // Writeback to xmm register, extrq instruction says top 64-bits are undefined but zeroed on
// care to preserve them // AMD CPUs
c.vmovq(xmm_dst, scratch1); c.vmovq(xmm_dst, scratch1);
c.pop(scratch2); c.pop(scratch2);
@ -245,7 +249,8 @@ static void GenerateEXTRQ(const ZydisDecodedOperand* operands, Xbyak::CodeGenera
} }
} }
static void GenerateINSERTQ(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) { static void GenerateINSERTQ(void* /* address */, const ZydisDecodedOperand* operands,
Xbyak::CodeGenerator& c) {
bool immediateForm = operands[2].type == ZYDIS_OPERAND_TYPE_IMMEDIATE && bool immediateForm = operands[2].type == ZYDIS_OPERAND_TYPE_IMMEDIATE &&
operands[3].type == ZYDIS_OPERAND_TYPE_IMMEDIATE; operands[3].type == ZYDIS_OPERAND_TYPE_IMMEDIATE;
@ -284,7 +289,9 @@ static void GenerateINSERTQ(const ZydisDecodedOperand* operands, Xbyak::CodeGene
mask_value = (1ULL << length) - 1; mask_value = (1ULL << length) - 1;
} }
ASSERT_MSG(length + index <= 64, "length + index must be less than or equal to 64."); if (length + index > 64) {
mask_value = 0xFFFF'FFFF'FFFF'FFFF;
}
c.vmovq(scratch1, xmm_src); c.vmovq(scratch1, xmm_src);
c.vmovq(scratch2, xmm_dst); c.vmovq(scratch2, xmm_dst);
@ -304,8 +311,9 @@ static void GenerateINSERTQ(const ZydisDecodedOperand* operands, Xbyak::CodeGene
// dst |= src // dst |= src
c.or_(scratch2, scratch1); c.or_(scratch2, scratch1);
// Insert scratch2 into low 64 bits of dst, upper 64 bits are unaffected // Insert scratch2 into low 64 bits of dst, upper 64 bits are undefined but zeroed on AMD
c.vpinsrq(xmm_dst, xmm_dst, scratch2, 0); // CPUs
c.vmovq(xmm_dst, scratch2);
c.pop(mask); c.pop(mask);
c.pop(scratch2); c.pop(scratch2);
@ -371,7 +379,7 @@ static void GenerateINSERTQ(const ZydisDecodedOperand* operands, Xbyak::CodeGene
c.and_(scratch2, mask); c.and_(scratch2, mask);
c.or_(scratch2, scratch1); c.or_(scratch2, scratch1);
// Upper 64 bits are undefined in insertq // Upper 64 bits are undefined in insertq but AMD CPUs zero them
c.vmovq(xmm_dst, scratch2); c.vmovq(xmm_dst, scratch2);
c.pop(mask); c.pop(mask);
@ -383,8 +391,44 @@ static void GenerateINSERTQ(const ZydisDecodedOperand* operands, Xbyak::CodeGene
} }
} }
static void ReplaceMOVNT(void* address, u8 rep_prefix) {
// Find the opcode byte
// There can be any amount of prefixes but the instruction can't be more than 15 bytes
// And we know for sure this is a MOVNTSS/MOVNTSD
bool found = false;
bool rep_prefix_found = false;
int index = 0;
u8* ptr = reinterpret_cast<u8*>(address);
for (int i = 0; i < 15; i++) {
if (ptr[i] == rep_prefix) {
rep_prefix_found = true;
} else if (ptr[i] == 0x2B) {
index = i;
found = true;
break;
}
}
// Some sanity checks
ASSERT(found);
ASSERT(index >= 2);
ASSERT(ptr[index - 1] == 0x0F);
ASSERT(rep_prefix_found);
// This turns the MOVNTSS/MOVNTSD to a MOVSS/MOVSD m, xmm
ptr[index] = 0x11;
}
static void ReplaceMOVNTSS(void* address, const ZydisDecodedOperand*, Xbyak::CodeGenerator&) {
ReplaceMOVNT(address, 0xF3);
}
static void ReplaceMOVNTSD(void* address, const ZydisDecodedOperand*, Xbyak::CodeGenerator&) {
ReplaceMOVNT(address, 0xF2);
}
using PatchFilter = bool (*)(const ZydisDecodedOperand*); using PatchFilter = bool (*)(const ZydisDecodedOperand*);
using InstructionGenerator = void (*)(const ZydisDecodedOperand*, Xbyak::CodeGenerator&); using InstructionGenerator = void (*)(void*, const ZydisDecodedOperand*, Xbyak::CodeGenerator&);
struct PatchInfo { struct PatchInfo {
/// Filter for more granular patch conditions past just the instruction mnemonic. /// Filter for more granular patch conditions past just the instruction mnemonic.
PatchFilter filter; PatchFilter filter;
@ -400,6 +444,8 @@ static const std::unordered_map<ZydisMnemonic, PatchInfo> Patches = {
// SSE4a // SSE4a
{ZYDIS_MNEMONIC_EXTRQ, {FilterNoSSE4a, GenerateEXTRQ, true}}, {ZYDIS_MNEMONIC_EXTRQ, {FilterNoSSE4a, GenerateEXTRQ, true}},
{ZYDIS_MNEMONIC_INSERTQ, {FilterNoSSE4a, GenerateINSERTQ, true}}, {ZYDIS_MNEMONIC_INSERTQ, {FilterNoSSE4a, GenerateINSERTQ, true}},
{ZYDIS_MNEMONIC_MOVNTSS, {FilterNoSSE4a, ReplaceMOVNTSS, false}},
{ZYDIS_MNEMONIC_MOVNTSD, {FilterNoSSE4a, ReplaceMOVNTSD, false}},
#if defined(_WIN32) #if defined(_WIN32)
// Windows needs a trampoline. // Windows needs a trampoline.
@ -477,7 +523,7 @@ static std::pair<bool, u64> TryPatch(u8* code, PatchModule* module) {
auto& trampoline_gen = module->trampoline_gen; auto& trampoline_gen = module->trampoline_gen;
const auto trampoline_ptr = trampoline_gen.getCurr(); const auto trampoline_ptr = trampoline_gen.getCurr();
patch_info.generator(operands, trampoline_gen); patch_info.generator(code, operands, trampoline_gen);
// Return to the following instruction at the end of the trampoline. // Return to the following instruction at the end of the trampoline.
trampoline_gen.jmp(code + instruction.length); trampoline_gen.jmp(code + instruction.length);
@ -485,7 +531,7 @@ static std::pair<bool, u64> TryPatch(u8* code, PatchModule* module) {
// Replace instruction with near jump to the trampoline. // Replace instruction with near jump to the trampoline.
patch_gen.jmp(trampoline_ptr, Xbyak::CodeGenerator::LabelType::T_NEAR); patch_gen.jmp(trampoline_ptr, Xbyak::CodeGenerator::LabelType::T_NEAR);
} else { } else {
patch_info.generator(operands, patch_gen); patch_info.generator(code, operands, patch_gen);
} }
const auto patch_size = patch_gen.getCurr() - code; const auto patch_size = patch_gen.getCurr() - code;
@ -594,6 +640,7 @@ static bool TryExecuteIllegalInstruction(void* ctx, void* code_address) {
lowQWordDst >>= index; lowQWordDst >>= index;
lowQWordDst &= mask; lowQWordDst &= mask;
memset((u8*)dst + sizeof(u64), 0, sizeof(u64));
memcpy(dst, &lowQWordDst, sizeof(lowQWordDst)); memcpy(dst, &lowQWordDst, sizeof(lowQWordDst));
Common::IncrementRip(ctx, 4); Common::IncrementRip(ctx, 4);
@ -634,6 +681,7 @@ static bool TryExecuteIllegalInstruction(void* ctx, void* code_address) {
lowQWordDst &= ~(mask << index); lowQWordDst &= ~(mask << index);
lowQWordDst |= lowQWordSrc << index; lowQWordDst |= lowQWordSrc << index;
memset((u8*)dst + sizeof(u64), 0, sizeof(u64));
memcpy(dst, &lowQWordDst, sizeof(lowQWordDst)); memcpy(dst, &lowQWordDst, sizeof(lowQWordDst));
Common::IncrementRip(ctx, 4); Common::IncrementRip(ctx, 4);

View File

@ -10,6 +10,8 @@
namespace Core::FileSys { namespace Core::FileSys {
bool MntPoints::ignore_game_patches = false;
std::string RemoveTrailingSlashes(const std::string& path) { std::string RemoveTrailingSlashes(const std::string& path) {
// Remove trailing slashes to make comparisons simpler. // Remove trailing slashes to make comparisons simpler.
std::string path_sanitized = path; std::string path_sanitized = path;
@ -77,7 +79,7 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea
patch_path /= rel_path; patch_path /= rel_path;
if ((corrected_path.starts_with("/app0") || corrected_path.starts_with("/hostapp")) && if ((corrected_path.starts_with("/app0") || corrected_path.starts_with("/hostapp")) &&
!force_base_path && std::filesystem::exists(patch_path)) { !force_base_path && !ignore_game_patches && std::filesystem::exists(patch_path)) {
return patch_path; return patch_path;
} }
@ -137,7 +139,7 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea
return std::optional<std::filesystem::path>(current_path); return std::optional<std::filesystem::path>(current_path);
}; };
if (!force_base_path) { if (!force_base_path && !ignore_game_patches) {
if (const auto path = search(patch_path)) { if (const auto path = search(patch_path)) {
return *path; return *path;
} }

View File

@ -21,6 +21,7 @@ class MntPoints {
static constexpr bool NeedsCaseInsensitiveSearch = true; static constexpr bool NeedsCaseInsensitiveSearch = true;
#endif #endif
public: public:
static bool ignore_game_patches;
struct MntPair { struct MntPair {
std::filesystem::path host_path; std::filesystem::path host_path;
std::string mount; // e.g /app0 std::string mount; // e.g /app0

View File

@ -3,6 +3,8 @@
#pragma once #pragma once
#include "common/types.h"
// companion_httpd error codes // companion_httpd error codes
constexpr int ORBIS_COMPANION_HTTPD_ERROR_UNKNOWN = 0x80E40001; constexpr int ORBIS_COMPANION_HTTPD_ERROR_UNKNOWN = 0x80E40001;
constexpr int ORBIS_COMPANION_HTTPD_ERROR_FATAL = 0x80E40002; constexpr int ORBIS_COMPANION_HTTPD_ERROR_FATAL = 0x80E40002;
@ -18,3 +20,8 @@ constexpr int ORBIS_COMPANION_HTTPD_ERROR_NOT_STARTED = 0x80E4000B;
constexpr int ORBIS_COMPANION_HTTPD_ERROR_ALREADY_REGISTERED = 0x80E4000; constexpr int ORBIS_COMPANION_HTTPD_ERROR_ALREADY_REGISTERED = 0x80E4000;
constexpr int ORBIS_COMPANION_HTTPD_ERROR_NOT_CONNECTED = 0x80E4000D; constexpr int ORBIS_COMPANION_HTTPD_ERROR_NOT_CONNECTED = 0x80E4000D;
constexpr int ORBIS_COMPANION_HTTPD_ERROR_USER_NOT_FOUND = 0x80E4000E; constexpr int ORBIS_COMPANION_HTTPD_ERROR_USER_NOT_FOUND = 0x80E4000E;
// companion_util error codes
constexpr u32 ORBIS_COMPANION_UTIL_INVALID_ARGUMENT = 0x80AD0004;
constexpr u32 ORBIS_COMPANION_UTIL_INVALID_POINTER = 0x80AD0006;
constexpr u32 ORBIS_COMPANION_UTIL_NO_EVENT = 0x80AD0008;

View File

@ -0,0 +1,72 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "companion_error.h"
#include "core/libraries/companion/companion_util.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
namespace Libraries::CompanionUtil {
u32 PS4_SYSV_ABI getEvent(sceCompanionUtilContext* ctx, sceCompanionUtilEvent* outEvent,
s32 param_3) {
if (outEvent == 0) {
return ORBIS_COMPANION_UTIL_INVALID_ARGUMENT;
}
if (ctx == nullptr) {
return ORBIS_COMPANION_UTIL_INVALID_POINTER;
}
uint8_t* base = ctx->blob;
int flag = *reinterpret_cast<int*>(base + 0x178);
if (flag == 0) {
return ORBIS_COMPANION_UTIL_NO_EVENT;
}
return ORBIS_COMPANION_UTIL_OK;
}
s32 PS4_SYSV_ABI sceCompanionUtilGetEvent(sceCompanionUtilEvent* outEvent) {
sceCompanionUtilContext* ctx = nullptr;
u32 ret = getEvent(ctx, outEvent, 1);
LOG_DEBUG(Lib_CompanionUtil, "(STUBBED) called ret: {}", ret);
return ret;
}
s32 PS4_SYSV_ABI sceCompanionUtilGetRemoteOskEvent() {
LOG_ERROR(Lib_CompanionUtil, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceCompanionUtilInitialize() {
LOG_ERROR(Lib_CompanionUtil, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceCompanionUtilOptParamInitialize() {
LOG_ERROR(Lib_CompanionUtil, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceCompanionUtilTerminate() {
LOG_ERROR(Lib_CompanionUtil, "(STUBBED) called");
return ORBIS_OK;
}
void RegisterlibSceCompanionUtil(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("cE5Msy11WhU", "libSceCompanionUtil", 1, "libSceCompanionUtil", 1, 1,
sceCompanionUtilGetEvent);
LIB_FUNCTION("MaVrz79mT5o", "libSceCompanionUtil", 1, "libSceCompanionUtil", 1, 1,
sceCompanionUtilGetRemoteOskEvent);
LIB_FUNCTION("xb1xlIhf0QY", "libSceCompanionUtil", 1, "libSceCompanionUtil", 1, 1,
sceCompanionUtilInitialize);
LIB_FUNCTION("IPN-FRSrafk", "libSceCompanionUtil", 1, "libSceCompanionUtil", 1, 1,
sceCompanionUtilOptParamInitialize);
LIB_FUNCTION("H1fYQd5lFAI", "libSceCompanionUtil", 1, "libSceCompanionUtil", 1, 1,
sceCompanionUtilTerminate);
};
} // namespace Libraries::CompanionUtil

View File

@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::CompanionUtil {
constexpr u32 ORBIS_COMPANION_UTIL_OK = 0;
struct sceCompanionUtilEvent {
std::uint8_t blob[0x104]{}; /// 0x104 bytes of data, dont know what it is exactly
};
struct sceCompanionUtilContext {
std::uint8_t blob[0x27B]{}; /// 0x27B bytes of data, dont know what it is exactly
};
u32 PS4_SYSV_ABI getEvent(sceCompanionUtilContext* ctx, sceCompanionUtilEvent* outEvent,
s32 param_3);
s32 PS4_SYSV_ABI sceCompanionUtilGetEvent(sceCompanionUtilEvent* outEvent);
s32 PS4_SYSV_ABI sceCompanionUtilGetRemoteOskEvent();
s32 PS4_SYSV_ABI sceCompanionUtilInitialize();
s32 PS4_SYSV_ABI sceCompanionUtilOptParamInitialize();
s32 PS4_SYSV_ABI sceCompanionUtilTerminate();
void RegisterlibSceCompanionUtil(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::CompanionUtil

View File

@ -2834,7 +2834,7 @@ void RegisterlibSceGnmDriver(Core::Loader::SymbolsResolver* sym) {
} }
if (Config::copyGPUCmdBuffers()) { if (Config::copyGPUCmdBuffers()) {
liverpool->reserveCopyBufferSpace(); liverpool->ReserveCopyBufferSpace();
} }
Platform::IrqC::Instance()->Register(Platform::InterruptId::GpuIdle, ResetSubmissionLock, Platform::IrqC::Instance()->Register(Platform::InterruptId::GpuIdle, ResetSubmissionLock,

View File

@ -43,8 +43,8 @@ public:
openEvent.param.rect.x = m_param.ime.posx; openEvent.param.rect.x = m_param.ime.posx;
openEvent.param.rect.y = m_param.ime.posy; openEvent.param.rect.y = m_param.ime.posy;
} else { } else {
openEvent.param.resource_id_array.userId = 1; openEvent.param.resource_id_array.user_id = 1;
openEvent.param.resource_id_array.resourceId[0] = 1; openEvent.param.resource_id_array.resource_id[0] = 1;
} }
// Are we supposed to call the event handler on init with // Are we supposed to call the event handler on init with
@ -59,10 +59,10 @@ public:
} }
} }
s32 Update(OrbisImeEventHandler handler) { Error Update(OrbisImeEventHandler handler) {
if (!m_ime_mode) { if (!m_ime_mode) {
/* We don't handle any events for ImeKeyboard */ /* We don't handle any events for ImeKeyboard */
return ORBIS_OK; return Error::OK;
} }
std::unique_lock lock{g_ime_state.queue_mutex}; std::unique_lock lock{g_ime_state.queue_mutex};
@ -73,7 +73,7 @@ public:
Execute(handler, &event, false); Execute(handler, &event, false);
} }
return ORBIS_OK; return Error::OK;
} }
void Execute(OrbisImeEventHandler handler, OrbisImeEvent* event, bool use_param_handler) { void Execute(OrbisImeEventHandler handler, OrbisImeEvent* event, bool use_param_handler) {
@ -94,14 +94,14 @@ public:
} }
} }
s32 SetText(const char16_t* text, u32 length) { Error SetText(const char16_t* text, u32 length) {
g_ime_state.SetText(text, length); g_ime_state.SetText(text, length);
return ORBIS_OK; return Error::OK;
} }
s32 SetCaret(const OrbisImeCaret* caret) { Error SetCaret(const OrbisImeCaret* caret) {
g_ime_state.SetCaret(caret->index); g_ime_state.SetCaret(caret->index);
return ORBIS_OK; return Error::OK;
} }
bool IsIme() { bool IsIme() {
@ -222,11 +222,11 @@ int PS4_SYSV_ABI sceImeGetPanelPositionAndForm() {
return ORBIS_OK; return ORBIS_OK;
} }
s32 PS4_SYSV_ABI sceImeGetPanelSize(const OrbisImeParam* param, u32* width, u32* height) { Error PS4_SYSV_ABI sceImeGetPanelSize(const OrbisImeParam* param, u32* width, u32* height) {
LOG_INFO(Lib_Ime, "called"); LOG_INFO(Lib_Ime, "called");
if (!width || !height) { if (!width || !height) {
return ORBIS_IME_ERROR_INVALID_ADDRESS; return Error::INVALID_ADDRESS;
} }
switch (param->type) { switch (param->type) {
@ -244,18 +244,18 @@ s32 PS4_SYSV_ABI sceImeGetPanelSize(const OrbisImeParam* param, u32* width, u32*
break; break;
} }
return ORBIS_OK; return Error::OK;
} }
s32 PS4_SYSV_ABI sceImeKeyboardClose(s32 userId) { Error PS4_SYSV_ABI sceImeKeyboardClose(s32 userId) {
LOG_INFO(Lib_Ime, "(STUBBED) called"); LOG_INFO(Lib_Ime, "(STUBBED) called");
if (!g_keyboard_handler) { if (!g_keyboard_handler) {
return ORBIS_IME_ERROR_NOT_OPENED; return Error::NOT_OPENED;
} }
g_keyboard_handler.release(); g_keyboard_handler.release();
return ORBIS_OK; return Error::OK;
} }
int PS4_SYSV_ABI sceImeKeyboardGetInfo() { int PS4_SYSV_ABI sceImeKeyboardGetInfo() {
@ -268,25 +268,25 @@ int PS4_SYSV_ABI sceImeKeyboardGetResourceId() {
return ORBIS_OK; return ORBIS_OK;
} }
s32 PS4_SYSV_ABI sceImeKeyboardOpen(s32 userId, const OrbisImeKeyboardParam* param) { Error PS4_SYSV_ABI sceImeKeyboardOpen(s32 userId, const OrbisImeKeyboardParam* param) {
LOG_INFO(Lib_Ime, "called"); LOG_INFO(Lib_Ime, "called");
if (!param) { if (!param) {
return ORBIS_IME_ERROR_INVALID_ADDRESS; return Error::INVALID_ADDRESS;
} }
if (!param->arg) { if (!param->arg) {
return ORBIS_IME_ERROR_INVALID_ARG; return Error::INVALID_ARG;
} }
if (!param->handler) { if (!param->handler) {
return ORBIS_IME_ERROR_INVALID_HANDLER; return Error::INVALID_HANDLER;
} }
if (g_keyboard_handler) { if (g_keyboard_handler) {
return ORBIS_IME_ERROR_BUSY; return Error::BUSY;
} }
g_keyboard_handler = std::make_unique<ImeHandler>(param); g_keyboard_handler = std::make_unique<ImeHandler>(param);
return ORBIS_OK; return Error::OK;
} }
int PS4_SYSV_ABI sceImeKeyboardOpenInternal() { int PS4_SYSV_ABI sceImeKeyboardOpenInternal() {
@ -304,18 +304,18 @@ int PS4_SYSV_ABI sceImeKeyboardUpdate() {
return ORBIS_OK; return ORBIS_OK;
} }
s32 PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const void* extended) { Error PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const OrbisImeParamExtended* extended) {
LOG_INFO(Lib_Ime, "called"); LOG_INFO(Lib_Ime, "called");
if (!param) { if (!param) {
return ORBIS_IME_ERROR_INVALID_ADDRESS; return Error::INVALID_ADDRESS;
} }
if (g_ime_handler) { if (g_ime_handler) {
return ORBIS_IME_ERROR_BUSY; return Error::BUSY;
} }
g_ime_handler = std::make_unique<ImeHandler>(param); g_ime_handler = std::make_unique<ImeHandler>(param);
return ORBIS_OK; return Error::OK;
} }
int PS4_SYSV_ABI sceImeOpenInternal() { int PS4_SYSV_ABI sceImeOpenInternal() {
@ -339,27 +339,27 @@ int PS4_SYSV_ABI sceImeSetCandidateIndex() {
return ORBIS_OK; return ORBIS_OK;
} }
int PS4_SYSV_ABI sceImeSetCaret(const OrbisImeCaret* caret) { Error PS4_SYSV_ABI sceImeSetCaret(const OrbisImeCaret* caret) {
LOG_TRACE(Lib_Ime, "called"); LOG_TRACE(Lib_Ime, "called");
if (!g_ime_handler) { if (!g_ime_handler) {
return ORBIS_IME_ERROR_NOT_OPENED; return Error::NOT_OPENED;
} }
if (!caret) { if (!caret) {
return ORBIS_IME_ERROR_INVALID_ADDRESS; return Error::INVALID_ADDRESS;
} }
return g_ime_handler->SetCaret(caret); return g_ime_handler->SetCaret(caret);
} }
s32 PS4_SYSV_ABI sceImeSetText(const char16_t* text, u32 length) { Error PS4_SYSV_ABI sceImeSetText(const char16_t* text, u32 length) {
LOG_TRACE(Lib_Ime, "called"); LOG_TRACE(Lib_Ime, "called");
if (!g_ime_handler) { if (!g_ime_handler) {
return ORBIS_IME_ERROR_NOT_OPENED; return Error::NOT_OPENED;
} }
if (!text) { if (!text) {
return ORBIS_IME_ERROR_INVALID_ADDRESS; return Error::INVALID_ADDRESS;
} }
return g_ime_handler->SetText(text, length); return g_ime_handler->SetText(text, length);
@ -370,7 +370,7 @@ int PS4_SYSV_ABI sceImeSetTextGeometry() {
return ORBIS_OK; return ORBIS_OK;
} }
s32 PS4_SYSV_ABI sceImeUpdate(OrbisImeEventHandler handler) { Error PS4_SYSV_ABI sceImeUpdate(OrbisImeEventHandler handler) {
if (g_ime_handler) { if (g_ime_handler) {
g_ime_handler->Update(handler); g_ime_handler->Update(handler);
} }
@ -380,10 +380,10 @@ s32 PS4_SYSV_ABI sceImeUpdate(OrbisImeEventHandler handler) {
} }
if (!g_ime_handler || !g_keyboard_handler) { if (!g_ime_handler || !g_keyboard_handler) {
return ORBIS_IME_ERROR_NOT_OPENED; return Error::NOT_OPENED;
} }
return ORBIS_OK; return Error::OK;
} }
int PS4_SYSV_ABI sceImeVshClearPreedit() { int PS4_SYSV_ABI sceImeVshClearPreedit() {

View File

@ -13,72 +13,6 @@ class SymbolsResolver;
namespace Libraries::Ime { namespace Libraries::Ime {
constexpr u32 ORBIS_IME_MAX_TEXT_LENGTH = 2048;
enum class OrbisImeKeyboardOption : u32 {
Default = 0,
Repeat = 1,
RepeatEachKey = 2,
AddOsk = 4,
EffectiveWithIme = 8,
DisableResume = 16,
DisableCapslockWithoutShift = 32,
};
DECLARE_ENUM_FLAG_OPERATORS(OrbisImeKeyboardOption)
enum class OrbisImeOption : u32 {
DEFAULT = 0,
MULTILINE = 1,
NO_AUTO_CAPITALIZATION = 2,
PASSWORD = 4,
LANGUAGES_FORCED = 8,
EXT_KEYBOARD = 16,
NO_LEARNING = 32,
FIXED_POSITION = 64,
DISABLE_RESUME = 256,
DISABLE_AUTO_SPACE = 512,
DISABLE_POSITION_ADJUSTMENT = 2048,
EXPANDED_PREEDIT_BUFFER = 4096,
USE_JAPANESE_EISUU_KEY_AS_CAPSLOCK = 8192,
USE_2K_COORDINATES = 16384,
};
DECLARE_ENUM_FLAG_OPERATORS(OrbisImeOption)
struct OrbisImeKeyboardParam {
OrbisImeKeyboardOption option;
s8 reserved1[4];
void* arg;
OrbisImeEventHandler handler;
s8 reserved2[8];
};
struct OrbisImeParam {
s32 user_id;
OrbisImeType type;
u64 supported_languages;
OrbisImeEnterLabel enter_label;
OrbisImeInputMethod input_method;
OrbisImeTextFilter filter;
OrbisImeOption option;
u32 maxTextLength;
char16_t* inputTextBuffer;
float posx;
float posy;
OrbisImeHorizontalAlignment horizontal_alignment;
OrbisImeVerticalAlignment vertical_alignment;
void* work;
void* arg;
OrbisImeEventHandler handler;
s8 reserved[8];
};
struct OrbisImeCaret {
f32 x;
f32 y;
u32 height;
u32 index;
};
int PS4_SYSV_ABI FinalizeImeModule(); int PS4_SYSV_ABI FinalizeImeModule();
int PS4_SYSV_ABI InitializeImeModule(); int PS4_SYSV_ABI InitializeImeModule();
int PS4_SYSV_ABI sceImeCheckFilterText(); int PS4_SYSV_ABI sceImeCheckFilterText();
@ -98,22 +32,22 @@ int PS4_SYSV_ABI sceImeDisableController();
int PS4_SYSV_ABI sceImeFilterText(); int PS4_SYSV_ABI sceImeFilterText();
int PS4_SYSV_ABI sceImeForTestFunction(); int PS4_SYSV_ABI sceImeForTestFunction();
int PS4_SYSV_ABI sceImeGetPanelPositionAndForm(); int PS4_SYSV_ABI sceImeGetPanelPositionAndForm();
s32 PS4_SYSV_ABI sceImeGetPanelSize(const OrbisImeParam* param, u32* width, u32* height); Error PS4_SYSV_ABI sceImeGetPanelSize(const OrbisImeParam* param, u32* width, u32* height);
s32 PS4_SYSV_ABI sceImeKeyboardClose(s32 userId); Error PS4_SYSV_ABI sceImeKeyboardClose(s32 userId);
int PS4_SYSV_ABI sceImeKeyboardGetInfo(); int PS4_SYSV_ABI sceImeKeyboardGetInfo();
int PS4_SYSV_ABI sceImeKeyboardGetResourceId(); int PS4_SYSV_ABI sceImeKeyboardGetResourceId();
s32 PS4_SYSV_ABI sceImeKeyboardOpen(s32 userId, const OrbisImeKeyboardParam* param); Error PS4_SYSV_ABI sceImeKeyboardOpen(s32 userId, const OrbisImeKeyboardParam* param);
int PS4_SYSV_ABI sceImeKeyboardOpenInternal(); int PS4_SYSV_ABI sceImeKeyboardOpenInternal();
int PS4_SYSV_ABI sceImeKeyboardSetMode(); int PS4_SYSV_ABI sceImeKeyboardSetMode();
int PS4_SYSV_ABI sceImeKeyboardUpdate(); int PS4_SYSV_ABI sceImeKeyboardUpdate();
s32 PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const void* extended); Error PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const OrbisImeParamExtended* extended);
int PS4_SYSV_ABI sceImeOpenInternal(); int PS4_SYSV_ABI sceImeOpenInternal();
void PS4_SYSV_ABI sceImeParamInit(OrbisImeParam* param); void PS4_SYSV_ABI sceImeParamInit(OrbisImeParam* param);
int PS4_SYSV_ABI sceImeSetCandidateIndex(); int PS4_SYSV_ABI sceImeSetCandidateIndex();
s32 PS4_SYSV_ABI sceImeSetCaret(const OrbisImeCaret* caret); Error PS4_SYSV_ABI sceImeSetCaret(const OrbisImeCaret* caret);
s32 PS4_SYSV_ABI sceImeSetText(const char16_t* text, u32 length); Error PS4_SYSV_ABI sceImeSetText(const char16_t* text, u32 length);
int PS4_SYSV_ABI sceImeSetTextGeometry(); int PS4_SYSV_ABI sceImeSetTextGeometry();
s32 PS4_SYSV_ABI sceImeUpdate(OrbisImeEventHandler handler); Error PS4_SYSV_ABI sceImeUpdate(OrbisImeEventHandler handler);
int PS4_SYSV_ABI sceImeVshClearPreedit(); int PS4_SYSV_ABI sceImeVshClearPreedit();
int PS4_SYSV_ABI sceImeVshClose(); int PS4_SYSV_ABI sceImeVshClose();
int PS4_SYSV_ABI sceImeVshConfirmPreedit(); int PS4_SYSV_ABI sceImeVshConfirmPreedit();

View File

@ -3,9 +3,108 @@
#pragma once #pragma once
#include "common/enum.h"
#include "common/types.h" #include "common/types.h"
#include "core/libraries/rtc/rtc.h" #include "core/libraries/rtc/rtc.h"
constexpr u32 ORBIS_IME_MAX_TEXT_LENGTH = 2048;
constexpr u32 ORBIS_IME_DIALOG_MAX_TEXT_LENGTH = 2048;
enum class Error : u32 {
OK = 0x0,
BUSY = 0x80bc0001,
NOT_OPENED = 0x80bc0002,
NO_MEMORY = 0x80bc0003,
CONNECTION_FAILED = 0x80bc0004,
TOO_MANY_REQUESTS = 0x80bc0005,
INVALID_TEXT = 0x80bc0006,
EVENT_OVERFLOW = 0x80bc0007,
NOT_ACTIVE = 0x80bc0008,
IME_SUSPENDING = 0x80bc0009,
DEVICE_IN_USE = 0x80bc000a,
INVALID_USER_ID = 0x80bc0010,
INVALID_TYPE = 0x80bc0011,
INVALID_SUPPORTED_LANGUAGES = 0x80bc0012,
INVALID_ENTER_LABEL = 0x80bc0013,
INVALID_INPUT_METHOD = 0x80bc0014,
INVALID_OPTION = 0x80bc0015,
INVALID_MAX_TEXT_LENGTH = 0x80bc0016,
INVALID_INPUT_TEXT_BUFFER = 0x80bc0017,
INVALID_POSX = 0x80bc0018,
INVALID_POSY = 0x80bc0019,
INVALID_HORIZONTALIGNMENT = 0x80bc001a,
INVALID_VERTICALALIGNMENT = 0x80bc001b,
INVALID_EXTENDED = 0x80bc001c,
INVALID_KEYBOARD_TYPE = 0x80bc001d,
INVALID_WORK = 0x80bc0020,
INVALID_ARG = 0x80bc0021,
INVALID_HANDLER = 0x80bc0022,
NO_RESOURCE_ID = 0x80bc0023,
INVALID_MODE = 0x80bc0024,
INVALID_PARAM = 0x80bc0030,
INVALID_ADDRESS = 0x80bc0031,
INVALID_RESERVED = 0x80bc0032,
INVALID_TIMING = 0x80bc0033,
INTERNAL = 0x80bc00ff,
DIALOG_INVALID_TITLE = 0x80bc0101,
DIALOG_NOT_RUNNING = 0x80bc0105,
DIALOG_NOT_FINISHED = 0x80bc0106,
DIALOG_NOT_IN_USE = 0x80bc0107
};
enum class OrbisImeOption : u32 {
DEFAULT = 0,
MULTILINE = 1,
NO_AUTO_CAPITALIZATION = 2,
PASSWORD = 4,
LANGUAGES_FORCED = 8,
EXT_KEYBOARD = 16,
NO_LEARNING = 32,
FIXED_POSITION = 64,
DISABLE_COPY_PASTE = 128,
DISABLE_RESUME = 256,
DISABLE_AUTO_SPACE = 512,
DISABLE_POSITION_ADJUSTMENT = 2048,
EXPANDED_PREEDIT_BUFFER = 4096,
USE_JAPANESE_EISUU_KEY_AS_CAPSLOCK = 8192,
USE_2K_COORDINATES = 16384,
};
DECLARE_ENUM_FLAG_OPERATORS(OrbisImeOption);
enum class OrbisImeLanguage : u64 {
DANISH = 0x0000000000000001,
GERMAN = 0x0000000000000002,
ENGLISH_US = 0x0000000000000004,
SPANISH = 0x0000000000000008,
FRENCH = 0x0000000000000010,
ITALIAN = 0x0000000000000020,
DUTCH = 0x0000000000000040,
NORWEGIAN = 0x0000000000000080,
POLISH = 0x0000000000000100,
PORTUGUESE_PT = 0x0000000000000200,
RUSSIAN = 0x0000000000000400,
FINNISH = 0x0000000000000800,
SWEDISH = 0x0000000000001000,
JAPANESE = 0x0000000000002000,
KOREAN = 0x0000000000004000,
SIMPLIFIED_CHINESE = 0x0000000000008000,
TRADITIONAL_CHINESE = 0x0000000000010000,
PORTUGUESE_BR = 0x0000000000020000,
ENGLISH_GB = 0x0000000000040000,
TURKISH = 0x0000000000080000,
SPANISH_LA = 0x0000000000100000,
ARABIC = 0x0000000001000000,
FRENCH_CA = 0x0000000002000000,
THAI = 0x0000000004000000,
CZECH = 0x0000000008000000,
GREEK = 0x0000000010000000,
INDONESIAN = 0x0000000020000000,
VIETNAMESE = 0x0000000040000000,
ROMANIAN = 0x0000000080000000,
HUNGARIAN = 0x0000000100000000,
};
DECLARE_ENUM_FLAG_OPERATORS(OrbisImeLanguage);
enum class OrbisImeType : u32 { enum class OrbisImeType : u32 {
Default = 0, Default = 0,
BasicLatin = 1, BasicLatin = 1,
@ -41,6 +140,7 @@ enum class OrbisImeEventId : u32 {
Open = 0, Open = 0,
UpdateText = 1, UpdateText = 1,
UpdateCaret = 2, UpdateCaret = 2,
ChangeSize = 3,
PressClose = 4, PressClose = 4,
PressEnter = 5, PressEnter = 5,
Abort = 6, Abort = 6,
@ -51,6 +151,10 @@ enum class OrbisImeEventId : u32 {
CandidateDone = 11, CandidateDone = 11,
CandidateCancel = 12, CandidateCancel = 12,
ChangeDevice = 14, ChangeDevice = 14,
JumpToNextObject = 15,
JumpToBeforeObject = 16,
ChangeWindowType = 17,
ChangeInputMethodState = 18, ChangeInputMethodState = 18,
KeyboardOpen = 256, KeyboardOpen = 256,
@ -110,6 +214,13 @@ enum class OrbisImeDeviceType : u32 {
RemoteOsk = 3, RemoteOsk = 3,
}; };
enum class OrbisImePanelPriority : u32 {
Default = 0,
Alphabet = 1,
Symbol = 2,
Accent = 3,
};
struct OrbisImeRect { struct OrbisImeRect {
f32 x; f32 x;
f32 y; f32 y;
@ -117,8 +228,22 @@ struct OrbisImeRect {
u32 height; u32 height;
}; };
struct OrbisImeColor {
u8 r;
u8 g;
u8 b;
u8 a;
};
enum class OrbisImeTextAreaMode : u32 {
Disable = 0,
Edit = 1,
Preedit = 2,
Select = 3,
};
struct OrbisImeTextAreaProperty { struct OrbisImeTextAreaProperty {
u32 mode; // OrbisImeTextAreaMode OrbisImeTextAreaMode mode;
u32 index; u32 index;
s32 length; s32 length;
}; };
@ -135,14 +260,14 @@ struct OrbisImeKeycode {
char16_t character; char16_t character;
u32 status; u32 status;
OrbisImeKeyboardType type; OrbisImeKeyboardType type;
s32 user_id; s32 user_id; // Todo: switch to OrbisUserServiceUserId
u32 resource_id; u32 resource_id;
Libraries::Rtc::OrbisRtcTick timestamp; Libraries::Rtc::OrbisRtcTick timestamp;
}; };
struct OrbisImeKeyboardResourceIdArray { struct OrbisImeKeyboardResourceIdArray {
s32 userId; s32 user_id; // Todo: switch to OrbisUserServiceUserId
u32 resourceId[5]; u32 resource_id[5];
}; };
enum class OrbisImeCaretMovementDirection : u32 { enum class OrbisImeCaretMovementDirection : u32 {
@ -159,6 +284,16 @@ enum class OrbisImeCaretMovementDirection : u32 {
Bottom = 10, Bottom = 10,
}; };
enum class OrbisImePanelType : u32 {
Hide = 0,
Osk = 1,
Dialog = 2,
Candidate = 3,
Edit = 4,
EditAndCandidate = 5,
Accessibility = 6,
};
union OrbisImeEventParam { union OrbisImeEventParam {
OrbisImeRect rect; OrbisImeRect rect;
OrbisImeEditText text; OrbisImeEditText text;
@ -168,6 +303,7 @@ union OrbisImeEventParam {
char16_t* candidate_word; char16_t* candidate_word;
s32 candidate_index; s32 candidate_index;
OrbisImeDeviceType device_type; OrbisImeDeviceType device_type;
OrbisImePanelType panel_type;
u32 input_method_state; u32 input_method_state;
s8 reserved[64]; s8 reserved[64];
}; };
@ -177,7 +313,95 @@ struct OrbisImeEvent {
OrbisImeEventParam param; OrbisImeEventParam param;
}; };
using OrbisImeExtKeyboardFilter = PS4_SYSV_ABI int (*)(const OrbisImeKeycode* srcKeycode,
u16* outKeycode, u32* outStatus,
void* reserved);
using OrbisImeTextFilter = PS4_SYSV_ABI int (*)(char16_t* outText, u32* outTextLength, using OrbisImeTextFilter = PS4_SYSV_ABI int (*)(char16_t* outText, u32* outTextLength,
const char16_t* srcText, u32 srcTextLength); const char16_t* srcText, u32 srcTextLength);
using OrbisImeEventHandler = PS4_SYSV_ABI void (*)(void* arg, const OrbisImeEvent* e); using OrbisImeEventHandler = PS4_SYSV_ABI void (*)(void* arg, const OrbisImeEvent* e);
enum class OrbisImeKeyboardOption : u32 {
Default = 0,
Repeat = 1,
RepeatEachKey = 2,
AddOsk = 4,
EffectiveWithIme = 8,
DisableResume = 16,
DisableCapslockWithoutShift = 32,
};
DECLARE_ENUM_FLAG_OPERATORS(OrbisImeKeyboardOption)
struct OrbisImeKeyboardParam {
OrbisImeKeyboardOption option;
s8 reserved1[4];
void* arg;
OrbisImeEventHandler handler;
s8 reserved2[8];
};
struct OrbisImeParam {
s32 user_id; // Todo: switch to OrbisUserServiceUserId
OrbisImeType type;
u64 supported_languages; // OrbisImeLanguage flags
OrbisImeEnterLabel enter_label;
OrbisImeInputMethod input_method;
OrbisImeTextFilter filter;
OrbisImeOption option;
u32 maxTextLength;
char16_t* inputTextBuffer;
f32 posx;
f32 posy;
OrbisImeHorizontalAlignment horizontal_alignment;
OrbisImeVerticalAlignment vertical_alignment;
void* work;
void* arg;
OrbisImeEventHandler handler;
s8 reserved[8];
};
struct OrbisImeCaret {
f32 x;
f32 y;
u32 height;
u32 index;
};
struct OrbisImeDialogParam {
s32 user_id;
OrbisImeType type;
u64 supported_languages; // OrbisImeLanguage flags
OrbisImeEnterLabel enter_label;
OrbisImeInputMethod input_method;
OrbisImeTextFilter filter;
OrbisImeOption option;
u32 max_text_length;
char16_t* input_text_buffer;
f32 posx;
f32 posy;
OrbisImeHorizontalAlignment horizontal_alignment;
OrbisImeVerticalAlignment vertical_alignment;
const char16_t* placeholder;
const char16_t* title;
s8 reserved[16];
};
struct OrbisImeParamExtended {
u32 option; // OrbisImeExtOption flags
OrbisImeColor color_base;
OrbisImeColor color_line;
OrbisImeColor color_text_field;
OrbisImeColor color_preedit;
OrbisImeColor color_button_default;
OrbisImeColor color_button_function;
OrbisImeColor color_button_symbol;
OrbisImeColor color_text;
OrbisImeColor color_special;
OrbisImePanelPriority priority;
char* additional_dictionary_path;
OrbisImeExtKeyboardFilter ext_keyboard_filter;
u32 disable_device;
u32 ext_keyboard_mode;
s8 reserved[60];
};

View File

@ -20,19 +20,19 @@ static OrbisImeDialogResult g_ime_dlg_result{};
static ImeDialogState g_ime_dlg_state{}; static ImeDialogState g_ime_dlg_state{};
static ImeDialogUi g_ime_dlg_ui; static ImeDialogUi g_ime_dlg_ui;
static bool IsValidOption(OrbisImeDialogOption option, OrbisImeType type) { static bool IsValidOption(OrbisImeOption option, OrbisImeType type) {
if (False(~option & if (False(~option & (OrbisImeOption::MULTILINE |
(OrbisImeDialogOption::Multiline | OrbisImeDialogOption::NoAutoCompletion))) { OrbisImeOption::NO_AUTO_CAPITALIZATION /* NoAutoCompletion */))) {
return false; return false;
} }
if (True(option & OrbisImeDialogOption::Multiline) && type != OrbisImeType::Default && if (True(option & OrbisImeOption::MULTILINE) && type != OrbisImeType::Default &&
type != OrbisImeType::BasicLatin) { type != OrbisImeType::BasicLatin) {
return false; return false;
} }
if (True(option & OrbisImeDialogOption::NoAutoCompletion) && type != OrbisImeType::Number && if (True(option & OrbisImeOption::NO_AUTO_CAPITALIZATION /* NoAutoCompletion */) &&
type != OrbisImeType::BasicLatin) { type != OrbisImeType::Number && type != OrbisImeType::BasicLatin) {
return false; return false;
} }
@ -96,7 +96,7 @@ Error PS4_SYSV_ABI sceImeDialogGetPanelSize(const OrbisImeDialogParam* param, u3
case OrbisImeType::Url: case OrbisImeType::Url:
case OrbisImeType::Mail: case OrbisImeType::Mail:
*width = 500; // original: 793 *width = 500; // original: 793
if (True(param->option & OrbisImeDialogOption::Multiline)) { if (True(param->option & OrbisImeOption::MULTILINE)) {
*height = 300; // original: 576 *height = 300; // original: 576
} else { } else {
*height = 150; // original: 476 *height = 150; // original: 476
@ -230,21 +230,20 @@ void DumpImeDialogParam(const Libraries::ImeDialog::OrbisImeDialogParam* param,
} }
Error PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExtended* extended) { Error PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExtended* extended) {
LOG_INFO(Lib_ImeDialog, ">> sceImeDialogInit: entering, param={}, extended={}",
DumpImeDialogParam(param, extended); static_cast<void*>(param), static_cast<void*>(extended));
if (g_ime_dlg_status != OrbisImeDialogStatus::None) { if (g_ime_dlg_status != OrbisImeDialogStatus::None) {
LOG_INFO(Lib_ImeDialog, "IME dialog is already running"); LOG_ERROR(Lib_ImeDialog, "sceImeDialogInit: busy (status=%u)", (u32)g_ime_dlg_status);
return Error::BUSY; return Error::BUSY;
} }
if (param == nullptr) { if (param == nullptr) {
LOG_INFO(Lib_ImeDialog, "called with param (NULL)"); LOG_ERROR(Lib_ImeDialog, "sceImeDialogInit: param is null");
return Error::INVALID_ADDRESS; return Error::INVALID_ADDRESS;
} }
if (!magic_enum::enum_contains(param->type)) { if (!magic_enum::enum_contains(param->type)) {
LOG_INFO(Lib_ImeDialog, "Invalid param->type"); LOG_ERROR(Lib_ImeDialog, "sceImeDialogInit: invalid param->type=%u", (u32)param->type);
return Error::INVALID_ADDRESS; return Error::INVALID_ADDRESS;
} }
@ -252,16 +251,14 @@ Error PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExt
// TODO: do correct param->supportedLanguages validation // TODO: do correct param->supportedLanguages validation
if (param->posx < 0.0f || if (param->posx < 0.0f ||
param->posx >= param->posx >= MAX_X_POSITIONS[False(param->option & OrbisImeOption::USE_2K_COORDINATES)]) {
MAX_X_POSITIONS[False(param->option & OrbisImeDialogOption::LargeResolution)]) { LOG_ERROR(Lib_ImeDialog, "sceImeDialogInit: invalid posx=%f", param->posx);
LOG_INFO(Lib_ImeDialog, "Invalid param->posx");
return Error::INVALID_POSX; return Error::INVALID_POSX;
} }
if (param->posy < 0.0f || if (param->posy < 0.0f ||
param->posy >= param->posy >= MAX_Y_POSITIONS[False(param->option & OrbisImeOption::USE_2K_COORDINATES)]) {
MAX_Y_POSITIONS[False(param->option & OrbisImeDialogOption::LargeResolution)]) { LOG_ERROR(Lib_ImeDialog, "sceImeDialogInit: invalid posy=%f", param->posy);
LOG_INFO(Lib_ImeDialog, "Invalid param->posy");
return Error::INVALID_POSY; return Error::INVALID_POSY;
} }
@ -276,12 +273,13 @@ Error PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExt
} }
if (!IsValidOption(param->option, param->type)) { if (!IsValidOption(param->option, param->type)) {
LOG_INFO(Lib_ImeDialog, "Invalid param->option"); LOG_ERROR(Lib_ImeDialog, "sceImeDialogInit: invalid option=0x%X for type=%u",
static_cast<u32>(param->option), (u32)param->type);
return Error::INVALID_PARAM; return Error::INVALID_PARAM;
} }
if (param->input_text_buffer == nullptr) { if (param->input_text_buffer == nullptr) {
LOG_INFO(Lib_ImeDialog, "Invalid param->inputTextBuffer"); LOG_ERROR(Lib_ImeDialog, "sceImeDialogInit: input_text_buffer is null");
return Error::INVALID_INPUT_TEXT_BUFFER; return Error::INVALID_INPUT_TEXT_BUFFER;
} }
@ -304,16 +302,24 @@ Error PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExt
} }
} }
if (param->max_text_length > ORBIS_IME_DIALOG_MAX_TEXT_LENGTH) { if (param->max_text_length == 0 || param->max_text_length > ORBIS_IME_MAX_TEXT_LENGTH) {
LOG_INFO(Lib_ImeDialog, "Invalid param->maxTextLength ({})", param->max_text_length); LOG_ERROR(Lib_ImeDialog, "sceImeDialogInit: invalid max_text_length=%u",
param->max_text_length);
return Error::INVALID_MAX_TEXT_LENGTH; return Error::INVALID_MAX_TEXT_LENGTH;
} }
// Title string validation
if (param->title != nullptr && !std::char_traits<char16_t>::length(param->title)) {
LOG_ERROR(Lib_ImeDialog, "sceImeDialogInit: title is empty");
return Error::INVALID_PARAM;
}
g_ime_dlg_result = {}; g_ime_dlg_result = {};
g_ime_dlg_state = ImeDialogState(param, extended); g_ime_dlg_state = ImeDialogState(param, extended);
g_ime_dlg_status = OrbisImeDialogStatus::Running; g_ime_dlg_status = OrbisImeDialogStatus::Running;
g_ime_dlg_ui = ImeDialogUi(&g_ime_dlg_state, &g_ime_dlg_status, &g_ime_dlg_result); g_ime_dlg_ui = ImeDialogUi(&g_ime_dlg_state, &g_ime_dlg_status, &g_ime_dlg_result);
LOG_INFO(Lib_ImeDialog, "<< sceImeDialogInit: successful, status now=Running");
return Error::OK; return Error::OK;
} }

View File

@ -13,50 +13,6 @@ class SymbolsResolver;
namespace Libraries::ImeDialog { namespace Libraries::ImeDialog {
constexpr u32 ORBIS_IME_DIALOG_MAX_TEXT_LENGTH = 2048;
enum class Error : u32 {
OK = 0x0,
BUSY = 0x80bc0001,
NOT_OPENED = 0x80bc0002,
NO_MEMORY = 0x80bc0003,
CONNECTION_FAILED = 0x80bc0004,
TOO_MANY_REQUESTS = 0x80bc0005,
INVALID_TEXT = 0x80bc0006,
EVENT_OVERFLOW = 0x80bc0007,
NOT_ACTIVE = 0x80bc0008,
IME_SUSPENDING = 0x80bc0009,
DEVICE_IN_USE = 0x80bc000a,
INVALID_USER_ID = 0x80bc0010,
INVALID_TYPE = 0x80bc0011,
INVALID_SUPPORTED_LANGUAGES = 0x80bc0012,
INVALID_ENTER_LABEL = 0x80bc0013,
INVALID_INPUT_METHOD = 0x80bc0014,
INVALID_OPTION = 0x80bc0015,
INVALID_MAX_TEXT_LENGTH = 0x80bc0016,
INVALID_INPUT_TEXT_BUFFER = 0x80bc0017,
INVALID_POSX = 0x80bc0018,
INVALID_POSY = 0x80bc0019,
INVALID_HORIZONTALIGNMENT = 0x80bc001a,
INVALID_VERTICALALIGNMENT = 0x80bc001b,
INVALID_EXTENDED = 0x80bc001c,
INVALID_KEYBOARD_TYPE = 0x80bc001d,
INVALID_WORK = 0x80bc0020,
INVALID_ARG = 0x80bc0021,
INVALID_HANDLER = 0x80bc0022,
NO_RESOURCE_ID = 0x80bc0023,
INVALID_MODE = 0x80bc0024,
INVALID_PARAM = 0x80bc0030,
INVALID_ADDRESS = 0x80bc0031,
INVALID_RESERVED = 0x80bc0032,
INVALID_TIMING = 0x80bc0033,
INTERNAL = 0x80bc00ff,
DIALOG_INVALID_TITLE = 0x80bc0101,
DIALOG_NOT_RUNNING = 0x80bc0105,
DIALOG_NOT_FINISHED = 0x80bc0106,
DIALOG_NOT_IN_USE = 0x80bc0107,
};
enum class OrbisImeDialogStatus : u32 { enum class OrbisImeDialogStatus : u32 {
None = 0, None = 0,
Running = 1, Running = 1,
@ -69,87 +25,11 @@ enum class OrbisImeDialogEndStatus : u32 {
Aborted = 2, Aborted = 2,
}; };
enum class OrbisImeDialogOption : u32 {
Default = 0,
Multiline = 1,
NoAutoCorrection = 2,
NoAutoCompletion = 4,
// TODO: Document missing options
LargeResolution = 1024,
};
DECLARE_ENUM_FLAG_OPERATORS(OrbisImeDialogOption)
enum class OrbisImePanelPriority : u32 {
Default = 0,
Alphabet = 1,
Symbol = 2,
Accent = 3,
};
struct OrbisImeColor {
u8 r;
u8 g;
u8 b;
u8 a;
};
struct OrbisImeDialogResult { struct OrbisImeDialogResult {
OrbisImeDialogEndStatus endstatus; OrbisImeDialogEndStatus endstatus;
s32 reserved[12]; s32 reserved[12];
}; };
struct OrbisImeKeycode {
u16 keycode;
char16_t character;
u32 status;
OrbisImeKeyboardType type;
s32 user_id;
u32 resource_id;
u64 timestamp;
};
using OrbisImeExtKeyboardFilter = PS4_SYSV_ABI int (*)(const OrbisImeKeycode* srcKeycode,
u16* outKeycode, u32* outStatus,
void* reserved);
struct OrbisImeDialogParam {
s32 user_id;
OrbisImeType type;
u64 supported_languages;
OrbisImeEnterLabel enter_label;
OrbisImeInputMethod input_method;
OrbisImeTextFilter filter;
OrbisImeDialogOption option;
u32 max_text_length;
char16_t* input_text_buffer;
float posx;
float posy;
OrbisImeHorizontalAlignment horizontal_alignment;
OrbisImeVerticalAlignment vertical_alignment;
const char16_t* placeholder;
const char16_t* title;
s8 reserved[16];
};
struct OrbisImeParamExtended {
u32 option; // OrbisImeDialogOptionExtended
OrbisImeColor color_base;
OrbisImeColor color_line;
OrbisImeColor color_text_field;
OrbisImeColor color_preedit;
OrbisImeColor color_button_default;
OrbisImeColor color_button_function;
OrbisImeColor color_button_symbol;
OrbisImeColor color_text;
OrbisImeColor color_special;
OrbisImePanelPriority priority;
char* additional_dictionary_path;
OrbisImeExtKeyboardFilter ext_keyboard_filter;
uint32_t disable_device;
uint32_t ext_keyboard_mode;
int8_t reserved[60];
};
Error PS4_SYSV_ABI sceImeDialogAbort(); Error PS4_SYSV_ABI sceImeDialogAbort();
Error PS4_SYSV_ABI sceImeDialogForceClose(); Error PS4_SYSV_ABI sceImeDialogForceClose();
Error PS4_SYSV_ABI sceImeDialogForTestFunction(); Error PS4_SYSV_ABI sceImeDialogForTestFunction();

View File

@ -48,15 +48,18 @@ static void KeyboardCallbackBridge(const VirtualKeyEvent* evt) {
* ImeDialogState : constructors, helpers * ImeDialogState : constructors, helpers
**/ **/
ImeDialogState::ImeDialogState(const OrbisImeDialogParam* param, ImeDialogState::ImeDialogState(const OrbisImeDialogParam* param,
const OrbisImeParamExtended* extended) const OrbisImeParamExtended* extended) {
: extended_param_(extended) { LOG_INFO(Lib_ImeDialog, ">> ImeDialogState::Ctor: param={}, text_buffer={}",
static_cast<const void*>(param),
static_cast<void*>(param ? param->input_text_buffer : nullptr));
if (!param) { if (!param) {
LOG_ERROR(Lib_ImeDialog, " param==nullptr, returning without init");
return; return;
} }
/* basic param copy */ /* basic param copy */
user_id = param->user_id; user_id = param->user_id;
is_multi_line = True(param->option & OrbisImeDialogOption::Multiline); is_multi_line = True(param->option & OrbisImeOption::MULTILINE);
is_numeric = param->type == OrbisImeType::Number; is_numeric = param->type == OrbisImeType::Number;
type = param->type; type = param->type;
enter_label = param->enter_label; enter_label = param->enter_label;
@ -292,6 +295,7 @@ void ImeDialogUi::Free() {
**/ **/
void ImeDialogUi::Draw() { void ImeDialogUi::Draw() {
std::unique_lock lock{draw_mutex}; std::unique_lock lock{draw_mutex};
LOG_INFO(Lib_ImeDialog, ">> ImeDialogUi::Draw: first_render=%d", first_render);
if (!state) { if (!state) {
return; return;
@ -329,9 +333,13 @@ void ImeDialogUi::Draw() {
/* ---------- input box ---------- */ /* ---------- input box ---------- */
if (state->is_multi_line) { if (state->is_multi_line) {
LOG_INFO(Lib_ImeDialog, " Drawing multi-line widget…");
DrawMultiLineInputText(); DrawMultiLineInputText();
LOG_INFO(Lib_ImeDialog, " Done DrawMultiLineInputText");
} else { } else {
LOG_INFO(Lib_ImeDialog, " Drawing input text widget…");
DrawInputText(); DrawInputText();
LOG_INFO(Lib_ImeDialog, " Done DrawInputText");
} }
/* ---------- dummy prediction bar with Cancel button ---------- */ /* ---------- dummy prediction bar with Cancel button ---------- */
@ -351,6 +359,7 @@ void ImeDialogUi::Draw() {
ImGui::PopStyleColor(); ImGui::PopStyleColor();
first_render = false; first_render = false;
LOG_INFO(Lib_ImeDialog, "<< ImeDialogUi::Draw complete");
} }
/*─────────────────────────────────────────────────────────────* /*─────────────────────────────────────────────────────────────*
@ -375,7 +384,7 @@ void ImeDialogUi::DrawInputText() {
const char* placeholder = state->placeholder.empty() ? nullptr : state->placeholder.data(); const char* placeholder = state->placeholder.empty() ? nullptr : state->placeholder.data();
if (InputTextEx("##ImeDialogInput", placeholder, state->current_text.begin(), if (InputTextEx("##ImeDialogInput", placeholder, state->current_text.begin(),
state->max_text_length, input_size, ImGuiInputTextFlags_CallbackCharFilter, state->max_text_length + 1, input_size, ImGuiInputTextFlags_CallbackCharFilter,
InputTextCallback, this)) { InputTextCallback, this)) {
state->input_changed = true; state->input_changed = true;
} }
@ -421,7 +430,7 @@ void ImeDialogUi::DrawMultiLineInputText() {
const char* placeholder = state->placeholder.empty() ? nullptr : state->placeholder.data(); const char* placeholder = state->placeholder.empty() ? nullptr : state->placeholder.data();
if (InputTextEx("##ImeDialogInput", placeholder, state->current_text.begin(), if (InputTextEx("##ImeDialogInput", placeholder, state->current_text.begin(),
state->max_text_length, input_size, flags, InputTextCallback, this)) { state->max_text_length + 1, input_size, flags, InputTextCallback, this)) {
state->input_changed = true; state->input_changed = true;
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
@ -437,13 +446,19 @@ int ImeDialogUi::InputTextCallback(ImGuiInputTextCallbackData* data) {
ImeDialogUi* ui = static_cast<ImeDialogUi*>(data->UserData); ImeDialogUi* ui = static_cast<ImeDialogUi*>(data->UserData);
ASSERT(ui); ASSERT(ui);
LOG_DEBUG(Lib_ImeDialog, ">> InputTextCallback: EventFlag={}, EventChar={}", data->EventFlag,
data->EventChar);
// Should we filter punctuation? // Should we filter punctuation?
if (ui->state->is_numeric && (data->EventChar < '0' || data->EventChar > '9') && if (ui->state->is_numeric && (data->EventChar < '0' || data->EventChar > '9') &&
data->EventChar != '\b' && data->EventChar != ',' && data->EventChar != '.') { data->EventChar != '\b' && data->EventChar != ',' && data->EventChar != '.') {
LOG_INFO(Lib_ImeDialog, "InputTextCallback: rejecting non-digit char '{}'",
static_cast<char>(data->EventChar));
return 1; return 1;
} }
if (!ui->state->keyboard_filter) { if (!ui->state->keyboard_filter) {
LOG_DEBUG(Lib_ImeDialog, "InputTextCallback: no keyboard_filter, accepting char");
return 0; return 0;
} }
@ -459,20 +474,24 @@ int ImeDialogUi::InputTextCallback(ImGuiInputTextCallbackData* data) {
// use the current language?) // use the current language?)
.user_id = ui->state->user_id, .user_id = ui->state->user_id,
.resource_id = 0, .resource_id = 0,
.timestamp = 0, .timestamp = {0},
}; };
if (!ui->state->ConvertUTF8ToOrbis(event_char, 4, &src_keycode.character, 1)) { if (!ui->state->ConvertUTF8ToOrbis(event_char, 4, &src_keycode.character, 1)) {
LOG_ERROR(Lib_ImeDialog, "Failed to convert orbis char to utf8"); LOG_ERROR(Lib_ImeDialog, "InputTextCallback: ConvertUTF8ToOrbis failed");
return 0; return 0;
} }
LOG_DEBUG(Lib_ImeDialog, "InputTextCallback: converted to Orbis char={:#X}",
static_cast<uint16_t>(src_keycode.character));
src_keycode.keycode = src_keycode.character; // TODO set this to the correct value src_keycode.keycode = src_keycode.character; // TODO set this to the correct value
u16 out_keycode; u16 out_keycode;
u32 out_status; u32 out_status;
ui->state->CallKeyboardFilter(&src_keycode, &out_keycode, &out_status); bool keep = ui->state->CallKeyboardFilter(&src_keycode, &out_keycode, &out_status);
LOG_DEBUG(Lib_ImeDialog,
"InputTextCallback: CallKeyboardFilter returned %s (keycode=0x%X, status=0x%X)",
keep ? "true" : "false", out_keycode, out_status);
// TODO. set the keycode // TODO. set the keycode
return 0; return 0;

View File

@ -199,7 +199,7 @@ int ImeUi::InputTextCallback(ImGuiInputTextCallbackData* data) {
eventParam.caret_index = data->CursorPos; eventParam.caret_index = data->CursorPos;
eventParam.area_num = 1; eventParam.area_num = 1;
eventParam.text_area[0].mode = 1; // Edit mode eventParam.text_area[0].mode = OrbisImeTextAreaMode::Edit;
eventParam.text_area[0].index = data->CursorPos; eventParam.text_area[0].index = data->CursorPos;
eventParam.text_area[0].length = data->BufTextLen; eventParam.text_area[0].length = data->BufTextLen;

View File

@ -98,6 +98,11 @@ bool EqueueInternal::RemoveEvent(u64 id, s16 filter) {
} }
int EqueueInternal::WaitForEvents(SceKernelEvent* ev, int num, u32 micros) { int EqueueInternal::WaitForEvents(SceKernelEvent* ev, int num, u32 micros) {
if (HasSmallTimer()) {
// If a small timer is set, just wait for it to expire.
return WaitForSmallTimer(ev, num, micros);
}
int count = 0; int count = 0;
const auto predicate = [&] { const auto predicate = [&] {
@ -120,7 +125,6 @@ int EqueueInternal::WaitForEvents(SceKernelEvent* ev, int num, u32 micros) {
.count(); .count();
count = WaitForSmallTimer(ev, num, std::max(0l, long(micros - time_waited))); count = WaitForSmallTimer(ev, num, std::max(0l, long(micros - time_waited)));
} }
small_timer_event.event.data = 0;
} }
if (ev->flags & SceKernelEvent::Flags::OneShot) { if (ev->flags & SceKernelEvent::Flags::OneShot) {
@ -140,6 +144,8 @@ bool EqueueInternal::TriggerEvent(u64 ident, s16 filter, void* trigger_data) {
if (event.event.ident == ident && event.event.filter == filter) { if (event.event.ident == ident && event.event.filter == filter) {
if (filter == SceKernelEvent::Filter::VideoOut) { if (filter == SceKernelEvent::Filter::VideoOut) {
event.TriggerDisplay(trigger_data); event.TriggerDisplay(trigger_data);
} else if (filter == SceKernelEvent::Filter::User) {
event.TriggerUser(trigger_data);
} else { } else {
event.Trigger(trigger_data); event.Trigger(trigger_data);
} }
@ -172,38 +178,46 @@ int EqueueInternal::GetTriggeredEvents(SceKernelEvent* ev, int num) {
} }
bool EqueueInternal::AddSmallTimer(EqueueEvent& ev) { bool EqueueInternal::AddSmallTimer(EqueueEvent& ev) {
// We assume that only one timer event (with the same ident across calls) SmallTimer st;
// can be posted to the queue, based on observations so far. In the opposite case, st.event = ev.event;
// the small timer storage and wait logic should be reworked. st.added = std::chrono::steady_clock::now();
ASSERT(!HasSmallTimer() || small_timer_event.event.ident == ev.event.ident); st.interval = std::chrono::microseconds{ev.event.data};
ev.time_added = std::chrono::steady_clock::now(); {
small_timer_event = std::move(ev); std::scoped_lock lock{m_mutex};
m_small_timers[st.event.ident] = std::move(st);
}
return true; return true;
} }
int EqueueInternal::WaitForSmallTimer(SceKernelEvent* ev, int num, u32 micros) { int EqueueInternal::WaitForSmallTimer(SceKernelEvent* ev, int num, u32 micros) {
int count{}; ASSERT(num >= 1);
ASSERT(num == 1);
auto curr_clock = std::chrono::steady_clock::now(); auto curr_clock = std::chrono::steady_clock::now();
const auto wait_end_us = curr_clock + std::chrono::microseconds{micros}; const auto wait_end_us = (micros == 0) ? std::chrono::steady_clock::time_point::max()
: curr_clock + std::chrono::microseconds{micros};
int count = 0;
do { do {
curr_clock = std::chrono::steady_clock::now(); curr_clock = std::chrono::steady_clock::now();
{ {
std::scoped_lock lock{m_mutex}; std::scoped_lock lock{m_mutex};
if ((curr_clock - small_timer_event.time_added) > for (auto it = m_small_timers.begin(); it != m_small_timers.end() && count < num;) {
std::chrono::microseconds{small_timer_event.event.data}) { const SmallTimer& st = it->second;
ev[count++] = small_timer_event.event;
small_timer_event.event.data = 0; if (curr_clock - st.added >= st.interval) {
break; ev[count++] = st.event;
it = m_small_timers.erase(it);
} else {
++it;
}
} }
if (count > 0)
return count;
} }
std::this_thread::yield(); std::this_thread::yield();
} while (curr_clock < wait_end_us); } while (curr_clock < wait_end_us);
return count; return 0;
} }
bool EqueueInternal::EventExists(u64 id, s16 filter) { bool EqueueInternal::EventExists(u64 id, s16 filter) {
@ -266,24 +280,15 @@ int PS4_SYSV_ABI sceKernelWaitEqueue(SceKernelEqueue eq, SceKernelEvent* ev, int
return ORBIS_KERNEL_ERROR_EINVAL; return ORBIS_KERNEL_ERROR_EINVAL;
} }
if (eq->HasSmallTimer()) { if (timo == nullptr) {
ASSERT(timo && *timo); // When the timeout is nullptr, we wait indefinitely
*out = eq->WaitForSmallTimer(ev, num, *timo); *out = eq->WaitForEvents(ev, num, 0);
} else if (*timo == 0) {
// Only events that have already arrived at the time of this function call can be received
*out = eq->GetTriggeredEvents(ev, num);
} else { } else {
if (timo == nullptr) { // wait until an event arrives without timing out // Wait for up to the specified timeout value
*out = eq->WaitForEvents(ev, num, 0); *out = eq->WaitForEvents(ev, num, *timo);
}
if (timo != nullptr) {
// Only events that have already arrived at the time of this function call can be
// received
if (*timo == 0) {
*out = eq->GetTriggeredEvents(ev, num);
} else {
// Wait until an event arrives with timing out
*out = eq->WaitForEvents(ev, num, *timo);
}
}
} }
if (*out == 0) { if (*out == 0) {
@ -327,6 +332,11 @@ s32 PS4_SYSV_ABI sceKernelAddHRTimerEvent(SceKernelEqueue eq, int id, timespec*
// `HrTimerSpinlockThresholdUs`) and fall back to boost asio timers if the time to tick is // `HrTimerSpinlockThresholdUs`) and fall back to boost asio timers if the time to tick is
// large. Even for large delays, we truncate a small portion to complete the wait // large. Even for large delays, we truncate a small portion to complete the wait
// using the spinlock, prioritizing precision. // using the spinlock, prioritizing precision.
if (eq->EventExists(event.event.ident, event.event.filter)) {
eq->RemoveEvent(id, SceKernelEvent::Filter::HrTimer);
}
if (total_us < HrTimerSpinlockThresholdUs) { if (total_us < HrTimerSpinlockThresholdUs) {
return eq->AddSmallTimer(event) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOMEM; return eq->AddSmallTimer(event) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOMEM;
} }

View File

@ -9,6 +9,7 @@
#include <vector> #include <vector>
#include <boost/asio/steady_timer.hpp> #include <boost/asio/steady_timer.hpp>
#include <unordered_map>
#include "common/rdtsc.h" #include "common/rdtsc.h"
#include "common/types.h" #include "common/types.h"
@ -98,6 +99,12 @@ struct EqueueEvent {
event.data = reinterpret_cast<uintptr_t>(data); event.data = reinterpret_cast<uintptr_t>(data);
} }
void TriggerUser(void* data) {
is_triggered = true;
event.fflags++;
event.udata = data;
}
void TriggerDisplay(void* data) { void TriggerDisplay(void* data) {
is_triggered = true; is_triggered = true;
if (data != nullptr) { if (data != nullptr) {
@ -129,6 +136,12 @@ private:
}; };
class EqueueInternal { class EqueueInternal {
struct SmallTimer {
SceKernelEvent event;
std::chrono::steady_clock::time_point added;
std::chrono::microseconds interval;
};
public: public:
explicit EqueueInternal(std::string_view name) : m_name(name) {} explicit EqueueInternal(std::string_view name) : m_name(name) {}
@ -145,13 +158,14 @@ public:
int GetTriggeredEvents(SceKernelEvent* ev, int num); int GetTriggeredEvents(SceKernelEvent* ev, int num);
bool AddSmallTimer(EqueueEvent& event); bool AddSmallTimer(EqueueEvent& event);
bool HasSmallTimer() const { bool HasSmallTimer() {
return small_timer_event.event.data != 0; std::scoped_lock lock{m_mutex};
return !m_small_timers.empty();
} }
bool RemoveSmallTimer(u64 id) { bool RemoveSmallTimer(u64 id) {
if (HasSmallTimer() && small_timer_event.event.ident == id) { if (HasSmallTimer()) {
small_timer_event = {}; std::scoped_lock lock{m_mutex};
return true; return m_small_timers.erase(id) > 0;
} }
return false; return false;
} }
@ -164,8 +178,8 @@ private:
std::string m_name; std::string m_name;
std::mutex m_mutex; std::mutex m_mutex;
std::vector<EqueueEvent> m_events; std::vector<EqueueEvent> m_events;
EqueueEvent small_timer_event{};
std::condition_variable m_cond; std::condition_variable m_cond;
std::unordered_map<u64, SmallTimer> m_small_timers;
}; };
u64 PS4_SYSV_ABI sceKernelGetEventData(const SceKernelEvent* ev); u64 PS4_SYSV_ABI sceKernelGetEventData(const SceKernelEvent* ev);

View File

@ -293,6 +293,7 @@ s64 PS4_SYSV_ABI write(s32 fd, const void* buf, size_t nbytes) {
} }
return result; return result;
} }
return file->f.WriteRaw<u8>(buf, nbytes); return file->f.WriteRaw<u8>(buf, nbytes);
} }
@ -750,7 +751,24 @@ s32 PS4_SYSV_ABI posix_rename(const char* from, const char* to) {
*__Error() = POSIX_ENOTEMPTY; *__Error() = POSIX_ENOTEMPTY;
return -1; return -1;
} }
// On Windows, std::filesystem::rename will error if the file has been opened before.
std::filesystem::copy(src_path, dst_path, std::filesystem::copy_options::overwrite_existing); std::filesystem::copy(src_path, dst_path, std::filesystem::copy_options::overwrite_existing);
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto file = h->GetFile(src_path);
if (file) {
// We need to force ReadWrite if the file had Write access before
// Otherwise f.Open will clear the file contents.
auto access_mode = file->f.GetAccessMode() == Common::FS::FileAccessMode::Write
? Common::FS::FileAccessMode::ReadWrite
: file->f.GetAccessMode();
file->f.Close();
std::filesystem::remove(src_path);
file->f.Open(dst_path, access_mode);
} else {
std::filesystem::remove(src_path);
}
return ORBIS_OK; return ORBIS_OK;
} }
@ -1050,6 +1068,7 @@ void RegisterFileSystem(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("4wSze92BhLI", "libkernel", 1, "libkernel", 1, 1, sceKernelWrite); LIB_FUNCTION("4wSze92BhLI", "libkernel", 1, "libkernel", 1, 1, sceKernelWrite);
LIB_FUNCTION("+WRlkKjZvag", "libkernel", 1, "libkernel", 1, 1, readv); LIB_FUNCTION("+WRlkKjZvag", "libkernel", 1, "libkernel", 1, 1, readv);
LIB_FUNCTION("YSHRBRLn2pI", "libkernel", 1, "libkernel", 1, 1, writev); LIB_FUNCTION("YSHRBRLn2pI", "libkernel", 1, "libkernel", 1, 1, writev);
LIB_FUNCTION("kAt6VDbHmro", "libkernel", 1, "libkernel", 1, 1, sceKernelWritev);
LIB_FUNCTION("Oy6IpwgtYOk", "libScePosix", 1, "libkernel", 1, 1, posix_lseek); LIB_FUNCTION("Oy6IpwgtYOk", "libScePosix", 1, "libkernel", 1, 1, posix_lseek);
LIB_FUNCTION("Oy6IpwgtYOk", "libkernel", 1, "libkernel", 1, 1, posix_lseek); LIB_FUNCTION("Oy6IpwgtYOk", "libkernel", 1, "libkernel", 1, 1, posix_lseek);
LIB_FUNCTION("oib76F-12fk", "libkernel", 1, "libkernel", 1, 1, sceKernelLseek); LIB_FUNCTION("oib76F-12fk", "libkernel", 1, "libkernel", 1, 1, sceKernelLseek);

View File

@ -76,21 +76,21 @@ static PS4_SYSV_ABI void stack_chk_fail() {
UNREACHABLE(); UNREACHABLE();
} }
static thread_local int g_posix_errno = 0; static thread_local s32 g_posix_errno = 0;
int* PS4_SYSV_ABI __Error() { s32* PS4_SYSV_ABI __Error() {
return &g_posix_errno; return &g_posix_errno;
} }
void ErrSceToPosix(int error) { void ErrSceToPosix(s32 error) {
g_posix_errno = error - ORBIS_KERNEL_ERROR_UNKNOWN; g_posix_errno = error - ORBIS_KERNEL_ERROR_UNKNOWN;
} }
int ErrnoToSceKernelError(int error) { s32 ErrnoToSceKernelError(s32 error) {
return error + ORBIS_KERNEL_ERROR_UNKNOWN; return error + ORBIS_KERNEL_ERROR_UNKNOWN;
} }
void SetPosixErrno(int e) { void SetPosixErrno(s32 e) {
// Some error numbers are different between supported OSes // Some error numbers are different between supported OSes
switch (e) { switch (e) {
case EPERM: case EPERM:
@ -132,15 +132,15 @@ void SetPosixErrno(int e) {
} }
} }
static uint64_t g_mspace_atomic_id_mask = 0; static u64 g_mspace_atomic_id_mask = 0;
static uint64_t g_mstate_table[64] = {0}; static u64 g_mstate_table[64] = {0};
struct HeapInfoInfo { struct HeapInfoInfo {
uint64_t size = sizeof(HeapInfoInfo); u64 size = sizeof(HeapInfoInfo);
uint32_t flag; u32 flag;
uint32_t getSegmentInfo; u32 getSegmentInfo;
uint64_t* mspace_atomic_id_mask; u64* mspace_atomic_id_mask;
uint64_t* mstate_table; u64* mstate_table;
}; };
void PS4_SYSV_ABI sceLibcHeapGetTraceInfo(HeapInfoInfo* info) { void PS4_SYSV_ABI sceLibcHeapGetTraceInfo(HeapInfoInfo* info) {
@ -159,7 +159,7 @@ struct OrbisKernelUuid {
}; };
static_assert(sizeof(OrbisKernelUuid) == 0x10); static_assert(sizeof(OrbisKernelUuid) == 0x10);
int PS4_SYSV_ABI sceKernelUuidCreate(OrbisKernelUuid* orbisUuid) { s32 PS4_SYSV_ABI sceKernelUuidCreate(OrbisKernelUuid* orbisUuid) {
if (!orbisUuid) { if (!orbisUuid) {
return ORBIS_KERNEL_ERROR_EINVAL; return ORBIS_KERNEL_ERROR_EINVAL;
} }
@ -176,7 +176,7 @@ int PS4_SYSV_ABI sceKernelUuidCreate(OrbisKernelUuid* orbisUuid) {
return ORBIS_OK; return ORBIS_OK;
} }
int PS4_SYSV_ABI kernel_ioctl(int fd, u64 cmd, VA_ARGS) { s32 PS4_SYSV_ABI kernel_ioctl(s32 fd, u64 cmd, VA_ARGS) {
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance(); auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* file = h->GetFile(fd); auto* file = h->GetFile(fd);
if (file == nullptr) { if (file == nullptr) {
@ -190,7 +190,7 @@ int PS4_SYSV_ABI kernel_ioctl(int fd, u64 cmd, VA_ARGS) {
return -1; return -1;
} }
VA_CTX(ctx); VA_CTX(ctx);
int result = file->device->ioctl(cmd, &ctx); s32 result = file->device->ioctl(cmd, &ctx);
LOG_TRACE(Lib_Kernel, "ioctl: fd = {:X} cmd = {:X} result = {}", fd, cmd, result); LOG_TRACE(Lib_Kernel, "ioctl: fd = {:X} cmd = {:X} result = {}", fd, cmd, result);
if (result < 0) { if (result < 0) {
ErrSceToPosix(result); ErrSceToPosix(result);
@ -204,15 +204,15 @@ const char* PS4_SYSV_ABI sceKernelGetFsSandboxRandomWord() {
return path; return path;
} }
int PS4_SYSV_ABI _sigprocmask() { s32 PS4_SYSV_ABI _sigprocmask() {
return ORBIS_OK; return ORBIS_OK;
} }
int PS4_SYSV_ABI posix_getpagesize() { s32 PS4_SYSV_ABI posix_getpagesize() {
return 16_KB; return 16_KB;
} }
int PS4_SYSV_ABI posix_getsockname(Libraries::Net::OrbisNetId s, s32 PS4_SYSV_ABI posix_getsockname(Libraries::Net::OrbisNetId s,
Libraries::Net::OrbisNetSockaddr* addr, u32* paddrlen) { Libraries::Net::OrbisNetSockaddr* addr, u32* paddrlen) {
auto* netcall = Common::Singleton<Libraries::Net::NetInternal>::Instance(); auto* netcall = Common::Singleton<Libraries::Net::NetInternal>::Instance();
auto sock = netcall->FindSocket(s); auto sock = netcall->FindSocket(s);
@ -221,7 +221,7 @@ int PS4_SYSV_ABI posix_getsockname(Libraries::Net::OrbisNetId s,
LOG_ERROR(Lib_Net, "socket id is invalid = {}", s); LOG_ERROR(Lib_Net, "socket id is invalid = {}", s);
return -1; return -1;
} }
int returncode = sock->GetSocketAddress(addr, paddrlen); s32 returncode = sock->GetSocketAddress(addr, paddrlen);
if (returncode >= 0) { if (returncode >= 0) {
LOG_ERROR(Lib_Net, "return code : {:#x}", (u32)returncode); LOG_ERROR(Lib_Net, "return code : {:#x}", (u32)returncode);
return 0; return 0;
@ -230,6 +230,19 @@ int PS4_SYSV_ABI posix_getsockname(Libraries::Net::OrbisNetId s,
LOG_ERROR(Lib_Net, "error code returned : {:#x}", (u32)returncode); LOG_ERROR(Lib_Net, "error code returned : {:#x}", (u32)returncode);
return -1; return -1;
} }
// stubbed on non-devkit consoles
s32 PS4_SYSV_ABI sceKernelGetGPI() {
LOG_DEBUG(Kernel, "called");
return ORBIS_OK;
}
// stubbed on non-devkit consoles
s32 PS4_SYSV_ABI sceKernelSetGPO() {
LOG_DEBUG(Kernel, "called");
return ORBIS_OK;
}
void RegisterKernel(Core::Loader::SymbolsResolver* sym) { void RegisterKernel(Core::Loader::SymbolsResolver* sym) {
service_thread = std::jthread{KernelServiceThread}; service_thread = std::jthread{KernelServiceThread};
@ -273,6 +286,13 @@ void RegisterKernel(Core::Loader::SymbolsResolver* sym) {
Libraries::Net::sceNetInetNtop); // TODO fix it to sys_ ... Libraries::Net::sceNetInetNtop); // TODO fix it to sys_ ...
LIB_FUNCTION("4n51s0zEf0c", "libScePosix", 1, "libkernel", 1, 1, LIB_FUNCTION("4n51s0zEf0c", "libScePosix", 1, "libkernel", 1, 1,
Libraries::Net::sceNetInetPton); // TODO fix it to sys_ ... Libraries::Net::sceNetInetPton); // TODO fix it to sys_ ...
LIB_FUNCTION("XVL8So3QJUk", "libScePosix", 1, "libkernel", 1, 1, Libraries::Net::sys_connect);
LIB_FUNCTION("3e+4Iv7IJ8U", "libScePosix", 1, "libkernel", 1, 1, Libraries::Net::sys_accept);
LIB_FUNCTION("aNeavPDNKzA", "libScePosix", 1, "libkernel", 1, 1, Libraries::Net::sys_sendmsg);
LIB_FUNCTION("pxnCmagrtao", "libScePosix", 1, "libkernel", 1, 1, Libraries::Net::sys_listen);
LIB_FUNCTION("4oXYe9Xmk0Q", "libkernel", 1, "libkernel", 1, 1, sceKernelGetGPI);
LIB_FUNCTION("ca7v6Cxulzs", "libkernel", 1, "libkernel", 1, 1, sceKernelSetGPO);
} }
} // namespace Libraries::Kernel } // namespace Libraries::Kernel

View File

@ -12,10 +12,10 @@ class SymbolsResolver;
namespace Libraries::Kernel { namespace Libraries::Kernel {
void ErrSceToPosix(int result); void ErrSceToPosix(s32 result);
int ErrnoToSceKernelError(int e); s32 ErrnoToSceKernelError(s32 e);
void SetPosixErrno(int e); void SetPosixErrno(s32 e);
int* PS4_SYSV_ABI __Error(); s32* PS4_SYSV_ABI __Error();
template <class F, F f> template <class F, F f>
struct OrbisWrapperImpl; struct OrbisWrapperImpl;
@ -33,7 +33,7 @@ struct OrbisWrapperImpl<PS4_SYSV_ABI R (*)(Args...), f> {
#define ORBIS(func) (Libraries::Kernel::OrbisWrapperImpl<decltype(&(func)), func>::wrap) #define ORBIS(func) (Libraries::Kernel::OrbisWrapperImpl<decltype(&(func)), func>::wrap)
int* PS4_SYSV_ABI __Error(); s32* PS4_SYSV_ABI __Error();
void RegisterKernel(Core::Loader::SymbolsResolver* sym); void RegisterKernel(Core::Loader::SymbolsResolver* sym);

View File

@ -8,7 +8,6 @@
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/scope_exit.h" #include "common/scope_exit.h"
#include "common/singleton.h" #include "common/singleton.h"
#include "core/file_sys/fs.h"
#include "core/libraries/kernel/kernel.h" #include "core/libraries/kernel/kernel.h"
#include "core/libraries/kernel/memory.h" #include "core/libraries/kernel/memory.h"
#include "core/libraries/kernel/orbis_error.h" #include "core/libraries/kernel/orbis_error.h"
@ -24,8 +23,8 @@ u64 PS4_SYSV_ABI sceKernelGetDirectMemorySize() {
return memory->GetTotalDirectSize(); return memory->GetTotalDirectSize();
} }
int PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u64 len, s32 PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u64 len,
u64 alignment, int memoryType, s64* physAddrOut) { u64 alignment, s32 memoryType, s64* physAddrOut) {
if (searchStart < 0 || searchEnd < 0) { if (searchStart < 0 || searchEnd < 0) {
LOG_ERROR(Kernel_Vmm, "Invalid parameters!"); LOG_ERROR(Kernel_Vmm, "Invalid parameters!");
return ORBIS_KERNEL_ERROR_EINVAL; return ORBIS_KERNEL_ERROR_EINVAL;
@ -72,13 +71,13 @@ int PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u
return ORBIS_OK; return ORBIS_OK;
} }
s32 PS4_SYSV_ABI sceKernelAllocateMainDirectMemory(size_t len, size_t alignment, int memoryType, s32 PS4_SYSV_ABI sceKernelAllocateMainDirectMemory(u64 len, u64 alignment, s32 memoryType,
s64* physAddrOut) { s64* physAddrOut) {
const auto searchEnd = static_cast<s64>(sceKernelGetDirectMemorySize()); const auto searchEnd = static_cast<s64>(sceKernelGetDirectMemorySize());
return sceKernelAllocateDirectMemory(0, searchEnd, len, alignment, memoryType, physAddrOut); return sceKernelAllocateDirectMemory(0, searchEnd, len, alignment, memoryType, physAddrOut);
} }
s32 PS4_SYSV_ABI sceKernelCheckedReleaseDirectMemory(u64 start, size_t len) { s32 PS4_SYSV_ABI sceKernelCheckedReleaseDirectMemory(u64 start, u64 len) {
if (len == 0) { if (len == 0) {
return ORBIS_OK; return ORBIS_OK;
} }
@ -88,7 +87,7 @@ s32 PS4_SYSV_ABI sceKernelCheckedReleaseDirectMemory(u64 start, size_t len) {
return ORBIS_OK; return ORBIS_OK;
} }
s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, size_t len) { s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, u64 len) {
if (len == 0) { if (len == 0) {
return ORBIS_OK; return ORBIS_OK;
} }
@ -97,11 +96,10 @@ s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, size_t len) {
return ORBIS_OK; return ORBIS_OK;
} }
s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchEnd, s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchEnd, u64 alignment,
size_t alignment, u64* physAddrOut, u64* physAddrOut, u64* sizeOut) {
size_t* sizeOut) { LOG_INFO(Kernel_Vmm, "called searchStart = {:#x}, searchEnd = {:#x}, alignment = {:#x}",
LOG_WARNING(Kernel_Vmm, "called searchStart = {:#x}, searchEnd = {:#x}, alignment = {:#x}", searchStart, searchEnd, alignment);
searchStart, searchEnd, alignment);
if (physAddrOut == nullptr || sizeOut == nullptr) { if (physAddrOut == nullptr || sizeOut == nullptr) {
return ORBIS_KERNEL_ERROR_EINVAL; return ORBIS_KERNEL_ERROR_EINVAL;
@ -110,7 +108,7 @@ s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchE
auto* memory = Core::Memory::Instance(); auto* memory = Core::Memory::Instance();
PAddr physAddr{}; PAddr physAddr{};
size_t size{}; u64 size{};
s32 result = memory->DirectQueryAvailable(searchStart, searchEnd, alignment, &physAddr, &size); s32 result = memory->DirectQueryAvailable(searchStart, searchEnd, alignment, &physAddr, &size);
if (size == 0) { if (size == 0) {
@ -123,14 +121,14 @@ s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchE
return result; return result;
} }
s32 PS4_SYSV_ABI sceKernelVirtualQuery(const void* addr, int flags, OrbisVirtualQueryInfo* info, s32 PS4_SYSV_ABI sceKernelVirtualQuery(const void* addr, s32 flags, OrbisVirtualQueryInfo* info,
size_t infoSize) { u64 infoSize) {
LOG_INFO(Kernel_Vmm, "called addr = {}, flags = {:#x}", fmt::ptr(addr), flags); LOG_INFO(Kernel_Vmm, "called addr = {}, flags = {:#x}", fmt::ptr(addr), flags);
auto* memory = Core::Memory::Instance(); auto* memory = Core::Memory::Instance();
return memory->VirtualQuery(std::bit_cast<VAddr>(addr), flags, info); return memory->VirtualQuery(std::bit_cast<VAddr>(addr), flags, info);
} }
s32 PS4_SYSV_ABI sceKernelReserveVirtualRange(void** addr, u64 len, int flags, u64 alignment) { s32 PS4_SYSV_ABI sceKernelReserveVirtualRange(void** addr, u64 len, s32 flags, u64 alignment) {
LOG_INFO(Kernel_Vmm, "addr = {}, len = {:#x}, flags = {:#x}, alignment = {:#x}", LOG_INFO(Kernel_Vmm, "addr = {}, len = {:#x}, flags = {:#x}, alignment = {:#x}",
fmt::ptr(*addr), len, flags, alignment); fmt::ptr(*addr), len, flags, alignment);
if (addr == nullptr) { if (addr == nullptr) {
@ -152,14 +150,15 @@ s32 PS4_SYSV_ABI sceKernelReserveVirtualRange(void** addr, u64 len, int flags, u
const VAddr in_addr = reinterpret_cast<VAddr>(*addr); const VAddr in_addr = reinterpret_cast<VAddr>(*addr);
const auto map_flags = static_cast<Core::MemoryMapFlags>(flags); const auto map_flags = static_cast<Core::MemoryMapFlags>(flags);
s32 result = memory->Reserve(addr, in_addr, len, map_flags, alignment); s32 result = memory->MapMemory(addr, in_addr, len, Core::MemoryProt::NoAccess, map_flags,
Core::VMAType::Reserved, "anon", false, -1, alignment);
if (result == 0) { if (result == 0) {
LOG_INFO(Kernel_Vmm, "out_addr = {}", fmt::ptr(*addr)); LOG_INFO(Kernel_Vmm, "out_addr = {}", fmt::ptr(*addr));
} }
return result; return result;
} }
int PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, int prot, int flags, s32 PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, s32 prot, s32 flags,
s64 directMemoryStart, u64 alignment, s64 directMemoryStart, u64 alignment,
const char* name) { const char* name) {
LOG_INFO(Kernel_Vmm, LOG_INFO(Kernel_Vmm,
@ -202,7 +201,7 @@ int PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, int prot, i
return ret; return ret;
} }
int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int flags, s32 PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, s32 prot, s32 flags,
s64 directMemoryStart, u64 alignment) { s64 directMemoryStart, u64 alignment) {
LOG_INFO(Kernel_Vmm, "called, redirected to sceKernelMapNamedDirectMemory"); LOG_INFO(Kernel_Vmm, "called, redirected to sceKernelMapNamedDirectMemory");
return sceKernelMapNamedDirectMemory(addr, len, prot, flags, directMemoryStart, alignment, return sceKernelMapNamedDirectMemory(addr, len, prot, flags, directMemoryStart, alignment,
@ -222,9 +221,10 @@ s32 PS4_SYSV_ABI sceKernelMapDirectMemory2(void** addr, u64 len, s32 type, s32 p
return ret; return ret;
} }
s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, std::size_t len, int prot, s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, u64 len, s32 prot, s32 flags,
int flags, const char* name) { const char* name) {
LOG_INFO(Kernel_Vmm, "in_addr = {}, len = {:#x}, prot = {:#x}, flags = {:#x}, name = '{}'",
fmt::ptr(*addr_in_out), len, prot, flags, name);
if (len == 0 || !Common::Is16KBAligned(len)) { if (len == 0 || !Common::Is16KBAligned(len)) {
LOG_ERROR(Kernel_Vmm, "len is 0 or not 16kb multiple"); LOG_ERROR(Kernel_Vmm, "len is 0 or not 16kb multiple");
return ORBIS_KERNEL_ERROR_EINVAL; return ORBIS_KERNEL_ERROR_EINVAL;
@ -243,46 +243,55 @@ s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, std::size_t
const VAddr in_addr = reinterpret_cast<VAddr>(*addr_in_out); const VAddr in_addr = reinterpret_cast<VAddr>(*addr_in_out);
const auto mem_prot = static_cast<Core::MemoryProt>(prot); const auto mem_prot = static_cast<Core::MemoryProt>(prot);
const auto map_flags = static_cast<Core::MemoryMapFlags>(flags); const auto map_flags = static_cast<Core::MemoryMapFlags>(flags);
SCOPE_EXIT {
LOG_INFO(Kernel_Vmm,
"in_addr = {:#x}, out_addr = {}, len = {:#x}, prot = {:#x}, flags = {:#x}",
in_addr, fmt::ptr(*addr_in_out), len, prot, flags);
};
auto* memory = Core::Memory::Instance(); auto* memory = Core::Memory::Instance();
return memory->MapMemory(addr_in_out, in_addr, len, mem_prot, map_flags, const auto ret = memory->MapMemory(addr_in_out, in_addr, len, mem_prot, map_flags,
Core::VMAType::Flexible, name); Core::VMAType::Flexible, name);
LOG_INFO(Kernel_Vmm, "out_addr = {}", fmt::ptr(*addr_in_out));
return ret;
} }
s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, std::size_t len, int prot, s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, u64 len, s32 prot, s32 flags) {
int flags) {
return sceKernelMapNamedFlexibleMemory(addr_in_out, len, prot, flags, "anon"); return sceKernelMapNamedFlexibleMemory(addr_in_out, len, prot, flags, "anon");
} }
int PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void** end, u32* prot) { s32 PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void** end, u32* prot) {
auto* memory = Core::Memory::Instance(); auto* memory = Core::Memory::Instance();
return memory->QueryProtection(std::bit_cast<VAddr>(addr), start, end, prot); return memory->QueryProtection(std::bit_cast<VAddr>(addr), start, end, prot);
} }
int PS4_SYSV_ABI sceKernelMProtect(const void* addr, size_t size, int prot) { s32 PS4_SYSV_ABI sceKernelMprotect(const void* addr, u64 size, s32 prot) {
LOG_INFO(Kernel_Vmm, "called addr = {}, size = {:#x}, prot = {:#x}", fmt::ptr(addr), size,
prot);
Core::MemoryManager* memory_manager = Core::Memory::Instance(); Core::MemoryManager* memory_manager = Core::Memory::Instance();
Core::MemoryProt protection_flags = static_cast<Core::MemoryProt>(prot); Core::MemoryProt protection_flags = static_cast<Core::MemoryProt>(prot);
return memory_manager->Protect(std::bit_cast<VAddr>(addr), size, protection_flags); return memory_manager->Protect(std::bit_cast<VAddr>(addr), size, protection_flags);
} }
int PS4_SYSV_ABI sceKernelMTypeProtect(const void* addr, size_t size, int mtype, int prot) { s32 PS4_SYSV_ABI posix_mprotect(const void* addr, u64 size, s32 prot) {
s32 result = sceKernelMprotect(addr, size, prot);
if (result < 0) {
ErrSceToPosix(result);
return -1;
}
return result;
}
s32 PS4_SYSV_ABI sceKernelMtypeprotect(const void* addr, u64 size, s32 mtype, s32 prot) {
LOG_INFO(Kernel_Vmm, "called addr = {}, size = {:#x}, prot = {:#x}", fmt::ptr(addr), size,
prot);
Core::MemoryManager* memory_manager = Core::Memory::Instance(); Core::MemoryManager* memory_manager = Core::Memory::Instance();
Core::MemoryProt protection_flags = static_cast<Core::MemoryProt>(prot); Core::MemoryProt protection_flags = static_cast<Core::MemoryProt>(prot);
return memory_manager->Protect(std::bit_cast<VAddr>(addr), size, protection_flags); return memory_manager->Protect(std::bit_cast<VAddr>(addr), size, protection_flags);
} }
int PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, int flags, OrbisQueryInfo* query_info, s32 PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, s32 flags, OrbisQueryInfo* query_info,
size_t infoSize) { u64 infoSize) {
LOG_WARNING(Kernel_Vmm, "called offset = {:#x}, flags = {:#x}", offset, flags); LOG_INFO(Kernel_Vmm, "called offset = {:#x}, flags = {:#x}", offset, flags);
auto* memory = Core::Memory::Instance(); auto* memory = Core::Memory::Instance();
return memory->DirectMemoryQuery(offset, flags == 1, query_info); return memory->DirectMemoryQuery(offset, flags == 1, query_info);
} }
s32 PS4_SYSV_ABI sceKernelAvailableFlexibleMemorySize(size_t* out_size) { s32 PS4_SYSV_ABI sceKernelAvailableFlexibleMemorySize(u64* out_size) {
auto* memory = Core::Memory::Instance(); auto* memory = Core::Memory::Instance();
*out_size = memory->GetAvailableFlexibleSize(); *out_size = memory->GetAvailableFlexibleSize();
LOG_INFO(Kernel_Vmm, "called size = {:#x}", *out_size); LOG_INFO(Kernel_Vmm, "called size = {:#x}", *out_size);
@ -294,7 +303,7 @@ void PS4_SYSV_ABI _sceKernelRtldSetApplicationHeapAPI(void* func[]) {
linker->SetHeapAPI(func); linker->SetHeapAPI(func);
} }
int PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, int* directMemoryTypeOut, s32 PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, s32* directMemoryTypeOut,
void** directMemoryStartOut, void** directMemoryStartOut,
void** directMemoryEndOut) { void** directMemoryEndOut) {
LOG_WARNING(Kernel_Vmm, "called, direct memory addr = {:#x}", addr); LOG_WARNING(Kernel_Vmm, "called, direct memory addr = {:#x}", addr);
@ -303,23 +312,23 @@ int PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, int* directMemoryTypeOut
directMemoryEndOut); directMemoryEndOut);
} }
int PS4_SYSV_ABI sceKernelIsStack(void* addr, void** start, void** end) { s32 PS4_SYSV_ABI sceKernelIsStack(void* addr, void** start, void** end) {
LOG_DEBUG(Kernel_Vmm, "called, addr = {}", fmt::ptr(addr)); LOG_DEBUG(Kernel_Vmm, "called, addr = {}", fmt::ptr(addr));
auto* memory = Core::Memory::Instance(); auto* memory = Core::Memory::Instance();
return memory->IsStack(std::bit_cast<VAddr>(addr), start, end); return memory->IsStack(std::bit_cast<VAddr>(addr), start, end);
} }
s32 PS4_SYSV_ABI sceKernelBatchMap(OrbisKernelBatchMapEntry* entries, int numEntries, s32 PS4_SYSV_ABI sceKernelBatchMap(OrbisKernelBatchMapEntry* entries, s32 numEntries,
int* numEntriesOut) { s32* numEntriesOut) {
return sceKernelBatchMap2(entries, numEntries, numEntriesOut, return sceKernelBatchMap2(entries, numEntries, numEntriesOut,
MemoryFlags::SCE_KERNEL_MAP_FIXED); // 0x10, 0x410? MemoryFlags::SCE_KERNEL_MAP_FIXED); // 0x10, 0x410?
} }
s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEntries, s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, s32 numEntries,
int* numEntriesOut, int flags) { s32* numEntriesOut, s32 flags) {
int result = ORBIS_OK; s32 result = ORBIS_OK;
int processed = 0; s32 processed = 0;
for (int i = 0; i < numEntries; i++, processed++) { for (s32 i = 0; i < numEntries; i++, processed++) {
if (entries == nullptr || entries[i].length == 0 || entries[i].operation > 4) { if (entries == nullptr || entries[i].length == 0 || entries[i].operation > 4) {
result = ORBIS_KERNEL_ERROR_EINVAL; result = ORBIS_KERNEL_ERROR_EINVAL;
break; // break and assign a value to numEntriesOut. break; // break and assign a value to numEntriesOut.
@ -344,7 +353,7 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn
break; break;
} }
case MemoryOpTypes::ORBIS_KERNEL_MAP_OP_PROTECT: { case MemoryOpTypes::ORBIS_KERNEL_MAP_OP_PROTECT: {
result = sceKernelMProtect(entries[i].start, entries[i].length, entries[i].protection); result = sceKernelMprotect(entries[i].start, entries[i].length, entries[i].protection);
LOG_INFO(Kernel_Vmm, "entry = {}, operation = {}, len = {:#x}, result = {}", i, LOG_INFO(Kernel_Vmm, "entry = {}, operation = {}, len = {:#x}, result = {}", i,
entries[i].operation, entries[i].length, result); entries[i].operation, entries[i].length, result);
break; break;
@ -359,7 +368,7 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn
break; break;
} }
case MemoryOpTypes::ORBIS_KERNEL_MAP_OP_TYPE_PROTECT: { case MemoryOpTypes::ORBIS_KERNEL_MAP_OP_TYPE_PROTECT: {
result = sceKernelMTypeProtect(entries[i].start, entries[i].length, entries[i].type, result = sceKernelMtypeprotect(entries[i].start, entries[i].length, entries[i].type,
entries[i].protection); entries[i].protection);
LOG_INFO(Kernel_Vmm, "entry = {}, operation = {}, len = {:#x}, result = {}", i, LOG_INFO(Kernel_Vmm, "entry = {}, operation = {}, len = {:#x}, result = {}", i,
entries[i].operation, entries[i].length, result); entries[i].operation, entries[i].length, result);
@ -380,7 +389,7 @@ s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEn
return result; return result;
} }
s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, size_t len, const char* name) { s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, u64 len, const char* name) {
if (name == nullptr) { if (name == nullptr) {
LOG_ERROR(Kernel_Vmm, "name is invalid!"); LOG_ERROR(Kernel_Vmm, "name is invalid!");
return ORBIS_KERNEL_ERROR_EFAULT; return ORBIS_KERNEL_ERROR_EFAULT;
@ -396,8 +405,8 @@ s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, size_t len, cons
return ORBIS_OK; return ORBIS_OK;
} }
s32 PS4_SYSV_ABI sceKernelMemoryPoolExpand(u64 searchStart, u64 searchEnd, size_t len, s32 PS4_SYSV_ABI sceKernelMemoryPoolExpand(u64 searchStart, u64 searchEnd, u64 len, u64 alignment,
size_t alignment, u64* physAddrOut) { u64* physAddrOut) {
if (searchStart < 0 || searchEnd <= searchStart) { if (searchStart < 0 || searchEnd <= searchStart) {
LOG_ERROR(Kernel_Vmm, "Provided address range is invalid!"); LOG_ERROR(Kernel_Vmm, "Provided address range is invalid!");
return ORBIS_KERNEL_ERROR_EINVAL; return ORBIS_KERNEL_ERROR_EINVAL;
@ -439,10 +448,10 @@ s32 PS4_SYSV_ABI sceKernelMemoryPoolExpand(u64 searchStart, u64 searchEnd, size_
return ORBIS_OK; return ORBIS_OK;
} }
s32 PS4_SYSV_ABI sceKernelMemoryPoolReserve(void* addrIn, size_t len, size_t alignment, int flags, s32 PS4_SYSV_ABI sceKernelMemoryPoolReserve(void* addr_in, u64 len, u64 alignment, s32 flags,
void** addrOut) { void** addr_out) {
LOG_INFO(Kernel_Vmm, "addrIn = {}, len = {:#x}, alignment = {:#x}, flags = {:#x}", LOG_INFO(Kernel_Vmm, "addr_in = {}, len = {:#x}, alignment = {:#x}, flags = {:#x}",
fmt::ptr(addrIn), len, alignment, flags); fmt::ptr(addr_in), len, alignment, flags);
if (len == 0 || !Common::Is2MBAligned(len)) { if (len == 0 || !Common::Is2MBAligned(len)) {
LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 2MB aligned!"); LOG_ERROR(Kernel_Vmm, "Map size is either zero or not 2MB aligned!");
@ -456,14 +465,16 @@ s32 PS4_SYSV_ABI sceKernelMemoryPoolReserve(void* addrIn, size_t len, size_t ali
} }
auto* memory = Core::Memory::Instance(); auto* memory = Core::Memory::Instance();
const VAddr in_addr = reinterpret_cast<VAddr>(addrIn); const VAddr in_addr = reinterpret_cast<VAddr>(addr_in);
const auto map_flags = static_cast<Core::MemoryMapFlags>(flags); const auto map_flags = static_cast<Core::MemoryMapFlags>(flags);
memory->PoolReserve(addrOut, in_addr, len, map_flags, alignment); u64 map_alignment = alignment == 0 ? 2_MB : alignment;
return ORBIS_OK; return memory->MapMemory(addr_out, std::bit_cast<VAddr>(addr_in), len,
Core::MemoryProt::NoAccess, map_flags, Core::VMAType::PoolReserved,
"anon", false, -1, map_alignment);
} }
s32 PS4_SYSV_ABI sceKernelMemoryPoolCommit(void* addr, size_t len, int type, int prot, int flags) { s32 PS4_SYSV_ABI sceKernelMemoryPoolCommit(void* addr, u64 len, s32 type, s32 prot, s32 flags) {
if (addr == nullptr) { if (addr == nullptr) {
LOG_ERROR(Kernel_Vmm, "Address is invalid!"); LOG_ERROR(Kernel_Vmm, "Address is invalid!");
return ORBIS_KERNEL_ERROR_EINVAL; return ORBIS_KERNEL_ERROR_EINVAL;
@ -482,7 +493,7 @@ s32 PS4_SYSV_ABI sceKernelMemoryPoolCommit(void* addr, size_t len, int type, int
return memory->PoolCommit(in_addr, len, mem_prot); return memory->PoolCommit(in_addr, len, mem_prot);
} }
s32 PS4_SYSV_ABI sceKernelMemoryPoolDecommit(void* addr, size_t len, int flags) { s32 PS4_SYSV_ABI sceKernelMemoryPoolDecommit(void* addr, u64 len, s32 flags) {
if (addr == nullptr) { if (addr == nullptr) {
LOG_ERROR(Kernel_Vmm, "Address is invalid!"); LOG_ERROR(Kernel_Vmm, "Address is invalid!");
return ORBIS_KERNEL_ERROR_EINVAL; return ORBIS_KERNEL_ERROR_EINVAL;
@ -523,12 +534,12 @@ s32 PS4_SYSV_ABI sceKernelMemoryPoolBatch(const OrbisKernelMemoryPoolBatchEntry*
break; break;
} }
case OrbisKernelMemoryPoolOpcode::Protect: { case OrbisKernelMemoryPoolOpcode::Protect: {
result = sceKernelMProtect(entry.protect_params.addr, entry.protect_params.len, result = sceKernelMprotect(entry.protect_params.addr, entry.protect_params.len,
entry.protect_params.prot); entry.protect_params.prot);
break; break;
} }
case OrbisKernelMemoryPoolOpcode::TypeProtect: { case OrbisKernelMemoryPoolOpcode::TypeProtect: {
result = sceKernelMTypeProtect( result = sceKernelMtypeprotect(
entry.type_protect_params.addr, entry.type_protect_params.len, entry.type_protect_params.addr, entry.type_protect_params.len,
entry.type_protect_params.type, entry.type_protect_params.prot); entry.type_protect_params.type, entry.type_protect_params.prot);
break; break;
@ -553,30 +564,49 @@ s32 PS4_SYSV_ABI sceKernelMemoryPoolBatch(const OrbisKernelMemoryPoolBatchEntry*
return result; return result;
} }
int PS4_SYSV_ABI sceKernelMmap(void* addr, u64 len, int prot, int flags, int fd, size_t offset, void* PS4_SYSV_ABI posix_mmap(void* addr, u64 len, s32 prot, s32 flags, s32 fd, s64 phys_addr) {
void** res) { LOG_INFO(Kernel_Vmm,
LOG_INFO(Kernel_Vmm, "called addr = {}, len = {}, prot = {}, flags = {}, fd = {}, offset = {}", "called addr = {}, len = {}, prot = {}, flags = {}, fd = {}, phys_addr = {}",
fmt::ptr(addr), len, prot, flags, fd, offset); fmt::ptr(addr), len, prot, flags, fd, phys_addr);
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
void* addr_out;
auto* memory = Core::Memory::Instance(); auto* memory = Core::Memory::Instance();
const auto mem_prot = static_cast<Core::MemoryProt>(prot); const auto mem_prot = static_cast<Core::MemoryProt>(prot);
const auto mem_flags = static_cast<Core::MemoryMapFlags>(flags); const auto mem_flags = static_cast<Core::MemoryMapFlags>(flags);
const auto is_exec = True(mem_prot & Core::MemoryProt::CpuExec);
s32 result = ORBIS_OK;
if (fd == -1) { if (fd == -1) {
return memory->MapMemory(res, std::bit_cast<VAddr>(addr), len, mem_prot, mem_flags, result = memory->MapMemory(&addr_out, std::bit_cast<VAddr>(addr), len, mem_prot, mem_flags,
Core::VMAType::Flexible); Core::VMAType::Flexible, "anon", is_exec);
} else { } else {
const uintptr_t handle = h->GetFile(fd)->f.GetFileMapping(); result = memory->MapFile(&addr_out, std::bit_cast<VAddr>(addr), len, mem_prot, mem_flags,
return memory->MapFile(res, std::bit_cast<VAddr>(addr), len, mem_prot, mem_flags, handle, fd, phys_addr);
offset);
} }
if (result != ORBIS_OK) {
// If the memory mappings fail, mmap sets errno to the appropriate error code,
// then returns (void*)-1;
ErrSceToPosix(result);
return reinterpret_cast<void*>(-1);
}
return addr_out;
} }
void* PS4_SYSV_ABI posix_mmap(void* addr, u64 len, int prot, int flags, int fd, u64 offset) { s32 PS4_SYSV_ABI sceKernelMmap(void* addr, u64 len, s32 prot, s32 flags, s32 fd, s64 phys_addr,
void* ptr; void** res) {
LOG_INFO(Kernel_Vmm, "posix mmap redirect to sceKernelMmap"); void* addr_out = posix_mmap(addr, len, prot, flags, fd, phys_addr);
int result = sceKernelMmap(addr, len, prot, flags, fd, offset, &ptr);
ASSERT(result == 0); if (addr_out == reinterpret_cast<void*>(-1)) {
return ptr; // posix_mmap failed, calculate and return the appropriate kernel error code using errno.
LOG_ERROR(Kernel_Fs, "error = {}", *__Error());
return ErrnoToSceKernelError(*__Error());
}
// Set the outputted address
*res = addr_out;
return ORBIS_OK;
} }
s32 PS4_SYSV_ABI sceKernelConfiguredFlexibleMemorySize(u64* sizeOut) { s32 PS4_SYSV_ABI sceKernelConfiguredFlexibleMemorySize(u64* sizeOut) {
@ -589,7 +619,7 @@ s32 PS4_SYSV_ABI sceKernelConfiguredFlexibleMemorySize(u64* sizeOut) {
return ORBIS_OK; return ORBIS_OK;
} }
int PS4_SYSV_ABI sceKernelMunmap(void* addr, size_t len) { s32 PS4_SYSV_ABI sceKernelMunmap(void* addr, u64 len) {
LOG_INFO(Kernel_Vmm, "addr = {}, len = {:#x}", fmt::ptr(addr), len); LOG_INFO(Kernel_Vmm, "addr = {}, len = {:#x}", fmt::ptr(addr), len);
if (len == 0) { if (len == 0) {
return ORBIS_KERNEL_ERROR_EINVAL; return ORBIS_KERNEL_ERROR_EINVAL;
@ -598,8 +628,8 @@ int PS4_SYSV_ABI sceKernelMunmap(void* addr, size_t len) {
return memory->UnmapMemory(std::bit_cast<VAddr>(addr), len); return memory->UnmapMemory(std::bit_cast<VAddr>(addr), len);
} }
int PS4_SYSV_ABI posix_munmap(void* addr, size_t len) { s32 PS4_SYSV_ABI posix_munmap(void* addr, u64 len) {
int result = sceKernelMunmap(addr, len); s32 result = sceKernelMunmap(addr, len);
if (result < 0) { if (result < 0) {
LOG_ERROR(Kernel_Pthread, "posix_munmap: error = {}", result); LOG_ERROR(Kernel_Pthread, "posix_munmap: error = {}", result);
ErrSceToPosix(result); ErrSceToPosix(result);
@ -608,12 +638,12 @@ int PS4_SYSV_ABI posix_munmap(void* addr, size_t len) {
return result; return result;
} }
static constexpr int MAX_PRT_APERTURES = 3; static constexpr s32 MAX_PRT_APERTURES = 3;
static constexpr VAddr PRT_AREA_START_ADDR = 0x1000000000; static constexpr VAddr PRT_AREA_START_ADDR = 0x1000000000;
static constexpr size_t PRT_AREA_SIZE = 0xec00000000; static constexpr u64 PRT_AREA_SIZE = 0xec00000000;
static std::array<std::pair<VAddr, size_t>, MAX_PRT_APERTURES> PrtApertures{}; static std::array<std::pair<VAddr, u64>, MAX_PRT_APERTURES> PrtApertures{};
int PS4_SYSV_ABI sceKernelSetPrtAperture(int id, VAddr address, size_t size) { s32 PS4_SYSV_ABI sceKernelSetPrtAperture(s32 id, VAddr address, u64 size) {
if (id < 0 || id >= MAX_PRT_APERTURES) { if (id < 0 || id >= MAX_PRT_APERTURES) {
return ORBIS_KERNEL_ERROR_EINVAL; return ORBIS_KERNEL_ERROR_EINVAL;
} }
@ -630,11 +660,14 @@ int PS4_SYSV_ABI sceKernelSetPrtAperture(int id, VAddr address, size_t size) {
"PRT aperture id = {}, address = {:#x}, size = {:#x} is set but not used", id, "PRT aperture id = {}, address = {:#x}, size = {:#x} is set but not used", id,
address, size); address, size);
auto* memory = Core::Memory::Instance();
memory->SetPrtArea(id, address, size);
PrtApertures[id] = {address, size}; PrtApertures[id] = {address, size};
return ORBIS_OK; return ORBIS_OK;
} }
int PS4_SYSV_ABI sceKernelGetPrtAperture(int id, VAddr* address, size_t* size) { s32 PS4_SYSV_ABI sceKernelGetPrtAperture(s32 id, VAddr* address, u64* size) {
if (id < 0 || id >= MAX_PRT_APERTURES) { if (id < 0 || id >= MAX_PRT_APERTURES) {
return ORBIS_KERNEL_ERROR_EINVAL; return ORBIS_KERNEL_ERROR_EINVAL;
} }
@ -678,8 +711,10 @@ void RegisterMemory(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("n1-v6FgU7MQ", "libkernel", 1, "libkernel", 1, 1, LIB_FUNCTION("n1-v6FgU7MQ", "libkernel", 1, "libkernel", 1, 1,
sceKernelConfiguredFlexibleMemorySize); sceKernelConfiguredFlexibleMemorySize);
LIB_FUNCTION("9bfdLIyuwCY", "libkernel", 1, "libkernel", 1, 1, sceKernelMTypeProtect); LIB_FUNCTION("vSMAm3cxYTY", "libkernel", 1, "libkernel", 1, 1, sceKernelMprotect);
LIB_FUNCTION("vSMAm3cxYTY", "libkernel", 1, "libkernel", 1, 1, sceKernelMProtect); LIB_FUNCTION("YQOfxL4QfeU", "libkernel", 1, "libkernel", 1, 1, posix_mprotect);
LIB_FUNCTION("YQOfxL4QfeU", "libScePosix", 1, "libkernel", 1, 1, posix_mprotect);
LIB_FUNCTION("9bfdLIyuwCY", "libkernel", 1, "libkernel", 1, 1, sceKernelMtypeprotect);
// Memory pool // Memory pool
LIB_FUNCTION("qCSfqDILlns", "libkernel", 1, "libkernel", 1, 1, sceKernelMemoryPoolExpand); LIB_FUNCTION("qCSfqDILlns", "libkernel", 1, "libkernel", 1, 1, sceKernelMemoryPoolExpand);

View File

@ -52,13 +52,13 @@ constexpr u32 ORBIS_KERNEL_MAXIMUM_NAME_LENGTH = 32;
struct OrbisQueryInfo { struct OrbisQueryInfo {
uintptr_t start; uintptr_t start;
uintptr_t end; uintptr_t end;
int memoryType; s32 memoryType;
}; };
struct OrbisVirtualQueryInfo { struct OrbisVirtualQueryInfo {
uintptr_t start; uintptr_t start;
uintptr_t end; uintptr_t end;
size_t offset; u64 offset;
s32 protection; s32 protection;
s32 memory_type; s32 memory_type;
u8 is_flexible : 1; u8 is_flexible : 1;
@ -73,12 +73,12 @@ static_assert(sizeof(OrbisVirtualQueryInfo) == 72,
struct OrbisKernelBatchMapEntry { struct OrbisKernelBatchMapEntry {
void* start; void* start;
size_t offset; u64 offset;
size_t length; u64 length;
char protection; char protection;
char type; char type;
short reserved; s16 reserved;
int operation; s32 operation;
}; };
enum class OrbisKernelMemoryPoolOpcode : u32 { enum class OrbisKernelMemoryPoolOpcode : u32 {
@ -124,59 +124,57 @@ struct OrbisKernelMemoryPoolBatchEntry {
}; };
u64 PS4_SYSV_ABI sceKernelGetDirectMemorySize(); u64 PS4_SYSV_ABI sceKernelGetDirectMemorySize();
int PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u64 len, s32 PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u64 len,
u64 alignment, int memoryType, s64* physAddrOut); u64 alignment, s32 memoryType, s64* physAddrOut);
int PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, int prot, int flags, s32 PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, s32 prot, s32 flags,
s64 directMemoryStart, u64 alignment, s64 directMemoryStart, u64 alignment,
const char* name); const char* name);
int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int flags, s32 PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, s32 prot, s32 flags,
s64 directMemoryStart, u64 alignment); s64 directMemoryStart, u64 alignment);
s32 PS4_SYSV_ABI sceKernelAllocateMainDirectMemory(size_t len, size_t alignment, int memoryType, s32 PS4_SYSV_ABI sceKernelAllocateMainDirectMemory(u64 len, u64 alignment, s32 memoryType,
s64* physAddrOut); s64* physAddrOut);
s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, size_t len); s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, u64 len);
s32 PS4_SYSV_ABI sceKernelCheckedReleaseDirectMemory(u64 start, size_t len); s32 PS4_SYSV_ABI sceKernelCheckedReleaseDirectMemory(u64 start, u64 len);
s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchEnd, s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchEnd, u64 alignment,
size_t alignment, u64* physAddrOut, u64* physAddrOut, u64* sizeOut);
size_t* sizeOut); s32 PS4_SYSV_ABI sceKernelVirtualQuery(const void* addr, s32 flags, OrbisVirtualQueryInfo* info,
s32 PS4_SYSV_ABI sceKernelVirtualQuery(const void* addr, int flags, OrbisVirtualQueryInfo* info, u64 infoSize);
size_t infoSize); s32 PS4_SYSV_ABI sceKernelReserveVirtualRange(void** addr, u64 len, s32 flags, u64 alignment);
s32 PS4_SYSV_ABI sceKernelReserveVirtualRange(void** addr, u64 len, int flags, u64 alignment); s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, u64 len, s32 prot, s32 flags,
s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addrInOut, std::size_t len, int prot, const char* name);
int flags, const char* name); s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, u64 len, s32 prot, s32 flags);
s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, std::size_t len, int prot, s32 PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void** end, u32* prot);
int flags);
int PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void** end, u32* prot);
int PS4_SYSV_ABI sceKernelMProtect(const void* addr, size_t size, int prot); s32 PS4_SYSV_ABI sceKernelMprotect(const void* addr, u64 size, s32 prot);
int PS4_SYSV_ABI sceKernelMTypeProtect(const void* addr, size_t size, int mtype, int prot); s32 PS4_SYSV_ABI sceKernelMtypeprotect(const void* addr, u64 size, s32 mtype, s32 prot);
int PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, int flags, OrbisQueryInfo* query_info, s32 PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, s32 flags, OrbisQueryInfo* query_info,
size_t infoSize); u64 infoSize);
s32 PS4_SYSV_ABI sceKernelAvailableFlexibleMemorySize(size_t* sizeOut); s32 PS4_SYSV_ABI sceKernelAvailableFlexibleMemorySize(u64* sizeOut);
void PS4_SYSV_ABI _sceKernelRtldSetApplicationHeapAPI(void* func[]); void PS4_SYSV_ABI _sceKernelRtldSetApplicationHeapAPI(void* func[]);
int PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, int* directMemoryTypeOut, s32 PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, s32* directMemoryTypeOut,
void** directMemoryStartOut, void** directMemoryStartOut,
void** directMemoryEndOut); void** directMemoryEndOut);
int PS4_SYSV_ABI sceKernelIsStack(void* addr, void** start, void** end); s32 PS4_SYSV_ABI sceKernelIsStack(void* addr, void** start, void** end);
s32 PS4_SYSV_ABI sceKernelBatchMap(OrbisKernelBatchMapEntry* entries, int numEntries, s32 PS4_SYSV_ABI sceKernelBatchMap(OrbisKernelBatchMapEntry* entries, s32 numEntries,
int* numEntriesOut); s32* numEntriesOut);
s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEntries, s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, s32 numEntries,
int* numEntriesOut, int flags); s32* numEntriesOut, s32 flags);
s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, size_t len, const char* name); s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, u64 len, const char* name);
s32 PS4_SYSV_ABI sceKernelMemoryPoolExpand(u64 searchStart, u64 searchEnd, size_t len, s32 PS4_SYSV_ABI sceKernelMemoryPoolExpand(u64 searchStart, u64 searchEnd, u64 len, u64 alignment,
size_t alignment, u64* physAddrOut); u64* physAddrOut);
s32 PS4_SYSV_ABI sceKernelMemoryPoolReserve(void* addrIn, size_t len, size_t alignment, int flags, s32 PS4_SYSV_ABI sceKernelMemoryPoolReserve(void* addr_in, u64 len, u64 alignment, s32 flags,
void** addrOut); void** addr_out);
s32 PS4_SYSV_ABI sceKernelMemoryPoolCommit(void* addr, size_t len, int type, int prot, int flags); s32 PS4_SYSV_ABI sceKernelMemoryPoolCommit(void* addr, u64 len, s32 type, s32 prot, s32 flags);
s32 PS4_SYSV_ABI sceKernelMemoryPoolDecommit(void* addr, size_t len, int flags); s32 PS4_SYSV_ABI sceKernelMemoryPoolDecommit(void* addr, u64 len, s32 flags);
s32 PS4_SYSV_ABI sceKernelMemoryPoolBatch(const OrbisKernelMemoryPoolBatchEntry* entries, s32 count, s32 PS4_SYSV_ABI sceKernelMemoryPoolBatch(const OrbisKernelMemoryPoolBatchEntry* entries, s32 count,
s32* num_processed, s32 flags); s32* num_processed, s32 flags);
int PS4_SYSV_ABI sceKernelMunmap(void* addr, size_t len); s32 PS4_SYSV_ABI sceKernelMunmap(void* addr, u64 len);
void RegisterMemory(Core::Loader::SymbolsResolver* sym); void RegisterMemory(Core::Loader::SymbolsResolver* sym);

View File

@ -103,7 +103,7 @@ s32 PS4_SYSV_ABI sceKernelGetModuleInfoForUnwind(VAddr addr, s32 flags,
auto* linker = Common::Singleton<Core::Linker>::Instance(); auto* linker = Common::Singleton<Core::Linker>::Instance();
auto* module = linker->FindByAddress(addr); auto* module = linker->FindByAddress(addr);
if (!module) { if (!module) {
return ORBIS_KERNEL_ERROR_EFAULT; return ORBIS_KERNEL_ERROR_ESRCH;
} }
const auto mod_info = module->GetModuleInfoEx(); const auto mod_info = module->GetModuleInfoEx();
@ -118,11 +118,23 @@ s32 PS4_SYSV_ABI sceKernelGetModuleInfoForUnwind(VAddr addr, s32 flags,
return ORBIS_OK; return ORBIS_OK;
} }
int PS4_SYSV_ABI sceKernelGetModuleInfoFromAddr(VAddr addr, int flags, s32 PS4_SYSV_ABI sceKernelGetModuleInfoFromAddr(VAddr addr, s32 flags,
Core::OrbisKernelModuleInfoEx* info) { Core::OrbisKernelModuleInfoEx* info) {
if (flags >= 3) {
std::memset(info, 0, sizeof(Core::OrbisKernelModuleInfoEx));
return ORBIS_KERNEL_ERROR_EINVAL;
}
if (info == nullptr) {
return ORBIS_KERNEL_ERROR_EFAULT;
}
LOG_INFO(Lib_Kernel, "called addr = {:#x}, flags = {:#x}", addr, flags); LOG_INFO(Lib_Kernel, "called addr = {:#x}, flags = {:#x}", addr, flags);
auto* linker = Common::Singleton<Core::Linker>::Instance(); auto* linker = Common::Singleton<Core::Linker>::Instance();
auto* module = linker->FindByAddress(addr); auto* module = linker->FindByAddress(addr);
if (!module) {
return ORBIS_KERNEL_ERROR_ESRCH;
}
*info = module->GetModuleInfoEx(); *info = module->GetModuleInfoEx();
return ORBIS_OK; return ORBIS_OK;
} }

View File

@ -426,6 +426,7 @@ void RegisterMutex(Core::Loader::SymbolsResolver* sym) {
// Posix // Posix
LIB_FUNCTION("ttHNfU+qDBU", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_init); LIB_FUNCTION("ttHNfU+qDBU", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_init);
LIB_FUNCTION("7H0iTOciTLo", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_lock); LIB_FUNCTION("7H0iTOciTLo", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_lock);
LIB_FUNCTION("Io9+nTKXZtA", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_timedlock);
LIB_FUNCTION("2Z+PpY6CaJg", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_unlock); LIB_FUNCTION("2Z+PpY6CaJg", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_unlock);
LIB_FUNCTION("ltCfaGr2JGE", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_destroy); LIB_FUNCTION("ltCfaGr2JGE", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_destroy);
LIB_FUNCTION("dQHWEsJtoE4", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutexattr_init); LIB_FUNCTION("dQHWEsJtoE4", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutexattr_init);

View File

@ -576,8 +576,19 @@ int PS4_SYSV_ABI posix_pthread_getaffinity_np(PthreadT thread, size_t cpusetsize
if (thread == nullptr || cpusetp == nullptr) { if (thread == nullptr || cpusetp == nullptr) {
return POSIX_EINVAL; return POSIX_EINVAL;
} }
auto* thread_state = ThrState::Instance();
if (thread == g_curthread) {
g_curthread->lock.lock();
} else if (auto ret = thread_state->FindThread(thread, /*include dead*/ 0); ret != 0) {
return ret;
}
auto* attr_ptr = &thread->attr; auto* attr_ptr = &thread->attr;
return posix_pthread_attr_getaffinity_np(&attr_ptr, cpusetsize, cpusetp); auto ret = posix_pthread_attr_getaffinity_np(&attr_ptr, cpusetsize, cpusetp);
thread->lock.unlock();
return ret;
} }
int PS4_SYSV_ABI posix_pthread_setaffinity_np(PthreadT thread, size_t cpusetsize, int PS4_SYSV_ABI posix_pthread_setaffinity_np(PthreadT thread, size_t cpusetsize,
@ -585,11 +596,23 @@ int PS4_SYSV_ABI posix_pthread_setaffinity_np(PthreadT thread, size_t cpusetsize
if (thread == nullptr || cpusetp == nullptr) { if (thread == nullptr || cpusetp == nullptr) {
return POSIX_EINVAL; return POSIX_EINVAL;
} }
auto* attr_ptr = &thread->attr;
if (const auto ret = posix_pthread_attr_setaffinity_np(&attr_ptr, cpusetsize, cpusetp)) { auto* thread_state = ThrState::Instance();
if (thread == g_curthread) {
g_curthread->lock.lock();
} else if (auto ret = thread_state->FindThread(thread, /*include dead*/ 0); ret != 0) {
return ret; return ret;
} }
return thread->SetAffinity(thread->attr.cpuset);
auto* attr_ptr = &thread->attr;
auto ret = posix_pthread_attr_setaffinity_np(&attr_ptr, cpusetsize, cpusetp);
if (ret == ORBIS_OK) {
ret = thread->SetAffinity(thread->attr.cpuset);
}
thread->lock.unlock();
return ret;
} }
int PS4_SYSV_ABI scePthreadGetaffinity(PthreadT thread, u64* mask) { int PS4_SYSV_ABI scePthreadGetaffinity(PthreadT thread, u64* mask) {

View File

@ -306,6 +306,8 @@ void RegisterThreadAttr(Core::Loader::SymbolsResolver* sym) {
posix_pthread_attr_getdetachstate); posix_pthread_attr_getdetachstate);
LIB_FUNCTION("JKyG3SWyA10", "libScePosix", 1, "libkernel", 1, 1, LIB_FUNCTION("JKyG3SWyA10", "libScePosix", 1, "libkernel", 1, 1,
posix_pthread_attr_setguardsize); posix_pthread_attr_setguardsize);
LIB_FUNCTION("qlk9pSLsUmM", "libScePosix", 1, "libkernel", 1, 1,
posix_pthread_attr_getschedparam);
// Orbis // Orbis
LIB_FUNCTION("4+h9EzwKF4I", "libkernel", 1, "libkernel", 1, 1, LIB_FUNCTION("4+h9EzwKF4I", "libkernel", 1, "libkernel", 1, 1,

View File

@ -10,6 +10,7 @@
#include "core/libraries/avplayer/avplayer.h" #include "core/libraries/avplayer/avplayer.h"
#include "core/libraries/camera/camera.h" #include "core/libraries/camera/camera.h"
#include "core/libraries/companion/companion_httpd.h" #include "core/libraries/companion/companion_httpd.h"
#include "core/libraries/companion/companion_util.h"
#include "core/libraries/disc_map/disc_map.h" #include "core/libraries/disc_map/disc_map.h"
#include "core/libraries/game_live_streaming/gamelivestreaming.h" #include "core/libraries/game_live_streaming/gamelivestreaming.h"
#include "core/libraries/gnmdriver/gnmdriver.h" #include "core/libraries/gnmdriver/gnmdriver.h"
@ -59,6 +60,7 @@
#include "core/libraries/videodec/videodec.h" #include "core/libraries/videodec/videodec.h"
#include "core/libraries/videodec/videodec2.h" #include "core/libraries/videodec/videodec2.h"
#include "core/libraries/videoout/video_out.h" #include "core/libraries/videoout/video_out.h"
#include "core/libraries/voice/voice.h"
#include "core/libraries/web_browser_dialog/webbrowserdialog.h" #include "core/libraries/web_browser_dialog/webbrowserdialog.h"
#include "core/libraries/zlib/zlib_sce.h" #include "core/libraries/zlib/zlib_sce.h"
#include "fiber/fiber.h" #include "fiber/fiber.h"
@ -126,6 +128,8 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
Libraries::SigninDialog::RegisterlibSceSigninDialog(sym); Libraries::SigninDialog::RegisterlibSceSigninDialog(sym);
Libraries::Camera::RegisterlibSceCamera(sym); Libraries::Camera::RegisterlibSceCamera(sym);
Libraries::CompanionHttpd::RegisterlibSceCompanionHttpd(sym); Libraries::CompanionHttpd::RegisterlibSceCompanionHttpd(sym);
Libraries::CompanionUtil::RegisterlibSceCompanionUtil(sym);
Libraries::Voice::RegisterlibSceVoice(sym);
} }
} // namespace Libraries } // namespace Libraries

View File

@ -955,16 +955,148 @@ u16 PS4_SYSV_ABI sceNetHtons(u16 host16) {
return htons(host16); return htons(host16);
} }
const char* PS4_SYSV_ABI sceNetInetNtop(int af, const void* src, char* dst, u32 size) {
#ifdef WIN32 #ifdef WIN32
const char* res = InetNtopA(af, src, dst, size); // there isn't a strlcpy function in windows so implement one
#else u64 strlcpy(char* dst, const char* src, u64 size) {
const char* res = inet_ntop(af, src, dst, size); u64 src_len = strlen(src);
#endif
if (res == nullptr) { if (size > 0) {
UNREACHABLE(); u64 copy_len = (src_len >= size) ? (size - 1) : src_len;
memcpy(dst, src, copy_len);
dst[copy_len] = '\0';
} }
return dst;
return src_len;
}
#endif
const char* freebsd_inet_ntop4(const char* src, char* dst, u64 size) {
static const char fmt[] = "%u.%u.%u.%u";
char tmp[sizeof "255.255.255.255"];
int l;
l = snprintf(tmp, sizeof(tmp), fmt, src[0], src[1], src[2], src[3]);
if (l <= 0 || (socklen_t)l >= size) {
return nullptr;
}
strlcpy(dst, tmp, size);
return (dst);
}
const char* freebsd_inet_ntop6(const char* src, char* dst, u64 size) {
/*
* Note that int32_t and int16_t need only be "at least" large enough
* to contain a value of the specified size. On some systems, like
* Crays, there is no such thing as an integer variable with 16 bits.
* Keep this in mind if you think this function should have been coded
* to use pointer overlays. All the world's not a VAX.
*/
char tmp[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"], *tp;
struct {
int base, len;
} best, cur;
#define NS_IN6ADDRSZ 16
#define NS_INT16SZ 2
u_int words[NS_IN6ADDRSZ / NS_INT16SZ];
int i;
/*
* Preprocess:
* Copy the input (bytewise) array into a wordwise array.
* Find the longest run of 0x00's in src[] for :: shorthanding.
*/
memset(words, '\0', sizeof words);
for (i = 0; i < NS_IN6ADDRSZ; i++)
words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3));
best.base = -1;
best.len = 0;
cur.base = -1;
cur.len = 0;
for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) {
if (words[i] == 0) {
if (cur.base == -1)
cur.base = i, cur.len = 1;
else
cur.len++;
} else {
if (cur.base != -1) {
if (best.base == -1 || cur.len > best.len)
best = cur;
cur.base = -1;
}
}
}
if (cur.base != -1) {
if (best.base == -1 || cur.len > best.len)
best = cur;
}
if (best.base != -1 && best.len < 2)
best.base = -1;
/*
* Format the result.
*/
tp = tmp;
for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) {
/* Are we inside the best run of 0x00's? */
if (best.base != -1 && i >= best.base && i < (best.base + best.len)) {
if (i == best.base)
*tp++ = ':';
continue;
}
/* Are we following an initial run of 0x00s or any real hex? */
if (i != 0)
*tp++ = ':';
/* Is this address an encapsulated IPv4? */
if (i == 6 && best.base == 0 &&
(best.len == 6 || (best.len == 7 && words[7] != 0x0001) ||
(best.len == 5 && words[5] == 0xffff))) {
if (!freebsd_inet_ntop4(src + 12, tp, sizeof tmp - (tp - tmp)))
return nullptr;
tp += strlen(tp);
break;
}
tp += sprintf(tp, "%x", words[i]);
}
/* Was it a trailing run of 0x00's? */
if (best.base != -1 && (best.base + best.len) == (NS_IN6ADDRSZ / NS_INT16SZ))
*tp++ = ':';
*tp++ = '\0';
/*
* Check for overflow, copy, and we're done.
*/
if ((u64)(tp - tmp) > size) {
return nullptr;
}
strcpy(dst, tmp);
return (dst);
}
const char* PS4_SYSV_ABI sceNetInetNtop(int af, const void* src, char* dst, u32 size) {
if (!(src && dst)) {
*sceNetErrnoLoc() = ORBIS_NET_ENOSPC;
LOG_ERROR(Lib_Net, "returned ORBIS_NET_ENOSPC");
return nullptr;
}
const char* returnvalue = nullptr;
switch (af) {
case ORBIS_NET_AF_INET:
returnvalue = freebsd_inet_ntop4((const char*)src, dst, size);
break;
case ORBIS_NET_AF_INET6:
returnvalue = freebsd_inet_ntop6((const char*)src, dst, size);
break;
default:
*sceNetErrnoLoc() = ORBIS_NET_EAFNOSUPPORT;
LOG_ERROR(Lib_Net, "returned ORBIS_NET_EAFNOSUPPORT");
return nullptr;
}
if (returnvalue == nullptr) {
*sceNetErrnoLoc() = ORBIS_NET_ENOSPC;
LOG_ERROR(Lib_Net, "returned ORBIS_NET_ENOSPC");
}
return returnvalue;
} }
int PS4_SYSV_ABI sceNetInetNtopWithScopeId() { int PS4_SYSV_ABI sceNetInetNtopWithScopeId() {

View File

@ -20,6 +20,10 @@ class SymbolsResolver;
namespace Libraries::Net { namespace Libraries::Net {
enum OrbisNetFamily : u32 {
ORBIS_NET_AF_INET = 2,
ORBIS_NET_AF_INET6 = 28,
};
enum OrbisNetSocketType : u32 { enum OrbisNetSocketType : u32 {
ORBIS_NET_SOCK_STREAM = 1, ORBIS_NET_SOCK_STREAM = 1,
ORBIS_NET_SOCK_DGRAM = 2, ORBIS_NET_SOCK_DGRAM = 2,

View File

@ -100,6 +100,11 @@ s32 SystemSetupCore(StackBuffer* stackBuffer, const OrbisNgs2SystemOption* optio
return ORBIS_NGS2_ERROR_INVALID_SAMPLE_RATE; return ORBIS_NGS2_ERROR_INVALID_SAMPLE_RATE;
} }
if (outSystem) {
// dummy handle
outSystem->systemHandle = 1;
}
return ORBIS_OK; return ORBIS_OK;
} }

View File

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "common/config.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/libraries/error_codes.h" #include "core/libraries/error_codes.h"
#include "core/libraries/libs.h" #include "core/libraries/libs.h"
@ -10,6 +11,8 @@
namespace Libraries::NpManager { namespace Libraries::NpManager {
#define SIGNEDIN_STATUS (Config::getPSNSignedIn() ? ORBIS_OK : ORBIS_NP_ERROR_SIGNED_OUT)
int PS4_SYSV_ABI Func_EF4378573542A508() { int PS4_SYSV_ABI Func_EF4378573542A508() {
LOG_ERROR(Lib_NpManager, "(STUBBED) called"); LOG_ERROR(Lib_NpManager, "(STUBBED) called");
return ORBIS_OK; return ORBIS_OK;
@ -921,9 +924,16 @@ int PS4_SYSV_ABI sceNpGetAccountCountry() {
return ORBIS_OK; return ORBIS_OK;
} }
int PS4_SYSV_ABI sceNpGetAccountCountryA() { int PS4_SYSV_ABI sceNpGetAccountCountryA(OrbisUserServiceUserId user_id,
LOG_ERROR(Lib_NpManager, "(STUBBED) called"); OrbisNpCountryCode* country_code) {
return ORBIS_OK; LOG_INFO(Lib_NpManager, "(STUBBED) called, user_id = {}", user_id);
if (country_code == nullptr) {
return ORBIS_NP_ERROR_INVALID_ARGUMENT;
}
::memset(country_code, 0, sizeof(OrbisNpCountryCode));
// TODO: get NP country code from config
::memcpy(country_code->country_code, "us", 2);
return SIGNEDIN_STATUS;
} }
int PS4_SYSV_ABI sceNpGetAccountDateOfBirth() { int PS4_SYSV_ABI sceNpGetAccountDateOfBirth() {
@ -941,8 +951,8 @@ int PS4_SYSV_ABI sceNpGetAccountId(OrbisNpOnlineId* online_id, u64* account_id)
if (online_id == nullptr || account_id == nullptr) { if (online_id == nullptr || account_id == nullptr) {
return ORBIS_NP_ERROR_INVALID_ARGUMENT; return ORBIS_NP_ERROR_INVALID_ARGUMENT;
} }
*account_id = 0; *account_id = 0xFEEDFACE;
return ORBIS_NP_ERROR_SIGNED_OUT; return SIGNEDIN_STATUS;
} }
int PS4_SYSV_ABI sceNpGetAccountIdA(OrbisUserServiceUserId user_id, u64* account_id) { int PS4_SYSV_ABI sceNpGetAccountIdA(OrbisUserServiceUserId user_id, u64* account_id) {
@ -950,8 +960,8 @@ int PS4_SYSV_ABI sceNpGetAccountIdA(OrbisUserServiceUserId user_id, u64* account
if (account_id == nullptr) { if (account_id == nullptr) {
return ORBIS_NP_ERROR_INVALID_ARGUMENT; return ORBIS_NP_ERROR_INVALID_ARGUMENT;
} }
*account_id = 0; *account_id = 0xFEEDFACE;
return ORBIS_NP_ERROR_SIGNED_OUT; return SIGNEDIN_STATUS;
} }
int PS4_SYSV_ABI sceNpGetAccountLanguage() { int PS4_SYSV_ABI sceNpGetAccountLanguage() {
@ -984,7 +994,9 @@ int PS4_SYSV_ABI sceNpGetNpId(OrbisUserServiceUserId user_id, OrbisNpId* np_id)
if (np_id == nullptr) { if (np_id == nullptr) {
return ORBIS_NP_ERROR_INVALID_ARGUMENT; return ORBIS_NP_ERROR_INVALID_ARGUMENT;
} }
return ORBIS_NP_ERROR_SIGNED_OUT; memset(np_id, 0, sizeof(OrbisNpId));
strncpy(np_id->handle.data, Config::getUserName().c_str(), sizeof(np_id->handle.data));
return SIGNEDIN_STATUS;
} }
int PS4_SYSV_ABI sceNpGetNpReachabilityState() { int PS4_SYSV_ABI sceNpGetNpReachabilityState() {
@ -997,7 +1009,9 @@ int PS4_SYSV_ABI sceNpGetOnlineId(OrbisUserServiceUserId user_id, OrbisNpOnlineI
if (online_id == nullptr) { if (online_id == nullptr) {
return ORBIS_NP_ERROR_INVALID_ARGUMENT; return ORBIS_NP_ERROR_INVALID_ARGUMENT;
} }
return ORBIS_NP_ERROR_SIGNED_OUT; memset(online_id, 0, sizeof(OrbisNpOnlineId));
strncpy(online_id->data, Config::getUserName().c_str(), sizeof(online_id->data));
return SIGNEDIN_STATUS;
} }
int PS4_SYSV_ABI sceNpGetParentalControlInfo() { int PS4_SYSV_ABI sceNpGetParentalControlInfo() {
@ -1014,8 +1028,8 @@ int PS4_SYSV_ABI sceNpGetState(OrbisUserServiceUserId user_id, OrbisNpState* sta
if (state == nullptr) { if (state == nullptr) {
return ORBIS_NP_ERROR_INVALID_ARGUMENT; return ORBIS_NP_ERROR_INVALID_ARGUMENT;
} }
*state = OrbisNpState::SignedOut; *state = Config::getPSNSignedIn() ? OrbisNpState::SignedIn : OrbisNpState::SignedOut;
LOG_DEBUG(Lib_NpManager, "Signed out"); LOG_DEBUG(Lib_NpManager, "Signed {}", Config::getPSNSignedIn() ? "in" : "out");
return ORBIS_OK; return ORBIS_OK;
} }

View File

@ -32,6 +32,12 @@ struct OrbisNpId {
u8 reserved[8]; u8 reserved[8];
}; };
struct OrbisNpCountryCode {
char country_code[2];
char end;
char pad;
};
int PS4_SYSV_ABI Func_EF4378573542A508(); int PS4_SYSV_ABI Func_EF4378573542A508();
int PS4_SYSV_ABI _sceNpIpcCreateMemoryFromKernel(); int PS4_SYSV_ABI _sceNpIpcCreateMemoryFromKernel();
int PS4_SYSV_ABI _sceNpIpcCreateMemoryFromPool(); int PS4_SYSV_ABI _sceNpIpcCreateMemoryFromPool();
@ -215,7 +221,8 @@ int PS4_SYSV_ABI sceNpCreateRequest();
int PS4_SYSV_ABI sceNpDeleteRequest(int reqId); int PS4_SYSV_ABI sceNpDeleteRequest(int reqId);
int PS4_SYSV_ABI sceNpGetAccountAge(); int PS4_SYSV_ABI sceNpGetAccountAge();
int PS4_SYSV_ABI sceNpGetAccountCountry(); int PS4_SYSV_ABI sceNpGetAccountCountry();
int PS4_SYSV_ABI sceNpGetAccountCountryA(); int PS4_SYSV_ABI sceNpGetAccountCountryA(OrbisUserServiceUserId user_id,
OrbisNpCountryCode* country_code);
int PS4_SYSV_ABI sceNpGetAccountDateOfBirth(); int PS4_SYSV_ABI sceNpGetAccountDateOfBirth();
int PS4_SYSV_ABI sceNpGetAccountDateOfBirthA(); int PS4_SYSV_ABI sceNpGetAccountDateOfBirthA();
int PS4_SYSV_ABI sceNpGetAccountId(OrbisNpOnlineId* online_id, u64* account_id); int PS4_SYSV_ABI sceNpGetAccountId(OrbisNpOnlineId* online_id, u64* account_id);

View File

@ -164,10 +164,12 @@ s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context, int32_t
} }
const auto ctx_id = trophy_contexts.insert(user_id, service_label); const auto ctx_id = trophy_contexts.insert(user_id, service_label);
contexts_internal[key].context_id = ctx_id.index;
LOG_INFO(Lib_NpTrophy, "New context = {}, user_id = {} service label = {}", ctx_id.index, *context = ctx_id.index + 1;
user_id, service_label); contexts_internal[key].context_id = *context;
*context = ctx_id.index; LOG_INFO(Lib_NpTrophy, "New context = {}, user_id = {} service label = {}", *context, user_id,
service_label);
return ORBIS_OK; return ORBIS_OK;
} }
@ -179,21 +181,27 @@ s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(OrbisNpTrophyHandle* handle) {
if (trophy_handles.size() >= MaxTrophyHandles) { if (trophy_handles.size() >= MaxTrophyHandles) {
return ORBIS_NP_TROPHY_ERROR_HANDLE_EXCEEDS_MAX; return ORBIS_NP_TROPHY_ERROR_HANDLE_EXCEEDS_MAX;
} }
const auto handle_id = trophy_handles.insert();
LOG_INFO(Lib_NpTrophy, "New handle = {}", handle_id.index);
*handle = handle_id.index; const auto handle_id = trophy_handles.insert();
*handle = handle_id.index + 1;
LOG_INFO(Lib_NpTrophy, "New handle = {}", *handle);
return ORBIS_OK; return ORBIS_OK;
} }
int PS4_SYSV_ABI sceNpTrophyDestroyContext(OrbisNpTrophyContext context) { int PS4_SYSV_ABI sceNpTrophyDestroyContext(OrbisNpTrophyContext context) {
LOG_INFO(Lib_NpTrophy, "Destroyed Context {}", context); LOG_INFO(Lib_NpTrophy, "Destroyed Context {}", context);
if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT) if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT) {
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT; return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
}
Common::SlotId contextId; Common::SlotId contextId;
contextId.index = context; contextId.index = context - 1;
if (contextId.index >= trophy_contexts.size()) {
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
}
ContextKey contextkey = trophy_contexts[contextId]; ContextKey contextkey = trophy_contexts[contextId];
trophy_contexts.erase(contextId); trophy_contexts.erase(contextId);
@ -206,15 +214,17 @@ s32 PS4_SYSV_ABI sceNpTrophyDestroyHandle(OrbisNpTrophyHandle handle) {
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE) if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE; return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
if (handle >= trophy_handles.size()) { s32 handle_index = handle - 1;
if (handle_index >= trophy_handles.size()) {
LOG_ERROR(Lib_NpTrophy, "Invalid handle {}", handle); LOG_ERROR(Lib_NpTrophy, "Invalid handle {}", handle);
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE; return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
} }
if (!trophy_handles.is_allocated({static_cast<u32>(handle)})) {
if (!trophy_handles.is_allocated({static_cast<u32>(handle_index)})) {
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE; return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
} }
trophy_handles.erase({static_cast<u32>(handle)}); trophy_handles.erase({static_cast<u32>(handle_index)});
LOG_INFO(Lib_NpTrophy, "Handle {} destroyed", handle); LOG_INFO(Lib_NpTrophy, "Handle {} destroyed", handle);
return ORBIS_OK; return ORBIS_OK;
} }

View File

@ -316,22 +316,79 @@ int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) {
pData[i].angularVelocity.y = states[i].angularVelocity.y; pData[i].angularVelocity.y = states[i].angularVelocity.y;
pData[i].angularVelocity.z = states[i].angularVelocity.z; pData[i].angularVelocity.z = states[i].angularVelocity.z;
pData[i].orientation = {0.0f, 0.0f, 0.0f, 1.0f}; pData[i].orientation = {0.0f, 0.0f, 0.0f, 1.0f};
if (engine) { pData[i].acceleration.x = states[i].acceleration.x * 0.098;
pData[i].acceleration.y = states[i].acceleration.y * 0.098;
pData[i].acceleration.z = states[i].acceleration.z * 0.098;
pData[i].angularVelocity.x = states[i].angularVelocity.x;
pData[i].angularVelocity.y = states[i].angularVelocity.y;
pData[i].angularVelocity.z = states[i].angularVelocity.z;
if (engine && handle == 1) {
const auto gyro_poll_rate = engine->GetAccelPollRate(); const auto gyro_poll_rate = engine->GetAccelPollRate();
if (gyro_poll_rate != 0.0f) { if (gyro_poll_rate != 0.0f) {
GameController::CalculateOrientation(pData[i].acceleration, auto now = std::chrono::steady_clock::now();
pData[i].angularVelocity, float deltaTime = std::chrono::duration_cast<std::chrono::microseconds>(
1.0f / gyro_poll_rate, pData[i].orientation); now - controller->GetLastUpdate())
.count() /
1000000.0f;
controller->SetLastUpdate(now);
Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation();
Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity,
deltaTime, lastOrientation, outputOrientation);
pData[i].orientation = outputOrientation;
controller->SetLastOrientation(outputOrientation);
} }
} }
pData[i].touchData.touchNum = pData[i].touchData.touchNum =
(states[i].touchpad[0].state ? 1 : 0) + (states[i].touchpad[1].state ? 1 : 0); (states[i].touchpad[0].state ? 1 : 0) + (states[i].touchpad[1].state ? 1 : 0);
if (handle == 1) {
if (controller->GetTouchCount() >= 127) {
controller->SetTouchCount(0);
}
if (controller->GetSecondaryTouchCount() >= 127) {
controller->SetSecondaryTouchCount(0);
}
if (pData->touchData.touchNum == 1 && controller->GetPreviousTouchNum() == 0) {
controller->SetTouchCount(controller->GetTouchCount() + 1);
controller->SetSecondaryTouchCount(controller->GetTouchCount());
} else if (pData->touchData.touchNum == 2 && controller->GetPreviousTouchNum() == 1) {
controller->SetSecondaryTouchCount(controller->GetSecondaryTouchCount() + 1);
} else if (pData->touchData.touchNum == 0 && controller->GetPreviousTouchNum() > 0) {
if (controller->GetTouchCount() < controller->GetSecondaryTouchCount()) {
controller->SetTouchCount(controller->GetSecondaryTouchCount());
} else {
if (controller->WasSecondaryTouchReset()) {
controller->SetTouchCount(controller->GetSecondaryTouchCount());
controller->UnsetSecondaryTouchResetBool();
}
}
}
controller->SetPreviousTouchNum(pData->touchData.touchNum);
if (pData->touchData.touchNum == 1) {
states[i].touchpad[0].ID = controller->GetTouchCount();
states[i].touchpad[1].ID = 0;
} else if (pData->touchData.touchNum == 2) {
states[i].touchpad[0].ID = controller->GetTouchCount();
states[i].touchpad[1].ID = controller->GetSecondaryTouchCount();
}
} else {
states[i].touchpad[0].ID = 1;
states[i].touchpad[1].ID = 2;
}
pData[i].touchData.touch[0].x = states[i].touchpad[0].x; pData[i].touchData.touch[0].x = states[i].touchpad[0].x;
pData[i].touchData.touch[0].y = states[i].touchpad[0].y; pData[i].touchData.touch[0].y = states[i].touchpad[0].y;
pData[i].touchData.touch[0].id = 1; pData[i].touchData.touch[0].id = states[i].touchpad[0].ID;
pData[i].touchData.touch[1].x = states[i].touchpad[1].x; pData[i].touchData.touch[1].x = states[i].touchpad[1].x;
pData[i].touchData.touch[1].y = states[i].touchpad[1].y; pData[i].touchData.touch[1].y = states[i].touchpad[1].y;
pData[i].touchData.touch[1].id = 2; pData[i].touchData.touch[1].id = states[i].touchpad[1].ID;
pData[i].connected = connected; pData[i].connected = connected;
pData[i].timestamp = states[i].time; pData[i].timestamp = states[i].time;
pData[i].connectedCount = connected_count; pData[i].connectedCount = connected_count;
@ -376,31 +433,82 @@ int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) {
pData->leftStick.x = state.axes[static_cast<int>(Input::Axis::LeftX)]; pData->leftStick.x = state.axes[static_cast<int>(Input::Axis::LeftX)];
pData->leftStick.y = state.axes[static_cast<int>(Input::Axis::LeftY)]; pData->leftStick.y = state.axes[static_cast<int>(Input::Axis::LeftY)];
pData->rightStick.x = state.axes[static_cast<int>(Input::Axis::RightX)]; pData->rightStick.x = state.axes[static_cast<int>(Input::Axis::RightX)];
pData->rightStick.x = state.axes[static_cast<int>(Input::Axis::RightX)];
pData->rightStick.y = state.axes[static_cast<int>(Input::Axis::RightY)]; pData->rightStick.y = state.axes[static_cast<int>(Input::Axis::RightY)];
pData->analogButtons.l2 = state.axes[static_cast<int>(Input::Axis::TriggerLeft)]; pData->analogButtons.l2 = state.axes[static_cast<int>(Input::Axis::TriggerLeft)];
pData->analogButtons.r2 = state.axes[static_cast<int>(Input::Axis::TriggerRight)]; pData->analogButtons.r2 = state.axes[static_cast<int>(Input::Axis::TriggerRight)];
pData->acceleration.x = state.acceleration.x; pData->acceleration.x = state.acceleration.x * 0.098;
pData->acceleration.y = state.acceleration.y; pData->acceleration.y = state.acceleration.y * 0.098;
pData->acceleration.z = state.acceleration.z; pData->acceleration.z = state.acceleration.z * 0.098;
pData->angularVelocity.x = state.angularVelocity.x; pData->angularVelocity.x = state.angularVelocity.x;
pData->angularVelocity.y = state.angularVelocity.y; pData->angularVelocity.y = state.angularVelocity.y;
pData->angularVelocity.z = state.angularVelocity.z; pData->angularVelocity.z = state.angularVelocity.z;
pData->orientation = {0.0f, 0.0f, 0.0f, 1.0f}; pData->orientation = {0.0f, 0.0f, 0.0f, 1.0f};
if (engine) {
const auto gyro_poll_rate = engine->GetAccelPollRate(); // Only do this on handle 1 for now
if (gyro_poll_rate != 0.0f) { if (engine && handle == 1) {
GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity, auto now = std::chrono::steady_clock::now();
1.0f / gyro_poll_rate, pData->orientation); float deltaTime =
} std::chrono::duration_cast<std::chrono::microseconds>(now - controller->GetLastUpdate())
.count() /
1000000.0f;
controller->SetLastUpdate(now);
Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation();
Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity, deltaTime,
lastOrientation, outputOrientation);
pData->orientation = outputOrientation;
controller->SetLastOrientation(outputOrientation);
} }
pData->touchData.touchNum = pData->touchData.touchNum =
(state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0); (state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0);
// Only do this on handle 1 for now
if (handle == 1) {
if (controller->GetTouchCount() >= 127) {
controller->SetTouchCount(0);
}
if (controller->GetSecondaryTouchCount() >= 127) {
controller->SetSecondaryTouchCount(0);
}
if (pData->touchData.touchNum == 1 && controller->GetPreviousTouchNum() == 0) {
controller->SetTouchCount(controller->GetTouchCount() + 1);
controller->SetSecondaryTouchCount(controller->GetTouchCount());
} else if (pData->touchData.touchNum == 2 && controller->GetPreviousTouchNum() == 1) {
controller->SetSecondaryTouchCount(controller->GetSecondaryTouchCount() + 1);
} else if (pData->touchData.touchNum == 0 && controller->GetPreviousTouchNum() > 0) {
if (controller->GetTouchCount() < controller->GetSecondaryTouchCount()) {
controller->SetTouchCount(controller->GetSecondaryTouchCount());
} else {
if (controller->WasSecondaryTouchReset()) {
controller->SetTouchCount(controller->GetSecondaryTouchCount());
controller->UnsetSecondaryTouchResetBool();
}
}
}
controller->SetPreviousTouchNum(pData->touchData.touchNum);
if (pData->touchData.touchNum == 1) {
state.touchpad[0].ID = controller->GetTouchCount();
state.touchpad[1].ID = 0;
} else if (pData->touchData.touchNum == 2) {
state.touchpad[0].ID = controller->GetTouchCount();
state.touchpad[1].ID = controller->GetSecondaryTouchCount();
}
} else {
state.touchpad[0].ID = 1;
state.touchpad[1].ID = 2;
}
pData->touchData.touch[0].x = state.touchpad[0].x; pData->touchData.touch[0].x = state.touchpad[0].x;
pData->touchData.touch[0].y = state.touchpad[0].y; pData->touchData.touch[0].y = state.touchpad[0].y;
pData->touchData.touch[0].id = 1; pData->touchData.touch[0].id = state.touchpad[0].ID;
pData->touchData.touch[1].x = state.touchpad[1].x; pData->touchData.touch[1].x = state.touchpad[1].x;
pData->touchData.touch[1].y = state.touchpad[1].y; pData->touchData.touch[1].y = state.touchpad[1].y;
pData->touchData.touch[1].id = 2; pData->touchData.touch[1].id = state.touchpad[1].ID;
pData->timestamp = state.time; pData->timestamp = state.time;
pData->connected = true; // isConnected; //TODO fix me proper pData->connected = true; // isConnected; //TODO fix me proper
pData->connectedCount = 1; // connectedCount; pData->connectedCount = 1; // connectedCount;

View File

@ -22,25 +22,25 @@ static Core::FileSys::MntPoints* g_mnt = Common::Singleton<Core::FileSys::MntPoi
namespace fs = std::filesystem; namespace fs = std::filesystem;
// clang-format off // clang-format off
static const std::unordered_map<std::string, std::string> default_title = { static const std::unordered_map<int, std::string> default_title = {
{"ja_JP", "セーブデータ"}, {0/*"ja_JP"*/, "セーブデータ"},
{"en_US", "Saved Data"}, {1/*"en_US"*/, "Saved Data"},
{"fr_FR", "Données sauvegardées"}, {2/*"fr_FR"*/, "Données sauvegardées"},
{"es_ES", "Datos guardados"}, {3/*"es_ES"*/, "Datos guardados"},
{"de_DE", "Gespeicherte Daten"}, {4/*"de_DE"*/, "Gespeicherte Daten"},
{"it_IT", "Dati salvati"}, {5/*"it_IT"*/, "Dati salvati"},
{"nl_NL", "Opgeslagen data"}, {6/*"nl_NL"*/, "Opgeslagen data"},
{"pt_PT", "Dados guardados"}, {7/*"pt_PT"*/, "Dados guardados"},
{"ru_RU", "Сохраненные данные"}, {8/*"ru_RU"*/, "Сохраненные данные"},
{"ko_KR", "저장 데이터"}, {9/*"ko_KR"*/, "저장 데이터"},
{"zh_CN", "保存数据"}, {10/*"zh_CN"*/, "保存数据"},
{"fi_FI", "Tallennetut tiedot"}, {12/*"fi_FI"*/, "Tallennetut tiedot"},
{"sv_SE", "Sparade data"}, {13/*"sv_SE"*/, "Sparade data"},
{"da_DK", "Gemte data"}, {14/*"da_DK"*/, "Gemte data"},
{"no_NO", "Lagrede data"}, {15/*"no_NO"*/, "Lagrede data"},
{"pl_PL", "Zapisane dane"}, {16/*"pl_PL"*/, "Zapisane dane"},
{"pt_BR", "Dados salvos"}, {17/*"pt_BR"*/, "Dados salvos"},
{"tr_TR", "Kayıtlı Veriler"}, {19/*"tr_TR"*/, "Kayıtlı Veriler"},
}; };
// clang-format on // clang-format on
@ -71,9 +71,9 @@ fs::path SaveInstance::GetParamSFOPath(const fs::path& dir_path) {
void SaveInstance::SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name, void SaveInstance::SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name,
std::string game_serial) { std::string game_serial) {
std::string locale = Config::getEmulatorLanguage(); int locale = Config::GetLanguage();
if (!default_title.contains(locale)) { if (!default_title.contains(locale)) {
locale = "en_US"; locale = 1; // default to en_US if not found
} }
#define P(type, key, ...) param_sfo.Add##type(std::string{key}, __VA_ARGS__) #define P(type, key, ...) param_sfo.Add##type(std::string{key}, __VA_ARGS__)

View File

@ -140,7 +140,7 @@ s32 PS4_SYSV_ABI sceVideodec2Flush(OrbisVideodec2Decoder decoder,
return ORBIS_VIDEODEC2_ERROR_ARGUMENT_POINTER; return ORBIS_VIDEODEC2_ERROR_ARGUMENT_POINTER;
} }
if (frameBuffer->thisSize != sizeof(OrbisVideodec2FrameBuffer) || if (frameBuffer->thisSize != sizeof(OrbisVideodec2FrameBuffer) ||
outputInfo->thisSize != sizeof(OrbisVideodec2OutputInfo)) { (outputInfo->thisSize | 8) != sizeof(OrbisVideodec2OutputInfo)) {
LOG_ERROR(Lib_Vdec2, "Invalid struct size"); LOG_ERROR(Lib_Vdec2, "Invalid struct size");
return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE;
} }
@ -167,23 +167,44 @@ s32 PS4_SYSV_ABI sceVideodec2GetPictureInfo(const OrbisVideodec2OutputInfo* outp
LOG_ERROR(Lib_Vdec2, "Invalid arguments"); LOG_ERROR(Lib_Vdec2, "Invalid arguments");
return ORBIS_VIDEODEC2_ERROR_ARGUMENT_POINTER; return ORBIS_VIDEODEC2_ERROR_ARGUMENT_POINTER;
} }
if (outputInfo->thisSize != sizeof(OrbisVideodec2OutputInfo)) { if ((outputInfo->thisSize | 8) != sizeof(OrbisVideodec2OutputInfo)) {
LOG_ERROR(Lib_Vdec2, "Invalid struct size"); LOG_ERROR(Lib_Vdec2, "Invalid struct size");
return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE;
} }
if (outputInfo->pictureCount == 0 || gPictureInfos.empty()) { if (outputInfo->pictureCount == 0) {
LOG_ERROR(Lib_Vdec2, "No picture info available"); LOG_ERROR(Lib_Vdec2, "No picture info available");
return ORBIS_OK; return ORBIS_OK;
} }
if (p1stPictureInfoOut) { // If the game uses the older Videodec2 structs, we need to accomodate that.
OrbisVideodec2AvcPictureInfo* picInfo = if (outputInfo->thisSize != sizeof(OrbisVideodec2OutputInfo)) {
static_cast<OrbisVideodec2AvcPictureInfo*>(p1stPictureInfoOut); if (gLegacyPictureInfos.empty()) {
if (picInfo->thisSize != sizeof(OrbisVideodec2AvcPictureInfo)) { LOG_ERROR(Lib_Vdec2, "No picture info available");
LOG_ERROR(Lib_Vdec2, "Invalid struct size"); return ORBIS_OK;
return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; }
if (p1stPictureInfoOut) {
OrbisVideodec2LegacyAvcPictureInfo* picInfo =
static_cast<OrbisVideodec2LegacyAvcPictureInfo*>(p1stPictureInfoOut);
if (picInfo->thisSize != sizeof(OrbisVideodec2LegacyAvcPictureInfo)) {
LOG_ERROR(Lib_Vdec2, "Invalid struct size");
return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE;
}
*picInfo = gLegacyPictureInfos.back();
}
} else {
if (gPictureInfos.empty()) {
LOG_ERROR(Lib_Vdec2, "No picture info available");
return ORBIS_OK;
}
if (p1stPictureInfoOut) {
OrbisVideodec2AvcPictureInfo* picInfo =
static_cast<OrbisVideodec2AvcPictureInfo*>(p1stPictureInfoOut);
if (picInfo->thisSize != sizeof(OrbisVideodec2AvcPictureInfo)) {
LOG_ERROR(Lib_Vdec2, "Invalid struct size");
return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE;
}
*picInfo = gPictureInfos.back();
} }
*picInfo = gPictureInfos.back();
} }
if (outputInfo->pictureCount > 1) { if (outputInfo->pictureCount > 1) {

View File

@ -73,8 +73,10 @@ struct OrbisVideodec2OutputInfo {
u32 frameHeight; u32 frameHeight;
void* frameBuffer; void* frameBuffer;
u64 frameBufferSize; u64 frameBufferSize;
u32 frameFormat;
u32 framePitchInBytes;
}; };
static_assert(sizeof(OrbisVideodec2OutputInfo) == 0x30); static_assert(sizeof(OrbisVideodec2OutputInfo) == 0x38);
struct OrbisVideodec2FrameBuffer { struct OrbisVideodec2FrameBuffer {
u64 thisSize; u64 thisSize;

View File

@ -55,6 +55,76 @@ struct OrbisVideodec2AvcPictureInfo {
u8 pic_struct; u8 pic_struct;
u8 field_pic_flag; u8 field_pic_flag;
u8 bottom_field_flag; u8 bottom_field_flag;
u8 sequenceParameterSetPresentFlag;
u8 pictureParameterSetPresentFlag;
u8 auDelimiterPresentFlag;
u8 endOfSequencePresentFlag;
u8 endOfStreamPresentFlag;
u8 fillerDataPresentFlag;
u8 pictureTimingSeiPresentFlag;
u8 bufferingPeriodSeiPresentFlag;
u8 constraint_set0_flag;
u8 constraint_set1_flag;
u8 constraint_set2_flag;
u8 constraint_set3_flag;
u8 constraint_set4_flag;
u8 constraint_set5_flag;
}; };
static_assert(sizeof(OrbisVideodec2AvcPictureInfo) == 0x78);
// An older version of the OrbisVideodec2AvcPictureInfo struct
// Keeping this is needed for compatiblity with older games.
struct OrbisVideodec2LegacyAvcPictureInfo {
u64 thisSize;
bool isValid;
u64 ptsData;
u64 dtsData;
u64 attachedData;
u8 idrPictureflag;
u8 profile_idc;
u8 level_idc;
u32 pic_width_in_mbs_minus1;
u32 pic_height_in_map_units_minus1;
u8 frame_mbs_only_flag;
u8 frame_cropping_flag;
u32 frameCropLeftOffset;
u32 frameCropRightOffset;
u32 frameCropTopOffset;
u32 frameCropBottomOffset;
u8 aspect_ratio_info_present_flag;
u8 aspect_ratio_idc;
u16 sar_width;
u16 sar_height;
u8 video_signal_type_present_flag;
u8 video_format;
u8 video_full_range_flag;
u8 colour_description_present_flag;
u8 colour_primaries;
u8 transfer_characteristics;
u8 matrix_coefficients;
u8 timing_info_present_flag;
u32 num_units_in_tick;
u32 time_scale;
u8 fixed_frame_rate_flag;
u8 bitstream_restriction_flag;
u8 max_dec_frame_buffering;
u8 pic_struct_present_flag;
u8 pic_struct;
u8 field_pic_flag;
u8 bottom_field_flag;
};
static_assert(sizeof(OrbisVideodec2LegacyAvcPictureInfo) == 0x68);
} // namespace Libraries::Vdec2 } // namespace Libraries::Vdec2

View File

@ -12,6 +12,7 @@
namespace Libraries::Vdec2 { namespace Libraries::Vdec2 {
std::vector<OrbisVideodec2AvcPictureInfo> gPictureInfos; std::vector<OrbisVideodec2AvcPictureInfo> gPictureInfos;
std::vector<OrbisVideodec2LegacyAvcPictureInfo> gLegacyPictureInfos;
static inline void CopyNV12Data(u8* dst, const AVFrame& src) { static inline void CopyNV12Data(u8* dst, const AVFrame& src) {
std::memcpy(dst, src.data[0], src.width * src.height); std::memcpy(dst, src.data[0], src.width * src.height);
@ -44,11 +45,15 @@ s32 VdecDecoder::Decode(const OrbisVideodec2InputData& inputData,
OrbisVideodec2FrameBuffer& frameBuffer, OrbisVideodec2FrameBuffer& frameBuffer,
OrbisVideodec2OutputInfo& outputInfo) { OrbisVideodec2OutputInfo& outputInfo) {
frameBuffer.isAccepted = false; frameBuffer.isAccepted = false;
outputInfo.thisSize = sizeof(OrbisVideodec2OutputInfo);
outputInfo.isValid = false; outputInfo.isValid = false;
outputInfo.isErrorFrame = true; outputInfo.isErrorFrame = true;
outputInfo.pictureCount = 0; outputInfo.pictureCount = 0;
// Only set frameFormat if the game uses the newer struct version.
if (outputInfo.thisSize == sizeof(OrbisVideodec2OutputInfo)) {
outputInfo.frameFormat = 0;
}
if (!inputData.auData) { if (!inputData.auData) {
return ORBIS_VIDEODEC2_ERROR_ACCESS_UNIT_POINTER; return ORBIS_VIDEODEC2_ERROR_ACCESS_UNIT_POINTER;
} }
@ -113,22 +118,46 @@ s32 VdecDecoder::Decode(const OrbisVideodec2InputData& inputData,
outputInfo.isErrorFrame = false; outputInfo.isErrorFrame = false;
outputInfo.pictureCount = 1; // TODO: 2 pictures for interlaced video outputInfo.pictureCount = 1; // TODO: 2 pictures for interlaced video
if (outputInfo.isValid) { // For proper compatibility with older games, check the inputted OutputInfo struct size.
OrbisVideodec2AvcPictureInfo pictureInfo = {}; if (outputInfo.thisSize == sizeof(OrbisVideodec2OutputInfo)) {
// framePitchInBytes only exists in the newer struct.
outputInfo.framePitchInBytes = frame->linesize[0];
if (outputInfo.isValid) {
OrbisVideodec2AvcPictureInfo pictureInfo = {};
pictureInfo.thisSize = sizeof(OrbisVideodec2AvcPictureInfo); pictureInfo.thisSize = sizeof(OrbisVideodec2AvcPictureInfo);
pictureInfo.isValid = true; pictureInfo.isValid = true;
pictureInfo.ptsData = inputData.ptsData; pictureInfo.ptsData = inputData.ptsData;
pictureInfo.dtsData = inputData.dtsData; pictureInfo.dtsData = inputData.dtsData;
pictureInfo.attachedData = inputData.attachedData; pictureInfo.attachedData = inputData.attachedData;
pictureInfo.frameCropLeftOffset = frame->crop_left; pictureInfo.frameCropLeftOffset = frame->crop_left;
pictureInfo.frameCropRightOffset = frame->crop_right; pictureInfo.frameCropRightOffset = frame->crop_right;
pictureInfo.frameCropTopOffset = frame->crop_top; pictureInfo.frameCropTopOffset = frame->crop_top;
pictureInfo.frameCropBottomOffset = frame->crop_bottom; pictureInfo.frameCropBottomOffset = frame->crop_bottom;
gPictureInfos.push_back(pictureInfo); gPictureInfos.push_back(pictureInfo);
}
} else {
if (outputInfo.isValid) {
// If the game uses the older struct versions, we need to use it too.
OrbisVideodec2LegacyAvcPictureInfo pictureInfo = {};
pictureInfo.thisSize = sizeof(OrbisVideodec2LegacyAvcPictureInfo);
pictureInfo.isValid = true;
pictureInfo.ptsData = inputData.ptsData;
pictureInfo.dtsData = inputData.dtsData;
pictureInfo.attachedData = inputData.attachedData;
pictureInfo.frameCropLeftOffset = frame->crop_left;
pictureInfo.frameCropRightOffset = frame->crop_right;
pictureInfo.frameCropTopOffset = frame->crop_top;
pictureInfo.frameCropBottomOffset = frame->crop_bottom;
gLegacyPictureInfos.push_back(pictureInfo);
}
} }
} }
@ -140,11 +169,15 @@ s32 VdecDecoder::Decode(const OrbisVideodec2InputData& inputData,
s32 VdecDecoder::Flush(OrbisVideodec2FrameBuffer& frameBuffer, s32 VdecDecoder::Flush(OrbisVideodec2FrameBuffer& frameBuffer,
OrbisVideodec2OutputInfo& outputInfo) { OrbisVideodec2OutputInfo& outputInfo) {
frameBuffer.isAccepted = false; frameBuffer.isAccepted = false;
outputInfo.thisSize = sizeof(OrbisVideodec2OutputInfo);
outputInfo.isValid = false; outputInfo.isValid = false;
outputInfo.isErrorFrame = true; outputInfo.isErrorFrame = true;
outputInfo.pictureCount = 0; outputInfo.pictureCount = 0;
// Only set frameFormat if the game uses the newer struct version.
if (outputInfo.thisSize == sizeof(OrbisVideodec2OutputInfo)) {
outputInfo.frameFormat = 0;
}
AVFrame* frame = av_frame_alloc(); AVFrame* frame = av_frame_alloc();
if (!frame) { if (!frame) {
LOG_ERROR(Lib_Vdec2, "Failed to allocate frame"); LOG_ERROR(Lib_Vdec2, "Failed to allocate frame");
@ -182,6 +215,11 @@ s32 VdecDecoder::Flush(OrbisVideodec2FrameBuffer& frameBuffer,
outputInfo.isErrorFrame = false; outputInfo.isErrorFrame = false;
outputInfo.pictureCount = 1; // TODO: 2 pictures for interlaced video outputInfo.pictureCount = 1; // TODO: 2 pictures for interlaced video
// Only set framePitchInBytes if the game uses the newer struct version.
if (outputInfo.thisSize == sizeof(OrbisVideodec2OutputInfo)) {
outputInfo.framePitchInBytes = frame->linesize[0];
}
// FIXME: Should we add picture info here too? // FIXME: Should we add picture info here too?
} }

View File

@ -16,6 +16,7 @@ extern "C" {
namespace Libraries::Vdec2 { namespace Libraries::Vdec2 {
extern std::vector<OrbisVideodec2AvcPictureInfo> gPictureInfos; extern std::vector<OrbisVideodec2AvcPictureInfo> gPictureInfos;
extern std::vector<OrbisVideodec2LegacyAvcPictureInfo> gLegacyPictureInfos;
class VdecDecoder { class VdecDecoder {
public: public:

View File

@ -282,7 +282,12 @@ s32 PS4_SYSV_ABI sceVideoOutGetVblankStatus(int handle, SceVideoOutVblankStatus*
s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutionStatus* status) { s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutionStatus* status) {
LOG_INFO(Lib_VideoOut, "called"); LOG_INFO(Lib_VideoOut, "called");
*status = driver->GetPort(handle)->resolution; auto* port = driver->GetPort(handle);
if (!port || !port->is_open) {
return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE;
}
*status = port->resolution;
return ORBIS_OK; return ORBIS_OK;
} }
@ -440,7 +445,8 @@ s32 PS4_SYSV_ABI sceVideoOutConfigureOutputMode_(s32 handle, u32 reserved, const
} }
void RegisterLib(Core::Loader::SymbolsResolver* sym) { void RegisterLib(Core::Loader::SymbolsResolver* sym) {
driver = std::make_unique<VideoOutDriver>(Config::getScreenWidth(), Config::getScreenHeight()); driver = std::make_unique<VideoOutDriver>(Config::getInternalScreenWidth(),
Config::getInternalScreenHeight());
LIB_FUNCTION("SbU3dwp80lQ", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, LIB_FUNCTION("SbU3dwp80lQ", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
sceVideoOutGetFlipStatus); sceVideoOutGetFlipStatus);

View File

@ -0,0 +1,203 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/voice/voice.h"
namespace Libraries::Voice {
s32 PS4_SYSV_ABI sceVoiceConnectIPortToOPort() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceCreatePort() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceDeletePort() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceDisconnectIPortFromOPort() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceEnd() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceGetBitRate(u32 port_id, u32* bitrate) {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
*bitrate = 48000;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceGetMuteFlag() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceGetPortAttr() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceGetPortInfo(u32 port_id, OrbisVoicePortInfo* info) {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
info->port_type = 0;
info->state = 3;
info->byte_count = 0;
info->frame_size = 1;
info->edge_count = 0;
info->reserved = 0;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceGetResourceInfo() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceGetVolume() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceInit() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceInitHQ() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoicePausePort() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoicePausePortAll() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceReadFromOPort() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceResetPort() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceResumePort() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceResumePortAll() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceSetBitRate() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceSetMuteFlag() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceSetMuteFlagAll() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceSetThreadsParams() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceSetVolume() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceStart() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceStop() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceUpdatePort() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceVADAdjustment() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceVADSetVersion() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceVoiceWriteToIPort() {
LOG_ERROR(Lib_Voice, "(STUBBED) called");
return ORBIS_OK;
}
void RegisterlibSceVoice(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("oV9GAdJ23Gw", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceConnectIPortToOPort);
LIB_FUNCTION("nXpje5yNpaE", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceCreatePort);
LIB_FUNCTION("b7kJI+nx2hg", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceDeletePort);
LIB_FUNCTION("ajVj3QG2um4", "libSceVoice", 1, "libSceVoice", 0, 0,
sceVoiceDisconnectIPortFromOPort);
LIB_FUNCTION("Oo0S5PH7FIQ", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceEnd);
LIB_FUNCTION("cJLufzou6bc", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceGetBitRate);
LIB_FUNCTION("Pc4z1QjForU", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceGetMuteFlag);
LIB_FUNCTION("elcxZTEfHZM", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceGetPortAttr);
LIB_FUNCTION("CrLqDwWLoXM", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceGetPortInfo);
LIB_FUNCTION("Z6QV6j7igvE", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceGetResourceInfo);
LIB_FUNCTION("jjkCjneOYSs", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceGetVolume);
LIB_FUNCTION("9TrhuGzberQ", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceInit);
LIB_FUNCTION("IPHvnM5+g04", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceInitHQ);
LIB_FUNCTION("x0slGBQW+wY", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoicePausePort);
LIB_FUNCTION("Dinob0yMRl8", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoicePausePortAll);
LIB_FUNCTION("cQ6DGsQEjV4", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceReadFromOPort);
LIB_FUNCTION("udAxvCePkUs", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceResetPort);
LIB_FUNCTION("gAgN+HkiEzY", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceResumePort);
LIB_FUNCTION("jbkJFmOZ9U0", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceResumePortAll);
LIB_FUNCTION("TexwmOHQsDg", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceSetBitRate);
LIB_FUNCTION("gwUynkEgNFY", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceSetMuteFlag);
LIB_FUNCTION("oUha0S-Ij9Q", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceSetMuteFlagAll);
LIB_FUNCTION("clyKUyi3RYU", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceSetThreadsParams);
LIB_FUNCTION("QBFoAIjJoXQ", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceSetVolume);
LIB_FUNCTION("54phPH2LZls", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceStart);
LIB_FUNCTION("Ao2YNSA7-Qo", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceStop);
LIB_FUNCTION("jSZNP7xJrcw", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceUpdatePort);
LIB_FUNCTION("hg9T73LlRiU", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceVADAdjustment);
LIB_FUNCTION("wFeAxEeEi-8", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceVADSetVersion);
LIB_FUNCTION("YeJl6yDlhW0", "libSceVoice", 1, "libSceVoice", 0, 0, sceVoiceWriteToIPort);
};
} // namespace Libraries::Voice

View File

@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Voice {
struct OrbisVoicePortInfo {
s32 port_type;
s32 state;
u32* edge;
u32 byte_count;
u32 frame_size;
u16 edge_count;
u16 reserved;
};
s32 PS4_SYSV_ABI sceVoiceConnectIPortToOPort();
s32 PS4_SYSV_ABI sceVoiceCreatePort();
s32 PS4_SYSV_ABI sceVoiceDeletePort();
s32 PS4_SYSV_ABI sceVoiceDisconnectIPortFromOPort();
s32 PS4_SYSV_ABI sceVoiceEnd();
s32 PS4_SYSV_ABI sceVoiceGetBitRate(u32 port_id, u32* bitrate);
s32 PS4_SYSV_ABI sceVoiceGetMuteFlag();
s32 PS4_SYSV_ABI sceVoiceGetPortAttr();
s32 PS4_SYSV_ABI sceVoiceGetPortInfo(u32 port_id, OrbisVoicePortInfo* info);
s32 PS4_SYSV_ABI sceVoiceGetResourceInfo();
s32 PS4_SYSV_ABI sceVoiceGetVolume();
s32 PS4_SYSV_ABI sceVoiceInit();
s32 PS4_SYSV_ABI sceVoiceInitHQ();
s32 PS4_SYSV_ABI sceVoicePausePort();
s32 PS4_SYSV_ABI sceVoicePausePortAll();
s32 PS4_SYSV_ABI sceVoiceReadFromOPort();
s32 PS4_SYSV_ABI sceVoiceResetPort();
s32 PS4_SYSV_ABI sceVoiceResumePort();
s32 PS4_SYSV_ABI sceVoiceResumePortAll();
s32 PS4_SYSV_ABI sceVoiceSetBitRate();
s32 PS4_SYSV_ABI sceVoiceSetMuteFlag();
s32 PS4_SYSV_ABI sceVoiceSetMuteFlagAll();
s32 PS4_SYSV_ABI sceVoiceSetThreadsParams();
s32 PS4_SYSV_ABI sceVoiceSetVolume();
s32 PS4_SYSV_ABI sceVoiceStart();
s32 PS4_SYSV_ABI sceVoiceStop();
s32 PS4_SYSV_ABI sceVoiceUpdatePort();
s32 PS4_SYSV_ABI sceVoiceVADAdjustment();
s32 PS4_SYSV_ABI sceVoiceVADSetVersion();
s32 PS4_SYSV_ABI sceVoiceWriteToIPort();
void RegisterlibSceVoice(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Voice

View File

@ -51,7 +51,7 @@ void ZlibTaskThread(const std::stop_token& stop) {
if (!task_queue_cv.wait(lock, stop, [&] { return !task_queue.empty(); })) { if (!task_queue_cv.wait(lock, stop, [&] { return !task_queue.empty(); })) {
break; break;
} }
task = task_queue.back(); task = task_queue.front();
task_queue.pop(); task_queue.pop();
} }
@ -136,7 +136,7 @@ s32 PS4_SYSV_ABI sceZlibWaitForDone(u64* request_id, const u32* timeout) {
} else { } else {
done_queue_cv.wait(lock, pred); done_queue_cv.wait(lock, pred);
} }
*request_id = done_queue.back(); *request_id = done_queue.front();
done_queue.pop(); done_queue.pop();
} }
return ORBIS_OK; return ORBIS_OK;

View File

@ -117,6 +117,18 @@ void Linker::Execute(const std::vector<std::string> args) {
Common::SetCurrentThreadName("GAME_MainThread"); Common::SetCurrentThreadName("GAME_MainThread");
LoadSharedLibraries(); LoadSharedLibraries();
// Simulate libSceGnmDriver initialization, which maps a chunk of direct memory.
// Some games fail without accurately emulating this behavior.
s64 phys_addr{};
s32 result = Libraries::Kernel::sceKernelAllocateDirectMemory(
0, Libraries::Kernel::sceKernelGetDirectMemorySize(), 0x10000, 0x10000, 3, &phys_addr);
if (result == 0) {
void* addr{reinterpret_cast<void*>(0xfe0000000)};
result = Libraries::Kernel::sceKernelMapNamedDirectMemory(
&addr, 0x10000, 0x13, 0, phys_addr, 0x10000, "SceGnmDriver");
}
ASSERT_MSG(result == 0, "Unable to emulate libSceGnmDriver initialization");
// Start main module. // Start main module.
EntryParams params{}; EntryParams params{};
params.argc = 1; params.argc = 1;
@ -320,21 +332,22 @@ bool Linker::Resolve(const std::string& name, Loader::SymbolType sym_type, Modul
sr.type = sym_type; sr.type = sym_type;
const auto* record = m_hle_symbols.FindSymbol(sr); const auto* record = m_hle_symbols.FindSymbol(sr);
if (!record) {
// Check if it an export function
const auto* p = FindExportedModule(*module, *library);
if (p && p->export_sym.GetSize() > 0) {
record = p->export_sym.FindSymbol(sr);
}
}
if (record) { if (record) {
*return_info = *record; *return_info = *record;
Core::Devtools::Widget::ModuleList::AddModule(sr.library); Core::Devtools::Widget::ModuleList::AddModule(sr.library);
return true; return true;
} }
// Check if it an export function
const auto* p = FindExportedModule(*module, *library);
if (p && p->export_sym.GetSize() > 0) {
record = p->export_sym.FindSymbol(sr);
if (record) {
*return_info = *record;
return true;
}
}
const auto aeronid = AeroLib::FindByNid(sr.name.c_str()); const auto aeronid = AeroLib::FindByNid(sr.name.c_str());
if (aeronid) { if (aeronid) {
return_info->name = aeronid->name; return_info->name = aeronid->name;

View File

@ -5,6 +5,7 @@
#include "common/assert.h" #include "common/assert.h"
#include "common/config.h" #include "common/config.h"
#include "common/debug.h" #include "common/debug.h"
#include "core/file_sys/fs.h"
#include "core/libraries/kernel/memory.h" #include "core/libraries/kernel/memory.h"
#include "core/libraries/kernel/orbis_error.h" #include "core/libraries/kernel/orbis_error.h"
#include "core/libraries/kernel/process.h" #include "core/libraries/kernel/process.h"
@ -16,11 +17,11 @@ namespace Core {
MemoryManager::MemoryManager() { MemoryManager::MemoryManager() {
// Insert a virtual memory area that covers the entire area we manage. // Insert a virtual memory area that covers the entire area we manage.
const VAddr system_managed_base = impl.SystemManagedVirtualBase(); const VAddr system_managed_base = impl.SystemManagedVirtualBase();
const size_t system_managed_size = impl.SystemManagedVirtualSize(); const u64 system_managed_size = impl.SystemManagedVirtualSize();
const VAddr system_reserved_base = impl.SystemReservedVirtualBase(); const VAddr system_reserved_base = impl.SystemReservedVirtualBase();
const size_t system_reserved_size = impl.SystemReservedVirtualSize(); const u64 system_reserved_size = impl.SystemReservedVirtualSize();
const VAddr user_base = impl.UserVirtualBase(); const VAddr user_base = impl.UserVirtualBase();
const size_t user_size = impl.UserVirtualSize(); const u64 user_size = impl.UserVirtualSize();
vma_map.emplace(system_managed_base, vma_map.emplace(system_managed_base,
VirtualMemoryArea{system_managed_base, system_managed_size}); VirtualMemoryArea{system_managed_base, system_managed_size});
vma_map.emplace(system_reserved_base, vma_map.emplace(system_reserved_base,
@ -67,7 +68,7 @@ void MemoryManager::SetupMemoryRegions(u64 flexible_size, bool use_extended_mem1
} }
u64 MemoryManager::ClampRangeSize(VAddr virtual_addr, u64 size) { u64 MemoryManager::ClampRangeSize(VAddr virtual_addr, u64 size) {
static constexpr u64 MinSizeToClamp = 512_MB; static constexpr u64 MinSizeToClamp = 2_MB;
// Dont bother with clamping if the size is small so we dont pay a map lookup on every buffer. // Dont bother with clamping if the size is small so we dont pay a map lookup on every buffer.
if (size < MinSizeToClamp) { if (size < MinSizeToClamp) {
return size; return size;
@ -94,6 +95,46 @@ u64 MemoryManager::ClampRangeSize(VAddr virtual_addr, u64 size) {
return clamped_size; return clamped_size;
} }
void MemoryManager::SetPrtArea(u32 id, VAddr address, u64 size) {
PrtArea& area = prt_areas[id];
if (area.mapped) {
rasterizer->UnmapMemory(area.start, area.end - area.start);
}
area.start = address;
area.end = address + size;
area.mapped = true;
// Pretend the entire PRT area is mapped to avoid GPU tracking errors.
// The caches will use CopySparseMemory to fetch data which avoids unmapped areas.
rasterizer->MapMemory(address, size);
}
void MemoryManager::CopySparseMemory(VAddr virtual_addr, u8* dest, u64 size) {
const bool is_sparse = std::ranges::any_of(
prt_areas, [&](const PrtArea& area) { return area.Overlaps(virtual_addr, size); });
if (!is_sparse) {
std::memcpy(dest, std::bit_cast<const u8*>(virtual_addr), size);
return;
}
auto vma = FindVMA(virtual_addr);
ASSERT_MSG(vma->second.Contains(virtual_addr, 0),
"Attempted to access invalid GPU address {:#x}", virtual_addr);
while (size) {
u64 copy_size = std::min<u64>(vma->second.size - (virtual_addr - vma->first), size);
if (vma->second.IsFree()) {
std::memset(dest, 0, copy_size);
} else {
std::memcpy(dest, std::bit_cast<const u8*>(virtual_addr), copy_size);
}
size -= copy_size;
virtual_addr += copy_size;
dest += copy_size;
++vma;
}
}
bool MemoryManager::TryWriteBacking(void* address, const void* data, u32 num_bytes) { bool MemoryManager::TryWriteBacking(void* address, const void* data, u32 num_bytes) {
const VAddr virtual_addr = std::bit_cast<VAddr>(address); const VAddr virtual_addr = std::bit_cast<VAddr>(address);
const auto& vma = FindVMA(virtual_addr)->second; const auto& vma = FindVMA(virtual_addr)->second;
@ -107,7 +148,7 @@ bool MemoryManager::TryWriteBacking(void* address, const void* data, u32 num_byt
return true; return true;
} }
PAddr MemoryManager::PoolExpand(PAddr search_start, PAddr search_end, size_t size, u64 alignment) { PAddr MemoryManager::PoolExpand(PAddr search_start, PAddr search_end, u64 size, u64 alignment) {
std::scoped_lock lk{mutex}; std::scoped_lock lk{mutex};
alignment = alignment > 0 ? alignment : 64_KB; alignment = alignment > 0 ? alignment : 64_KB;
@ -147,8 +188,8 @@ PAddr MemoryManager::PoolExpand(PAddr search_start, PAddr search_end, size_t siz
return mapping_start; return mapping_start;
} }
PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, u64 alignment, PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, u64 size, u64 alignment,
int memory_type) { s32 memory_type) {
std::scoped_lock lk{mutex}; std::scoped_lock lk{mutex};
alignment = alignment > 0 ? alignment : 16_KB; alignment = alignment > 0 ? alignment : 16_KB;
@ -181,10 +222,11 @@ PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size,
auto& area = CarveDmemArea(mapping_start, size)->second; auto& area = CarveDmemArea(mapping_start, size)->second;
area.memory_type = memory_type; area.memory_type = memory_type;
area.is_free = false; area.is_free = false;
MergeAdjacent(dmem_map, dmem_area);
return mapping_start; return mapping_start;
} }
void MemoryManager::Free(PAddr phys_addr, size_t size) { void MemoryManager::Free(PAddr phys_addr, u64 size) {
std::scoped_lock lk{mutex}; std::scoped_lock lk{mutex};
auto dmem_area = CarveDmemArea(phys_addr, size); auto dmem_area = CarveDmemArea(phys_addr, size);
@ -214,91 +256,7 @@ void MemoryManager::Free(PAddr phys_addr, size_t size) {
MergeAdjacent(dmem_map, dmem_area); MergeAdjacent(dmem_map, dmem_area);
} }
int MemoryManager::PoolReserve(void** out_addr, VAddr virtual_addr, size_t size, s32 MemoryManager::PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot) {
MemoryMapFlags flags, u64 alignment) {
std::scoped_lock lk{mutex};
alignment = alignment > 0 ? alignment : 2_MB;
VAddr min_address = Common::AlignUp(impl.SystemManagedVirtualBase(), alignment);
VAddr mapped_addr = Common::AlignUp(virtual_addr, alignment);
// Fixed mapping means the virtual address must exactly match the provided one.
if (True(flags & MemoryMapFlags::Fixed)) {
// Make sure we're mapping to a valid address
mapped_addr = mapped_addr > min_address ? mapped_addr : min_address;
auto vma = FindVMA(mapped_addr)->second;
size_t remaining_size = vma.base + vma.size - mapped_addr;
// If the VMA is mapped or there's not enough space, unmap the region first.
if (vma.IsMapped() || remaining_size < size) {
UnmapMemoryImpl(mapped_addr, size);
vma = FindVMA(mapped_addr)->second;
}
}
if (False(flags & MemoryMapFlags::Fixed)) {
// When MemoryMapFlags::Fixed is not specified, and mapped_addr is 0,
// search from address 0x200000000 instead.
mapped_addr = mapped_addr == 0 ? 0x200000000 : mapped_addr;
mapped_addr = SearchFree(mapped_addr, size, alignment);
if (mapped_addr == -1) {
// No suitable memory areas to map to
return ORBIS_KERNEL_ERROR_ENOMEM;
}
}
// Add virtual memory area
const auto new_vma_handle = CarveVMA(mapped_addr, size);
auto& new_vma = new_vma_handle->second;
new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce);
new_vma.prot = MemoryProt::NoAccess;
new_vma.name = "anon";
new_vma.type = VMAType::PoolReserved;
*out_addr = std::bit_cast<void*>(mapped_addr);
return ORBIS_OK;
}
int MemoryManager::Reserve(void** out_addr, VAddr virtual_addr, size_t size, MemoryMapFlags flags,
u64 alignment) {
std::scoped_lock lk{mutex};
virtual_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr;
alignment = alignment > 0 ? alignment : 16_KB;
VAddr mapped_addr = alignment > 0 ? Common::AlignUp(virtual_addr, alignment) : virtual_addr;
// Fixed mapping means the virtual address must exactly match the provided one.
if (True(flags & MemoryMapFlags::Fixed)) {
auto vma = FindVMA(mapped_addr)->second;
size_t remaining_size = vma.base + vma.size - mapped_addr;
// If the VMA is mapped or there's not enough space, unmap the region first.
if (vma.IsMapped() || remaining_size < size) {
UnmapMemoryImpl(mapped_addr, size);
vma = FindVMA(mapped_addr)->second;
}
}
// Find the first free area starting with provided virtual address.
if (False(flags & MemoryMapFlags::Fixed)) {
mapped_addr = SearchFree(mapped_addr, size, alignment);
if (mapped_addr == -1) {
// No suitable memory areas to map to
return ORBIS_KERNEL_ERROR_ENOMEM;
}
}
// Add virtual memory area
const auto new_vma_handle = CarveVMA(mapped_addr, size);
auto& new_vma = new_vma_handle->second;
new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce);
new_vma.prot = MemoryProt::NoAccess;
new_vma.name = "anon";
new_vma.type = VMAType::Reserved;
MergeAdjacent(vma_map, new_vma_handle);
*out_addr = std::bit_cast<void*>(mapped_addr);
return ORBIS_OK;
}
int MemoryManager::PoolCommit(VAddr virtual_addr, size_t size, MemoryProt prot) {
std::scoped_lock lk{mutex}; std::scoped_lock lk{mutex};
const u64 alignment = 64_KB; const u64 alignment = 64_KB;
@ -351,7 +309,7 @@ int MemoryManager::PoolCommit(VAddr virtual_addr, size_t size, MemoryProt prot)
return ORBIS_OK; return ORBIS_OK;
} }
int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot, s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, MemoryProt prot,
MemoryMapFlags flags, VMAType type, std::string_view name, MemoryMapFlags flags, VMAType type, std::string_view name,
bool is_exec, PAddr phys_addr, u64 alignment) { bool is_exec, PAddr phys_addr, u64 alignment) {
std::scoped_lock lk{mutex}; std::scoped_lock lk{mutex};
@ -362,21 +320,44 @@ int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, M
return ORBIS_KERNEL_ERROR_ENOMEM; return ORBIS_KERNEL_ERROR_ENOMEM;
} }
// Limit the minumum address to SystemManagedVirtualBase to prevent hardware-specific issues. // Validate the requested physical address range
if (phys_addr != -1) {
u64 validated_size = 0;
do {
auto dmem_area = FindDmemArea(phys_addr + validated_size)->second;
// If any requested dmem area is not allocated, return an error.
if (dmem_area.is_free) {
LOG_ERROR(Kernel_Vmm, "Unable to map {:#x} bytes at physical address {:#x}", size,
phys_addr);
return ORBIS_KERNEL_ERROR_ENOMEM;
}
// Track how much we've validated.
validated_size += dmem_area.size - (phys_addr + validated_size - dmem_area.base);
} while (validated_size < size && phys_addr + validated_size < GetTotalDirectSize());
// If the requested range goes outside the dmem map, return an error.
if (validated_size < size) {
LOG_ERROR(Kernel_Vmm, "Unable to map {:#x} bytes at physical address {:#x}", size,
phys_addr);
return ORBIS_KERNEL_ERROR_ENOMEM;
}
}
// Limit the minimum address to SystemManagedVirtualBase to prevent hardware-specific issues.
VAddr mapped_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr; VAddr mapped_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr;
// Fixed mapping means the virtual address must exactly match the provided one. // Fixed mapping means the virtual address must exactly match the provided one.
if (True(flags & MemoryMapFlags::Fixed)) { // On a PS4, the Fixed flag is ignored if address 0 is provided.
if (True(flags & MemoryMapFlags::Fixed) && virtual_addr != 0) {
auto vma = FindVMA(mapped_addr)->second; auto vma = FindVMA(mapped_addr)->second;
size_t remaining_size = vma.base + vma.size - mapped_addr;
// There's a possible edge case where we're mapping to a partially reserved range. // There's a possible edge case where we're mapping to a partially reserved range.
// To account for this, unmap any reserved areas within this mapping range first. // To account for this, unmap any reserved areas within this mapping range first.
auto unmap_addr = mapped_addr; auto unmap_addr = mapped_addr;
auto unmap_size = size; auto unmap_size = size;
// If flag NoOverwrite is provided, don't overwrite mapped VMAs. // If flag NoOverwrite is provided, don't overwrite mapped VMAs.
// When it isn't provided, VMAs can be overwritten regardless of if they're mapped. // When it isn't provided, VMAs can be overwritten regardless of if they're mapped.
while ((False(flags & MemoryMapFlags::NoOverwrite) || !vma.IsMapped()) && while ((False(flags & MemoryMapFlags::NoOverwrite) || !vma.IsMapped()) &&
unmap_addr < mapped_addr + size && remaining_size < size) { unmap_addr < mapped_addr + size) {
auto unmapped = UnmapBytesFromEntry(unmap_addr, vma, unmap_size); auto unmapped = UnmapBytesFromEntry(unmap_addr, vma, unmap_size);
unmap_addr += unmapped; unmap_addr += unmapped;
unmap_size -= unmapped; unmap_size -= unmapped;
@ -384,53 +365,68 @@ int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, M
} }
vma = FindVMA(mapped_addr)->second; vma = FindVMA(mapped_addr)->second;
remaining_size = vma.base + vma.size - mapped_addr; auto remaining_size = vma.base + vma.size - mapped_addr;
if (vma.IsMapped() || remaining_size < size) { if (vma.IsMapped() || remaining_size < size) {
LOG_ERROR(Kernel_Vmm, "Unable to map {:#x} bytes at address {:#x}", size, mapped_addr); LOG_ERROR(Kernel_Vmm, "Unable to map {:#x} bytes at address {:#x}", size, mapped_addr);
return ORBIS_KERNEL_ERROR_ENOMEM; return ORBIS_KERNEL_ERROR_ENOMEM;
} }
} } else {
// When MemoryMapFlags::Fixed is not specified, and mapped_addr is 0,
// Find the first free area starting with provided virtual address. // search from address 0x200000000 instead.
if (False(flags & MemoryMapFlags::Fixed)) {
// Provided address needs to be aligned before we can map.
alignment = alignment > 0 ? alignment : 16_KB; alignment = alignment > 0 ? alignment : 16_KB;
mapped_addr = SearchFree(Common::AlignUp(mapped_addr, alignment), size, alignment); mapped_addr = virtual_addr == 0 ? 0x200000000 : mapped_addr;
mapped_addr = SearchFree(mapped_addr, size, alignment);
if (mapped_addr == -1) { if (mapped_addr == -1) {
// No suitable memory areas to map to // No suitable memory areas to map to
return ORBIS_KERNEL_ERROR_ENOMEM; return ORBIS_KERNEL_ERROR_ENOMEM;
} }
} }
// Perform the mapping. // Create a memory area representing this mapping.
*out_addr = impl.Map(mapped_addr, size, alignment, phys_addr, is_exec); const auto new_vma_handle = CarveVMA(mapped_addr, size);
TRACK_ALLOC(*out_addr, size, "VMEM"); auto& new_vma = new_vma_handle->second;
auto& new_vma = CarveVMA(mapped_addr, size)->second; // If type is Flexible, we need to track how much flexible memory is used here.
new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce);
new_vma.prot = prot;
new_vma.name = name;
new_vma.type = type;
new_vma.is_exec = is_exec;
if (type == VMAType::Direct) {
new_vma.phys_base = phys_addr;
}
if (type == VMAType::Flexible) { if (type == VMAType::Flexible) {
flexible_usage += size; flexible_usage += size;
} }
if (IsValidGpuMapping(mapped_addr, size)) { new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce);
rasterizer->MapMemory(mapped_addr, size); new_vma.prot = prot;
new_vma.name = name;
new_vma.type = type;
new_vma.phys_base = phys_addr == -1 ? 0 : phys_addr;
new_vma.is_exec = is_exec;
if (type == VMAType::Reserved) {
// Technically this should be done for direct and flexible mappings too,
// But some Windows-specific limitations make that hard to accomplish.
MergeAdjacent(vma_map, new_vma_handle);
}
if (type == VMAType::Reserved || type == VMAType::PoolReserved) {
// For Reserved/PoolReserved mappings, we don't perform any address space allocations.
// Just set out_addr to mapped_addr instead.
*out_addr = std::bit_cast<void*>(mapped_addr);
} else {
// If this is not a reservation, then map to GPU and address space
if (IsValidGpuMapping(mapped_addr, size)) {
rasterizer->MapMemory(mapped_addr, size);
}
*out_addr = impl.Map(mapped_addr, size, alignment, phys_addr, is_exec);
TRACK_ALLOC(*out_addr, size, "VMEM");
} }
return ORBIS_OK; return ORBIS_OK;
} }
int MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot, s32 MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, u64 size, MemoryProt prot,
MemoryMapFlags flags, uintptr_t fd, size_t offset) { MemoryMapFlags flags, s32 fd, s64 phys_addr) {
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
VAddr mapped_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr; VAddr mapped_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr;
const size_t size_aligned = Common::AlignUp(size, 16_KB); const u64 size_aligned = Common::AlignUp(size, 16_KB);
// Find first free area to map the file. // Find first free area to map the file.
if (False(flags & MemoryMapFlags::Fixed)) { if (False(flags & MemoryMapFlags::Fixed)) {
@ -443,14 +439,25 @@ int MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, size_t size, Mem
if (True(flags & MemoryMapFlags::Fixed)) { if (True(flags & MemoryMapFlags::Fixed)) {
const auto& vma = FindVMA(virtual_addr)->second; const auto& vma = FindVMA(virtual_addr)->second;
const size_t remaining_size = vma.base + vma.size - virtual_addr; const u64 remaining_size = vma.base + vma.size - virtual_addr;
ASSERT_MSG(!vma.IsMapped() && remaining_size >= size, ASSERT_MSG(!vma.IsMapped() && remaining_size >= size,
"Memory region {:#x} to {:#x} isn't free enough to map region {:#x} to {:#x}", "Memory region {:#x} to {:#x} isn't free enough to map region {:#x} to {:#x}",
vma.base, vma.base + vma.size, virtual_addr, virtual_addr + size); vma.base, vma.base + vma.size, virtual_addr, virtual_addr + size);
} }
// Map the file. // Get the file to map
impl.MapFile(mapped_addr, size_aligned, offset, std::bit_cast<u32>(prot), fd); auto file = h->GetFile(fd);
if (file == nullptr) {
return ORBIS_KERNEL_ERROR_EBADF;
}
const auto handle = file->f.GetFileMapping();
impl.MapFile(mapped_addr, size_aligned, phys_addr, std::bit_cast<u32>(prot), handle);
if (prot >= MemoryProt::GpuRead) {
ASSERT_MSG(false, "Files cannot be mapped to GPU memory");
}
// Add virtual memory area // Add virtual memory area
auto& new_vma = CarveVMA(mapped_addr, size_aligned)->second; auto& new_vma = CarveVMA(mapped_addr, size_aligned)->second;
@ -464,7 +471,7 @@ int MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, size_t size, Mem
return ORBIS_OK; return ORBIS_OK;
} }
s32 MemoryManager::PoolDecommit(VAddr virtual_addr, size_t size) { s32 MemoryManager::PoolDecommit(VAddr virtual_addr, u64 size) {
std::scoped_lock lk{mutex}; std::scoped_lock lk{mutex};
const auto it = FindVMA(virtual_addr); const auto it = FindVMA(virtual_addr);
@ -485,14 +492,15 @@ s32 MemoryManager::PoolDecommit(VAddr virtual_addr, size_t size) {
} }
if (type == VMAType::Pooled) { if (type == VMAType::Pooled) {
// We always map PoolCommitted memory to GPU, so unmap when decomitting.
if (IsValidGpuMapping(virtual_addr, size)) {
rasterizer->UnmapMemory(virtual_addr, size);
}
// Track how much pooled memory is decommitted // Track how much pooled memory is decommitted
pool_budget += size; pool_budget += size;
} }
if (IsValidGpuMapping(virtual_addr, size)) {
rasterizer->UnmapMemory(virtual_addr, size);
}
// Mark region as free and attempt to coalesce it with neighbours. // Mark region as free and attempt to coalesce it with neighbours.
const auto new_it = CarveVMA(virtual_addr, size); const auto new_it = CarveVMA(virtual_addr, size);
auto& vma = new_it->second; auto& vma = new_it->second;
@ -513,7 +521,7 @@ s32 MemoryManager::PoolDecommit(VAddr virtual_addr, size_t size) {
return ORBIS_OK; return ORBIS_OK;
} }
s32 MemoryManager::UnmapMemory(VAddr virtual_addr, size_t size) { s32 MemoryManager::UnmapMemory(VAddr virtual_addr, u64 size) {
std::scoped_lock lk{mutex}; std::scoped_lock lk{mutex};
return UnmapMemoryImpl(virtual_addr, size); return UnmapMemoryImpl(virtual_addr, size);
} }
@ -528,18 +536,16 @@ u64 MemoryManager::UnmapBytesFromEntry(VAddr virtual_addr, VirtualMemoryArea vma
const auto adjusted_size = const auto adjusted_size =
vma_base_size - start_in_vma < size ? vma_base_size - start_in_vma : size; vma_base_size - start_in_vma < size ? vma_base_size - start_in_vma : size;
const bool has_backing = type == VMAType::Direct || type == VMAType::File; const bool has_backing = type == VMAType::Direct || type == VMAType::File;
const auto prot = vma_base.prot;
if (type == VMAType::Free) { if (type == VMAType::Free) {
return adjusted_size; return adjusted_size;
} }
if (type == VMAType::Flexible) { if (type == VMAType::Flexible) {
flexible_usage -= adjusted_size; flexible_usage -= adjusted_size;
} }
if (IsValidGpuMapping(virtual_addr, adjusted_size)) {
rasterizer->UnmapMemory(virtual_addr, adjusted_size);
}
// Mark region as free and attempt to coalesce it with neighbours. // Mark region as free and attempt to coalesce it with neighbours.
const auto new_it = CarveVMA(virtual_addr, adjusted_size); const auto new_it = CarveVMA(virtual_addr, adjusted_size);
auto& vma = new_it->second; auto& vma = new_it->second;
@ -552,6 +558,11 @@ u64 MemoryManager::UnmapBytesFromEntry(VAddr virtual_addr, VirtualMemoryArea vma
auto& post_merge_vma = post_merge_it->second; auto& post_merge_vma = post_merge_it->second;
bool readonly_file = post_merge_vma.prot == MemoryProt::CpuRead && type == VMAType::File; bool readonly_file = post_merge_vma.prot == MemoryProt::CpuRead && type == VMAType::File;
if (type != VMAType::Reserved && type != VMAType::PoolReserved) { if (type != VMAType::Reserved && type != VMAType::PoolReserved) {
// If this mapping has GPU access, unmap from GPU.
if (IsValidGpuMapping(virtual_addr, size)) {
rasterizer->UnmapMemory(virtual_addr, size);
}
// Unmap the memory region. // Unmap the memory region.
impl.Unmap(vma_base_addr, vma_base_size, start_in_vma, start_in_vma + adjusted_size, impl.Unmap(vma_base_addr, vma_base_size, start_in_vma, start_in_vma + adjusted_size,
phys_base, is_exec, has_backing, readonly_file); phys_base, is_exec, has_backing, readonly_file);
@ -576,7 +587,7 @@ s32 MemoryManager::UnmapMemoryImpl(VAddr virtual_addr, u64 size) {
return ORBIS_OK; return ORBIS_OK;
} }
int MemoryManager::QueryProtection(VAddr addr, void** start, void** end, u32* prot) { s32 MemoryManager::QueryProtection(VAddr addr, void** start, void** end, u32* prot) {
std::scoped_lock lk{mutex}; std::scoped_lock lk{mutex};
const auto it = FindVMA(addr); const auto it = FindVMA(addr);
@ -598,27 +609,14 @@ int MemoryManager::QueryProtection(VAddr addr, void** start, void** end, u32* pr
return ORBIS_OK; return ORBIS_OK;
} }
s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea vma_base, size_t size, s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea vma_base, u64 size, MemoryProt prot) {
MemoryProt prot) {
const auto start_in_vma = addr - vma_base.base; const auto start_in_vma = addr - vma_base.base;
const auto adjusted_size = const auto adjusted_size =
vma_base.size - start_in_vma < size ? vma_base.size - start_in_vma : size; vma_base.size - start_in_vma < size ? vma_base.size - start_in_vma : size;
if (vma_base.type == VMAType::Free) { if (vma_base.type == VMAType::Free) {
LOG_ERROR(Kernel_Vmm, "Cannot change protection on free memory region"); // On PS4, protecting freed memory does nothing.
return ORBIS_KERNEL_ERROR_EINVAL; return adjusted_size;
}
// Validate protection flags
constexpr static MemoryProt valid_flags = MemoryProt::NoAccess | MemoryProt::CpuRead |
MemoryProt::CpuReadWrite | MemoryProt::GpuRead |
MemoryProt::GpuWrite | MemoryProt::GpuReadWrite;
MemoryProt invalid_flags = prot & ~valid_flags;
if (u32(invalid_flags) != 0 && u32(invalid_flags) != u32(MemoryProt::NoAccess)) {
LOG_ERROR(Kernel_Vmm, "Invalid protection flags: prot = {:#x}, invalid flags = {:#x}",
u32(prot), u32(invalid_flags));
return ORBIS_KERNEL_ERROR_EINVAL;
} }
// Change protection // Change protection
@ -633,6 +631,9 @@ s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea vma_base, size_t s
if (True(prot & MemoryProt::CpuReadWrite)) { if (True(prot & MemoryProt::CpuReadWrite)) {
perms |= Core::MemoryPermission::ReadWrite; perms |= Core::MemoryPermission::ReadWrite;
} }
if (True(prot & MemoryProt::CpuExec)) {
perms |= Core::MemoryPermission::Execute;
}
if (True(prot & MemoryProt::GpuRead)) { if (True(prot & MemoryProt::GpuRead)) {
perms |= Core::MemoryPermission::Read; perms |= Core::MemoryPermission::Read;
} }
@ -648,31 +649,44 @@ s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea vma_base, size_t s
return adjusted_size; return adjusted_size;
} }
s32 MemoryManager::Protect(VAddr addr, size_t size, MemoryProt prot) { s32 MemoryManager::Protect(VAddr addr, u64 size, MemoryProt prot) {
std::scoped_lock lk{mutex}; std::scoped_lock lk{mutex};
s64 protected_bytes = 0;
// Validate protection flags
constexpr static MemoryProt valid_flags =
MemoryProt::NoAccess | MemoryProt::CpuRead | MemoryProt::CpuReadWrite |
MemoryProt::CpuExec | MemoryProt::GpuRead | MemoryProt::GpuWrite | MemoryProt::GpuReadWrite;
MemoryProt invalid_flags = prot & ~valid_flags;
if (invalid_flags != MemoryProt::NoAccess) {
LOG_ERROR(Kernel_Vmm, "Invalid protection flags");
return ORBIS_KERNEL_ERROR_EINVAL;
}
// Align addr and size to the nearest page boundary.
auto aligned_addr = Common::AlignDown(addr, 16_KB); auto aligned_addr = Common::AlignDown(addr, 16_KB);
auto aligned_size = Common::AlignUp(size + addr - aligned_addr, 16_KB); auto aligned_size = Common::AlignUp(size + addr - aligned_addr, 16_KB);
do {
// Protect all VMAs between aligned_addr and aligned_addr + aligned_size.
s64 protected_bytes = 0;
while (protected_bytes < aligned_size) {
auto it = FindVMA(aligned_addr + protected_bytes); auto it = FindVMA(aligned_addr + protected_bytes);
auto& vma_base = it->second; auto& vma_base = it->second;
ASSERT_MSG(vma_base.Contains(addr + protected_bytes, 0), "Address {:#x} is out of bounds", ASSERT_MSG(vma_base.Contains(addr + protected_bytes, 0), "Address {:#x} is out of bounds",
addr + protected_bytes); addr + protected_bytes);
auto result = 0; auto result = ProtectBytes(aligned_addr + protected_bytes, vma_base,
result = ProtectBytes(aligned_addr + protected_bytes, vma_base, aligned_size - protected_bytes, prot);
aligned_size - protected_bytes, prot);
if (result < 0) { if (result < 0) {
// ProtectBytes returned an error, return it // ProtectBytes returned an error, return it
return result; return result;
} }
protected_bytes += result; protected_bytes += result;
} while (protected_bytes < aligned_size); }
return ORBIS_OK; return ORBIS_OK;
} }
int MemoryManager::VirtualQuery(VAddr addr, int flags, s32 MemoryManager::VirtualQuery(VAddr addr, s32 flags,
::Libraries::Kernel::OrbisVirtualQueryInfo* info) { ::Libraries::Kernel::OrbisVirtualQueryInfo* info) {
std::scoped_lock lk{mutex}; std::scoped_lock lk{mutex};
@ -717,7 +731,7 @@ int MemoryManager::VirtualQuery(VAddr addr, int flags,
return ORBIS_OK; return ORBIS_OK;
} }
int MemoryManager::DirectMemoryQuery(PAddr addr, bool find_next, s32 MemoryManager::DirectMemoryQuery(PAddr addr, bool find_next,
::Libraries::Kernel::OrbisQueryInfo* out_info) { ::Libraries::Kernel::OrbisQueryInfo* out_info) {
std::scoped_lock lk{mutex}; std::scoped_lock lk{mutex};
@ -738,13 +752,13 @@ int MemoryManager::DirectMemoryQuery(PAddr addr, bool find_next,
return ORBIS_OK; return ORBIS_OK;
} }
int MemoryManager::DirectQueryAvailable(PAddr search_start, PAddr search_end, size_t alignment, s32 MemoryManager::DirectQueryAvailable(PAddr search_start, PAddr search_end, u64 alignment,
PAddr* phys_addr_out, size_t* size_out) { PAddr* phys_addr_out, u64* size_out) {
std::scoped_lock lk{mutex}; std::scoped_lock lk{mutex};
auto dmem_area = FindDmemArea(search_start); auto dmem_area = FindDmemArea(search_start);
PAddr paddr{}; PAddr paddr{};
size_t max_size{}; u64 max_size{};
while (dmem_area != dmem_map.end()) { while (dmem_area != dmem_map.end()) {
if (!dmem_area->second.is_free) { if (!dmem_area->second.is_free) {
@ -798,12 +812,78 @@ s32 MemoryManager::SetDirectMemoryType(s64 phys_addr, s32 memory_type) {
return ORBIS_OK; return ORBIS_OK;
} }
void MemoryManager::NameVirtualRange(VAddr virtual_addr, size_t size, std::string_view name) { void MemoryManager::NameVirtualRange(VAddr virtual_addr, u64 size, std::string_view name) {
auto it = FindVMA(virtual_addr); // Sizes are aligned up to the nearest 16_KB
auto aligned_size = Common::AlignUp(size, 16_KB);
// Addresses are aligned down to the nearest 16_KB
auto aligned_addr = Common::AlignDown(virtual_addr, 16_KB);
ASSERT_MSG(it->second.Contains(virtual_addr, size), auto it = FindVMA(aligned_addr);
"Range provided is not fully contained in vma"); s64 remaining_size = aligned_size;
it->second.name = name; auto current_addr = aligned_addr;
while (remaining_size > 0) {
// Nothing needs to be done to free VMAs
if (!it->second.IsFree()) {
if (remaining_size < it->second.size) {
// We should split VMAs here, but this could cause trouble for Windows.
// Instead log a warning and name the whole VMA.
// it = CarveVMA(current_addr, remaining_size);
LOG_WARNING(Kernel_Vmm, "Trying to partially name a range");
}
auto& vma = it->second;
vma.name = name;
}
remaining_size -= it->second.size;
current_addr += it->second.size;
it = FindVMA(current_addr);
}
}
s32 MemoryManager::GetDirectMemoryType(PAddr addr, s32* directMemoryTypeOut,
void** directMemoryStartOut, void** directMemoryEndOut) {
std::scoped_lock lk{mutex};
auto dmem_area = FindDmemArea(addr);
if (addr > dmem_area->second.GetEnd() || dmem_area->second.is_free) {
LOG_ERROR(Core, "Unable to find allocated direct memory region to check type!");
return ORBIS_KERNEL_ERROR_ENOENT;
}
const auto& area = dmem_area->second;
*directMemoryStartOut = reinterpret_cast<void*>(area.base);
*directMemoryEndOut = reinterpret_cast<void*>(area.GetEnd());
*directMemoryTypeOut = area.memory_type;
return ORBIS_OK;
}
s32 MemoryManager::IsStack(VAddr addr, void** start, void** end) {
auto vma_handle = FindVMA(addr);
if (vma_handle == vma_map.end()) {
return ORBIS_KERNEL_ERROR_EINVAL;
}
const VirtualMemoryArea& vma = vma_handle->second;
if (!vma.Contains(addr, 0) || vma.IsFree()) {
return ORBIS_KERNEL_ERROR_EACCES;
}
u64 stack_start = 0;
u64 stack_end = 0;
if (vma.type == VMAType::Stack) {
stack_start = vma.base;
stack_end = vma.base + vma.size;
}
if (start != nullptr) {
*start = reinterpret_cast<void*>(stack_start);
}
if (end != nullptr) {
*end = reinterpret_cast<void*>(stack_end);
}
return ORBIS_OK;
} }
void MemoryManager::InvalidateMemory(const VAddr addr, const u64 size) const { void MemoryManager::InvalidateMemory(const VAddr addr, const u64 size) const {
@ -812,7 +892,7 @@ void MemoryManager::InvalidateMemory(const VAddr addr, const u64 size) const {
} }
} }
VAddr MemoryManager::SearchFree(VAddr virtual_addr, size_t size, u32 alignment) { VAddr MemoryManager::SearchFree(VAddr virtual_addr, u64 size, u32 alignment) {
// If the requested address is below the mapped range, start search from the lowest address // If the requested address is below the mapped range, start search from the lowest address
auto min_search_address = impl.SystemManagedVirtualBase(); auto min_search_address = impl.SystemManagedVirtualBase();
if (virtual_addr < min_search_address) { if (virtual_addr < min_search_address) {
@ -824,6 +904,8 @@ VAddr MemoryManager::SearchFree(VAddr virtual_addr, size_t size, u32 alignment)
ASSERT_MSG(virtual_addr <= max_search_address, "Input address {:#x} is out of bounds", ASSERT_MSG(virtual_addr <= max_search_address, "Input address {:#x} is out of bounds",
virtual_addr); virtual_addr);
// Align up the virtual_addr first.
virtual_addr = Common::AlignUp(virtual_addr, alignment);
auto it = FindVMA(virtual_addr); auto it = FindVMA(virtual_addr);
// If the VMA is free and contains the requested mapping we are done. // If the VMA is free and contains the requested mapping we are done.
@ -853,7 +935,7 @@ VAddr MemoryManager::SearchFree(VAddr virtual_addr, size_t size, u32 alignment)
} }
// If there's enough space in the VMA, return the address. // If there's enough space in the VMA, return the address.
const size_t remaining_size = vma.base + vma.size - virtual_addr; const u64 remaining_size = vma.base + vma.size - virtual_addr;
if (remaining_size >= size) { if (remaining_size >= size) {
return virtual_addr; return virtual_addr;
} }
@ -866,7 +948,7 @@ VAddr MemoryManager::SearchFree(VAddr virtual_addr, size_t size, u32 alignment)
return -1; return -1;
} }
MemoryManager::VMAHandle MemoryManager::CarveVMA(VAddr virtual_addr, size_t size) { MemoryManager::VMAHandle MemoryManager::CarveVMA(VAddr virtual_addr, u64 size) {
auto vma_handle = FindVMA(virtual_addr); auto vma_handle = FindVMA(virtual_addr);
ASSERT_MSG(vma_handle->second.Contains(virtual_addr, 0), "Virtual address not in vm_map"); ASSERT_MSG(vma_handle->second.Contains(virtual_addr, 0), "Virtual address not in vm_map");
@ -895,7 +977,7 @@ MemoryManager::VMAHandle MemoryManager::CarveVMA(VAddr virtual_addr, size_t size
return vma_handle; return vma_handle;
} }
MemoryManager::DMemHandle MemoryManager::CarveDmemArea(PAddr addr, size_t size) { MemoryManager::DMemHandle MemoryManager::CarveDmemArea(PAddr addr, u64 size) {
auto dmem_handle = FindDmemArea(addr); auto dmem_handle = FindDmemArea(addr);
ASSERT_MSG(addr <= dmem_handle->second.GetEnd(), "Physical address not in dmem_map"); ASSERT_MSG(addr <= dmem_handle->second.GetEnd(), "Physical address not in dmem_map");
@ -919,7 +1001,7 @@ MemoryManager::DMemHandle MemoryManager::CarveDmemArea(PAddr addr, size_t size)
return dmem_handle; return dmem_handle;
} }
MemoryManager::VMAHandle MemoryManager::Split(VMAHandle vma_handle, size_t offset_in_vma) { MemoryManager::VMAHandle MemoryManager::Split(VMAHandle vma_handle, u64 offset_in_vma) {
auto& old_vma = vma_handle->second; auto& old_vma = vma_handle->second;
ASSERT(offset_in_vma < old_vma.size && offset_in_vma > 0); ASSERT(offset_in_vma < old_vma.size && offset_in_vma > 0);
@ -934,7 +1016,7 @@ MemoryManager::VMAHandle MemoryManager::Split(VMAHandle vma_handle, size_t offse
return vma_map.emplace_hint(std::next(vma_handle), new_vma.base, new_vma); return vma_map.emplace_hint(std::next(vma_handle), new_vma.base, new_vma);
} }
MemoryManager::DMemHandle MemoryManager::Split(DMemHandle dmem_handle, size_t offset_in_area) { MemoryManager::DMemHandle MemoryManager::Split(DMemHandle dmem_handle, u64 offset_in_area) {
auto& old_area = dmem_handle->second; auto& old_area = dmem_handle->second;
ASSERT(offset_in_area < old_area.size && offset_in_area > 0); ASSERT(offset_in_area < old_area.size && offset_in_area > 0);
@ -946,51 +1028,4 @@ MemoryManager::DMemHandle MemoryManager::Split(DMemHandle dmem_handle, size_t of
return dmem_map.emplace_hint(std::next(dmem_handle), new_area.base, new_area); return dmem_map.emplace_hint(std::next(dmem_handle), new_area.base, new_area);
} }
int MemoryManager::GetDirectMemoryType(PAddr addr, int* directMemoryTypeOut,
void** directMemoryStartOut, void** directMemoryEndOut) {
std::scoped_lock lk{mutex};
auto dmem_area = FindDmemArea(addr);
if (addr > dmem_area->second.GetEnd() || dmem_area->second.is_free) {
LOG_ERROR(Core, "Unable to find allocated direct memory region to check type!");
return ORBIS_KERNEL_ERROR_ENOENT;
}
const auto& area = dmem_area->second;
*directMemoryStartOut = reinterpret_cast<void*>(area.base);
*directMemoryEndOut = reinterpret_cast<void*>(area.GetEnd());
*directMemoryTypeOut = area.memory_type;
return ORBIS_OK;
}
int MemoryManager::IsStack(VAddr addr, void** start, void** end) {
auto vma_handle = FindVMA(addr);
if (vma_handle == vma_map.end()) {
return ORBIS_KERNEL_ERROR_EINVAL;
}
const VirtualMemoryArea& vma = vma_handle->second;
if (!vma.Contains(addr, 0) || vma.IsFree()) {
return ORBIS_KERNEL_ERROR_EACCES;
}
auto stack_start = 0ul;
auto stack_end = 0ul;
if (vma.type == VMAType::Stack) {
stack_start = vma.base;
stack_end = vma.base + vma.size;
}
if (start != nullptr) {
*start = reinterpret_cast<void*>(stack_start);
}
if (end != nullptr) {
*end = reinterpret_cast<void*>(stack_end);
}
return ORBIS_OK;
}
} // namespace Core } // namespace Core

View File

@ -5,6 +5,7 @@
#include <map> #include <map>
#include <mutex> #include <mutex>
#include <string>
#include <string_view> #include <string_view>
#include "common/enum.h" #include "common/enum.h"
#include "common/singleton.h" #include "common/singleton.h"
@ -30,6 +31,7 @@ enum class MemoryProt : u32 {
NoAccess = 0, NoAccess = 0,
CpuRead = 1, CpuRead = 1,
CpuReadWrite = 2, CpuReadWrite = 2,
CpuExec = 4,
GpuRead = 16, GpuRead = 16,
GpuWrite = 32, GpuWrite = 32,
GpuReadWrite = 48, GpuReadWrite = 48,
@ -62,8 +64,8 @@ enum class VMAType : u32 {
struct DirectMemoryArea { struct DirectMemoryArea {
PAddr base = 0; PAddr base = 0;
size_t size = 0; u64 size = 0;
int memory_type = 0; s32 memory_type = 0;
bool is_pooled = false; bool is_pooled = false;
bool is_free = true; bool is_free = true;
@ -75,6 +77,9 @@ struct DirectMemoryArea {
if (base + size != next.base) { if (base + size != next.base) {
return false; return false;
} }
if (memory_type != next.memory_type) {
return false;
}
if (is_free != next.is_free) { if (is_free != next.is_free) {
return false; return false;
} }
@ -84,7 +89,7 @@ struct DirectMemoryArea {
struct VirtualMemoryArea { struct VirtualMemoryArea {
VAddr base = 0; VAddr base = 0;
size_t size = 0; u64 size = 0;
PAddr phys_base = 0; PAddr phys_base = 0;
VMAType type = VMAType::Free; VMAType type = VMAType::Free;
MemoryProt prot = MemoryProt::NoAccess; MemoryProt prot = MemoryProt::NoAccess;
@ -93,7 +98,7 @@ struct VirtualMemoryArea {
uintptr_t fd = 0; uintptr_t fd = 0;
bool is_exec = false; bool is_exec = false;
bool Contains(VAddr addr, size_t size) const { bool Contains(VAddr addr, u64 size) const {
return addr >= base && (addr + size) <= (base + this->size); return addr >= base && (addr + size) <= (base + this->size);
} }
@ -172,61 +177,58 @@ public:
u64 ClampRangeSize(VAddr virtual_addr, u64 size); u64 ClampRangeSize(VAddr virtual_addr, u64 size);
void SetPrtArea(u32 id, VAddr address, u64 size);
void CopySparseMemory(VAddr source, u8* dest, u64 size);
bool TryWriteBacking(void* address, const void* data, u32 num_bytes); bool TryWriteBacking(void* address, const void* data, u32 num_bytes);
void SetupMemoryRegions(u64 flexible_size, bool use_extended_mem1, bool use_extended_mem2); void SetupMemoryRegions(u64 flexible_size, bool use_extended_mem1, bool use_extended_mem2);
PAddr PoolExpand(PAddr search_start, PAddr search_end, size_t size, u64 alignment); PAddr PoolExpand(PAddr search_start, PAddr search_end, u64 size, u64 alignment);
PAddr Allocate(PAddr search_start, PAddr search_end, size_t size, u64 alignment, PAddr Allocate(PAddr search_start, PAddr search_end, u64 size, u64 alignment, s32 memory_type);
int memory_type);
void Free(PAddr phys_addr, size_t size); void Free(PAddr phys_addr, u64 size);
int PoolReserve(void** out_addr, VAddr virtual_addr, size_t size, MemoryMapFlags flags, s32 PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot);
u64 alignment = 0);
int Reserve(void** out_addr, VAddr virtual_addr, size_t size, MemoryMapFlags flags, s32 MapMemory(void** out_addr, VAddr virtual_addr, u64 size, MemoryProt prot,
u64 alignment = 0);
int PoolCommit(VAddr virtual_addr, size_t size, MemoryProt prot);
int MapMemory(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
MemoryMapFlags flags, VMAType type, std::string_view name = "anon", MemoryMapFlags flags, VMAType type, std::string_view name = "anon",
bool is_exec = false, PAddr phys_addr = -1, u64 alignment = 0); bool is_exec = false, PAddr phys_addr = -1, u64 alignment = 0);
int MapFile(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot, s32 MapFile(void** out_addr, VAddr virtual_addr, u64 size, MemoryProt prot,
MemoryMapFlags flags, uintptr_t fd, size_t offset); MemoryMapFlags flags, s32 fd, s64 phys_addr);
s32 PoolDecommit(VAddr virtual_addr, size_t size); s32 PoolDecommit(VAddr virtual_addr, u64 size);
s32 UnmapMemory(VAddr virtual_addr, size_t size); s32 UnmapMemory(VAddr virtual_addr, u64 size);
int QueryProtection(VAddr addr, void** start, void** end, u32* prot); s32 QueryProtection(VAddr addr, void** start, void** end, u32* prot);
s32 Protect(VAddr addr, size_t size, MemoryProt prot); s32 Protect(VAddr addr, u64 size, MemoryProt prot);
s64 ProtectBytes(VAddr addr, VirtualMemoryArea vma_base, size_t size, MemoryProt prot); s64 ProtectBytes(VAddr addr, VirtualMemoryArea vma_base, u64 size, MemoryProt prot);
int VirtualQuery(VAddr addr, int flags, ::Libraries::Kernel::OrbisVirtualQueryInfo* info); s32 VirtualQuery(VAddr addr, s32 flags, ::Libraries::Kernel::OrbisVirtualQueryInfo* info);
int DirectMemoryQuery(PAddr addr, bool find_next, s32 DirectMemoryQuery(PAddr addr, bool find_next,
::Libraries::Kernel::OrbisQueryInfo* out_info); ::Libraries::Kernel::OrbisQueryInfo* out_info);
int DirectQueryAvailable(PAddr search_start, PAddr search_end, size_t alignment, s32 DirectQueryAvailable(PAddr search_start, PAddr search_end, u64 alignment,
PAddr* phys_addr_out, size_t* size_out); PAddr* phys_addr_out, u64* size_out);
int GetDirectMemoryType(PAddr addr, int* directMemoryTypeOut, void** directMemoryStartOut, s32 GetDirectMemoryType(PAddr addr, s32* directMemoryTypeOut, void** directMemoryStartOut,
void** directMemoryEndOut); void** directMemoryEndOut);
s32 IsStack(VAddr addr, void** start, void** end);
s32 SetDirectMemoryType(s64 phys_addr, s32 memory_type); s32 SetDirectMemoryType(s64 phys_addr, s32 memory_type);
void NameVirtualRange(VAddr virtual_addr, size_t size, std::string_view name); void NameVirtualRange(VAddr virtual_addr, u64 size, std::string_view name);
void InvalidateMemory(VAddr addr, u64 size) const; void InvalidateMemory(VAddr addr, u64 size) const;
int IsStack(VAddr addr, void** start, void** end);
private: private:
VMAHandle FindVMA(VAddr target) { VMAHandle FindVMA(VAddr target) {
return std::prev(vma_map.upper_bound(target)); return std::prev(vma_map.upper_bound(target));
@ -256,15 +258,15 @@ private:
return iter; return iter;
} }
VAddr SearchFree(VAddr virtual_addr, size_t size, u32 alignment = 0); VAddr SearchFree(VAddr virtual_addr, u64 size, u32 alignment = 0);
VMAHandle CarveVMA(VAddr virtual_addr, size_t size); VMAHandle CarveVMA(VAddr virtual_addr, u64 size);
DMemHandle CarveDmemArea(PAddr addr, size_t size); DMemHandle CarveDmemArea(PAddr addr, u64 size);
VMAHandle Split(VMAHandle vma_handle, size_t offset_in_vma); VMAHandle Split(VMAHandle vma_handle, u64 offset_in_vma);
DMemHandle Split(DMemHandle dmem_handle, size_t offset_in_area); DMemHandle Split(DMemHandle dmem_handle, u64 offset_in_area);
u64 UnmapBytesFromEntry(VAddr virtual_addr, VirtualMemoryArea vma_base, u64 size); u64 UnmapBytesFromEntry(VAddr virtual_addr, VirtualMemoryArea vma_base, u64 size);
@ -275,12 +277,24 @@ private:
DMemMap dmem_map; DMemMap dmem_map;
VMAMap vma_map; VMAMap vma_map;
std::mutex mutex; std::mutex mutex;
size_t total_direct_size{}; u64 total_direct_size{};
size_t total_flexible_size{}; u64 total_flexible_size{};
size_t flexible_usage{}; u64 flexible_usage{};
size_t pool_budget{}; u64 pool_budget{};
Vulkan::Rasterizer* rasterizer{}; Vulkan::Rasterizer* rasterizer{};
struct PrtArea {
VAddr start;
VAddr end;
bool mapped;
bool Overlaps(VAddr test_address, u64 test_size) const {
const VAddr overlap_end = test_address + test_size;
return start < overlap_end && test_address < end;
}
};
std::array<PrtArea, 3> prt_areas{};
friend class ::Core::Devtools::Widget::MemoryMapViewer; friend class ::Core::Devtools::Widget::MemoryMapViewer;
}; };

View File

@ -11,6 +11,7 @@
#include <windows.h> #include <windows.h>
#else #else
#include <csignal> #include <csignal>
#include <pthread.h>
#ifdef ARCH_X86_64 #ifdef ARCH_X86_64
#include <Zydis/Formatter.h> #include <Zydis/Formatter.h>
#endif #endif

View File

@ -5,6 +5,7 @@
#include <set> #include <set>
#include "common/singleton.h" #include "common/singleton.h"
#include "common/types.h"
namespace Core { namespace Core {

View File

@ -51,7 +51,7 @@ Tcb* GetTcbBase() {
// Apple x86_64 // Apple x86_64
// Reserve space in the 32-bit address range for allocating TCB pages. // Reserve space in the 32-bit address range for allocating TCB pages.
asm(".zerofill TCB_SPACE,TCB_SPACE,__guest_system,0x3FC000"); asm(".zerofill TCB_SPACE,TCB_SPACE,__tcb_space,0x3FC000");
struct LdtPage { struct LdtPage {
void* tcb; void* tcb;

View File

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <filesystem>
#include <set> #include <set>
#include <fmt/core.h> #include <fmt/core.h>
@ -57,19 +58,21 @@ Emulator::Emulator() {
#endif #endif
} }
Emulator::~Emulator() { Emulator::~Emulator() {}
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
Config::saveMainWindow(config_dir / "config.toml"); void Emulator::Run(std::filesystem::path file, const std::vector<std::string> args) {
} if (std::filesystem::is_directory(file)) {
file /= "eboot.bin";
}
void Emulator::Run(const std::filesystem::path& file, const std::vector<std::string> args) {
const auto eboot_name = file.filename().string(); const auto eboot_name = file.filename().string();
auto game_folder = file.parent_path(); auto game_folder = file.parent_path();
if (const auto game_folder_name = game_folder.filename().string(); if (const auto game_folder_name = game_folder.filename().string();
game_folder_name.ends_with("-UPDATE") || game_folder_name.ends_with("-patch")) { game_folder_name.ends_with("-UPDATE") || game_folder_name.ends_with("-patch")) {
// If an executable was launched from a separate update directory, // If an executable was launched from a separate update directory,
// use the base game directory as the game folder. // use the base game directory as the game folder.
const auto base_name = game_folder_name.substr(0, game_folder_name.size() - 7); const std::string base_name = game_folder_name.substr(0, game_folder_name.rfind('-'));
const auto base_path = game_folder.parent_path() / base_name; const auto base_path = game_folder.parent_path() / base_name;
if (std::filesystem::is_directory(base_path)) { if (std::filesystem::is_directory(base_path)) {
game_folder = base_path; game_folder = base_path;
@ -114,6 +117,11 @@ void Emulator::Run(const std::filesystem::path& file, const std::vector<std::str
Common::Log::Initialize(); Common::Log::Initialize();
} }
Common::Log::Start(); Common::Log::Start();
if (!std::filesystem::exists(file)) {
LOG_CRITICAL(Loader, "eboot.bin does not exist: {}",
std::filesystem::absolute(file).string());
std::quick_exit(0);
}
LOG_INFO(Loader, "Starting shadps4 emulator v{} ", Common::g_version); LOG_INFO(Loader, "Starting shadps4 emulator v{} ", Common::g_version);
LOG_INFO(Loader, "Revision {}", Common::g_scm_rev); LOG_INFO(Loader, "Revision {}", Common::g_scm_rev);
@ -124,6 +132,9 @@ void Emulator::Run(const std::filesystem::path& file, const std::vector<std::str
LOG_INFO(Config, "General LogType: {}", Config::getLogType()); LOG_INFO(Config, "General LogType: {}", Config::getLogType());
LOG_INFO(Config, "General isNeo: {}", Config::isNeoModeConsole()); LOG_INFO(Config, "General isNeo: {}", Config::isNeoModeConsole());
LOG_INFO(Config, "GPU isNullGpu: {}", Config::nullGpu()); LOG_INFO(Config, "GPU isNullGpu: {}", Config::nullGpu());
LOG_INFO(Config, "GPU readbacks: {}", Config::readbacks());
LOG_INFO(Config, "GPU readbackLinearImages: {}", Config::readbackLinearImages());
LOG_INFO(Config, "GPU directMemoryAccess: {}", Config::directMemoryAccess());
LOG_INFO(Config, "GPU shouldDumpShaders: {}", Config::dumpShaders()); LOG_INFO(Config, "GPU shouldDumpShaders: {}", Config::dumpShaders());
LOG_INFO(Config, "GPU vblankDivider: {}", Config::vblankDiv()); LOG_INFO(Config, "GPU vblankDivider: {}", Config::vblankDiv());
LOG_INFO(Config, "Vulkan gpuId: {}", Config::getGpuId()); LOG_INFO(Config, "Vulkan gpuId: {}", Config::getGpuId());
@ -194,15 +205,7 @@ void Emulator::Run(const std::filesystem::path& file, const std::vector<std::str
std::string game_title = fmt::format("{} - {} <{}>", id, title, app_version); std::string game_title = fmt::format("{} - {} <{}>", id, title, app_version);
std::string window_title = ""; std::string window_title = "";
std::string remote_url(Common::g_scm_remote_url); std::string remote_url(Common::g_scm_remote_url);
std::string remote_host; std::string remote_host = Common::GetRemoteNameFromLink();
try {
if (*remote_url.rbegin() == '/') {
remote_url.pop_back();
}
remote_host = remote_url.substr(19, remote_url.rfind('/') - 19);
} catch (...) {
remote_host = "unknown";
}
if (Common::g_is_release) { if (Common::g_is_release) {
if (remote_host == "shadps4-emu" || remote_url.length() == 0) { if (remote_host == "shadps4-emu" || remote_url.length() == 0) {
window_title = fmt::format("shadPS4 v{} | {}", Common::g_version, game_title); window_title = fmt::format("shadPS4 v{} | {}", Common::g_version, game_title);
@ -220,7 +223,7 @@ void Emulator::Run(const std::filesystem::path& file, const std::vector<std::str
} }
} }
window = std::make_unique<Frontend::WindowSDL>( window = std::make_unique<Frontend::WindowSDL>(
Config::getScreenWidth(), Config::getScreenHeight(), controller, window_title); Config::getWindowWidth(), Config::getWindowHeight(), controller, window_title);
g_window = window.get(); g_window = window.get();
@ -258,7 +261,11 @@ void Emulator::Run(const std::filesystem::path& file, const std::vector<std::str
// Load the module with the linker // Load the module with the linker
const auto eboot_path = mnt->GetHostPath("/app0/" + eboot_name); const auto eboot_path = mnt->GetHostPath("/app0/" + eboot_name);
linker->LoadModule(eboot_path); if (linker->LoadModule(eboot_path) == -1) {
LOG_CRITICAL(Loader, "Failed to load game's eboot.bin: {}",
std::filesystem::absolute(eboot_path).string());
std::quick_exit(0);
}
// check if we have system modules to load // check if we have system modules to load
LoadSystemModules(game_info.game_serial); LoadSystemModules(game_info.game_serial);

View File

@ -25,7 +25,7 @@ public:
Emulator(); Emulator();
~Emulator(); ~Emulator();
void Run(const std::filesystem::path& file, const std::vector<std::string> args = {}); void Run(std::filesystem::path file, const std::vector<std::string> args = {});
void UpdatePlayTime(const std::string& serial); void UpdatePlayTime(const std::string& serial);
private: private:

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
@ -165,69 +165,37 @@ void GameController::Acceleration(int id, const float acceleration[3]) {
AddState(state); AddState(state);
} }
// Stolen from
// https://github.com/xioTechnologies/Open-Source-AHRS-With-x-IMU/blob/master/x-IMU%20IMU%20and%20AHRS%20Algorithms/x-IMU%20IMU%20and%20AHRS%20Algorithms/AHRS/MahonyAHRS.cs
float eInt[3] = {0.0f, 0.0f, 0.0f}; // Integral error terms
const float Kp = 50.0f; // Proportional gain
const float Ki = 1.0f; // Integral gain
Libraries::Pad::OrbisFQuaternion o = {1, 0, 0, 0};
void GameController::CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration, void GameController::CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration,
Libraries::Pad::OrbisFVector3& angularVelocity, Libraries::Pad::OrbisFVector3& angularVelocity,
float deltaTime, float deltaTime,
Libraries::Pad::OrbisFQuaternion& lastOrientation,
Libraries::Pad::OrbisFQuaternion& orientation) { Libraries::Pad::OrbisFQuaternion& orientation) {
float ax = acceleration.x, ay = acceleration.y, az = acceleration.z; Libraries::Pad::OrbisFQuaternion q = lastOrientation;
float gx = angularVelocity.x, gy = angularVelocity.y, gz = angularVelocity.z; Libraries::Pad::OrbisFQuaternion ω = {angularVelocity.x, angularVelocity.y, angularVelocity.z,
0.0f};
float q1 = o.w, q2 = o.x, q3 = o.y, q4 = o.z; Libraries::Pad::OrbisFQuaternion = {q.w * ω.x + q.x * ω.w + q.y * ω.z - q.z * ω.y,
q.w * ω.y + q.y * ω.w + q.z * ω.x - q.x * ω.z,
q.w * ω.z + q.z * ω.w + q.x * ω.y - q.y * ω.x,
q.w * ω.w - q.x * ω.x - q.y * ω.y - q.z * ω.z};
// Normalize accelerometer measurement Libraries::Pad::OrbisFQuaternion qDot = {0.5f * .x, 0.5f * .y, 0.5f * .z, 0.5f * .w};
float norm = std::sqrt(ax * ax + ay * ay + az * az);
if (norm == 0.0f || deltaTime == 0.0f)
return; // Handle NaN
norm = 1.0f / norm;
ax *= norm;
ay *= norm;
az *= norm;
// Estimated direction of gravity q.x += qDot.x * deltaTime;
float vx = 2.0f * (q2 * q4 - q1 * q3); q.y += qDot.y * deltaTime;
float vy = 2.0f * (q1 * q2 + q3 * q4); q.z += qDot.z * deltaTime;
float vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4; q.w += qDot.w * deltaTime;
// Error is cross product between estimated direction and measured direction of gravity float norm = std::sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w);
float ex = (ay * vz - az * vy); q.x /= norm;
float ey = (az * vx - ax * vz); q.y /= norm;
float ez = (ax * vy - ay * vx); q.z /= norm;
if (Ki > 0.0f) { q.w /= norm;
eInt[0] += ex * deltaTime; // Accumulate integral error
eInt[1] += ey * deltaTime;
eInt[2] += ez * deltaTime;
} else {
eInt[0] = eInt[1] = eInt[2] = 0.0f; // Prevent integral wind-up
}
// Apply feedback terms orientation.x = q.x;
gx += Kp * ex + Ki * eInt[0]; orientation.y = q.y;
gy += Kp * ey + Ki * eInt[1]; orientation.z = q.z;
gz += Kp * ez + Ki * eInt[2]; orientation.w = q.w;
//// Integrate rate of change of quaternion
q1 += (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * deltaTime);
q2 += (q1 * gx + q3 * gz - q4 * gy) * (0.5f * deltaTime);
q3 += (q1 * gy - q2 * gz + q4 * gx) * (0.5f * deltaTime);
q4 += (q1 * gz + q2 * gy - q3 * gx) * (0.5f * deltaTime);
// Normalize quaternion
norm = std::sqrt(q1 * q1 + q2 * q2 + q3 * q3 + q4 * q4);
norm = 1.0f / norm;
orientation.w = q1 * norm;
orientation.x = q2 * norm;
orientation.y = q3 * norm;
orientation.z = q4 * norm;
o.w = q1 * norm;
o.x = q2 * norm;
o.y = q3 * norm;
o.z = q4 * norm;
LOG_DEBUG(Lib_Pad, "Calculated orientation: {:.2f} {:.2f} {:.2f} {:.2f}", orientation.x, LOG_DEBUG(Lib_Pad, "Calculated orientation: {:.2f} {:.2f} {:.2f} {:.2f}", orientation.x,
orientation.y, orientation.z, orientation.w); orientation.y, orientation.z, orientation.w);
} }
@ -260,6 +228,69 @@ void GameController::SetTouchpadState(int touchIndex, bool touchDown, float x, f
} }
} }
u8 GameController::GetTouchCount() {
std::scoped_lock lock{m_mutex};
return m_touch_count;
}
void GameController::SetTouchCount(u8 touchCount) {
std::scoped_lock lock{m_mutex};
m_touch_count = touchCount;
}
u8 GameController::GetSecondaryTouchCount() {
std::scoped_lock lock{m_mutex};
return m_secondary_touch_count;
}
void GameController::SetSecondaryTouchCount(u8 touchCount) {
std::scoped_lock lock{m_mutex};
m_secondary_touch_count = touchCount;
if (touchCount == 0) {
m_was_secondary_reset = true;
}
}
u8 GameController::GetPreviousTouchNum() {
std::scoped_lock lock{m_mutex};
return m_previous_touchnum;
}
void GameController::SetPreviousTouchNum(u8 touchNum) {
std::scoped_lock lock{m_mutex};
m_previous_touchnum = touchNum;
}
bool GameController::WasSecondaryTouchReset() {
std::scoped_lock lock{m_mutex};
return m_was_secondary_reset;
}
void GameController::UnsetSecondaryTouchResetBool() {
std::scoped_lock lock{m_mutex};
m_was_secondary_reset = false;
}
void GameController::SetLastOrientation(Libraries::Pad::OrbisFQuaternion& orientation) {
std::scoped_lock lock{m_mutex};
m_orientation = orientation;
}
Libraries::Pad::OrbisFQuaternion GameController::GetLastOrientation() {
std::scoped_lock lock{m_mutex};
return m_orientation;
}
std::chrono::steady_clock::time_point GameController::GetLastUpdate() {
std::scoped_lock lock{m_mutex};
return m_last_update;
}
void GameController::SetLastUpdate(std::chrono::steady_clock::time_point lastUpdate) {
std::scoped_lock lock{m_mutex};
m_last_update = lastUpdate;
}
void GameController::SetEngine(std::unique_ptr<Engine> engine) { void GameController::SetEngine(std::unique_ptr<Engine> engine) {
std::scoped_lock _{m_mutex}; std::scoped_lock _{m_mutex};
m_engine = std::move(engine); m_engine = std::move(engine);

View File

@ -23,6 +23,7 @@ enum class Axis {
}; };
struct TouchpadEntry { struct TouchpadEntry {
u8 ID = 0;
bool state{}; bool state{};
u16 x{}; u16 x{};
u16 y{}; u16 y{};
@ -82,9 +83,23 @@ public:
Engine* GetEngine(); Engine* GetEngine();
u32 Poll(); u32 Poll();
u8 GetTouchCount();
void SetTouchCount(u8 touchCount);
u8 GetSecondaryTouchCount();
void SetSecondaryTouchCount(u8 touchCount);
u8 GetPreviousTouchNum();
void SetPreviousTouchNum(u8 touchNum);
bool WasSecondaryTouchReset();
void UnsetSecondaryTouchResetBool();
void SetLastOrientation(Libraries::Pad::OrbisFQuaternion& orientation);
Libraries::Pad::OrbisFQuaternion GetLastOrientation();
std::chrono::steady_clock::time_point GetLastUpdate();
void SetLastUpdate(std::chrono::steady_clock::time_point lastUpdate);
static void CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration, static void CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration,
Libraries::Pad::OrbisFVector3& angularVelocity, Libraries::Pad::OrbisFVector3& angularVelocity,
float deltaTime, float deltaTime,
Libraries::Pad::OrbisFQuaternion& lastOrientation,
Libraries::Pad::OrbisFQuaternion& orientation); Libraries::Pad::OrbisFQuaternion& orientation);
private: private:
@ -98,8 +113,15 @@ private:
int m_connected_count = 0; int m_connected_count = 0;
u32 m_states_num = 0; u32 m_states_num = 0;
u32 m_first_state = 0; u32 m_first_state = 0;
u8 m_touch_count = 0;
u8 m_secondary_touch_count = 0;
u8 m_previous_touch_count = 0;
u8 m_previous_touchnum = 0;
bool m_was_secondary_reset = false;
std::array<State, MAX_STATES> m_states; std::array<State, MAX_STATES> m_states;
std::array<StateInternal, MAX_STATES> m_private; std::array<StateInternal, MAX_STATES> m_private;
std::chrono::steady_clock::time_point m_last_update = {};
Libraries::Pad::OrbisFQuaternion m_orientation = {0.0f, 0.0f, 0.0f, 1.0f};
std::unique_ptr<Engine> m_engine = nullptr; std::unique_ptr<Engine> m_engine = nullptr;
}; };

View File

@ -66,22 +66,25 @@ auto output_array = std::array{
ControllerOutput(LEFTJOYSTICK_HALFMODE), ControllerOutput(LEFTJOYSTICK_HALFMODE),
ControllerOutput(RIGHTJOYSTICK_HALFMODE), ControllerOutput(RIGHTJOYSTICK_HALFMODE),
ControllerOutput(KEY_TOGGLE), ControllerOutput(KEY_TOGGLE),
ControllerOutput(MOUSE_GYRO_ROLL_MODE),
// Button mappings // Button mappings
ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle
ControllerOutput(SDL_GAMEPAD_BUTTON_EAST), // Circle ControllerOutput(SDL_GAMEPAD_BUTTON_EAST), // Circle
ControllerOutput(SDL_GAMEPAD_BUTTON_SOUTH), // Cross ControllerOutput(SDL_GAMEPAD_BUTTON_SOUTH), // Cross
ControllerOutput(SDL_GAMEPAD_BUTTON_WEST), // Square ControllerOutput(SDL_GAMEPAD_BUTTON_WEST), // Square
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), // L1 ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), // L1
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_STICK), // L3 ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_STICK), // L3
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), // R1 ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), // R1
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_STICK), // R3 ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_STICK), // R3
ControllerOutput(SDL_GAMEPAD_BUTTON_START), // Options ControllerOutput(SDL_GAMEPAD_BUTTON_START), // Options
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD), // TouchPad ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT), // TouchPad
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_UP), // Up ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER), // TouchPad
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_DOWN), // Down ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT), // TouchPad
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_LEFT), // Left ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_UP), // Up
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), // Right ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_DOWN), // Down
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_LEFT), // Left
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), // Right
// Axis mappings // Axis mappings
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX, false), // ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX, false),
@ -130,6 +133,12 @@ static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) {
return OPBDO::Options; return OPBDO::Options;
case SDL_GAMEPAD_BUTTON_TOUCHPAD: case SDL_GAMEPAD_BUTTON_TOUCHPAD:
return OPBDO::TouchPad; return OPBDO::TouchPad;
case SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT:
return OPBDO::TouchPad;
case SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER:
return OPBDO::TouchPad;
case SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT:
return OPBDO::TouchPad;
case SDL_GAMEPAD_BUTTON_BACK: case SDL_GAMEPAD_BUTTON_BACK:
return OPBDO::TouchPad; return OPBDO::TouchPad;
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
@ -176,7 +185,7 @@ InputBinding GetBindingFromString(std::string& line) {
if (string_to_keyboard_key_map.find(t) != string_to_keyboard_key_map.end()) { if (string_to_keyboard_key_map.find(t) != string_to_keyboard_key_map.end()) {
input = InputID(InputType::KeyboardMouse, string_to_keyboard_key_map.at(t)); input = InputID(InputType::KeyboardMouse, string_to_keyboard_key_map.at(t));
} else if (string_to_axis_map.find(t) != string_to_axis_map.end()) { } else if (string_to_axis_map.find(t) != string_to_axis_map.end()) {
input = InputID(InputType::Axis, (u32)string_to_axis_map.at(t).axis); input = InputID(InputType::Axis, string_to_axis_map.at(t).axis);
} else if (string_to_cbutton_map.find(t) != string_to_cbutton_map.end()) { } else if (string_to_cbutton_map.find(t) != string_to_cbutton_map.end()) {
input = InputID(InputType::Controller, string_to_cbutton_map.at(t)); input = InputID(InputType::Controller, string_to_cbutton_map.at(t));
} else { } else {
@ -227,19 +236,15 @@ void ParseInputConfig(const std::string game_id = "") {
line.erase(std::remove_if(line.begin(), line.end(), line.erase(std::remove_if(line.begin(), line.end(),
[](unsigned char c) { return std::isspace(c); }), [](unsigned char c) { return std::isspace(c); }),
line.end()); line.end());
if (line.empty()) { if (line.empty()) {
continue; continue;
} }
// Truncate lines starting at # // Truncate lines starting at #
std::size_t comment_pos = line.find('#'); std::size_t comment_pos = line.find('#');
if (comment_pos != std::string::npos) { if (comment_pos != std::string::npos) {
line = line.substr(0, comment_pos); line = line.substr(0, comment_pos);
} }
// Remove trailing semicolon
if (!line.empty() && line[line.length() - 1] == ';') {
line = line.substr(0, line.length() - 1);
}
if (line.empty()) { if (line.empty()) {
continue; continue;
} }
@ -254,8 +259,13 @@ void ParseInputConfig(const std::string game_id = "") {
std::string output_string = line.substr(0, equal_pos); std::string output_string = line.substr(0, equal_pos);
std::string input_string = line.substr(equal_pos + 1); std::string input_string = line.substr(equal_pos + 1);
std::size_t comma_pos = input_string.find(','); // Remove trailing semicolon from input_string
if (!input_string.empty() && input_string[input_string.length() - 1] == ';' &&
input_string != ";") {
line = line.substr(0, line.length() - 1);
}
std::size_t comma_pos = input_string.find(',');
auto parseInt = [](const std::string& s) -> std::optional<int> { auto parseInt = [](const std::string& s) -> std::optional<int> {
try { try {
return std::stoi(s); return std::stoi(s);
@ -373,7 +383,6 @@ void ParseInputConfig(const std::string game_id = "") {
BindingConnection connection(InputID(), nullptr); BindingConnection connection(InputID(), nullptr);
auto button_it = string_to_cbutton_map.find(output_string); auto button_it = string_to_cbutton_map.find(output_string);
auto axis_it = string_to_axis_map.find(output_string); auto axis_it = string_to_axis_map.find(output_string);
if (binding.IsEmpty()) { if (binding.IsEmpty()) {
LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.", LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.",
lineCount, line); lineCount, line);
@ -411,7 +420,7 @@ void ParseInputConfig(const std::string game_id = "") {
u32 GetMouseWheelEvent(const SDL_Event& event) { u32 GetMouseWheelEvent(const SDL_Event& event) {
if (event.type != SDL_EVENT_MOUSE_WHEEL && event.type != SDL_EVENT_MOUSE_WHEEL_OFF) { if (event.type != SDL_EVENT_MOUSE_WHEEL && event.type != SDL_EVENT_MOUSE_WHEEL_OFF) {
LOG_WARNING(Input, "Something went wrong with wheel input parsing!"); LOG_WARNING(Input, "Something went wrong with wheel input parsing!");
return (u32)-1; return SDL_UNMAPPED;
} }
if (event.wheel.y > 0) { if (event.wheel.y > 0) {
return SDL_MOUSE_WHEEL_UP; return SDL_MOUSE_WHEEL_UP;
@ -422,7 +431,7 @@ u32 GetMouseWheelEvent(const SDL_Event& event) {
} else if (event.wheel.x < 0) { } else if (event.wheel.x < 0) {
return SDL_MOUSE_WHEEL_LEFT; return SDL_MOUSE_WHEEL_LEFT;
} }
return (u32)-1; return SDL_UNMAPPED;
} }
InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) { InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) {
@ -432,16 +441,19 @@ InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) {
return InputEvent(InputType::KeyboardMouse, e.key.key, e.key.down, 0); return InputEvent(InputType::KeyboardMouse, e.key.key, e.key.down, 0);
case SDL_EVENT_MOUSE_BUTTON_DOWN: case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP: case SDL_EVENT_MOUSE_BUTTON_UP:
return InputEvent(InputType::KeyboardMouse, (u32)e.button.button, e.button.down, 0); return InputEvent(InputType::KeyboardMouse, static_cast<u32>(e.button.button),
e.button.down, 0);
case SDL_EVENT_MOUSE_WHEEL: case SDL_EVENT_MOUSE_WHEEL:
case SDL_EVENT_MOUSE_WHEEL_OFF: case SDL_EVENT_MOUSE_WHEEL_OFF:
return InputEvent(InputType::KeyboardMouse, GetMouseWheelEvent(e), return InputEvent(InputType::KeyboardMouse, GetMouseWheelEvent(e),
e.type == SDL_EVENT_MOUSE_WHEEL, 0); e.type == SDL_EVENT_MOUSE_WHEEL, 0);
case SDL_EVENT_GAMEPAD_BUTTON_DOWN: case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_BUTTON_UP: case SDL_EVENT_GAMEPAD_BUTTON_UP:
return InputEvent(InputType::Controller, (u32)e.gbutton.button, e.gbutton.down, 0); return InputEvent(InputType::Controller, static_cast<u32>(e.gbutton.button), e.gbutton.down,
0); // clang made me do it
case SDL_EVENT_GAMEPAD_AXIS_MOTION: case SDL_EVENT_GAMEPAD_AXIS_MOTION:
return InputEvent(InputType::Axis, (u32)e.gaxis.axis, true, e.gaxis.value / 256); return InputEvent(InputType::Axis, static_cast<u32>(e.gaxis.axis), true,
e.gaxis.value / 256); // this too
default: default:
return InputEvent(); return InputEvent();
} }
@ -499,14 +511,18 @@ void ControllerOutput::FinalizeUpdate() {
} }
old_button_state = new_button_state; old_button_state = new_button_state;
old_param = *new_param; old_param = *new_param;
float touchpad_x = 0;
if (button != SDL_GAMEPAD_BUTTON_INVALID) { if (button != SDL_GAMEPAD_BUTTON_INVALID) {
switch (button) { switch (button) {
case SDL_GAMEPAD_BUTTON_TOUCHPAD: case SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT:
touchpad_x = Config::getBackButtonBehavior() == "left" ? 0.25f controller->SetTouchpadState(0, new_button_state, 0.25f, 0.5f);
: Config::getBackButtonBehavior() == "right" ? 0.75f controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state);
: 0.5f; break;
controller->SetTouchpadState(0, new_button_state, touchpad_x, 0.5f); case SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER:
controller->SetTouchpadState(0, new_button_state, 0.50f, 0.5f);
controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state);
break;
case SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT:
controller->SetTouchpadState(0, new_button_state, 0.75f, 0.5f);
controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state);
break; break;
case LEFTJOYSTICK_HALFMODE: case LEFTJOYSTICK_HALFMODE:
@ -519,6 +535,9 @@ void ControllerOutput::FinalizeUpdate() {
// to do it, and it would be inconvenient to force it here, when AddUpdate does the job just // to do it, and it would be inconvenient to force it here, when AddUpdate does the job just
// fine, and a toggle doesn't have to checked against every input that's bound to it, it's // fine, and a toggle doesn't have to checked against every input that's bound to it, it's
// enough that one is pressed // enough that one is pressed
case MOUSE_GYRO_ROLL_MODE:
SetMouseGyroRollMode(new_button_state);
break;
default: // is a normal key (hopefully) default: // is a normal key (hopefully)
controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state);
break; break;
@ -570,7 +589,7 @@ void ControllerOutput::FinalizeUpdate() {
bool UpdatePressedKeys(InputEvent event) { bool UpdatePressedKeys(InputEvent event) {
// Skip invalid inputs // Skip invalid inputs
InputID input = event.input; InputID input = event.input;
if (input.sdl_id == (u32)-1) { if (input.sdl_id == SDL_UNMAPPED) {
return false; return false;
} }
if (input.type == InputType::Axis) { if (input.type == InputType::Axis) {

View File

@ -23,6 +23,10 @@
#define SDL_MOUSE_WHEEL_LEFT SDL_EVENT_MOUSE_WHEEL + 5 #define SDL_MOUSE_WHEEL_LEFT SDL_EVENT_MOUSE_WHEEL + 5
#define SDL_MOUSE_WHEEL_RIGHT SDL_EVENT_MOUSE_WHEEL + 7 #define SDL_MOUSE_WHEEL_RIGHT SDL_EVENT_MOUSE_WHEEL + 7
#define SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT SDL_GAMEPAD_BUTTON_COUNT + 1
#define SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER SDL_GAMEPAD_BUTTON_COUNT + 2
#define SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT SDL_GAMEPAD_BUTTON_COUNT + 3
// idk who already used what where so I just chose a big number // idk who already used what where so I just chose a big number
#define SDL_EVENT_MOUSE_WHEEL_OFF SDL_EVENT_USER + 10 #define SDL_EVENT_MOUSE_WHEEL_OFF SDL_EVENT_USER + 10
@ -31,6 +35,9 @@
#define BACK_BUTTON 0x00040000 #define BACK_BUTTON 0x00040000
#define KEY_TOGGLE 0x00200000 #define KEY_TOGGLE 0x00200000
#define MOUSE_GYRO_ROLL_MODE 0x00400000
#define SDL_UNMAPPED UINT32_MAX - 1
namespace Input { namespace Input {
using Input::Axis; using Input::Axis;
@ -49,7 +56,7 @@ class InputID {
public: public:
InputType type; InputType type;
u32 sdl_id; u32 sdl_id;
InputID(InputType d = InputType::Count, u32 i = (u32)-1) : type(d), sdl_id(i) {} InputID(InputType d = InputType::Count, u32 i = SDL_UNMAPPED) : type(d), sdl_id(i) {}
bool operator==(const InputID& o) const { bool operator==(const InputID& o) const {
return type == o.type && sdl_id == o.sdl_id; return type == o.type && sdl_id == o.sdl_id;
} }
@ -63,7 +70,7 @@ public:
return *this != InputID(); return *this != InputID();
} }
std::string ToString() { std::string ToString() {
return fmt::format("({}: {:x})", input_type_names[(u8)type], sdl_id); return fmt::format("({}: {:x})", input_type_names[static_cast<u8>(type)], sdl_id);
} }
}; };
@ -96,12 +103,19 @@ const std::map<std::string, u32> string_to_cbutton_map = {
{"options", SDL_GAMEPAD_BUTTON_START}, {"options", SDL_GAMEPAD_BUTTON_START},
// these are outputs only (touchpad can only be bound to itself) // these are outputs only (touchpad can only be bound to itself)
{"touchpad", SDL_GAMEPAD_BUTTON_TOUCHPAD}, {"touchpad_left", SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT},
{"touchpad_center", SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER},
{"touchpad_right", SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT},
{"leftjoystick_halfmode", LEFTJOYSTICK_HALFMODE}, {"leftjoystick_halfmode", LEFTJOYSTICK_HALFMODE},
{"rightjoystick_halfmode", RIGHTJOYSTICK_HALFMODE}, {"rightjoystick_halfmode", RIGHTJOYSTICK_HALFMODE},
// this is only for input // this is only for input
{"back", SDL_GAMEPAD_BUTTON_BACK}, {"back", SDL_GAMEPAD_BUTTON_BACK},
{"lpaddle_high", SDL_GAMEPAD_BUTTON_LEFT_PADDLE1},
{"lpaddle_low", SDL_GAMEPAD_BUTTON_LEFT_PADDLE2},
{"rpaddle_high", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1},
{"rpaddle_low", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2},
{"mouse_gyro_roll_mode", MOUSE_GYRO_ROLL_MODE},
}; };
const std::map<std::string, AxisMapping> string_to_axis_map = { const std::map<std::string, AxisMapping> string_to_axis_map = {
@ -124,6 +138,7 @@ const std::map<std::string, AxisMapping> string_to_axis_map = {
{"axis_right_y", {SDL_GAMEPAD_AXIS_RIGHTY, 127}}, {"axis_right_y", {SDL_GAMEPAD_AXIS_RIGHTY, 127}},
}; };
const std::map<std::string, u32> string_to_keyboard_key_map = { const std::map<std::string, u32> string_to_keyboard_key_map = {
// alphanumeric
{"a", SDLK_A}, {"a", SDLK_A},
{"b", SDLK_B}, {"b", SDLK_B},
{"c", SDLK_C}, {"c", SDLK_C},
@ -160,6 +175,73 @@ const std::map<std::string, u32> string_to_keyboard_key_map = {
{"7", SDLK_7}, {"7", SDLK_7},
{"8", SDLK_8}, {"8", SDLK_8},
{"9", SDLK_9}, {"9", SDLK_9},
// symbols
{"grave", SDLK_GRAVE},
{"tilde", SDLK_TILDE},
{"exclamation", SDLK_EXCLAIM},
{"at", SDLK_AT},
{"hash", SDLK_HASH},
{"dollar", SDLK_DOLLAR},
{"percent", SDLK_PERCENT},
{"caret", SDLK_CARET},
{"ampersand", SDLK_AMPERSAND},
{"asterisk", SDLK_ASTERISK},
{"lparen", SDLK_LEFTPAREN},
{"rparen", SDLK_RIGHTPAREN},
{"minus", SDLK_MINUS},
{"underscore", SDLK_UNDERSCORE},
{"equals", SDLK_EQUALS},
{"plus", SDLK_PLUS},
{"lbracket", SDLK_LEFTBRACKET},
{"rbracket", SDLK_RIGHTBRACKET},
{"lbrace", SDLK_LEFTBRACE},
{"rbrace", SDLK_RIGHTBRACE},
{"backslash", SDLK_BACKSLASH},
{"pipe", SDLK_PIPE},
{"semicolon", SDLK_SEMICOLON},
{"colon", SDLK_COLON},
{"apostrophe", SDLK_APOSTROPHE},
{"quote", SDLK_DBLAPOSTROPHE},
{"comma", SDLK_COMMA},
{"less", SDLK_LESS},
{"period", SDLK_PERIOD},
{"greater", SDLK_GREATER},
{"slash", SDLK_SLASH},
{"question", SDLK_QUESTION},
// special keys
{"escape", SDLK_ESCAPE},
{"printscreen", SDLK_PRINTSCREEN},
{"scrolllock", SDLK_SCROLLLOCK},
{"pausebreak", SDLK_PAUSE},
{"backspace", SDLK_BACKSPACE},
{"delete", SDLK_DELETE},
{"insert", SDLK_INSERT},
{"home", SDLK_HOME},
{"end", SDLK_END},
{"pgup", SDLK_PAGEUP},
{"pgdown", SDLK_PAGEDOWN},
{"tab", SDLK_TAB},
{"capslock", SDLK_CAPSLOCK},
{"enter", SDLK_RETURN},
{"lshift", SDLK_LSHIFT},
{"rshift", SDLK_RSHIFT},
{"lctrl", SDLK_LCTRL},
{"rctrl", SDLK_RCTRL},
{"lalt", SDLK_LALT},
{"ralt", SDLK_RALT},
{"lmeta", SDLK_LGUI},
{"rmeta", SDLK_RGUI},
{"lwin", SDLK_LGUI},
{"rwin", SDLK_RGUI},
{"space", SDLK_SPACE},
{"up", SDLK_UP},
{"down", SDLK_DOWN},
{"left", SDLK_LEFT},
{"right", SDLK_RIGHT},
// keypad
{"kp0", SDLK_KP_0}, {"kp0", SDLK_KP_0},
{"kp1", SDLK_KP_1}, {"kp1", SDLK_KP_1},
{"kp2", SDLK_KP_2}, {"kp2", SDLK_KP_2},
@ -170,43 +252,16 @@ const std::map<std::string, u32> string_to_keyboard_key_map = {
{"kp7", SDLK_KP_7}, {"kp7", SDLK_KP_7},
{"kp8", SDLK_KP_8}, {"kp8", SDLK_KP_8},
{"kp9", SDLK_KP_9}, {"kp9", SDLK_KP_9},
{"comma", SDLK_COMMA}, {"kpperiod", SDLK_KP_PERIOD},
{"period", SDLK_PERIOD}, {"kpcomma", SDLK_KP_COMMA},
{"question", SDLK_QUESTION}, {"kpslash", SDLK_KP_DIVIDE},
{"semicolon", SDLK_SEMICOLON}, {"kpasterisk", SDLK_KP_MULTIPLY},
{"minus", SDLK_MINUS}, {"kpminus", SDLK_KP_MINUS},
{"underscore", SDLK_UNDERSCORE}, {"kpplus", SDLK_KP_PLUS},
{"lparenthesis", SDLK_LEFTPAREN}, {"kpequals", SDLK_KP_EQUALS},
{"rparenthesis", SDLK_RIGHTPAREN}, {"kpenter", SDLK_KP_ENTER},
{"lbracket", SDLK_LEFTBRACKET},
{"rbracket", SDLK_RIGHTBRACKET}, // mouse
{"lbrace", SDLK_LEFTBRACE},
{"rbrace", SDLK_RIGHTBRACE},
{"backslash", SDLK_BACKSLASH},
{"dash", SDLK_SLASH},
{"enter", SDLK_RETURN},
{"space", SDLK_SPACE},
{"tab", SDLK_TAB},
{"backspace", SDLK_BACKSPACE},
{"escape", SDLK_ESCAPE},
{"left", SDLK_LEFT},
{"right", SDLK_RIGHT},
{"up", SDLK_UP},
{"down", SDLK_DOWN},
{"lctrl", SDLK_LCTRL},
{"rctrl", SDLK_RCTRL},
{"lshift", SDLK_LSHIFT},
{"rshift", SDLK_RSHIFT},
{"lalt", SDLK_LALT},
{"ralt", SDLK_RALT},
{"lmeta", SDLK_LGUI},
{"rmeta", SDLK_RGUI},
{"lwin", SDLK_LGUI},
{"rwin", SDLK_RGUI},
{"home", SDLK_HOME},
{"end", SDLK_END},
{"pgup", SDLK_PAGEUP},
{"pgdown", SDLK_PAGEDOWN},
{"leftbutton", SDL_BUTTON_LEFT}, {"leftbutton", SDL_BUTTON_LEFT},
{"rightbutton", SDL_BUTTON_RIGHT}, {"rightbutton", SDL_BUTTON_RIGHT},
{"middlebutton", SDL_BUTTON_MIDDLE}, {"middlebutton", SDL_BUTTON_MIDDLE},
@ -216,15 +271,9 @@ const std::map<std::string, u32> string_to_keyboard_key_map = {
{"mousewheeldown", SDL_MOUSE_WHEEL_DOWN}, {"mousewheeldown", SDL_MOUSE_WHEEL_DOWN},
{"mousewheelleft", SDL_MOUSE_WHEEL_LEFT}, {"mousewheelleft", SDL_MOUSE_WHEEL_LEFT},
{"mousewheelright", SDL_MOUSE_WHEEL_RIGHT}, {"mousewheelright", SDL_MOUSE_WHEEL_RIGHT},
{"kpperiod", SDLK_KP_PERIOD},
{"kpcomma", SDLK_KP_COMMA}, // no binding
{"kpdivide", SDLK_KP_DIVIDE}, {"unmapped", SDL_UNMAPPED},
{"kpmultiply", SDLK_KP_MULTIPLY},
{"kpminus", SDLK_KP_MINUS},
{"kpplus", SDLK_KP_PLUS},
{"kpenter", SDLK_KP_ENTER},
{"kpequals", SDLK_KP_EQUALS},
{"capslock", SDLK_CAPSLOCK},
}; };
void ParseInputConfig(const std::string game_id); void ParseInputConfig(const std::string game_id);
@ -320,6 +369,7 @@ public:
// returns an InputEvent based on the event type (keyboard, mouse buttons/wheel, or controller) // returns an InputEvent based on the event type (keyboard, mouse buttons/wheel, or controller)
static InputEvent GetInputEventFromSDLEvent(const SDL_Event& e); static InputEvent GetInputEventFromSDLEvent(const SDL_Event& e);
}; };
class ControllerOutput { class ControllerOutput {
static GameController* controller; static GameController* controller;

View File

@ -3,6 +3,7 @@
#include <cmath> #include <cmath>
#include "common/assert.h"
#include "common/types.h" #include "common/types.h"
#include "input/controller.h" #include "input/controller.h"
#include "input_mouse.h" #include "input_mouse.h"
@ -13,12 +14,19 @@ namespace Input {
int mouse_joystick_binding = 0; int mouse_joystick_binding = 0;
float mouse_deadzone_offset = 0.5, mouse_speed = 1, mouse_speed_offset = 0.1250; float mouse_deadzone_offset = 0.5, mouse_speed = 1, mouse_speed_offset = 0.1250;
bool mouse_gyro_roll_mode = false;
Uint32 mouse_polling_id = 0; Uint32 mouse_polling_id = 0;
bool mouse_enabled = false; MouseMode mouse_mode = MouseMode::Off;
// We had to go through 3 files of indirection just to update a flag // Switches mouse to a set mode or turns mouse emulation off if it was already in that mode.
void ToggleMouseEnabled() { // Returns whether the mode is turned on.
mouse_enabled = !mouse_enabled; bool ToggleMouseModeTo(MouseMode m) {
if (mouse_mode == m) {
mouse_mode = MouseMode::Off;
} else {
mouse_mode = m;
}
return mouse_mode == m;
} }
void SetMouseToJoystick(int joystick) { void SetMouseToJoystick(int joystick) {
@ -31,10 +39,11 @@ void SetMouseParams(float mdo, float ms, float mso) {
mouse_speed_offset = mso; mouse_speed_offset = mso;
} }
Uint32 MousePolling(void* param, Uint32 id, Uint32 interval) { void SetMouseGyroRollMode(bool mode) {
auto* controller = (GameController*)param; mouse_gyro_roll_mode = mode;
if (!mouse_enabled) }
return interval;
void EmulateJoystick(GameController* controller, u32 interval) {
Axis axis_x, axis_y; Axis axis_x, axis_y;
switch (mouse_joystick_binding) { switch (mouse_joystick_binding) {
@ -47,7 +56,7 @@ Uint32 MousePolling(void* param, Uint32 id, Uint32 interval) {
axis_y = Axis::RightY; axis_y = Axis::RightY;
break; break;
default: default:
return interval; // no update needed return; // no update needed
} }
float d_x = 0, d_y = 0; float d_x = 0, d_y = 0;
@ -67,7 +76,35 @@ Uint32 MousePolling(void* param, Uint32 id, Uint32 interval) {
controller->Axis(0, axis_x, GetAxis(-0x80, 0x7f, 0)); controller->Axis(0, axis_x, GetAxis(-0x80, 0x7f, 0));
controller->Axis(0, axis_y, GetAxis(-0x80, 0x7f, 0)); controller->Axis(0, axis_y, GetAxis(-0x80, 0x7f, 0));
} }
}
constexpr float constant_down_accel[3] = {0.0f, 10.0f, 0.0f};
void EmulateGyro(GameController* controller, u32 interval) {
// LOG_INFO(Input, "todo gyro");
float d_x = 0, d_y = 0;
SDL_GetRelativeMouseState(&d_x, &d_y);
controller->Acceleration(1, constant_down_accel);
float gyro_from_mouse[3] = {-d_y / 100, -d_x / 100, 0.0f};
if (mouse_gyro_roll_mode) {
gyro_from_mouse[1] = 0.0f;
gyro_from_mouse[2] = -d_x / 100;
}
controller->Gyro(1, gyro_from_mouse);
}
Uint32 MousePolling(void* param, Uint32 id, Uint32 interval) {
auto* controller = (GameController*)param;
switch (mouse_mode) {
case MouseMode::Joystick:
EmulateJoystick(controller, interval);
break;
case MouseMode::Gyro:
EmulateGyro(controller, interval);
break;
default:
break;
}
return interval; return interval;
} }

View File

@ -8,11 +8,21 @@
namespace Input { namespace Input {
void ToggleMouseEnabled(); enum MouseMode {
Off = 0,
Joystick,
Gyro,
};
bool ToggleMouseModeTo(MouseMode m);
void SetMouseToJoystick(int joystick); void SetMouseToJoystick(int joystick);
void SetMouseParams(float mouse_deadzone_offset, float mouse_speed, float mouse_speed_offset); void SetMouseParams(float mouse_deadzone_offset, float mouse_speed, float mouse_speed_offset);
void SetMouseGyroRollMode(bool mode);
// Polls the mouse for changes, and simulates joystick movement from it. void EmulateJoystick(GameController* controller, u32 interval);
void EmulateGyro(GameController* controller, u32 interval);
// Polls the mouse for changes
Uint32 MousePolling(void* param, Uint32 id, Uint32 interval); Uint32 MousePolling(void* param, Uint32 id, Uint32 interval);
} // namespace Input } // namespace Input

View File

@ -35,17 +35,20 @@ int main(int argc, char* argv[]) {
std::unordered_map<std::string, std::function<void(int&)>> arg_map = { std::unordered_map<std::string, std::function<void(int&)>> arg_map = {
{"-h", {"-h",
[&](int&) { [&](int&) {
std::cout << "Usage: shadps4 [options] <elf or eboot.bin path>\n" std::cout
"Options:\n" << "Usage: shadps4 [options] <elf or eboot.bin path>\n"
" -g, --game <path|ID> Specify game path to launch\n" "Options:\n"
" -- ... Parameters passed to the game ELF. " " -g, --game <path|ID> Specify game path to launch\n"
"Needs to be at the end of the line, and everything after \"--\" is a " " -- ... Parameters passed to the game ELF. "
"game argument.\n" "Needs to be at the end of the line, and everything after \"--\" is a "
" -p, --patch <patch_file> Apply specified patch file\n" "game argument.\n"
" -f, --fullscreen <true|false> Specify window initial fullscreen " " -p, --patch <patch_file> Apply specified patch file\n"
"state. Does not overwrite the config file.\n" " -i, --ignore-game-patch Disable automatic loading of game patch\n"
" --add-game-folder <folder> Adds a new game folder to the config.\n" " -f, --fullscreen <true|false> Specify window initial fullscreen "
" -h, --help Display this help message\n"; "state. Does not overwrite the config file.\n"
" --add-game-folder <folder> Adds a new game folder to the config.\n"
" --set-addon-folder <folder> Sets the addon folder to the config.\n"
" -h, --help Display this help message\n";
exit(0); exit(0);
}}, }},
{"--help", [&](int& i) { arg_map["-h"](i); }}, {"--help", [&](int& i) { arg_map["-h"](i); }},
@ -72,6 +75,8 @@ int main(int argc, char* argv[]) {
} }
}}, }},
{"--patch", [&](int& i) { arg_map["-p"](i); }}, {"--patch", [&](int& i) { arg_map["-p"](i); }},
{"-i", [&](int&) { Core::FileSys::MntPoints::ignore_game_patches = true; }},
{"--ignore-game-patch", [&](int& i) { arg_map["-i"](i); }},
{"-f", {"-f",
[&](int& i) { [&](int& i) {
if (++i >= argc) { if (++i >= argc) {
@ -112,7 +117,24 @@ int main(int argc, char* argv[]) {
std::cout << "Game folder successfully saved.\n"; std::cout << "Game folder successfully saved.\n";
exit(0); exit(0);
}}, }},
}; {"--set-addon-folder", [&](int& i) {
if (++i >= argc) {
std::cerr << "Error: Missing argument for --add-addon-folder\n";
exit(1);
}
std::string config_dir(argv[i]);
std::filesystem::path config_path = std::filesystem::path(config_dir);
std::error_code discard;
if (!std::filesystem::exists(config_path, discard)) {
std::cerr << "Error: File does not exist: " << config_path << "\n";
exit(1);
}
Config::setAddonInstallDir(config_path);
Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml");
std::cout << "Addon folder successfully saved.\n";
exit(0);
}}};
if (argc == 1) { if (argc == 1) {
int dummy = 0; // one does not simply pass 0 directly int dummy = 0; // one does not simply pass 0 directly

View File

@ -12,7 +12,8 @@
#include "main_window_themes.h" #include "main_window_themes.h"
#include "ui_about_dialog.h" #include "ui_about_dialog.h"
AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDialog) { AboutDialog::AboutDialog(std::shared_ptr<gui_settings> gui_settings, QWidget* parent)
: QDialog(parent), ui(new Ui::AboutDialog), m_gui_settings(std::move(gui_settings)) {
ui->setupUi(this); ui->setupUi(this);
preloadImages(); preloadImages();
@ -57,7 +58,7 @@ void AboutDialog::preloadImages() {
} }
void AboutDialog::updateImagesForCurrentTheme() { void AboutDialog::updateImagesForCurrentTheme() {
Theme currentTheme = static_cast<Theme>(Config::getMainWindowTheme()); Theme currentTheme = static_cast<Theme>(m_gui_settings->GetValue(gui::gen_theme).toInt());
bool isDarkTheme = (currentTheme == Theme::Dark || currentTheme == Theme::Green || bool isDarkTheme = (currentTheme == Theme::Dark || currentTheme == Theme::Green ||
currentTheme == Theme::Blue || currentTheme == Theme::Violet); currentTheme == Theme::Blue || currentTheme == Theme::Violet);
if (isDarkTheme) { if (isDarkTheme) {
@ -188,7 +189,7 @@ void AboutDialog::removeHoverEffect(QLabel* label) {
} }
bool AboutDialog::isDarkTheme() const { bool AboutDialog::isDarkTheme() const {
Theme currentTheme = static_cast<Theme>(Config::getMainWindowTheme()); Theme currentTheme = static_cast<Theme>(m_gui_settings->GetValue(gui::gen_theme).toInt());
return currentTheme == Theme::Dark || currentTheme == Theme::Green || return currentTheme == Theme::Dark || currentTheme == Theme::Green ||
currentTheme == Theme::Blue || currentTheme == Theme::Violet; currentTheme == Theme::Blue || currentTheme == Theme::Violet;
} }

View File

@ -8,6 +8,7 @@
#include <QLabel> #include <QLabel>
#include <QPixmap> #include <QPixmap>
#include <QUrl> #include <QUrl>
#include "gui_settings.h"
namespace Ui { namespace Ui {
class AboutDialog; class AboutDialog;
@ -17,7 +18,7 @@ class AboutDialog : public QDialog {
Q_OBJECT Q_OBJECT
public: public:
explicit AboutDialog(QWidget* parent = nullptr); explicit AboutDialog(std::shared_ptr<gui_settings> gui_settings, QWidget* parent = nullptr);
~AboutDialog(); ~AboutDialog();
bool eventFilter(QObject* obj, QEvent* event); bool eventFilter(QObject* obj, QEvent* event);
@ -33,4 +34,5 @@ private:
QPixmap originalImages[5]; QPixmap originalImages[5];
QPixmap invertedImages[5]; QPixmap invertedImages[5];
std::shared_ptr<gui_settings> m_gui_settings;
}; };

View File

@ -28,8 +28,10 @@
using namespace Common::FS; using namespace Common::FS;
CheckUpdate::CheckUpdate(const bool showMessage, QWidget* parent) CheckUpdate::CheckUpdate(std::shared_ptr<gui_settings> gui_settings, const bool showMessage,
: QDialog(parent), networkManager(new QNetworkAccessManager(this)) { QWidget* parent)
: QDialog(parent), m_gui_settings(std::move(gui_settings)),
networkManager(new QNetworkAccessManager(this)) {
setWindowTitle(tr("Auto Updater")); setWindowTitle(tr("Auto Updater"));
setFixedSize(0, 0); setFixedSize(0, 0);
CheckForUpdates(showMessage); CheckForUpdates(showMessage);
@ -43,7 +45,7 @@ void CheckUpdate::CheckForUpdates(const bool showMessage) {
bool checkName = true; bool checkName = true;
while (checkName) { while (checkName) {
updateChannel = QString::fromStdString(Config::getUpdateChannel()); updateChannel = m_gui_settings->GetValue(gui::gen_updateChannel).toString();
if (updateChannel == "Nightly") { if (updateChannel == "Nightly") {
url = QUrl("https://api.github.com/repos/shadps4-emu/shadPS4/releases"); url = QUrl("https://api.github.com/repos/shadps4-emu/shadPS4/releases");
checkName = false; checkName = false;
@ -52,12 +54,10 @@ void CheckUpdate::CheckForUpdates(const bool showMessage) {
checkName = false; checkName = false;
} else { } else {
if (Common::g_is_release) { if (Common::g_is_release) {
Config::setUpdateChannel("Release"); m_gui_settings->SetValue(gui::gen_updateChannel, "Release");
} else { } else {
Config::setUpdateChannel("Nightly"); m_gui_settings->SetValue(gui::gen_updateChannel, "Nightly");
} }
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
Config::save(config_dir / "config.toml");
} }
} }
@ -137,7 +137,7 @@ tr("The Auto Updater allows up to 60 update checks per hour.\\nYou have reached
} }
} }
latestRev = latestVersion.right(7); latestRev = latestVersion.right(40);
latestDate = jsonObj["published_at"].toString(); latestDate = jsonObj["published_at"].toString();
QJsonArray assets = jsonObj["assets"].toArray(); QJsonArray assets = jsonObj["assets"].toArray();
@ -167,7 +167,7 @@ tr("The Auto Updater allows up to 60 update checks per hour.\\nYou have reached
QDateTime dateTime = QDateTime::fromString(latestDate, Qt::ISODate); QDateTime dateTime = QDateTime::fromString(latestDate, Qt::ISODate);
latestDate = dateTime.isValid() ? dateTime.toString("yyyy-MM-dd HH:mm:ss") : "Unknown date"; latestDate = dateTime.isValid() ? dateTime.toString("yyyy-MM-dd HH:mm:ss") : "Unknown date";
if (latestRev == currentRev.left(7)) { if (latestRev == currentRev) {
if (showMessage) { if (showMessage) {
QMessageBox::information(this, tr("Auto Updater"), QMessageBox::information(this, tr("Auto Updater"),
tr("Your version is already up to date!")); tr("Your version is already up to date!"));
@ -198,7 +198,7 @@ void CheckUpdate::setupUI(const QString& downloadUrl, const QString& latestDate,
titleLayout->addWidget(titleLabel); titleLayout->addWidget(titleLabel);
layout->addLayout(titleLayout); layout->addLayout(titleLayout);
QString updateChannel = QString::fromStdString(Config::getUpdateChannel()); QString updateChannel = m_gui_settings->GetValue(gui::gen_updateChannel).toString();
QString updateText = QString("<p><b>" + tr("Update Channel") + ": </b>" + updateChannel + QString updateText = QString("<p><b>" + tr("Update Channel") + ": </b>" + updateChannel +
"<br>" "<br>"
@ -215,7 +215,7 @@ void CheckUpdate::setupUI(const QString& downloadUrl, const QString& latestDate,
"<td>%3</td>" "<td>%3</td>"
"<td>(%4)</td>" "<td>(%4)</td>"
"</tr></table></p>") "</tr></table></p>")
.arg(currentRev.left(7), currentDate, latestRev, latestDate); .arg(currentRev.left(7), currentDate, latestRev.left(7), latestDate);
QLabel* updateLabel = new QLabel(updateText, this); QLabel* updateLabel = new QLabel(updateText, this);
layout->addWidget(updateLabel); layout->addWidget(updateLabel);
@ -273,7 +273,7 @@ void CheckUpdate::setupUI(const QString& downloadUrl, const QString& latestDate,
} }
}); });
if (Config::alwaysShowChangelog()) { if (m_gui_settings->GetValue(gui::gen_showChangeLog).toBool()) {
requestChangelog(currentRev, latestRev, downloadUrl, latestDate, currentDate); requestChangelog(currentRev, latestRev, downloadUrl, latestDate, currentDate);
textField->setVisible(true); textField->setVisible(true);
toggleButton->setText(tr("Hide Changelog")); toggleButton->setText(tr("Hide Changelog"));
@ -290,14 +290,14 @@ void CheckUpdate::setupUI(const QString& downloadUrl, const QString& latestDate,
connect(noButton, &QPushButton::clicked, this, [this]() { close(); }); connect(noButton, &QPushButton::clicked, this, [this]() { close(); });
autoUpdateCheckBox->setChecked(Config::autoUpdate()); autoUpdateCheckBox->setChecked(m_gui_settings->GetValue(gui::gen_checkForUpdates).toBool());
#if (QT_VERSION < QT_VERSION_CHECK(6, 7, 0)) #if (QT_VERSION < QT_VERSION_CHECK(6, 7, 0))
connect(autoUpdateCheckBox, &QCheckBox::stateChanged, this, [](int state) { connect(autoUpdateCheckBox, &QCheckBox::stateChanged, this, [this](int state) {
#else #else
connect(autoUpdateCheckBox, &QCheckBox::checkStateChanged, this, [](Qt::CheckState state) { connect(autoUpdateCheckBox, &QCheckBox::checkStateChanged, this, [this](Qt::CheckState state) {
#endif #endif
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
Config::setAutoUpdate(state == Qt::Checked); m_gui_settings->SetValue(gui::gen_checkForUpdates, (state == Qt::Checked));
Config::save(user_dir / "config.toml"); Config::save(user_dir / "config.toml");
}); });

View File

@ -8,12 +8,14 @@
#include <QDialog> #include <QDialog>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QPushButton> #include <QPushButton>
#include "gui_settings.h"
class CheckUpdate : public QDialog { class CheckUpdate : public QDialog {
Q_OBJECT Q_OBJECT
public: public:
explicit CheckUpdate(const bool showMessage, QWidget* parent = nullptr); explicit CheckUpdate(std::shared_ptr<gui_settings> gui_settings, const bool showMessage,
QWidget* parent = nullptr);
~CheckUpdate(); ~CheckUpdate();
private slots: private slots:
@ -35,6 +37,7 @@ private:
QString updateDownloadUrl; QString updateDownloadUrl;
QNetworkAccessManager* networkManager; QNetworkAccessManager* networkManager;
std::shared_ptr<gui_settings> m_gui_settings;
}; };
#endif // CHECKUPDATE_H #endif // CHECKUPDATE_H

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,10 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <QDialog> #include <QDialog>
#include <SDL3/SDL.h>
#include <SDL3/SDL_gamepad.h>
#include "game_info.h" #include "game_info.h"
#include "sdl_event_wrapper.h"
namespace Ui { namespace Ui {
class ControlSettings; class ControlSettings;
@ -11,22 +14,56 @@ class ControlSettings;
class ControlSettings : public QDialog { class ControlSettings : public QDialog {
Q_OBJECT Q_OBJECT
public: public:
explicit ControlSettings(std::shared_ptr<GameInfoClass> game_info_get, explicit ControlSettings(std::shared_ptr<GameInfoClass> game_info_get, bool GameRunning,
QWidget* parent = nullptr); std::string GameRunningSerial, QWidget* parent = nullptr);
~ControlSettings(); ~ControlSettings();
signals:
void PushGamepadEvent();
void AxisChanged();
private Q_SLOTS: private Q_SLOTS:
void SaveControllerConfig(bool CloseOnSave); void SaveControllerConfig(bool CloseOnSave);
void SetDefault(); void SetDefault();
void UpdateLightbarColor(); void UpdateLightbarColor();
void CheckMapping(QPushButton*& button);
void StartTimer(QPushButton*& button, bool isButton);
void ConnectAxisInputs(QPushButton*& button);
private: private:
std::unique_ptr<Ui::ControlSettings> ui; std::unique_ptr<Ui::ControlSettings> ui;
std::shared_ptr<GameInfoClass> m_game_info; std::shared_ptr<GameInfoClass> m_game_info;
bool eventFilter(QObject* obj, QEvent* event) override;
void AddBoxItems(); void AddBoxItems();
void SetUIValuestoMappings(); void SetUIValuestoMappings();
void GetGameTitle(); void GetGameTitle();
void CheckGamePad();
void processSDLEvents(int Type, int Input, int Value);
void pollSDLEvents();
void SetMapping(QString input);
void DisableMappingButtons();
void EnableMappingButtons();
void Cleanup();
QList<QPushButton*> ButtonsList;
QList<QPushButton*> AxisList;
QSet<QString> pressedButtons;
std::string RunningGameSerial;
bool GameRunning;
bool L2Pressed = false;
bool R2Pressed = false;
bool EnableButtonMapping = false;
bool EnableAxisMapping = false;
bool MappingCompleted = false;
QString mapping;
int MappingTimer;
QTimer* timer;
QPushButton* MappingButton;
SDL_Gamepad* gamepad = nullptr;
SdlEventWrapper::Wrapper* RemapWrapper;
QFuture<void> Polling;
const std::vector<std::string> ControllerInputs = { const std::vector<std::string> ControllerInputs = {
"cross", "circle", "square", "triangle", "l1", "cross", "circle", "square", "triangle", "l1",
@ -39,15 +76,8 @@ private:
"pad_left", "pad_right", "axis_left_x", "axis_left_y", "axis_right_x", "pad_left", "pad_right", "axis_left_x", "axis_left_y", "axis_right_x",
"axis_right_y", "back"}; "axis_right_y", "back"};
const QStringList ButtonOutputs = {"cross", "circle", "square", "triangle", "l1", protected:
"r1", "l2", "r2", "l3", void closeEvent(QCloseEvent* event) override {
Cleanup();
"r3", "options", "pad_up", }
"pad_down",
"pad_left", "pad_right", "touchpad", "unmapped"};
const QStringList StickOutputs = {"axis_left_x", "axis_left_y", "axis_right_x", "axis_right_y",
"unmapped"};
}; };

View File

@ -11,8 +11,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1043</width> <width>1114</width>
<height>792</height> <height>794</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -33,8 +33,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1019</width> <width>1094</width>
<height>732</height> <height>744</height>
</rect> </rect>
</property> </property>
<widget class="QWidget" name="layoutWidget"> <widget class="QWidget" name="layoutWidget">
@ -42,8 +42,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1021</width> <width>1091</width>
<height>731</height> <height>741</height>
</rect> </rect>
</property> </property>
<layout class="QHBoxLayout" name="RemapLayout"> <layout class="QHBoxLayout" name="RemapLayout">
@ -110,7 +110,7 @@
<widget class="QGroupBox" name="gb_dpad_up"> <widget class="QGroupBox" name="gb_dpad_up">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>124</width> <width>152</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
@ -125,12 +125,9 @@
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout_4"> <layout class="QHBoxLayout" name="horizontalLayout_4">
<item> <item>
<widget class="QComboBox" name="DpadUpBox"> <widget class="QPushButton" name="DpadUpButton">
<property name="editable"> <property name="text">
<bool>false</bool> <string>unmapped</string>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::SizeAdjustPolicy::AdjustToContents</enum>
</property> </property>
</widget> </widget>
</item> </item>
@ -161,7 +158,11 @@
<number>5</number> <number>5</number>
</property> </property>
<item> <item>
<widget class="QComboBox" name="DpadLeftBox"/> <widget class="QPushButton" name="DpadLeftButton">
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -185,9 +186,9 @@
<number>5</number> <number>5</number>
</property> </property>
<item> <item>
<widget class="QComboBox" name="DpadRightBox"> <widget class="QPushButton" name="DpadRightButton">
<property name="editable"> <property name="text">
<bool>false</bool> <string>unmapped</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -213,6 +214,12 @@
</property> </property>
<item> <item>
<widget class="QGroupBox" name="gb_dpad_down"> <widget class="QGroupBox" name="gb_dpad_down">
<property name="minimumSize">
<size>
<width>152</width>
<height>0</height>
</size>
</property>
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>124</width> <width>124</width>
@ -224,21 +231,9 @@
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout_3"> <layout class="QHBoxLayout" name="horizontalLayout_3">
<item> <item>
<widget class="QComboBox" name="DpadDownBox"> <widget class="QPushButton" name="DpadDownButton">
<property name="enabled"> <property name="text">
<bool>true</bool> <string>unmapped</string>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property> </property>
</widget> </widget>
</item> </item>
@ -378,7 +373,7 @@
</property> </property>
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>124</width> <width>152</width>
<height>16777215</height> <height>16777215</height>
</size> </size>
</property> </property>
@ -387,9 +382,9 @@
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_10"> <layout class="QVBoxLayout" name="verticalLayout_10">
<item> <item>
<widget class="QComboBox" name="LStickUpBox"> <widget class="QPushButton" name="LStickUpButton">
<property name="enabled"> <property name="text">
<bool>true</bool> <string>unmapped</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -420,9 +415,9 @@
<number>5</number> <number>5</number>
</property> </property>
<item> <item>
<widget class="QComboBox" name="LStickLeftBox"> <widget class="QPushButton" name="LStickLeftButton">
<property name="enabled"> <property name="text">
<bool>true</bool> <string>unmapped</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -454,9 +449,9 @@
<number>5</number> <number>5</number>
</property> </property>
<item> <item>
<widget class="QComboBox" name="LStickRightBox"> <widget class="QPushButton" name="LStickRightButton">
<property name="enabled"> <property name="text">
<bool>true</bool> <string>unmapped</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -484,7 +479,7 @@
<widget class="QGroupBox" name="gb_left_stick_down"> <widget class="QGroupBox" name="gb_left_stick_down">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>124</width> <width>152</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
@ -499,15 +494,9 @@
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_8"> <layout class="QVBoxLayout" name="verticalLayout_8">
<item> <item>
<widget class="QComboBox" name="LStickDownBox"> <widget class="QPushButton" name="LStickDownButton">
<property name="enabled"> <property name="text">
<bool>true</bool> <string>unmapped</string>
</property>
<property name="editable">
<bool>false</bool>
</property>
<property name="frame">
<bool>false</bool>
</property> </property>
</widget> </widget>
</item> </item>
@ -617,149 +606,190 @@
<property name="spacing"> <property name="spacing">
<number>0</number> <number>0</number>
</property> </property>
<item>
<layout class="QVBoxLayout" name="layout_l1_l2">
<item>
<widget class="QGroupBox" name="gb_l1">
<property name="title">
<string>L1 / LB</string>
</property>
<layout class="QVBoxLayout" name="gb_l1_layout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QComboBox" name="LBBox"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_l2">
<property name="title">
<string>L2 / LT</string>
</property>
<layout class="QVBoxLayout" name="gb_l2_layout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QComboBox" name="LTBox"/>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item> <item>
<layout class="QVBoxLayout" name="layout_system_buttons"> <layout class="QVBoxLayout" name="layout_system_buttons">
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Policy::Preferred</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item> <item>
<layout class="QVBoxLayout" name="verticalLayout_4"> <layout class="QVBoxLayout" name="verticalLayout_4">
<property name="spacing">
<number>10</number>
</property>
<item> <item>
<widget class="QGroupBox" name="groupBox_2"> <layout class="QHBoxLayout" name="horizontalLayout_13">
<property name="title"> <item>
<string>Back</string> <widget class="QGroupBox" name="gb_l1">
</property> <property name="sizePolicy">
<layout class="QVBoxLayout" name="verticalLayout_9"> <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<item> <horstretch>0</horstretch>
<widget class="QComboBox" name="BackBox"/> <verstretch>0</verstretch>
</item> </sizepolicy>
</layout> </property>
</widget> <property name="title">
<string>L1</string>
</property>
<layout class="QVBoxLayout" name="gb_l1_layout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QPushButton" name="L1Button">
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Policy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>133</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QGroupBox" name="gb_r1">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>R1</string>
</property>
<layout class="QVBoxLayout" name="gb_r1_layout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QPushButton" name="R1Button">
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_14">
<item>
<widget class="QGroupBox" name="gb_l2">
<property name="title">
<string>L2</string>
</property>
<layout class="QVBoxLayout" name="gb_l2_layout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QPushButton" name="L2Button">
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_start">
<property name="title">
<string>Options</string>
</property>
<layout class="QVBoxLayout" name="gb_start_layout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QPushButton" name="OptionsButton">
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_r2">
<property name="title">
<string>R2</string>
</property>
<layout class="QVBoxLayout" name="gb_r2_layout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QPushButton" name="R2Button">
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item> </item>
</layout> </layout>
</item> </item>
</layout> </layout>
</item> </item>
<item>
<layout class="QVBoxLayout" name="layout_r1_r2">
<item>
<widget class="QGroupBox" name="gb_r1">
<property name="title">
<string>R1 / RB</string>
</property>
<layout class="QVBoxLayout" name="gb_r1_layout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QComboBox" name="RBBox"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_r2">
<property name="title">
<string>R2 / RT</string>
</property>
<layout class="QVBoxLayout" name="gb_r2_layout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QComboBox" name="RTBox"/>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout> </layout>
</item> </item>
<item> <item>
@ -806,7 +836,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="layout_middle_bottom"> <layout class="QVBoxLayout" name="verticalLayout_9">
<property name="spacing"> <property name="spacing">
<number>10</number> <number>10</number>
</property> </property>
@ -814,76 +844,144 @@
<enum>QLayout::SizeConstraint::SetDefaultConstraint</enum> <enum>QLayout::SizeConstraint::SetDefaultConstraint</enum>
</property> </property>
<item> <item>
<widget class="QGroupBox" name="gb_l3"> <layout class="QHBoxLayout" name="horizontalLayout">
<property name="title"> <item>
<string>L3</string> <widget class="QGroupBox" name="gb_l3">
</property> <property name="sizePolicy">
<layout class="QVBoxLayout" name="gb_l3_layout"> <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<property name="leftMargin"> <horstretch>0</horstretch>
<number>5</number> <verstretch>0</verstretch>
</property> </sizepolicy>
<property name="topMargin"> </property>
<number>5</number> <property name="title">
</property> <string>L3</string>
<property name="rightMargin"> </property>
<number>5</number> <layout class="QVBoxLayout" name="gb_l3_layout">
</property> <property name="leftMargin">
<property name="bottomMargin"> <number>5</number>
<number>5</number> </property>
</property> <property name="topMargin">
<item> <number>5</number>
<widget class="QComboBox" name="LClickBox"/> </property>
</item> <property name="rightMargin">
</layout> <number>5</number>
</widget> </property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QPushButton" name="L3Button">
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Policy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>133</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QGroupBox" name="gb_r3">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>R3</string>
</property>
<layout class="QVBoxLayout" name="gb_r3_layout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QPushButton" name="R3Button">
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item> </item>
<item> <item>
<widget class="QGroupBox" name="gb_start"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="title"> <item>
<string>Options / Start</string> <widget class="QGroupBox" name="gb_touchleft">
</property> <property name="title">
<layout class="QVBoxLayout" name="gb_start_layout"> <string>Touchpad Left</string>
<property name="leftMargin"> </property>
<number>5</number> <layout class="QHBoxLayout" name="horizontalLayout_5">
</property> <item>
<property name="topMargin"> <widget class="QPushButton" name="TouchpadLeftButton">
<number>5</number> <property name="text">
</property> <string>unmapped</string>
<property name="rightMargin"> </property>
<number>5</number> </widget>
</property> </item>
<property name="bottomMargin"> </layout>
<number>5</number> </widget>
</property> </item>
<item> <item>
<widget class="QComboBox" name="StartBox"/> <widget class="QGroupBox" name="gb_touchcenter">
</item> <property name="title">
</layout> <string>Touchpad Center</string>
</widget> </property>
</item> <layout class="QVBoxLayout" name="verticalLayout_11">
<item> <item>
<widget class="QGroupBox" name="gb_r3"> <widget class="QPushButton" name="TouchpadCenterButton">
<property name="title"> <property name="text">
<string>R3</string> <string>unmapped</string>
</property> </property>
<layout class="QVBoxLayout" name="gb_r3_layout"> </widget>
<property name="leftMargin"> </item>
<number>5</number> </layout>
</property> </widget>
<property name="topMargin"> </item>
<number>5</number> <item>
</property> <widget class="QGroupBox" name="gb_touchright">
<property name="rightMargin"> <property name="title">
<number>5</number> <string>Touchpad Right</string>
</property> </property>
<property name="bottomMargin"> <layout class="QVBoxLayout" name="verticalLayout_12">
<number>5</number> <item>
</property> <widget class="QPushButton" name="TouchpadRightButton">
<item> <property name="text">
<widget class="QComboBox" name="RClickBox"/> <string>unmapped</string>
</item> </property>
</layout> </widget>
</widget> </item>
</layout>
</widget>
</item>
</layout>
</item> </item>
</layout> </layout>
</item> </item>
@ -1104,7 +1202,7 @@
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>124</width> <width>152</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
@ -1115,19 +1213,13 @@
</size> </size>
</property> </property>
<property name="title"> <property name="title">
<string>Triangle / Y</string> <string>Triangle</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QVBoxLayout" name="verticalLayout_3">
<item> <item>
<widget class="QComboBox" name="YBox"> <widget class="QPushButton" name="TriangleButton">
<property name="enabled"> <property name="text">
<bool>true</bool> <string>unmapped</string>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property> </property>
</widget> </widget>
</item> </item>
@ -1142,7 +1234,7 @@
<item> <item>
<widget class="QGroupBox" name="gb_square"> <widget class="QGroupBox" name="gb_square">
<property name="title"> <property name="title">
<string>Square / X</string> <string>Square</string>
</property> </property>
<layout class="QVBoxLayout" name="gb_square_layout"> <layout class="QVBoxLayout" name="gb_square_layout">
<property name="leftMargin"> <property name="leftMargin">
@ -1158,7 +1250,11 @@
<number>5</number> <number>5</number>
</property> </property>
<item> <item>
<widget class="QComboBox" name="XBox"/> <widget class="QPushButton" name="SquareButton">
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -1166,7 +1262,7 @@
<item> <item>
<widget class="QGroupBox" name="gb_circle"> <widget class="QGroupBox" name="gb_circle">
<property name="title"> <property name="title">
<string>Circle / B</string> <string>Circle</string>
</property> </property>
<layout class="QVBoxLayout" name="gb_circle_layout"> <layout class="QVBoxLayout" name="gb_circle_layout">
<property name="leftMargin"> <property name="leftMargin">
@ -1182,7 +1278,11 @@
<number>5</number> <number>5</number>
</property> </property>
<item> <item>
<widget class="QComboBox" name="BBox"/> <widget class="QPushButton" name="CircleButton">
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -1208,7 +1308,7 @@
<widget class="QGroupBox" name="gb_cross"> <widget class="QGroupBox" name="gb_cross">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>124</width> <width>152</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
@ -1219,11 +1319,15 @@
</size> </size>
</property> </property>
<property name="title"> <property name="title">
<string>Cross / A</string> <string>Cross</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_5"> <layout class="QVBoxLayout" name="verticalLayout_5">
<item> <item>
<widget class="QComboBox" name="ABox"/> <widget class="QPushButton" name="CrossButton">
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -1361,7 +1465,7 @@
</property> </property>
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>124</width> <width>152</width>
<height>1231321</height> <height>1231321</height>
</size> </size>
</property> </property>
@ -1370,9 +1474,9 @@
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_7"> <layout class="QVBoxLayout" name="verticalLayout_7">
<item> <item>
<widget class="QComboBox" name="RStickUpBox"> <widget class="QPushButton" name="RStickUpButton">
<property name="enabled"> <property name="text">
<bool>true</bool> <string>unmapped</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1403,9 +1507,9 @@
<number>5</number> <number>5</number>
</property> </property>
<item> <item>
<widget class="QComboBox" name="RStickLeftBox"> <widget class="QPushButton" name="RStickLeftButton">
<property name="enabled"> <property name="text">
<bool>true</bool> <string>unmapped</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1431,7 +1535,11 @@
<number>5</number> <number>5</number>
</property> </property>
<item> <item>
<widget class="QComboBox" name="RStickRightBox"/> <widget class="QPushButton" name="RStickRightButton">
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -1457,7 +1565,7 @@
<widget class="QGroupBox" name="gb_right_stick_down"> <widget class="QGroupBox" name="gb_right_stick_down">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>124</width> <width>152</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
@ -1472,9 +1580,9 @@
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_6"> <layout class="QVBoxLayout" name="verticalLayout_6">
<item> <item>
<widget class="QComboBox" name="RStickDownBox"> <widget class="QPushButton" name="RStickDownButton">
<property name="enabled"> <property name="text">
<bool>true</bool> <string>unmapped</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -3,10 +3,12 @@
#include "elf_viewer.h" #include "elf_viewer.h"
ElfViewer::ElfViewer(QWidget* parent) : QTableWidget(parent) { ElfViewer::ElfViewer(std::shared_ptr<gui_settings> gui_settings, QWidget* parent)
dir_list_std = Config::getElfViewer(); : QTableWidget(parent), m_gui_settings(std::move(gui_settings)) {
for (const auto& str : dir_list_std) {
dir_list.append(QString::fromStdString(str)); list = gui_settings::Var2List(m_gui_settings->GetValue(gui::gen_elfDirs));
for (const auto& str : list) {
dir_list.append(str);
} }
CheckElfFolders(); CheckElfFolders();
@ -55,11 +57,11 @@ void ElfViewer::OpenElfFolder() {
} }
std::ranges::sort(m_elf_list); std::ranges::sort(m_elf_list);
OpenElfFiles(); OpenElfFiles();
dir_list_std.clear(); list.clear();
for (auto dir : dir_list) { for (auto dir : dir_list) {
dir_list_std.push_back(dir.toStdString()); list.push_back(dir);
} }
Config::setElfViewer(dir_list_std); m_gui_settings->SetValue(gui::gen_elfDirs, gui_settings::List2Var(list));
} else { } else {
// qDebug() << "Folder selection canceled."; // qDebug() << "Folder selection canceled.";
} }

View File

@ -11,7 +11,7 @@
class ElfViewer : public QTableWidget { class ElfViewer : public QTableWidget {
Q_OBJECT Q_OBJECT
public: public:
explicit ElfViewer(QWidget* parent = nullptr); explicit ElfViewer(std::shared_ptr<gui_settings> gui_settings, QWidget* parent = nullptr);
QStringList m_elf_list; QStringList m_elf_list;
private: private:
@ -21,7 +21,8 @@ private:
Core::Loader::Elf m_elf_file; Core::Loader::Elf m_elf_file;
QStringList dir_list; QStringList dir_list;
QStringList elf_headers_list; QStringList elf_headers_list;
std::vector<std::string> dir_list_std; QList<QString> list;
std::shared_ptr<gui_settings> m_gui_settings;
void SetTableItem(QTableWidget* game_list, int row, int column, QString itemStr) { void SetTableItem(QTableWidget* game_list, int row, int column, QString itemStr) {
QTableWidgetItem* item = new QTableWidgetItem(); QTableWidgetItem* item = new QTableWidgetItem();

View File

@ -5,11 +5,13 @@
#include "game_grid_frame.h" #include "game_grid_frame.h"
#include "qt_gui/compatibility_info.h" #include "qt_gui/compatibility_info.h"
GameGridFrame::GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get, GameGridFrame::GameGridFrame(std::shared_ptr<gui_settings> gui_settings,
std::shared_ptr<GameInfoClass> game_info_get,
std::shared_ptr<CompatibilityInfoClass> compat_info_get, std::shared_ptr<CompatibilityInfoClass> compat_info_get,
QWidget* parent) QWidget* parent)
: QTableWidget(parent), m_game_info(game_info_get), m_compat_info(compat_info_get) { : QTableWidget(parent), m_gui_settings(std::move(gui_settings)), m_game_info(game_info_get),
icon_size = Config::getIconSizeGrid(); m_compat_info(compat_info_get) {
icon_size = m_gui_settings->GetValue(gui::gg_icon_size).toInt();
windowWidth = parent->width(); windowWidth = parent->width();
this->setShowGrid(false); this->setShowGrid(false);
this->setEditTriggers(QAbstractItemView::NoEditTriggers); this->setEditTriggers(QAbstractItemView::NoEditTriggers);
@ -32,7 +34,9 @@ GameGridFrame::GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get,
connect(this->horizontalScrollBar(), &QScrollBar::valueChanged, this, connect(this->horizontalScrollBar(), &QScrollBar::valueChanged, this,
&GameGridFrame::RefreshGridBackgroundImage); &GameGridFrame::RefreshGridBackgroundImage);
connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) { connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) {
m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, m_compat_info, this, false); m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, m_compat_info,
m_gui_settings, this, false);
PopulateGameGrid(m_game_info->m_games, false);
}); });
} }
@ -74,7 +78,7 @@ void GameGridFrame::onCurrentCellChanged(int currentRow, int currentColumn, int
} }
void GameGridFrame::PlayBackgroundMusic(QString path) { void GameGridFrame::PlayBackgroundMusic(QString path) {
if (path.isEmpty() || !Config::getPlayBGM()) { if (path.isEmpty() || !m_gui_settings->GetValue(gui::gl_playBackgroundMusic).toBool()) {
BackgroundMusicPlayer::getInstance().stopMusic(); BackgroundMusicPlayer::getInstance().stopMusic();
return; return;
} }
@ -86,12 +90,16 @@ void GameGridFrame::PopulateGameGrid(QVector<GameInfo> m_games_search, bool from
this->crtColumn = -1; this->crtColumn = -1;
QVector<GameInfo> m_games_; QVector<GameInfo> m_games_;
this->clearContents(); this->clearContents();
if (fromSearch) if (fromSearch) {
SortByFavorite(&m_games_search);
m_games_ = m_games_search; m_games_ = m_games_search;
else } else {
SortByFavorite(&(m_game_info->m_games));
m_games_ = m_game_info->m_games; m_games_ = m_game_info->m_games;
}
m_games_shared = std::make_shared<QVector<GameInfo>>(m_games_); m_games_shared = std::make_shared<QVector<GameInfo>>(m_games_);
icon_size = Config::getIconSizeGrid(); // update icon size for resize event. icon_size =
m_gui_settings->GetValue(gui::gg_icon_size).toInt(); // update icon size for resize event.
int gamesPerRow = windowWidth / (icon_size + 20); // 2 x cell widget border size. int gamesPerRow = windowWidth / (icon_size + 20); // 2 x cell widget border size.
int row = 0; int row = 0;
@ -107,18 +115,25 @@ void GameGridFrame::PopulateGameGrid(QVector<GameInfo> m_games_search, bool from
for (int i = 0; i < m_games_.size(); i++) { for (int i = 0; i < m_games_.size(); i++) {
QWidget* widget = new QWidget(); QWidget* widget = new QWidget();
QVBoxLayout* layout = new QVBoxLayout(); QVBoxLayout* layout = new QVBoxLayout();
QLabel* image_label = new QLabel();
QWidget* image_container = new QWidget();
image_container->setFixedSize(icon_size, icon_size);
QLabel* image_label = new QLabel(image_container);
QImage icon = m_games_[gameCounter].icon.scaled( QImage icon = m_games_[gameCounter].icon.scaled(
QSize(icon_size, icon_size), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); QSize(icon_size, icon_size), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
image_label->setFixedSize(icon.width(), icon.height()); image_label->setFixedSize(icon.width(), icon.height());
image_label->setPixmap(QPixmap::fromImage(icon)); image_label->setPixmap(QPixmap::fromImage(icon));
image_label->move(0, 0);
SetFavoriteIcon(image_container, m_games_, gameCounter);
QLabel* name_label = new QLabel(QString::fromStdString(m_games_[gameCounter].serial)); QLabel* name_label = new QLabel(QString::fromStdString(m_games_[gameCounter].serial));
name_label->setAlignment(Qt::AlignHCenter); name_label->setAlignment(Qt::AlignHCenter);
layout->addWidget(image_label); layout->addWidget(image_container);
layout->addWidget(name_label); layout->addWidget(name_label);
// Resizing of font-size. // Resizing of font-size.
float fontSize = (Config::getIconSizeGrid() / 5.5f); float fontSize = (m_gui_settings->GetValue(gui::gg_icon_size).toInt() / 5.5f);
QString styleSheet = QString styleSheet =
QString("color: white; font-weight: bold; font-size: %1px;").arg(fontSize); QString("color: white; font-weight: bold; font-size: %1px;").arg(fontSize);
name_label->setStyleSheet(styleSheet); name_label->setStyleSheet(styleSheet);
@ -168,7 +183,7 @@ void GameGridFrame::SetGridBackgroundImage(int row, int column) {
} }
// If background images are hidden, clear the background image // If background images are hidden, clear the background image
if (!Config::getShowBackgroundImage()) { if (!m_gui_settings->GetValue(gui::gl_showBackgroundImage).toBool()) {
backgroundImage = QImage(); backgroundImage = QImage();
m_last_opacity = -1; // Reset opacity tracking when disabled m_last_opacity = -1; // Reset opacity tracking when disabled
m_current_game_path.clear(); // Reset current game path m_current_game_path.clear(); // Reset current game path
@ -177,7 +192,7 @@ void GameGridFrame::SetGridBackgroundImage(int row, int column) {
} }
const auto& game = (*m_games_shared)[itemID]; const auto& game = (*m_games_shared)[itemID];
const int opacity = Config::getBackgroundImageOpacity(); const int opacity = m_gui_settings->GetValue(gui::gl_backgroundImageOpacity).toInt();
// Recompute if opacity changed or we switched to a different game // Recompute if opacity changed or we switched to a different game
if (opacity != m_last_opacity || game.pic_path != m_current_game_path) { if (opacity != m_last_opacity || game.pic_path != m_current_game_path) {
@ -195,7 +210,8 @@ void GameGridFrame::SetGridBackgroundImage(int row, int column) {
void GameGridFrame::RefreshGridBackgroundImage() { void GameGridFrame::RefreshGridBackgroundImage() {
QPalette palette; QPalette palette;
if (!backgroundImage.isNull() && Config::getShowBackgroundImage()) { if (!backgroundImage.isNull() &&
m_gui_settings->GetValue(gui::gl_showBackgroundImage).toBool()) {
QSize widgetSize = size(); QSize widgetSize = size();
QPixmap scaledPixmap = QPixmap scaledPixmap =
QPixmap::fromImage(backgroundImage) QPixmap::fromImage(backgroundImage)
@ -221,3 +237,43 @@ void GameGridFrame::resizeEvent(QResizeEvent* event) {
bool GameGridFrame::IsValidCellSelected() { bool GameGridFrame::IsValidCellSelected() {
return validCellSelected; return validCellSelected;
} }
void GameGridFrame::SetFavoriteIcon(QWidget* parentWidget, QVector<GameInfo> m_games_,
int gameCounter) {
QString serialStr = QString::fromStdString(m_games_[gameCounter].serial);
QList<QString> list = gui_settings::Var2List(m_gui_settings->GetValue(gui::favorites_list));
bool isFavorite = list.contains(serialStr);
QLabel* label = new QLabel(parentWidget);
label->setPixmap(QPixmap(":images/favorite_icon.png")
.scaled(icon_size / 3.8, icon_size / 3.8, Qt::KeepAspectRatio,
Qt::SmoothTransformation));
label->move(icon_size - icon_size / 4, 2);
label->raise();
label->setVisible(isFavorite);
label->setObjectName("favoriteIcon");
}
void GameGridFrame::SortByFavorite(QVector<GameInfo>* game_list) {
std::sort(game_list->begin(), game_list->end(), [this](const GameInfo& a, const GameInfo& b) {
return this->CompareWithFavorite(a, b);
});
}
bool GameGridFrame::CompareWithFavorite(GameInfo a, GameInfo b) {
std::string serial_a = a.serial;
std::string serial_b = b.serial;
QString serialStr_a = QString::fromStdString(a.serial);
QString serialStr_b = QString::fromStdString(b.serial);
QList<QString> list = gui_settings::Var2List(m_gui_settings->GetValue(gui::favorites_list));
bool isFavorite_a = list.contains(serialStr_a);
bool isFavorite_b = list.contains(serialStr_b);
if (isFavorite_a != isFavorite_b) {
return isFavorite_a;
} else {
std::string name_a = a.name, name_b = b.name;
std::transform(name_a.begin(), name_a.end(), name_a.begin(), ::tolower);
std::transform(name_b.begin(), name_b.end(), name_b.begin(), ::tolower);
return name_a < name_b;
}
}

View File

@ -11,6 +11,7 @@
#include "game_info.h" #include "game_info.h"
#include "game_list_utils.h" #include "game_list_utils.h"
#include "gui_context_menus.h" #include "gui_context_menus.h"
#include "gui_settings.h"
#include "qt_gui/compatibility_info.h" #include "qt_gui/compatibility_info.h"
class GameGridFrame : public QTableWidget { class GameGridFrame : public QTableWidget {
@ -37,13 +38,18 @@ private:
bool validCellSelected = false; bool validCellSelected = false;
int m_last_opacity = -1; // Track last opacity to avoid unnecessary recomputation int m_last_opacity = -1; // Track last opacity to avoid unnecessary recomputation
std::filesystem::path m_current_game_path; // Track current game path to detect changes std::filesystem::path m_current_game_path; // Track current game path to detect changes
std::shared_ptr<gui_settings> m_gui_settings;
void SetFavoriteIcon(QWidget* parentWidget, QVector<GameInfo> m_games_, int gameCounter);
bool CompareWithFavorite(GameInfo a, GameInfo b);
public: public:
explicit GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get, explicit GameGridFrame(std::shared_ptr<gui_settings> gui_settings,
std::shared_ptr<GameInfoClass> game_info_get,
std::shared_ptr<CompatibilityInfoClass> compat_info_get, std::shared_ptr<CompatibilityInfoClass> compat_info_get,
QWidget* parent = nullptr); QWidget* parent = nullptr);
void PopulateGameGrid(QVector<GameInfo> m_games, bool fromSearch); void PopulateGameGrid(QVector<GameInfo> m_games, bool fromSearch);
bool IsValidCellSelected(); bool IsValidCellSelected();
void SortByFavorite(QVector<GameInfo>* game_list);
bool cellClicked = false; bool cellClicked = false;
int icon_size; int icon_size;

View File

@ -9,11 +9,14 @@
#include "game_list_frame.h" #include "game_list_frame.h"
#include "game_list_utils.h" #include "game_list_utils.h"
GameListFrame::GameListFrame(std::shared_ptr<GameInfoClass> game_info_get, GameListFrame::GameListFrame(std::shared_ptr<gui_settings> gui_settings,
std::shared_ptr<GameInfoClass> game_info_get,
std::shared_ptr<CompatibilityInfoClass> compat_info_get, std::shared_ptr<CompatibilityInfoClass> compat_info_get,
QWidget* parent) QWidget* parent)
: QTableWidget(parent), m_game_info(game_info_get), m_compat_info(compat_info_get) { : QTableWidget(parent), m_gui_settings(std::move(gui_settings)), m_game_info(game_info_get),
icon_size = Config::getIconSize(); m_compat_info(compat_info_get) {
icon_size = m_gui_settings->GetValue(gui::gl_icon_size).toInt();
last_favorite = "";
this->setShowGrid(false); this->setShowGrid(false);
this->setEditTriggers(QAbstractItemView::NoEditTriggers); this->setEditTriggers(QAbstractItemView::NoEditTriggers);
this->setSelectionBehavior(QAbstractItemView::SelectRows); this->setSelectionBehavior(QAbstractItemView::SelectRows);
@ -28,9 +31,8 @@ GameListFrame::GameListFrame(std::shared_ptr<GameInfoClass> game_info_get,
this->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); this->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
this->horizontalHeader()->setHighlightSections(false); this->horizontalHeader()->setHighlightSections(false);
this->horizontalHeader()->setSortIndicatorShown(true); this->horizontalHeader()->setSortIndicatorShown(true);
this->horizontalHeader()->setStretchLastSection(true);
this->setContextMenuPolicy(Qt::CustomContextMenu); this->setContextMenuPolicy(Qt::CustomContextMenu);
this->setColumnCount(10); this->setColumnCount(11);
this->setColumnWidth(1, 300); // Name this->setColumnWidth(1, 300); // Name
this->setColumnWidth(2, 140); // Compatibility this->setColumnWidth(2, 140); // Compatibility
this->setColumnWidth(3, 120); // Serial this->setColumnWidth(3, 120); // Serial
@ -39,14 +41,18 @@ GameListFrame::GameListFrame(std::shared_ptr<GameInfoClass> game_info_get,
this->setColumnWidth(6, 90); // Size this->setColumnWidth(6, 90); // Size
this->setColumnWidth(7, 90); // Version this->setColumnWidth(7, 90); // Version
this->setColumnWidth(8, 120); // Play Time this->setColumnWidth(8, 120); // Play Time
this->setColumnWidth(10, 90); // Favorite
QStringList headers; QStringList headers;
headers << tr("Icon") << tr("Name") << tr("Compatibility") << tr("Serial") << tr("Region") headers << tr("Icon") << tr("Name") << tr("Compatibility") << tr("Serial") << tr("Region")
<< tr("Firmware") << tr("Size") << tr("Version") << tr("Play Time") << tr("Path"); << tr("Firmware") << tr("Size") << tr("Version") << tr("Play Time") << tr("Path")
<< tr("Favorite");
this->setHorizontalHeaderLabels(headers); this->setHorizontalHeaderLabels(headers);
this->horizontalHeader()->setSortIndicatorShown(true); this->horizontalHeader()->setSortIndicatorShown(true);
this->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); this->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
this->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed); this->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed);
this->horizontalHeader()->setSectionResizeMode(4, QHeaderView::Fixed); this->horizontalHeader()->setSectionResizeMode(4, QHeaderView::Fixed);
this->horizontalHeader()->setSectionResizeMode(9, QHeaderView::Stretch);
this->horizontalHeader()->setSectionResizeMode(10, QHeaderView::Fixed);
PopulateGameList(); PopulateGameList();
connect(this, &QTableWidget::currentCellChanged, this, &GameListFrame::onCurrentCellChanged); connect(this, &QTableWidget::currentCellChanged, this, &GameListFrame::onCurrentCellChanged);
@ -63,17 +69,24 @@ GameListFrame::GameListFrame(std::shared_ptr<GameInfoClass> game_info_get,
SortNameDescending(columnIndex); SortNameDescending(columnIndex);
this->horizontalHeader()->setSortIndicator(columnIndex, Qt::DescendingOrder); this->horizontalHeader()->setSortIndicator(columnIndex, Qt::DescendingOrder);
ListSortedAsc = false; ListSortedAsc = false;
sortColumn = columnIndex;
} else { } else {
SortNameAscending(columnIndex); SortNameAscending(columnIndex);
this->horizontalHeader()->setSortIndicator(columnIndex, Qt::AscendingOrder); this->horizontalHeader()->setSortIndicator(columnIndex, Qt::AscendingOrder);
ListSortedAsc = true; ListSortedAsc = true;
sortColumn = columnIndex;
} }
this->clearContents(); this->clearContents();
PopulateGameList(false); PopulateGameList(false);
}); });
connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) { connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) {
m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, m_compat_info, this, true); int changedFavorite = m_gui_context_menus.RequestGameMenu(
pos, m_game_info->m_games, m_compat_info, m_gui_settings, this, true);
if (changedFavorite) {
last_favorite = m_game_info->m_games[this->currentRow()].serial;
PopulateGameList(false);
}
}); });
connect(this, &QTableWidget::cellClicked, this, [=, this](int row, int column) { connect(this, &QTableWidget::cellClicked, this, [=, this](int row, int column) {
@ -81,6 +94,19 @@ GameListFrame::GameListFrame(std::shared_ptr<GameInfoClass> game_info_get,
auto url_issues = "https://github.com/shadps4-emu/shadps4-game-compatibility/issues/"; auto url_issues = "https://github.com/shadps4-emu/shadps4-game-compatibility/issues/";
QDesktopServices::openUrl( QDesktopServices::openUrl(
QUrl(url_issues + m_game_info->m_games[row].compatibility.issue_number)); QUrl(url_issues + m_game_info->m_games[row].compatibility.issue_number));
} else if (column == 10) {
last_favorite = m_game_info->m_games[row].serial;
QString serialStr = QString::fromStdString(last_favorite);
QList<QString> list =
gui_settings::Var2List(m_gui_settings->GetValue(gui::favorites_list));
bool isFavorite = list.contains(serialStr);
if (isFavorite) {
list.removeOne(serialStr);
} else {
list.append(serialStr);
}
m_gui_settings->SetValue(gui::favorites_list, gui_settings::List2Var(list));
PopulateGameList(false);
} }
}); });
} }
@ -97,7 +123,7 @@ void GameListFrame::onCurrentCellChanged(int currentRow, int currentColumn, int
} }
void GameListFrame::PlayBackgroundMusic(QTableWidgetItem* item) { void GameListFrame::PlayBackgroundMusic(QTableWidgetItem* item) {
if (!item || !Config::getPlayBGM()) { if (!item || !m_gui_settings->GetValue(gui::gl_playBackgroundMusic).toBool()) {
BackgroundMusicPlayer::getInstance().stopMusic(); BackgroundMusicPlayer::getInstance().stopMusic();
return; return;
} }
@ -115,10 +141,7 @@ void GameListFrame::PopulateGameList(bool isInitialPopulation) {
this->setRowCount(m_game_info->m_games.size()); this->setRowCount(m_game_info->m_games.size());
ResizeIcons(icon_size); ResizeIcons(icon_size);
if (isInitialPopulation) { ApplyLastSorting(isInitialPopulation);
SortNameAscending(1); // Column 1 = Name
ResizeIcons(icon_size);
}
for (int i = 0; i < m_game_info->m_games.size(); i++) { for (int i = 0; i < m_game_info->m_games.size(); i++) {
SetTableItem(i, 1, QString::fromStdString(m_game_info->m_games[i].name)); SetTableItem(i, 1, QString::fromStdString(m_game_info->m_games[i].name));
@ -127,6 +150,11 @@ void GameListFrame::PopulateGameList(bool isInitialPopulation) {
SetTableItem(i, 5, QString::fromStdString(m_game_info->m_games[i].fw)); SetTableItem(i, 5, QString::fromStdString(m_game_info->m_games[i].fw));
SetTableItem(i, 6, QString::fromStdString(m_game_info->m_games[i].size)); SetTableItem(i, 6, QString::fromStdString(m_game_info->m_games[i].size));
SetTableItem(i, 7, QString::fromStdString(m_game_info->m_games[i].version)); SetTableItem(i, 7, QString::fromStdString(m_game_info->m_games[i].version));
SetFavoriteIcon(i, 10);
if (m_game_info->m_games[i].serial == last_favorite && !isInitialPopulation) {
this->setCurrentCell(i, 10);
}
m_game_info->m_games[i].compatibility = m_game_info->m_games[i].compatibility =
m_compat_info->GetCompatibilityInfo(m_game_info->m_games[i].serial); m_compat_info->GetCompatibilityInfo(m_game_info->m_games[i].serial);
@ -172,7 +200,7 @@ void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) {
} }
// If background images are hidden, clear the background image // If background images are hidden, clear the background image
if (!Config::getShowBackgroundImage()) { if (!m_gui_settings->GetValue(gui::gl_showBackgroundImage).toBool()) {
backgroundImage = QImage(); backgroundImage = QImage();
m_last_opacity = -1; // Reset opacity tracking when disabled m_last_opacity = -1; // Reset opacity tracking when disabled
m_current_game_path.clear(); // Reset current game path m_current_game_path.clear(); // Reset current game path
@ -181,7 +209,7 @@ void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) {
} }
const auto& game = m_game_info->m_games[item->row()]; const auto& game = m_game_info->m_games[item->row()];
const int opacity = Config::getBackgroundImageOpacity(); const int opacity = m_gui_settings->GetValue(gui::gl_backgroundImageOpacity).toInt();
// Recompute if opacity changed or we switched to a different game // Recompute if opacity changed or we switched to a different game
if (opacity != m_last_opacity || game.pic_path != m_current_game_path) { if (opacity != m_last_opacity || game.pic_path != m_current_game_path) {
@ -200,7 +228,8 @@ void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) {
void GameListFrame::RefreshListBackgroundImage() { void GameListFrame::RefreshListBackgroundImage() {
QPalette palette; QPalette palette;
if (!backgroundImage.isNull() && Config::getShowBackgroundImage()) { if (!backgroundImage.isNull() &&
m_gui_settings->GetValue(gui::gl_showBackgroundImage).toBool()) {
QSize widgetSize = size(); QSize widgetSize = size();
QPixmap scaledPixmap = QPixmap scaledPixmap =
QPixmap::fromImage(backgroundImage) QPixmap::fromImage(backgroundImage)
@ -223,20 +252,50 @@ void GameListFrame::resizeEvent(QResizeEvent* event) {
RefreshListBackgroundImage(); RefreshListBackgroundImage();
} }
bool GameListFrame::CompareWithFavorite(GameInfo a, GameInfo b, int columnIndex, bool ascending) {
std::string serial_a = a.serial;
std::string serial_b = b.serial;
QString serialStr_a = QString::fromStdString(a.serial);
QString serialStr_b = QString::fromStdString(b.serial);
QList<QString> list = gui_settings::Var2List(m_gui_settings->GetValue(gui::favorites_list));
bool isFavorite_a = list.contains(serialStr_a);
bool isFavorite_b = list.contains(serialStr_b);
if (isFavorite_a != isFavorite_b) {
return isFavorite_a;
} else if (ascending) {
return CompareStringsAscending(a, b, columnIndex);
} else {
return CompareStringsDescending(a, b, columnIndex);
}
}
void GameListFrame::SortNameAscending(int columnIndex) { void GameListFrame::SortNameAscending(int columnIndex) {
std::sort(m_game_info->m_games.begin(), m_game_info->m_games.end(), std::sort(m_game_info->m_games.begin(), m_game_info->m_games.end(),
[columnIndex](const GameInfo& a, const GameInfo& b) { [this, columnIndex](const GameInfo& a, const GameInfo& b) {
return CompareStringsAscending(a, b, columnIndex); return this->CompareWithFavorite(a, b, columnIndex, true);
}); });
} }
void GameListFrame::SortNameDescending(int columnIndex) { void GameListFrame::SortNameDescending(int columnIndex) {
std::sort(m_game_info->m_games.begin(), m_game_info->m_games.end(), std::sort(m_game_info->m_games.begin(), m_game_info->m_games.end(),
[columnIndex](const GameInfo& a, const GameInfo& b) { [this, columnIndex](const GameInfo& a, const GameInfo& b) {
return CompareStringsDescending(a, b, columnIndex); return this->CompareWithFavorite(a, b, columnIndex, false);
}); });
} }
void GameListFrame::ApplyLastSorting(bool isInitialPopulation) {
if (isInitialPopulation) {
SortNameAscending(1); // Column 1 = Name
ResizeIcons(icon_size);
} else if (ListSortedAsc) {
SortNameAscending(sortColumn);
ResizeIcons(icon_size);
} else {
SortNameDescending(sortColumn);
ResizeIcons(icon_size);
}
}
void GameListFrame::ResizeIcons(int iconSize) { void GameListFrame::ResizeIcons(int iconSize) {
for (int index = 0; auto& game : m_game_info->m_games) { for (int index = 0; auto& game : m_game_info->m_games) {
QImage scaledPixmap = game.icon.scaled(QSize(iconSize, iconSize), Qt::KeepAspectRatio, QImage scaledPixmap = game.icon.scaled(QSize(iconSize, iconSize), Qt::KeepAspectRatio,
@ -387,6 +446,35 @@ void GameListFrame::SetRegionFlag(int row, int column, QString itemStr) {
this->setCellWidget(row, column, widget); this->setCellWidget(row, column, widget);
} }
void GameListFrame::SetFavoriteIcon(int row, int column) {
QString serialStr = QString::fromStdString(m_game_info->m_games[row].serial);
QList<QString> list = gui_settings::Var2List(m_gui_settings->GetValue(gui::favorites_list));
bool isFavorite = list.contains(serialStr);
QTableWidgetItem* item = new QTableWidgetItem();
QImage scaledPixmap = QImage(":images/favorite_icon.png");
scaledPixmap = scaledPixmap.scaledToHeight(this->columnWidth(column) / 2.5);
scaledPixmap = scaledPixmap.scaledToWidth(this->columnWidth(column) / 2.5);
QWidget* widget = new QWidget(this);
QVBoxLayout* layout = new QVBoxLayout(widget);
QLabel* label = new QLabel(widget);
label->setPixmap(QPixmap::fromImage(scaledPixmap));
label->setObjectName("favoriteIcon");
label->setVisible(isFavorite);
layout->setAlignment(Qt::AlignCenter);
layout->addWidget(label);
widget->setLayout(layout);
this->setItem(row, column, item);
this->setCellWidget(row, column, widget);
if (column > 0) {
this->horizontalHeader()->setSectionResizeMode(column - 1, QHeaderView::Stretch);
}
}
QString GameListFrame::GetPlayTime(const std::string& serial) { QString GameListFrame::GetPlayTime(const std::string& serial) {
QString playTime; QString playTime;
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
@ -419,4 +507,4 @@ QString GameListFrame::GetPlayTime(const std::string& serial) {
QTableWidgetItem* GameListFrame::GetCurrentItem() { QTableWidgetItem* GameListFrame::GetCurrentItem() {
return m_current_item; return m_current_item;
} }

View File

@ -17,11 +17,13 @@
#include "game_info.h" #include "game_info.h"
#include "game_list_utils.h" #include "game_list_utils.h"
#include "gui_context_menus.h" #include "gui_context_menus.h"
#include "gui_settings.h"
class GameListFrame : public QTableWidget { class GameListFrame : public QTableWidget {
Q_OBJECT Q_OBJECT
public: public:
explicit GameListFrame(std::shared_ptr<GameInfoClass> game_info_get, explicit GameListFrame(std::shared_ptr<gui_settings> gui_settings,
std::shared_ptr<GameInfoClass> game_info_get,
std::shared_ptr<CompatibilityInfoClass> compat_info_get, std::shared_ptr<CompatibilityInfoClass> compat_info_get,
QWidget* parent = nullptr); QWidget* parent = nullptr);
Q_SIGNALS: Q_SIGNALS:
@ -40,18 +42,22 @@ public Q_SLOTS:
private: private:
void SetTableItem(int row, int column, QString itemStr); void SetTableItem(int row, int column, QString itemStr);
void SetRegionFlag(int row, int column, QString itemStr); void SetRegionFlag(int row, int column, QString itemStr);
void SetFavoriteIcon(int row, int column);
void SetCompatibilityItem(int row, int column, CompatibilityEntry entry); void SetCompatibilityItem(int row, int column, CompatibilityEntry entry);
QString GetPlayTime(const std::string& serial); QString GetPlayTime(const std::string& serial);
QList<QAction*> m_columnActs; QList<QAction*> m_columnActs;
GameInfoClass* game_inf_get = nullptr; GameInfoClass* game_inf_get = nullptr;
bool ListSortedAsc = true; bool ListSortedAsc = true;
int sortColumn = 1;
QTableWidgetItem* m_current_item = nullptr; QTableWidgetItem* m_current_item = nullptr;
int m_last_opacity = -1; // Track last opacity to avoid unnecessary recomputation int m_last_opacity = -1; // Track last opacity to avoid unnecessary recomputation
std::filesystem::path m_current_game_path; // Track current game path to detect changes std::filesystem::path m_current_game_path; // Track current game path to detect changes
std::shared_ptr<gui_settings> m_gui_settings;
public: public:
void PopulateGameList(bool isInitialPopulation = true); void PopulateGameList(bool isInitialPopulation = true);
void ResizeIcons(int iconSize); void ResizeIcons(int iconSize);
void ApplyLastSorting(bool isInitialPopulation);
QTableWidgetItem* GetCurrentItem(); QTableWidgetItem* GetCurrentItem();
QImage backgroundImage; QImage backgroundImage;
GameListUtils m_game_list_utils; GameListUtils m_game_list_utils;
@ -60,6 +66,7 @@ public:
std::shared_ptr<CompatibilityInfoClass> m_compat_info; std::shared_ptr<CompatibilityInfoClass> m_compat_info;
int icon_size; int icon_size;
std::string last_favorite;
static float parseAsFloat(const std::string& str, const int& offset) { static float parseAsFloat(const std::string& str, const int& offset) {
return std::stof(str.substr(0, str.size() - offset)); return std::stof(str.substr(0, str.size() - offset));
@ -127,4 +134,6 @@ public:
return false; return false;
} }
} }
bool CompareWithFavorite(GameInfo a, GameInfo b, int columnIndex, bool ascending);
}; };

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