mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-04-21 12:04:45 +00:00
Merge branch 'shadps4-emu:main' into patch-321
This commit is contained in:
commit
3c277f0c53
237 changed files with 12616 additions and 3615 deletions
BIN
.github/shadps4.png
vendored
BIN
.github/shadps4.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 197 KiB After Width: | Height: | Size: 130 KiB |
5
.github/workflows/linux-qt.yml
vendored
5
.github/workflows/linux-qt.yml
vendored
|
@ -10,7 +10,6 @@ on:
|
|||
branches: [ "main" ]
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
|
@ -19,8 +18,8 @@ jobs:
|
|||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Fetch submodules
|
||||
run: git submodule update --init --recursive
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install misc packages
|
||||
run: >
|
||||
|
|
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
|
@ -8,10 +8,8 @@ on:
|
|||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
|
|
5
.github/workflows/macos-qt.yml
vendored
5
.github/workflows/macos-qt.yml
vendored
|
@ -8,10 +8,8 @@ on:
|
|||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
|
@ -36,10 +34,11 @@ jobs:
|
|||
- name: Setup Qt
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: 6.7.2
|
||||
host: mac
|
||||
target: desktop
|
||||
arch: clang_64
|
||||
version: 6.7.2
|
||||
archives: qtbase
|
||||
|
||||
- name: Configure CMake
|
||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DENABLE_QT_GUI=ON
|
||||
|
|
2
.github/workflows/macos.yml
vendored
2
.github/workflows/macos.yml
vendored
|
@ -8,10 +8,8 @@ on:
|
|||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
|
|
12
.github/workflows/windows-qt.yml
vendored
12
.github/workflows/windows-qt.yml
vendored
|
@ -10,12 +10,8 @@ on:
|
|||
branches: [ "main" ]
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
|
@ -28,16 +24,16 @@ jobs:
|
|||
- name: Setup Qt
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
arch: win64_msvc2019_64
|
||||
version: 6.7.2
|
||||
host: windows
|
||||
target: desktop
|
||||
arch: win64_msvc2019_64
|
||||
archives: qtbase
|
||||
|
||||
- name: Configure CMake
|
||||
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
|
||||
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
|
||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -T ClangCL -DENABLE_QT_GUI=ON
|
||||
|
||||
- name: Build
|
||||
# Build your program with the given configuration
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel
|
||||
|
||||
- name: Deploy
|
||||
|
|
12
.github/workflows/windows.yml
vendored
12
.github/workflows/windows.yml
vendored
|
@ -10,12 +10,8 @@ on:
|
|||
branches: [ "main" ]
|
||||
|
||||
env:
|
||||
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
|
||||
BUILD_TYPE: Release
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
|
@ -25,16 +21,14 @@ jobs:
|
|||
submodules: recursive
|
||||
|
||||
- name: Configure CMake
|
||||
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
|
||||
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
|
||||
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -T ClangCL
|
||||
|
||||
- name: Build
|
||||
# Build your program with the given configuration
|
||||
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel
|
||||
- name: Upload a Build Artifact
|
||||
|
||||
- name: Upload executable
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: shadps4-win64
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
path: |
|
||||
${{github.workspace}}/build/Release/shadPS4.exe
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -408,3 +408,4 @@ FodyWeavers.xsd
|
|||
/emulator/eboot.bin
|
||||
/out/*
|
||||
/third-party/out/*
|
||||
/src/common/scm_rev.cpp
|
||||
|
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -61,3 +61,6 @@
|
|||
[submodule "externals/date"]
|
||||
path = externals/date
|
||||
url = https://github.com/HowardHinnant/date.git
|
||||
[submodule "externals/ffmpeg-core"]
|
||||
path = externals/ffmpeg-core
|
||||
url = https://github.com/shadps4-emu/ext-ffmpeg-core
|
||||
|
|
19
.reuse/dep5
19
.reuse/dep5
|
@ -14,8 +14,6 @@ Files: CMakeSettings.json
|
|||
documents/Screenshots/Sonic Mania.png
|
||||
documents/Screenshots/Undertale.png
|
||||
documents/Screenshots/We are DOOMED.png
|
||||
externals/stb_image.h
|
||||
externals/tracy/*
|
||||
scripts/ps4_names.txt
|
||||
src/images/controller_icon.png
|
||||
src/images/exit_icon.png
|
||||
|
@ -36,9 +34,26 @@ Files: CMakeSettings.json
|
|||
src/images/refresh_icon.png
|
||||
src/images/settings_icon.png
|
||||
src/images/stop_icon.png
|
||||
src/images/shadPS4.icns
|
||||
src/images/shadps4.ico
|
||||
src/images/themes_icon.png
|
||||
src/shadps4.qrc
|
||||
src/shadps4.rc
|
||||
Copyright: shadPS4 Emulator Project
|
||||
License: GPL-2.0-or-later
|
||||
|
||||
Files: externals/cmake-modules/*
|
||||
Copyright: 2009-2010 Iowa State University
|
||||
License: BSL-1.0
|
||||
|
||||
Files: externals/renderdoc/*
|
||||
Copyright: 2019-2024 Baldur Karlsson
|
||||
License: MIT
|
||||
|
||||
Files: externals/stb_image.h
|
||||
Copyright: 2017 Sean Barrett
|
||||
License: MIT
|
||||
|
||||
Files: externals/tracy/*
|
||||
Copyright: 2017-2024 Bartosz Taudul <wolf@nereid.pl>
|
||||
License: BSD-3-Clause
|
||||
|
|
150
CMakeLists.txt
150
CMakeLists.txt
|
@ -19,12 +19,11 @@ project(shadPS4)
|
|||
|
||||
option(ENABLE_QT_GUI "Enable the Qt GUI. If not selected then the emulator uses a minimal SDL-based UI instead" OFF)
|
||||
|
||||
# This function should be passed a list of all files in a target. It will automatically generate
|
||||
# file groups following the directory hierarchy, so that the layout of the files in IDEs matches the
|
||||
# one in the filesystem.
|
||||
# This function should be passed a list of all files in a target. It will automatically generate file groups
|
||||
# following the directory hierarchy, so that the layout of the files in IDEs matches the one in the filesystem.
|
||||
function(create_target_directory_groups target_name)
|
||||
# Place any files that aren't in the source list in a separate group so that they don't get in
|
||||
# the way.
|
||||
|
||||
# Place any files that aren't in the source list in a separate group so that they don't get in the way.
|
||||
source_group("Other Files" REGULAR_EXPRESSION ".")
|
||||
|
||||
get_target_property(target_sources "${target_name}" SOURCES)
|
||||
|
@ -39,14 +38,6 @@ endfunction()
|
|||
|
||||
# Setup a custom clang-format target (if clang-format can be found) that will run
|
||||
# against all the src files. This should be used before making a pull request.
|
||||
# =======================================================================
|
||||
|
||||
set(CLANG_FORMAT_POSTFIX "-17")
|
||||
find_program(CLANG_FORMAT
|
||||
NAMES clang-format${CLANG_FORMAT_POSTFIX}
|
||||
clang-format
|
||||
PATHS ${PROJECT_BINARY_DIR}/externals)
|
||||
|
||||
if (CLANG_FORMAT)
|
||||
set(SRCS ${PROJECT_SOURCE_DIR}/src)
|
||||
set(CCOMMENT "Running clang format against all the .h and .cpp files in src/")
|
||||
|
@ -65,6 +56,15 @@ endif()
|
|||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
|
||||
# generate git revision information
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules/")
|
||||
include(GetGitRevisionDescription)
|
||||
get_git_head_revision(GIT_REF_SPEC GIT_REV)
|
||||
git_describe(GIT_DESC --always --long --dirty)
|
||||
git_branch_name(GIT_BRANCH)
|
||||
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp" @ONLY)
|
||||
|
||||
find_package(Boost 1.84.0 CONFIG)
|
||||
find_package(cryptopp 8.9.0 MODULE)
|
||||
find_package(fmt 10.2.1 CONFIG)
|
||||
|
@ -79,17 +79,31 @@ find_package(xbyak 7.07 CONFIG)
|
|||
find_package(xxHash 0.8.2 MODULE)
|
||||
find_package(zlib-ng 2.2.0 MODULE)
|
||||
find_package(Zydis 4.1.0 CONFIG)
|
||||
find_package(RenderDoc MODULE)
|
||||
|
||||
if (APPLE)
|
||||
find_package(date 3.0.1 CONFIG)
|
||||
endif()
|
||||
|
||||
# Note: Windows always has these functions through winpthreads
|
||||
include(CheckSymbolExists)
|
||||
check_symbol_exists(pthread_mutex_timedlock "pthread.h" HAVE_PTHREAD_MUTEX_TIMEDLOCK)
|
||||
# Windows always has the function through winpthreads
|
||||
if(HAVE_PTHREAD_MUTEX_TIMEDLOCK OR WIN32)
|
||||
add_compile_options(-DHAVE_PTHREAD_MUTEX_TIMEDLOCK)
|
||||
endif()
|
||||
check_symbol_exists(sem_timedwait "semaphore.h" HAVE_SEM_TIMEDWAIT)
|
||||
if(HAVE_SEM_TIMEDWAIT OR WIN32)
|
||||
add_compile_options(-DHAVE_SEM_TIMEDWAIT)
|
||||
endif()
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
|
||||
# libc++ requires -fexperimental-library to enable std::jthread and std::stop_token support.
|
||||
include(CheckCXXSymbolExists)
|
||||
check_cxx_symbol_exists(_LIBCPP_VERSION version LIBCPP)
|
||||
if(LIBCPP)
|
||||
add_compile_options(-fexperimental-library)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_subdirectory(externals)
|
||||
include_directories(src)
|
||||
|
@ -98,9 +112,11 @@ if(ENABLE_QT_GUI)
|
|||
find_package(Qt6 REQUIRED COMPONENTS Widgets Concurrent)
|
||||
qt_standard_project_setup()
|
||||
set(CMAKE_AUTORCC ON)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
endif()
|
||||
|
||||
set(AUDIO_CORE src/audio_core/sdl_audio.cpp
|
||||
set(AUDIO_CORE src/audio_core/sdl_audio.cpp
|
||||
src/audio_core/sdl_audio.h
|
||||
)
|
||||
|
||||
|
@ -177,6 +193,16 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp
|
|||
src/core/libraries/disc_map/disc_map.cpp
|
||||
src/core/libraries/disc_map/disc_map.h
|
||||
src/core/libraries/disc_map/disc_map_codes.h
|
||||
src/core/libraries/avplayer/avplayer_common.cpp
|
||||
src/core/libraries/avplayer/avplayer_common.h
|
||||
src/core/libraries/avplayer/avplayer_file_streamer.cpp
|
||||
src/core/libraries/avplayer/avplayer_file_streamer.h
|
||||
src/core/libraries/avplayer/avplayer_impl.cpp
|
||||
src/core/libraries/avplayer/avplayer_impl.h
|
||||
src/core/libraries/avplayer/avplayer_source.cpp
|
||||
src/core/libraries/avplayer/avplayer_source.h
|
||||
src/core/libraries/avplayer/avplayer_state.cpp
|
||||
src/core/libraries/avplayer/avplayer_state.h
|
||||
src/core/libraries/avplayer/avplayer.cpp
|
||||
src/core/libraries/avplayer/avplayer.h
|
||||
)
|
||||
|
@ -226,6 +252,10 @@ set(PLAYGO_LIB src/core/libraries/playgo/playgo.cpp
|
|||
src/core/libraries/playgo/playgo_types.h
|
||||
)
|
||||
|
||||
set(RANDOM_LIB src/core/libraries/random/random.cpp
|
||||
src/core/libraries/random/random.h
|
||||
)
|
||||
|
||||
set(USBD_LIB src/core/libraries/usbd/usbd.cpp
|
||||
src/core/libraries/usbd/usbd.h
|
||||
)
|
||||
|
@ -274,6 +304,7 @@ set(COMMON src/common/logging/backend.cpp
|
|||
src/common/native_clock.h
|
||||
src/common/path_util.cpp
|
||||
src/common/path_util.h
|
||||
src/common/object_pool.h
|
||||
src/common/polyfill_thread.h
|
||||
src/common/rdtsc.cpp
|
||||
src/common/rdtsc.h
|
||||
|
@ -285,9 +316,12 @@ set(COMMON src/common/logging/backend.cpp
|
|||
src/common/thread.h
|
||||
src/common/types.h
|
||||
src/common/uint128.h
|
||||
src/common/unique_function.h
|
||||
src/common/version.h
|
||||
src/common/ntapi.h
|
||||
src/common/ntapi.cpp
|
||||
src/common/scm_rev.cpp
|
||||
src/common/scm_rev.h
|
||||
)
|
||||
|
||||
set(CORE src/core/aerolib/stubs.cpp
|
||||
|
@ -308,6 +342,8 @@ set(CORE src/core/aerolib/stubs.cpp
|
|||
src/core/file_format/pkg_type.h
|
||||
src/core/file_format/psf.cpp
|
||||
src/core/file_format/psf.h
|
||||
src/core/file_format/playgo_chunk.cpp
|
||||
src/core/file_format/playgo_chunk.h
|
||||
src/core/file_format/trp.cpp
|
||||
src/core/file_format/trp.h
|
||||
src/core/file_format/splash.h
|
||||
|
@ -336,6 +372,7 @@ set(CORE src/core/aerolib/stubs.cpp
|
|||
${NP_LIBS}
|
||||
${PNG_LIB}
|
||||
${PLAYGO_LIB}
|
||||
${RANDOM_LIB}
|
||||
${USBD_LIB}
|
||||
${MISC_LIBS}
|
||||
${DIALOGS_LIB}
|
||||
|
@ -353,7 +390,6 @@ set(CORE src/core/aerolib/stubs.cpp
|
|||
)
|
||||
|
||||
set(SHADER_RECOMPILER src/shader_recompiler/exception.h
|
||||
src/shader_recompiler/object_pool.h
|
||||
src/shader_recompiler/profile.h
|
||||
src/shader_recompiler/recompiler.cpp
|
||||
src/shader_recompiler/recompiler.h
|
||||
|
@ -404,6 +440,7 @@ set(SHADER_RECOMPILER src/shader_recompiler/exception.h
|
|||
src/shader_recompiler/ir/passes/dead_code_elimination_pass.cpp
|
||||
src/shader_recompiler/ir/passes/identity_removal_pass.cpp
|
||||
src/shader_recompiler/ir/passes/ir_passes.h
|
||||
src/shader_recompiler/ir/passes/lower_shared_mem_to_registers.cpp
|
||||
src/shader_recompiler/ir/passes/resource_tracking_pass.cpp
|
||||
src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp
|
||||
src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp
|
||||
|
@ -437,6 +474,13 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp
|
|||
src/video_core/amdgpu/pm4_cmds.h
|
||||
src/video_core/amdgpu/pm4_opcodes.h
|
||||
src/video_core/amdgpu/resource.h
|
||||
src/video_core/buffer_cache/buffer.cpp
|
||||
src/video_core/buffer_cache/buffer.h
|
||||
src/video_core/buffer_cache/buffer_cache.cpp
|
||||
src/video_core/buffer_cache/buffer_cache.h
|
||||
src/video_core/buffer_cache/memory_tracker_base.h
|
||||
src/video_core/buffer_cache/range_set.h
|
||||
src/video_core/buffer_cache/word_manager.h
|
||||
src/video_core/renderer_vulkan/liverpool_to_vk.cpp
|
||||
src/video_core/renderer_vulkan/liverpool_to_vk.h
|
||||
src/video_core/renderer_vulkan/renderer_vulkan.cpp
|
||||
|
@ -465,8 +509,6 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp
|
|||
src/video_core/renderer_vulkan/vk_scheduler.h
|
||||
src/video_core/renderer_vulkan/vk_shader_util.cpp
|
||||
src/video_core/renderer_vulkan/vk_shader_util.h
|
||||
src/video_core/renderer_vulkan/vk_stream_buffer.cpp
|
||||
src/video_core/renderer_vulkan/vk_stream_buffer.h
|
||||
src/video_core/renderer_vulkan/vk_swapchain.cpp
|
||||
src/video_core/renderer_vulkan/vk_swapchain.h
|
||||
src/video_core/texture_cache/image.cpp
|
||||
|
@ -482,6 +524,11 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp
|
|||
src/video_core/texture_cache/tile_manager.cpp
|
||||
src/video_core/texture_cache/tile_manager.h
|
||||
src/video_core/texture_cache/types.h
|
||||
src/video_core/page_manager.cpp
|
||||
src/video_core/page_manager.h
|
||||
src/video_core/multi_level_page_table.h
|
||||
src/video_core/renderdoc.cpp
|
||||
src/video_core/renderdoc.h
|
||||
)
|
||||
|
||||
set(INPUT src/input/controller.cpp
|
||||
|
@ -494,37 +541,39 @@ set(EMULATOR src/emulator.cpp
|
|||
src/sdl_window.cpp
|
||||
)
|
||||
|
||||
# the above is shared in sdl and qt version (TODO share them all)
|
||||
# The above is shared in SDL and Qt version (TODO share them all)
|
||||
|
||||
if(ENABLE_QT_GUI)
|
||||
qt_add_resources(RESOURCE_FILES src/shadps4.qrc)
|
||||
|
||||
set(QT_GUI
|
||||
src/qt_gui/main_window_ui.h
|
||||
src/qt_gui/main_window.cpp
|
||||
src/qt_gui/main_window.h
|
||||
src/qt_gui/gui_context_menus.h
|
||||
src/qt_gui/game_list_utils.h
|
||||
src/qt_gui/game_info.cpp
|
||||
src/qt_gui/game_info.h
|
||||
src/qt_gui/game_list_frame.cpp
|
||||
src/qt_gui/game_list_frame.h
|
||||
src/qt_gui/game_grid_frame.cpp
|
||||
src/qt_gui/game_grid_frame.h
|
||||
src/qt_gui/game_install_dialog.cpp
|
||||
src/qt_gui/game_install_dialog.h
|
||||
src/qt_gui/pkg_viewer.cpp
|
||||
src/qt_gui/pkg_viewer.h
|
||||
src/qt_gui/trophy_viewer.cpp
|
||||
src/qt_gui/trophy_viewer.h
|
||||
src/qt_gui/elf_viewer.cpp
|
||||
src/qt_gui/elf_viewer.h
|
||||
src/qt_gui/main_window_themes.cpp
|
||||
src/qt_gui/main_window_themes.h
|
||||
src/qt_gui/main.cpp
|
||||
${EMULATOR}
|
||||
${RESOURCE_FILES}
|
||||
)
|
||||
set(QT_GUI src/qt_gui/main_window_ui.h
|
||||
src/qt_gui/main_window.cpp
|
||||
src/qt_gui/main_window.h
|
||||
src/qt_gui/gui_context_menus.h
|
||||
src/qt_gui/game_list_utils.h
|
||||
src/qt_gui/game_info.cpp
|
||||
src/qt_gui/game_info.h
|
||||
src/qt_gui/game_list_frame.cpp
|
||||
src/qt_gui/game_list_frame.h
|
||||
src/qt_gui/game_grid_frame.cpp
|
||||
src/qt_gui/game_grid_frame.h
|
||||
src/qt_gui/game_install_dialog.cpp
|
||||
src/qt_gui/game_install_dialog.h
|
||||
src/qt_gui/pkg_viewer.cpp
|
||||
src/qt_gui/pkg_viewer.h
|
||||
src/qt_gui/trophy_viewer.cpp
|
||||
src/qt_gui/trophy_viewer.h
|
||||
src/qt_gui/elf_viewer.cpp
|
||||
src/qt_gui/elf_viewer.h
|
||||
src/qt_gui/main_window_themes.cpp
|
||||
src/qt_gui/main_window_themes.h
|
||||
src/qt_gui/settings_dialog.cpp
|
||||
src/qt_gui/settings_dialog.h
|
||||
src/qt_gui/settings_dialog.ui
|
||||
src/qt_gui/main.cpp
|
||||
${EMULATOR}
|
||||
${RESOURCE_FILES}
|
||||
)
|
||||
endif()
|
||||
|
||||
if (ENABLE_QT_GUI)
|
||||
|
@ -537,6 +586,7 @@ if (ENABLE_QT_GUI)
|
|||
${SHADER_RECOMPILER}
|
||||
${VIDEO_CORE}
|
||||
${EMULATOR}
|
||||
src/images/shadPS4.icns
|
||||
)
|
||||
else()
|
||||
add_executable(shadps4
|
||||
|
@ -557,7 +607,7 @@ endif()
|
|||
|
||||
create_target_directory_groups(shadps4)
|
||||
|
||||
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient)
|
||||
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API ffmpeg)
|
||||
target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::SPIRV glslang::glslang SDL3::SDL3)
|
||||
|
||||
if (APPLE)
|
||||
|
@ -623,6 +673,10 @@ target_include_directories(shadps4 PRIVATE ${HOST_SHADERS_INCLUDE})
|
|||
|
||||
if (ENABLE_QT_GUI)
|
||||
set_target_properties(shadps4 PROPERTIES
|
||||
# WIN32_EXECUTABLE ON
|
||||
MACOSX_BUNDLE ON)
|
||||
# WIN32_EXECUTABLE ON
|
||||
MACOSX_BUNDLE ON
|
||||
MACOSX_BUNDLE_ICON_FILE shadPS4.icns)
|
||||
|
||||
set_source_files_properties(src/images/shadPS4.icns PROPERTIES
|
||||
MACOSX_PACKAGE_LOCATION Resources)
|
||||
endif()
|
||||
|
|
7
LICENSES/BSL-1.0.txt
Normal file
7
LICENSES/BSL-1.0.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
Boost Software License - Version 1.0 - August 17th, 2003
|
||||
|
||||
Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following:
|
||||
|
||||
The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
35
README.md
35
README.md
|
@ -34,7 +34,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||
|
||||
# shadPS4
|
||||
|
||||
shadPS4 is an early PS4 emulator for Windows and Linux 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).
|
||||
|
||||
|
@ -42,13 +42,13 @@ To verify that a game works, you can look at [**shadPS4 Game Compatibility**](ht
|
|||
|
||||
To discuss shadPS4 development or suggest ideas, join the [**Discord server**](https://discord.gg/MyZRaBngxA).
|
||||
|
||||
Check us on [**X (twitter)**](https://x.com/shadps4) or on 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/).
|
||||
|
||||
# Status
|
||||
|
||||
In development, small games are working like [**Sonic Mania**](https://www.youtube.com/watch?v=AAHoNzhHyCU), [**Undertale**](https://youtu.be/5zIvdy65Ro4), [**Dysmantle**](https://youtu.be/b9xzhLBdESE) and others...
|
||||
|
||||
# Why?
|
||||
# Why
|
||||
|
||||
The project started as a fun project. Due to limited free time, it will probably take a while before shadPS4 is able to run anything decent, but we're trying to make small, regular commits.
|
||||
|
||||
|
@ -64,20 +64,37 @@ Check the build instructions for [**Linux**](https://github.com/shadps4-emu/shad
|
|||
|
||||
## Build status
|
||||
|
||||
|Windows|Build status|
|
||||
|--------|------------|
|
||||
<details>
|
||||
<summary><b>Windows</b></summary>
|
||||
|
||||
| Windows | Build status |
|
||||
|--------|--------|
|
||||
|Windows SDL Build|[](https://github.com/shadps4-emu/shadPS4/actions/workflows/windows.yml)
|
||||
|Windows Qt Build|[](https://github.com/shadps4-emu/shadPS4/actions/workflows/windows-qt.yml)
|
||||
</details>
|
||||
|
||||
|Linux|Build status|
|
||||
|--------|------------|
|
||||
<details>
|
||||
<summary><b>Linux</b></summary>
|
||||
|
||||
| Linux | Build status |
|
||||
|--------|--------|
|
||||
|Linux SDL Build|[](https://github.com/shadps4-emu/shadPS4/actions/workflows/linux.yml)
|
||||
|Linux Qt Build|[](https://github.com/shadps4-emu/shadPS4/actions/workflows/linux-qt.yml)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>macOS</b></summary>
|
||||
|
||||
| macOS | Build status |
|
||||
|--------|--------|
|
||||
|macOS SDL Build|[](https://github.com/shadps4-emu/shadPS4/actions/workflows/macos.yml)
|
||||
|macOS Qt Build|[](https://github.com/shadps4-emu/shadPS4/actions/workflows/macos-qt.yml)
|
||||
</details>
|
||||
|
||||
# Keyboard Mapping
|
||||
|
||||
| Controller button | Keyboard |
|
||||
| ------------- | ------------- |
|
||||
|-------------|-------------|
|
||||
LEFT AXIS UP | W |
|
||||
LEFT AXIS DOWN | S |
|
||||
LEFT AXIS LEFT | A |
|
||||
|
@ -123,7 +140,7 @@ Open a PR and we'll check it :)
|
|||
# Contributors
|
||||
|
||||
<a href="https://github.com/shadps4-emu/shadPS4/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=shadps4-emu/shadPS4&max=15" />
|
||||
<img src="https://contrib.rocks/image?repo=shadps4-emu/shadPS4&max=15">
|
||||
</a>
|
||||
|
||||
# Sister Projects
|
||||
|
|
|
@ -37,13 +37,18 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||
|
||||
- Windows 10 or Ubuntu 22.04
|
||||
|
||||
## Have the latest WIP version
|
||||
## How to run the latest Work-in-Progress builds of ShadPS4
|
||||
|
||||
When you go to Github Release, you have the latest major versions (e.g. v0.0.3), but if you want to have the latest Work-In-Progress version, you can go to Actions on Github to download it (Please note a Github account is required to be able to download).
|
||||
1. Go to <https://github.com/shadps4-emu/shadPS4/actions> and make sure you are logged into your GitHub account (important!)
|
||||
2. On the left side of the page, select your operating system of choice (the "**qt**" versions have a user interface, which is probably the one you want. The others are SDL versions, which can only be run via command line). 
|
||||
|
||||
<img src="https://github.com/shadps4-emu/shadPS4/blob/main/documents/Quickstart/1.png" width="800"></a>
|
||||
3. In the workflow list, select the latest entry with a green :white_check_mark: icon in front of it. (or the latest entry for whatever pull request you wish to test). 
|
||||
|
||||
After downloading the version suitable for you (Windows or Linux), you must unzip the file and then you can run it. Please note, there are two versions for each platform, a Qt version with user interface and one without (SDL Builds).
|
||||
4. On the bottom of this page, select the name of the file, and it should start downloading. (If there is no file here, double check that you are indeed logged into a GitHub account, and that there is a green :white_check_mark: icon. 
|
||||
|
||||
5. Once downloaded, extract to its own folder, and run ShadPS4's executable from the extracted folder.
|
||||
|
||||
6. Upon first launch, ShadPS4 will prompt you to select a folder to store your installed games in. Select "Browse" and then select a folder that ShadPS4 can use to install your PKG files to.
|
||||
|
||||
## Install PKG files
|
||||
|
||||
|
|
12
externals/CMakeLists.txt
vendored
12
externals/CMakeLists.txt
vendored
|
@ -47,6 +47,11 @@ else()
|
|||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT TARGET ffmpeg)
|
||||
set(ARCHITECTURE "x86_64")
|
||||
add_subdirectory(ffmpeg-core)
|
||||
endif()
|
||||
|
||||
# Zlib-Ng
|
||||
if (NOT TARGET zlib-ng::zlib)
|
||||
set(ZLIB_ENABLE_TESTS OFF)
|
||||
|
@ -74,6 +79,13 @@ if (NOT TARGET GPUOpen::VulkanMemoryAllocator)
|
|||
add_subdirectory(vma)
|
||||
endif()
|
||||
|
||||
# RenderDoc
|
||||
if (NOT TARGET RenderDoc::API)
|
||||
add_library(renderdoc INTERFACE)
|
||||
target_include_directories(renderdoc SYSTEM INTERFACE ./renderdoc)
|
||||
add_library(RenderDoc::API ALIAS renderdoc)
|
||||
endif()
|
||||
|
||||
# glslang
|
||||
if (NOT TARGET glslang::glslang)
|
||||
set(SKIP_GLSLANG_INSTALL ON CACHE BOOL "")
|
||||
|
|
158
externals/cmake-modules/GetGitRevisionDescription.cmake
vendored
Normal file
158
externals/cmake-modules/GetGitRevisionDescription.cmake
vendored
Normal file
|
@ -0,0 +1,158 @@
|
|||
# - Returns a version string from Git
|
||||
#
|
||||
# These functions force a re-configure on each git commit so that you can
|
||||
# trust the values of the variables in your build system.
|
||||
#
|
||||
# get_git_head_revision(<refspecvar> <hashvar> [<additional arguments to git describe> ...])
|
||||
#
|
||||
# Returns the refspec and sha hash of the current head revision
|
||||
#
|
||||
# git_describe(<var> [<additional arguments to git describe> ...])
|
||||
#
|
||||
# Returns the results of git describe on the source tree, and adjusting
|
||||
# the output so that it tests false if an error occurs.
|
||||
#
|
||||
# git_get_exact_tag(<var> [<additional arguments to git describe> ...])
|
||||
#
|
||||
# Returns the results of git describe --exact-match on the source tree,
|
||||
# and adjusting the output so that it tests false if there was no exact
|
||||
# matching tag.
|
||||
#
|
||||
# Requires CMake 2.6 or newer (uses the 'function' command)
|
||||
#
|
||||
# Original Author:
|
||||
# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
|
||||
# http://academic.cleardefinition.com
|
||||
# Iowa State University HCI Graduate Program/VRAC
|
||||
#
|
||||
# Copyright Iowa State University 2009-2010.
|
||||
# Distributed under the Boost Software License, Version 1.0.
|
||||
# (See accompanying file LICENSE_1_0.txt or copy at
|
||||
# http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
if(__get_git_revision_description)
|
||||
return()
|
||||
endif()
|
||||
set(__get_git_revision_description YES)
|
||||
|
||||
# We must run the following at "include" time, not at function call time,
|
||||
# to find the path to this module rather than the path to a calling list file
|
||||
get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH)
|
||||
|
||||
function(get_git_head_revision _refspecvar _hashvar)
|
||||
set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
set(GIT_DIR "${GIT_PARENT_DIR}/.git")
|
||||
while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories
|
||||
set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}")
|
||||
get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH)
|
||||
if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT)
|
||||
# We have reached the root directory, we are not in git
|
||||
set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
|
||||
set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
set(GIT_DIR "${GIT_PARENT_DIR}/.git")
|
||||
endwhile()
|
||||
# check if this is a submodule
|
||||
if(NOT IS_DIRECTORY ${GIT_DIR})
|
||||
file(READ ${GIT_DIR} submodule)
|
||||
string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule})
|
||||
get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH)
|
||||
get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE)
|
||||
endif()
|
||||
set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data")
|
||||
if(NOT EXISTS "${GIT_DATA}")
|
||||
file(MAKE_DIRECTORY "${GIT_DATA}")
|
||||
endif()
|
||||
|
||||
if(NOT EXISTS "${GIT_DIR}/HEAD")
|
||||
return()
|
||||
endif()
|
||||
set(HEAD_FILE "${GIT_DATA}/HEAD")
|
||||
configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY)
|
||||
|
||||
configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in"
|
||||
"${GIT_DATA}/grabRef.cmake"
|
||||
@ONLY)
|
||||
include("${GIT_DATA}/grabRef.cmake")
|
||||
|
||||
set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE)
|
||||
set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(git_branch_name _var)
|
||||
if(NOT GIT_FOUND)
|
||||
find_package(Git QUIET)
|
||||
endif()
|
||||
|
||||
if(NOT GIT_FOUND)
|
||||
set(${_var} "GIT-NOTFOUND" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
execute_process(COMMAND
|
||||
"${GIT_EXECUTABLE}"
|
||||
rev-parse --abbrev-ref HEAD
|
||||
WORKING_DIRECTORY
|
||||
"${CMAKE_SOURCE_DIR}"
|
||||
RESULT_VARIABLE
|
||||
res
|
||||
OUTPUT_VARIABLE
|
||||
out
|
||||
ERROR_QUIET
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
if(NOT res EQUAL 0)
|
||||
set(out "${out}-${res}-NOTFOUND")
|
||||
endif()
|
||||
|
||||
set(${_var} "${out}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(git_describe _var)
|
||||
if(NOT GIT_FOUND)
|
||||
find_package(Git QUIET)
|
||||
endif()
|
||||
#get_git_head_revision(refspec hash)
|
||||
if(NOT GIT_FOUND)
|
||||
set(${_var} "GIT-NOTFOUND" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
#if(NOT hash)
|
||||
# set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE)
|
||||
# return()
|
||||
#endif()
|
||||
|
||||
# TODO sanitize
|
||||
#if((${ARGN}" MATCHES "&&") OR
|
||||
# (ARGN MATCHES "||") OR
|
||||
# (ARGN MATCHES "\\;"))
|
||||
# message("Please report the following error to the project!")
|
||||
# message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}")
|
||||
#endif()
|
||||
|
||||
#message(STATUS "Arguments to execute_process: ${ARGN}")
|
||||
|
||||
execute_process(COMMAND
|
||||
"${GIT_EXECUTABLE}"
|
||||
describe
|
||||
${hash}
|
||||
${ARGN}
|
||||
WORKING_DIRECTORY
|
||||
"${CMAKE_SOURCE_DIR}"
|
||||
RESULT_VARIABLE
|
||||
res
|
||||
OUTPUT_VARIABLE
|
||||
out
|
||||
ERROR_QUIET
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
if(NOT res EQUAL 0)
|
||||
set(out "${out}-${res}-NOTFOUND")
|
||||
endif()
|
||||
|
||||
set(${_var} "${out}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(git_get_exact_tag _var)
|
||||
git_describe(out --exact-match ${ARGN})
|
||||
set(${_var} "${out}" PARENT_SCOPE)
|
||||
endfunction()
|
42
externals/cmake-modules/GetGitRevisionDescription.cmake.in
vendored
Normal file
42
externals/cmake-modules/GetGitRevisionDescription.cmake.in
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
#
|
||||
# Internal file for GetGitRevisionDescription.cmake
|
||||
#
|
||||
# Requires CMake 2.6 or newer (uses the 'function' command)
|
||||
#
|
||||
# Original Author:
|
||||
# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
|
||||
# http://academic.cleardefinition.com
|
||||
# Iowa State University HCI Graduate Program/VRAC
|
||||
#
|
||||
# Copyright Iowa State University 2009-2010.
|
||||
# Distributed under the Boost Software License, Version 1.0.
|
||||
# (See accompanying file LICENSE_1_0.txt or copy at
|
||||
# http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
set(HEAD_HASH)
|
||||
|
||||
file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024)
|
||||
|
||||
string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS)
|
||||
if(HEAD_CONTENTS MATCHES "ref")
|
||||
# named branch
|
||||
string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}")
|
||||
if(EXISTS "@GIT_DIR@/${HEAD_REF}")
|
||||
configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
|
||||
elseif(EXISTS "@GIT_DIR@/logs/${HEAD_REF}")
|
||||
configure_file("@GIT_DIR@/logs/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
|
||||
set(HEAD_HASH "${HEAD_REF}")
|
||||
endif()
|
||||
else()
|
||||
# detached HEAD
|
||||
configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY)
|
||||
endif()
|
||||
|
||||
if(NOT HEAD_HASH)
|
||||
if(EXISTS "@GIT_DATA@/head-ref")
|
||||
file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024)
|
||||
string(STRIP "${HEAD_HASH}" HEAD_HASH)
|
||||
else()
|
||||
set(HEAD_HASH "Unknown")
|
||||
endif()
|
||||
endif()
|
2
externals/ext-boost
vendored
2
externals/ext-boost
vendored
|
@ -1 +1 @@
|
|||
Subproject commit 147b2de7734f5dc3b9aeb1f4135ae15fcd44b9d7
|
||||
Subproject commit a04136add1e469f46d8ae8d3e8307779240a5c53
|
1
externals/ffmpeg-core
vendored
Submodule
1
externals/ffmpeg-core
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit e30b7d7fe228bfb3f6e41ce1040b44a15eb7d5e0
|
2
externals/fmt
vendored
2
externals/fmt
vendored
|
@ -1 +1 @@
|
|||
Subproject commit bc8d32e9643d2be5fc070abf2a50bc021544545d
|
||||
Subproject commit c98518351efd5a46f5d448e947e0b7242d197d07
|
2
externals/glslang
vendored
2
externals/glslang
vendored
|
@ -1 +1 @@
|
|||
Subproject commit 52f68dc6b2a9d017b43161f31f13a6f44636ee7c
|
||||
Subproject commit 7c4d91e7819a1d27213aa3499953d54ae1a00e8f
|
2
externals/magic_enum
vendored
2
externals/magic_enum
vendored
|
@ -1 +1 @@
|
|||
Subproject commit 664ee62c12570948b0e025d15b42d641fba8d54a
|
||||
Subproject commit dae6bbf16c363e9ead4e628a47fdb02956a634f3
|
741
externals/renderdoc/renderdoc_app.h
vendored
Normal file
741
externals/renderdoc/renderdoc_app.h
vendored
Normal file
|
@ -0,0 +1,741 @@
|
|||
/******************************************************************************
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019-2024 Baldur Karlsson
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Documentation for the API is available at https://renderdoc.org/docs/in_application_api.html
|
||||
//
|
||||
|
||||
#if !defined(RENDERDOC_NO_STDINT)
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER)
|
||||
#define RENDERDOC_CC __cdecl
|
||||
#elif defined(__linux__) || defined(__FreeBSD__)
|
||||
#define RENDERDOC_CC
|
||||
#elif defined(__APPLE__)
|
||||
#define RENDERDOC_CC
|
||||
#else
|
||||
#error "Unknown platform"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constants not used directly in below API
|
||||
|
||||
// This is a GUID/magic value used for when applications pass a path where shader debug
|
||||
// information can be found to match up with a stripped shader.
|
||||
// the define can be used like so: const GUID RENDERDOC_ShaderDebugMagicValue =
|
||||
// RENDERDOC_ShaderDebugMagicValue_value
|
||||
#define RENDERDOC_ShaderDebugMagicValue_struct \
|
||||
{ \
|
||||
0xeab25520, 0x6670, 0x4865, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \
|
||||
}
|
||||
|
||||
// as an alternative when you want a byte array (assuming x86 endianness):
|
||||
#define RENDERDOC_ShaderDebugMagicValue_bytearray \
|
||||
{ \
|
||||
0x20, 0x55, 0xb2, 0xea, 0x70, 0x66, 0x65, 0x48, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \
|
||||
}
|
||||
|
||||
// truncated version when only a uint64_t is available (e.g. Vulkan tags):
|
||||
#define RENDERDOC_ShaderDebugMagicValue_truncated 0x48656670eab25520ULL
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// RenderDoc capture options
|
||||
//
|
||||
|
||||
typedef enum RENDERDOC_CaptureOption
|
||||
{
|
||||
// Allow the application to enable vsync
|
||||
//
|
||||
// Default - enabled
|
||||
//
|
||||
// 1 - The application can enable or disable vsync at will
|
||||
// 0 - vsync is force disabled
|
||||
eRENDERDOC_Option_AllowVSync = 0,
|
||||
|
||||
// Allow the application to enable fullscreen
|
||||
//
|
||||
// Default - enabled
|
||||
//
|
||||
// 1 - The application can enable or disable fullscreen at will
|
||||
// 0 - fullscreen is force disabled
|
||||
eRENDERDOC_Option_AllowFullscreen = 1,
|
||||
|
||||
// Record API debugging events and messages
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - Enable built-in API debugging features and records the results into
|
||||
// the capture, which is matched up with events on replay
|
||||
// 0 - no API debugging is forcibly enabled
|
||||
eRENDERDOC_Option_APIValidation = 2,
|
||||
eRENDERDOC_Option_DebugDeviceMode = 2, // deprecated name of this enum
|
||||
|
||||
// Capture CPU callstacks for API events
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - Enables capturing of callstacks
|
||||
// 0 - no callstacks are captured
|
||||
eRENDERDOC_Option_CaptureCallstacks = 3,
|
||||
|
||||
// When capturing CPU callstacks, only capture them from actions.
|
||||
// This option does nothing without the above option being enabled
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - Only captures callstacks for actions.
|
||||
// Ignored if CaptureCallstacks is disabled
|
||||
// 0 - Callstacks, if enabled, are captured for every event.
|
||||
eRENDERDOC_Option_CaptureCallstacksOnlyDraws = 4,
|
||||
eRENDERDOC_Option_CaptureCallstacksOnlyActions = 4,
|
||||
|
||||
// Specify a delay in seconds to wait for a debugger to attach, after
|
||||
// creating or injecting into a process, before continuing to allow it to run.
|
||||
//
|
||||
// 0 indicates no delay, and the process will run immediately after injection
|
||||
//
|
||||
// Default - 0 seconds
|
||||
//
|
||||
eRENDERDOC_Option_DelayForDebugger = 5,
|
||||
|
||||
// Verify buffer access. This includes checking the memory returned by a Map() call to
|
||||
// detect any out-of-bounds modification, as well as initialising buffers with undefined contents
|
||||
// to a marker value to catch use of uninitialised memory.
|
||||
//
|
||||
// NOTE: This option is only valid for OpenGL and D3D11. Explicit APIs such as D3D12 and Vulkan do
|
||||
// not do the same kind of interception & checking and undefined contents are really undefined.
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - Verify buffer access
|
||||
// 0 - No verification is performed, and overwriting bounds may cause crashes or corruption in
|
||||
// RenderDoc.
|
||||
eRENDERDOC_Option_VerifyBufferAccess = 6,
|
||||
|
||||
// The old name for eRENDERDOC_Option_VerifyBufferAccess was eRENDERDOC_Option_VerifyMapWrites.
|
||||
// This option now controls the filling of uninitialised buffers with 0xdddddddd which was
|
||||
// previously always enabled
|
||||
eRENDERDOC_Option_VerifyMapWrites = eRENDERDOC_Option_VerifyBufferAccess,
|
||||
|
||||
// Hooks any system API calls that create child processes, and injects
|
||||
// RenderDoc into them recursively with the same options.
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - Hooks into spawned child processes
|
||||
// 0 - Child processes are not hooked by RenderDoc
|
||||
eRENDERDOC_Option_HookIntoChildren = 7,
|
||||
|
||||
// By default RenderDoc only includes resources in the final capture necessary
|
||||
// for that frame, this allows you to override that behaviour.
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - all live resources at the time of capture are included in the capture
|
||||
// and available for inspection
|
||||
// 0 - only the resources referenced by the captured frame are included
|
||||
eRENDERDOC_Option_RefAllResources = 8,
|
||||
|
||||
// **NOTE**: As of RenderDoc v1.1 this option has been deprecated. Setting or
|
||||
// getting it will be ignored, to allow compatibility with older versions.
|
||||
// In v1.1 the option acts as if it's always enabled.
|
||||
//
|
||||
// By default RenderDoc skips saving initial states for resources where the
|
||||
// previous contents don't appear to be used, assuming that writes before
|
||||
// reads indicate previous contents aren't used.
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - initial contents at the start of each captured frame are saved, even if
|
||||
// they are later overwritten or cleared before being used.
|
||||
// 0 - unless a read is detected, initial contents will not be saved and will
|
||||
// appear as black or empty data.
|
||||
eRENDERDOC_Option_SaveAllInitials = 9,
|
||||
|
||||
// In APIs that allow for the recording of command lists to be replayed later,
|
||||
// RenderDoc may choose to not capture command lists before a frame capture is
|
||||
// triggered, to reduce overheads. This means any command lists recorded once
|
||||
// and replayed many times will not be available and may cause a failure to
|
||||
// capture.
|
||||
//
|
||||
// NOTE: This is only true for APIs where multithreading is difficult or
|
||||
// discouraged. Newer APIs like Vulkan and D3D12 will ignore this option
|
||||
// and always capture all command lists since the API is heavily oriented
|
||||
// around it and the overheads have been reduced by API design.
|
||||
//
|
||||
// 1 - All command lists are captured from the start of the application
|
||||
// 0 - Command lists are only captured if their recording begins during
|
||||
// the period when a frame capture is in progress.
|
||||
eRENDERDOC_Option_CaptureAllCmdLists = 10,
|
||||
|
||||
// Mute API debugging output when the API validation mode option is enabled
|
||||
//
|
||||
// Default - enabled
|
||||
//
|
||||
// 1 - Mute any API debug messages from being displayed or passed through
|
||||
// 0 - API debugging is displayed as normal
|
||||
eRENDERDOC_Option_DebugOutputMute = 11,
|
||||
|
||||
// Option to allow vendor extensions to be used even when they may be
|
||||
// incompatible with RenderDoc and cause corrupted replays or crashes.
|
||||
//
|
||||
// Default - inactive
|
||||
//
|
||||
// No values are documented, this option should only be used when absolutely
|
||||
// necessary as directed by a RenderDoc developer.
|
||||
eRENDERDOC_Option_AllowUnsupportedVendorExtensions = 12,
|
||||
|
||||
// Define a soft memory limit which some APIs may aim to keep overhead under where
|
||||
// possible. Anything above this limit will where possible be saved directly to disk during
|
||||
// capture.
|
||||
// This will cause increased disk space use (which may cause a capture to fail if disk space is
|
||||
// exhausted) as well as slower capture times.
|
||||
//
|
||||
// Not all memory allocations may be deferred like this so it is not a guarantee of a memory
|
||||
// limit.
|
||||
//
|
||||
// Units are in MBs, suggested values would range from 200MB to 1000MB.
|
||||
//
|
||||
// Default - 0 Megabytes
|
||||
eRENDERDOC_Option_SoftMemoryLimit = 13,
|
||||
} RENDERDOC_CaptureOption;
|
||||
|
||||
// Sets an option that controls how RenderDoc behaves on capture.
|
||||
//
|
||||
// Returns 1 if the option and value are valid
|
||||
// Returns 0 if either is invalid and the option is unchanged
|
||||
typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionU32)(RENDERDOC_CaptureOption opt, uint32_t val);
|
||||
typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionF32)(RENDERDOC_CaptureOption opt, float val);
|
||||
|
||||
// Gets the current value of an option as a uint32_t
|
||||
//
|
||||
// If the option is invalid, 0xffffffff is returned
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionU32)(RENDERDOC_CaptureOption opt);
|
||||
|
||||
// Gets the current value of an option as a float
|
||||
//
|
||||
// If the option is invalid, -FLT_MAX is returned
|
||||
typedef float(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionF32)(RENDERDOC_CaptureOption opt);
|
||||
|
||||
typedef enum RENDERDOC_InputButton
|
||||
{
|
||||
// '0' - '9' matches ASCII values
|
||||
eRENDERDOC_Key_0 = 0x30,
|
||||
eRENDERDOC_Key_1 = 0x31,
|
||||
eRENDERDOC_Key_2 = 0x32,
|
||||
eRENDERDOC_Key_3 = 0x33,
|
||||
eRENDERDOC_Key_4 = 0x34,
|
||||
eRENDERDOC_Key_5 = 0x35,
|
||||
eRENDERDOC_Key_6 = 0x36,
|
||||
eRENDERDOC_Key_7 = 0x37,
|
||||
eRENDERDOC_Key_8 = 0x38,
|
||||
eRENDERDOC_Key_9 = 0x39,
|
||||
|
||||
// 'A' - 'Z' matches ASCII values
|
||||
eRENDERDOC_Key_A = 0x41,
|
||||
eRENDERDOC_Key_B = 0x42,
|
||||
eRENDERDOC_Key_C = 0x43,
|
||||
eRENDERDOC_Key_D = 0x44,
|
||||
eRENDERDOC_Key_E = 0x45,
|
||||
eRENDERDOC_Key_F = 0x46,
|
||||
eRENDERDOC_Key_G = 0x47,
|
||||
eRENDERDOC_Key_H = 0x48,
|
||||
eRENDERDOC_Key_I = 0x49,
|
||||
eRENDERDOC_Key_J = 0x4A,
|
||||
eRENDERDOC_Key_K = 0x4B,
|
||||
eRENDERDOC_Key_L = 0x4C,
|
||||
eRENDERDOC_Key_M = 0x4D,
|
||||
eRENDERDOC_Key_N = 0x4E,
|
||||
eRENDERDOC_Key_O = 0x4F,
|
||||
eRENDERDOC_Key_P = 0x50,
|
||||
eRENDERDOC_Key_Q = 0x51,
|
||||
eRENDERDOC_Key_R = 0x52,
|
||||
eRENDERDOC_Key_S = 0x53,
|
||||
eRENDERDOC_Key_T = 0x54,
|
||||
eRENDERDOC_Key_U = 0x55,
|
||||
eRENDERDOC_Key_V = 0x56,
|
||||
eRENDERDOC_Key_W = 0x57,
|
||||
eRENDERDOC_Key_X = 0x58,
|
||||
eRENDERDOC_Key_Y = 0x59,
|
||||
eRENDERDOC_Key_Z = 0x5A,
|
||||
|
||||
// leave the rest of the ASCII range free
|
||||
// in case we want to use it later
|
||||
eRENDERDOC_Key_NonPrintable = 0x100,
|
||||
|
||||
eRENDERDOC_Key_Divide,
|
||||
eRENDERDOC_Key_Multiply,
|
||||
eRENDERDOC_Key_Subtract,
|
||||
eRENDERDOC_Key_Plus,
|
||||
|
||||
eRENDERDOC_Key_F1,
|
||||
eRENDERDOC_Key_F2,
|
||||
eRENDERDOC_Key_F3,
|
||||
eRENDERDOC_Key_F4,
|
||||
eRENDERDOC_Key_F5,
|
||||
eRENDERDOC_Key_F6,
|
||||
eRENDERDOC_Key_F7,
|
||||
eRENDERDOC_Key_F8,
|
||||
eRENDERDOC_Key_F9,
|
||||
eRENDERDOC_Key_F10,
|
||||
eRENDERDOC_Key_F11,
|
||||
eRENDERDOC_Key_F12,
|
||||
|
||||
eRENDERDOC_Key_Home,
|
||||
eRENDERDOC_Key_End,
|
||||
eRENDERDOC_Key_Insert,
|
||||
eRENDERDOC_Key_Delete,
|
||||
eRENDERDOC_Key_PageUp,
|
||||
eRENDERDOC_Key_PageDn,
|
||||
|
||||
eRENDERDOC_Key_Backspace,
|
||||
eRENDERDOC_Key_Tab,
|
||||
eRENDERDOC_Key_PrtScrn,
|
||||
eRENDERDOC_Key_Pause,
|
||||
|
||||
eRENDERDOC_Key_Max,
|
||||
} RENDERDOC_InputButton;
|
||||
|
||||
// Sets which key or keys can be used to toggle focus between multiple windows
|
||||
//
|
||||
// If keys is NULL or num is 0, toggle keys will be disabled
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetFocusToggleKeys)(RENDERDOC_InputButton *keys, int num);
|
||||
|
||||
// Sets which key or keys can be used to capture the next frame
|
||||
//
|
||||
// If keys is NULL or num is 0, captures keys will be disabled
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureKeys)(RENDERDOC_InputButton *keys, int num);
|
||||
|
||||
typedef enum RENDERDOC_OverlayBits
|
||||
{
|
||||
// This single bit controls whether the overlay is enabled or disabled globally
|
||||
eRENDERDOC_Overlay_Enabled = 0x1,
|
||||
|
||||
// Show the average framerate over several seconds as well as min/max
|
||||
eRENDERDOC_Overlay_FrameRate = 0x2,
|
||||
|
||||
// Show the current frame number
|
||||
eRENDERDOC_Overlay_FrameNumber = 0x4,
|
||||
|
||||
// Show a list of recent captures, and how many captures have been made
|
||||
eRENDERDOC_Overlay_CaptureList = 0x8,
|
||||
|
||||
// Default values for the overlay mask
|
||||
eRENDERDOC_Overlay_Default = (eRENDERDOC_Overlay_Enabled | eRENDERDOC_Overlay_FrameRate |
|
||||
eRENDERDOC_Overlay_FrameNumber | eRENDERDOC_Overlay_CaptureList),
|
||||
|
||||
// Enable all bits
|
||||
eRENDERDOC_Overlay_All = ~0U,
|
||||
|
||||
// Disable all bits
|
||||
eRENDERDOC_Overlay_None = 0,
|
||||
} RENDERDOC_OverlayBits;
|
||||
|
||||
// returns the overlay bits that have been set
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetOverlayBits)();
|
||||
// sets the overlay bits with an and & or mask
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_MaskOverlayBits)(uint32_t And, uint32_t Or);
|
||||
|
||||
// this function will attempt to remove RenderDoc's hooks in the application.
|
||||
//
|
||||
// Note: that this can only work correctly if done immediately after
|
||||
// the module is loaded, before any API work happens. RenderDoc will remove its
|
||||
// injected hooks and shut down. Behaviour is undefined if this is called
|
||||
// after any API functions have been called, and there is still no guarantee of
|
||||
// success.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_RemoveHooks)();
|
||||
|
||||
// DEPRECATED: compatibility for code compiled against pre-1.4.1 headers.
|
||||
typedef pRENDERDOC_RemoveHooks pRENDERDOC_Shutdown;
|
||||
|
||||
// This function will unload RenderDoc's crash handler.
|
||||
//
|
||||
// If you use your own crash handler and don't want RenderDoc's handler to
|
||||
// intercede, you can call this function to unload it and any unhandled
|
||||
// exceptions will pass to the next handler.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_UnloadCrashHandler)();
|
||||
|
||||
// Sets the capture file path template
|
||||
//
|
||||
// pathtemplate is a UTF-8 string that gives a template for how captures will be named
|
||||
// and where they will be saved.
|
||||
//
|
||||
// Any extension is stripped off the path, and captures are saved in the directory
|
||||
// specified, and named with the filename and the frame number appended. If the
|
||||
// directory does not exist it will be created, including any parent directories.
|
||||
//
|
||||
// If pathtemplate is NULL, the template will remain unchanged
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// SetCaptureFilePathTemplate("my_captures/example");
|
||||
//
|
||||
// Capture #1 -> my_captures/example_frame123.rdc
|
||||
// Capture #2 -> my_captures/example_frame456.rdc
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFilePathTemplate)(const char *pathtemplate);
|
||||
|
||||
// returns the current capture path template, see SetCaptureFileTemplate above, as a UTF-8 string
|
||||
typedef const char *(RENDERDOC_CC *pRENDERDOC_GetCaptureFilePathTemplate)();
|
||||
|
||||
// DEPRECATED: compatibility for code compiled against pre-1.1.2 headers.
|
||||
typedef pRENDERDOC_SetCaptureFilePathTemplate pRENDERDOC_SetLogFilePathTemplate;
|
||||
typedef pRENDERDOC_GetCaptureFilePathTemplate pRENDERDOC_GetLogFilePathTemplate;
|
||||
|
||||
// returns the number of captures that have been made
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetNumCaptures)();
|
||||
|
||||
// This function returns the details of a capture, by index. New captures are added
|
||||
// to the end of the list.
|
||||
//
|
||||
// filename will be filled with the absolute path to the capture file, as a UTF-8 string
|
||||
// pathlength will be written with the length in bytes of the filename string
|
||||
// timestamp will be written with the time of the capture, in seconds since the Unix epoch
|
||||
//
|
||||
// Any of the parameters can be NULL and they'll be skipped.
|
||||
//
|
||||
// The function will return 1 if the capture index is valid, or 0 if the index is invalid
|
||||
// If the index is invalid, the values will be unchanged
|
||||
//
|
||||
// Note: when captures are deleted in the UI they will remain in this list, so the
|
||||
// capture path may not exist anymore.
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCapture)(uint32_t idx, char *filename,
|
||||
uint32_t *pathlength, uint64_t *timestamp);
|
||||
|
||||
// Sets the comments associated with a capture file. These comments are displayed in the
|
||||
// UI program when opening.
|
||||
//
|
||||
// filePath should be a path to the capture file to add comments to. If set to NULL or ""
|
||||
// the most recent capture file created made will be used instead.
|
||||
// comments should be a NULL-terminated UTF-8 string to add as comments.
|
||||
//
|
||||
// Any existing comments will be overwritten.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFileComments)(const char *filePath,
|
||||
const char *comments);
|
||||
|
||||
// returns 1 if the RenderDoc UI is connected to this application, 0 otherwise
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsTargetControlConnected)();
|
||||
|
||||
// DEPRECATED: compatibility for code compiled against pre-1.1.1 headers.
|
||||
// This was renamed to IsTargetControlConnected in API 1.1.1, the old typedef is kept here for
|
||||
// backwards compatibility with old code, it is castable either way since it's ABI compatible
|
||||
// as the same function pointer type.
|
||||
typedef pRENDERDOC_IsTargetControlConnected pRENDERDOC_IsRemoteAccessConnected;
|
||||
|
||||
// This function will launch the Replay UI associated with the RenderDoc library injected
|
||||
// into the running application.
|
||||
//
|
||||
// if connectTargetControl is 1, the Replay UI will be launched with a command line parameter
|
||||
// to connect to this application
|
||||
// cmdline is the rest of the command line, as a UTF-8 string. E.g. a captures to open
|
||||
// if cmdline is NULL, the command line will be empty.
|
||||
//
|
||||
// returns the PID of the replay UI if successful, 0 if not successful.
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_LaunchReplayUI)(uint32_t connectTargetControl,
|
||||
const char *cmdline);
|
||||
|
||||
// RenderDoc can return a higher version than requested if it's backwards compatible,
|
||||
// this function returns the actual version returned. If a parameter is NULL, it will be
|
||||
// ignored and the others will be filled out.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_GetAPIVersion)(int *major, int *minor, int *patch);
|
||||
|
||||
// Requests that the replay UI show itself (if hidden or not the current top window). This can be
|
||||
// used in conjunction with IsTargetControlConnected and LaunchReplayUI to intelligently handle
|
||||
// showing the UI after making a capture.
|
||||
//
|
||||
// This will return 1 if the request was successfully passed on, though it's not guaranteed that
|
||||
// the UI will be on top in all cases depending on OS rules. It will return 0 if there is no current
|
||||
// target control connection to make such a request, or if there was another error
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_ShowReplayUI)();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Capturing functions
|
||||
//
|
||||
|
||||
// A device pointer is a pointer to the API's root handle.
|
||||
//
|
||||
// This would be an ID3D11Device, HGLRC/GLXContext, ID3D12Device, etc
|
||||
typedef void *RENDERDOC_DevicePointer;
|
||||
|
||||
// A window handle is the OS's native window handle
|
||||
//
|
||||
// This would be an HWND, GLXDrawable, etc
|
||||
typedef void *RENDERDOC_WindowHandle;
|
||||
|
||||
// A helper macro for Vulkan, where the device handle cannot be used directly.
|
||||
//
|
||||
// Passing the VkInstance to this macro will return the RENDERDOC_DevicePointer to use.
|
||||
//
|
||||
// Specifically, the value needed is the dispatch table pointer, which sits as the first
|
||||
// pointer-sized object in the memory pointed to by the VkInstance. Thus we cast to a void** and
|
||||
// indirect once.
|
||||
#define RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(inst) (*((void **)(inst)))
|
||||
|
||||
// This sets the RenderDoc in-app overlay in the API/window pair as 'active' and it will
|
||||
// respond to keypresses. Neither parameter can be NULL
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetActiveWindow)(RENDERDOC_DevicePointer device,
|
||||
RENDERDOC_WindowHandle wndHandle);
|
||||
|
||||
// capture the next frame on whichever window and API is currently considered active
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_TriggerCapture)();
|
||||
|
||||
// capture the next N frames on whichever window and API is currently considered active
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_TriggerMultiFrameCapture)(uint32_t numFrames);
|
||||
|
||||
// When choosing either a device pointer or a window handle to capture, you can pass NULL.
|
||||
// Passing NULL specifies a 'wildcard' match against anything. This allows you to specify
|
||||
// any API rendering to a specific window, or a specific API instance rendering to any window,
|
||||
// or in the simplest case of one window and one API, you can just pass NULL for both.
|
||||
//
|
||||
// In either case, if there are two or more possible matching (device,window) pairs it
|
||||
// is undefined which one will be captured.
|
||||
//
|
||||
// Note: for headless rendering you can pass NULL for the window handle and either specify
|
||||
// a device pointer or leave it NULL as above.
|
||||
|
||||
// Immediately starts capturing API calls on the specified device pointer and window handle.
|
||||
//
|
||||
// If there is no matching thing to capture (e.g. no supported API has been initialised),
|
||||
// this will do nothing.
|
||||
//
|
||||
// The results are undefined (including crashes) if two captures are started overlapping,
|
||||
// even on separate devices and/oror windows.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_StartFrameCapture)(RENDERDOC_DevicePointer device,
|
||||
RENDERDOC_WindowHandle wndHandle);
|
||||
|
||||
// Returns whether or not a frame capture is currently ongoing anywhere.
|
||||
//
|
||||
// This will return 1 if a capture is ongoing, and 0 if there is no capture running
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsFrameCapturing)();
|
||||
|
||||
// Ends capturing immediately.
|
||||
//
|
||||
// This will return 1 if the capture succeeded, and 0 if there was an error capturing.
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_EndFrameCapture)(RENDERDOC_DevicePointer device,
|
||||
RENDERDOC_WindowHandle wndHandle);
|
||||
|
||||
// Ends capturing immediately and discard any data stored without saving to disk.
|
||||
//
|
||||
// This will return 1 if the capture was discarded, and 0 if there was an error or no capture
|
||||
// was in progress
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_DiscardFrameCapture)(RENDERDOC_DevicePointer device,
|
||||
RENDERDOC_WindowHandle wndHandle);
|
||||
|
||||
// Only valid to be called between a call to StartFrameCapture and EndFrameCapture. Gives a custom
|
||||
// title to the capture produced which will be displayed in the UI.
|
||||
//
|
||||
// If multiple captures are ongoing, this title will be applied to the first capture to end after
|
||||
// this call. The second capture to end will have no title, unless this function is called again.
|
||||
//
|
||||
// Calling this function has no effect if no capture is currently running, and if it is called
|
||||
// multiple times only the last title will be used.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureTitle)(const char *title);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// RenderDoc API versions
|
||||
//
|
||||
|
||||
// RenderDoc uses semantic versioning (http://semver.org/).
|
||||
//
|
||||
// MAJOR version is incremented when incompatible API changes happen.
|
||||
// MINOR version is incremented when functionality is added in a backwards-compatible manner.
|
||||
// PATCH version is incremented when backwards-compatible bug fixes happen.
|
||||
//
|
||||
// Note that this means the API returned can be higher than the one you might have requested.
|
||||
// e.g. if you are running against a newer RenderDoc that supports 1.0.1, it will be returned
|
||||
// instead of 1.0.0. You can check this with the GetAPIVersion entry point
|
||||
typedef enum RENDERDOC_Version
|
||||
{
|
||||
eRENDERDOC_API_Version_1_0_0 = 10000, // RENDERDOC_API_1_0_0 = 1 00 00
|
||||
eRENDERDOC_API_Version_1_0_1 = 10001, // RENDERDOC_API_1_0_1 = 1 00 01
|
||||
eRENDERDOC_API_Version_1_0_2 = 10002, // RENDERDOC_API_1_0_2 = 1 00 02
|
||||
eRENDERDOC_API_Version_1_1_0 = 10100, // RENDERDOC_API_1_1_0 = 1 01 00
|
||||
eRENDERDOC_API_Version_1_1_1 = 10101, // RENDERDOC_API_1_1_1 = 1 01 01
|
||||
eRENDERDOC_API_Version_1_1_2 = 10102, // RENDERDOC_API_1_1_2 = 1 01 02
|
||||
eRENDERDOC_API_Version_1_2_0 = 10200, // RENDERDOC_API_1_2_0 = 1 02 00
|
||||
eRENDERDOC_API_Version_1_3_0 = 10300, // RENDERDOC_API_1_3_0 = 1 03 00
|
||||
eRENDERDOC_API_Version_1_4_0 = 10400, // RENDERDOC_API_1_4_0 = 1 04 00
|
||||
eRENDERDOC_API_Version_1_4_1 = 10401, // RENDERDOC_API_1_4_1 = 1 04 01
|
||||
eRENDERDOC_API_Version_1_4_2 = 10402, // RENDERDOC_API_1_4_2 = 1 04 02
|
||||
eRENDERDOC_API_Version_1_5_0 = 10500, // RENDERDOC_API_1_5_0 = 1 05 00
|
||||
eRENDERDOC_API_Version_1_6_0 = 10600, // RENDERDOC_API_1_6_0 = 1 06 00
|
||||
} RENDERDOC_Version;
|
||||
|
||||
// API version changelog:
|
||||
//
|
||||
// 1.0.0 - initial release
|
||||
// 1.0.1 - Bugfix: IsFrameCapturing() was returning false for captures that were triggered
|
||||
// by keypress or TriggerCapture, instead of Start/EndFrameCapture.
|
||||
// 1.0.2 - Refactor: Renamed eRENDERDOC_Option_DebugDeviceMode to eRENDERDOC_Option_APIValidation
|
||||
// 1.1.0 - Add feature: TriggerMultiFrameCapture(). Backwards compatible with 1.0.x since the new
|
||||
// function pointer is added to the end of the struct, the original layout is identical
|
||||
// 1.1.1 - Refactor: Renamed remote access to target control (to better disambiguate from remote
|
||||
// replay/remote server concept in replay UI)
|
||||
// 1.1.2 - Refactor: Renamed "log file" in function names to just capture, to clarify that these
|
||||
// are captures and not debug logging files. This is the first API version in the v1.0
|
||||
// branch.
|
||||
// 1.2.0 - Added feature: SetCaptureFileComments() to add comments to a capture file that will be
|
||||
// displayed in the UI program on load.
|
||||
// 1.3.0 - Added feature: New capture option eRENDERDOC_Option_AllowUnsupportedVendorExtensions
|
||||
// which allows users to opt-in to allowing unsupported vendor extensions to function.
|
||||
// Should be used at the user's own risk.
|
||||
// Refactor: Renamed eRENDERDOC_Option_VerifyMapWrites to
|
||||
// eRENDERDOC_Option_VerifyBufferAccess, which now also controls initialisation to
|
||||
// 0xdddddddd of uninitialised buffer contents.
|
||||
// 1.4.0 - Added feature: DiscardFrameCapture() to discard a frame capture in progress and stop
|
||||
// capturing without saving anything to disk.
|
||||
// 1.4.1 - Refactor: Renamed Shutdown to RemoveHooks to better clarify what is happening
|
||||
// 1.4.2 - Refactor: Renamed 'draws' to 'actions' in callstack capture option.
|
||||
// 1.5.0 - Added feature: ShowReplayUI() to request that the replay UI show itself if connected
|
||||
// 1.6.0 - Added feature: SetCaptureTitle() which can be used to set a title for a
|
||||
// capture made with StartFrameCapture() or EndFrameCapture()
|
||||
|
||||
typedef struct RENDERDOC_API_1_6_0
|
||||
{
|
||||
pRENDERDOC_GetAPIVersion GetAPIVersion;
|
||||
|
||||
pRENDERDOC_SetCaptureOptionU32 SetCaptureOptionU32;
|
||||
pRENDERDOC_SetCaptureOptionF32 SetCaptureOptionF32;
|
||||
|
||||
pRENDERDOC_GetCaptureOptionU32 GetCaptureOptionU32;
|
||||
pRENDERDOC_GetCaptureOptionF32 GetCaptureOptionF32;
|
||||
|
||||
pRENDERDOC_SetFocusToggleKeys SetFocusToggleKeys;
|
||||
pRENDERDOC_SetCaptureKeys SetCaptureKeys;
|
||||
|
||||
pRENDERDOC_GetOverlayBits GetOverlayBits;
|
||||
pRENDERDOC_MaskOverlayBits MaskOverlayBits;
|
||||
|
||||
// Shutdown was renamed to RemoveHooks in 1.4.1.
|
||||
// These unions allow old code to continue compiling without changes
|
||||
union
|
||||
{
|
||||
pRENDERDOC_Shutdown Shutdown;
|
||||
pRENDERDOC_RemoveHooks RemoveHooks;
|
||||
};
|
||||
pRENDERDOC_UnloadCrashHandler UnloadCrashHandler;
|
||||
|
||||
// Get/SetLogFilePathTemplate was renamed to Get/SetCaptureFilePathTemplate in 1.1.2.
|
||||
// These unions allow old code to continue compiling without changes
|
||||
union
|
||||
{
|
||||
// deprecated name
|
||||
pRENDERDOC_SetLogFilePathTemplate SetLogFilePathTemplate;
|
||||
// current name
|
||||
pRENDERDOC_SetCaptureFilePathTemplate SetCaptureFilePathTemplate;
|
||||
};
|
||||
union
|
||||
{
|
||||
// deprecated name
|
||||
pRENDERDOC_GetLogFilePathTemplate GetLogFilePathTemplate;
|
||||
// current name
|
||||
pRENDERDOC_GetCaptureFilePathTemplate GetCaptureFilePathTemplate;
|
||||
};
|
||||
|
||||
pRENDERDOC_GetNumCaptures GetNumCaptures;
|
||||
pRENDERDOC_GetCapture GetCapture;
|
||||
|
||||
pRENDERDOC_TriggerCapture TriggerCapture;
|
||||
|
||||
// IsRemoteAccessConnected was renamed to IsTargetControlConnected in 1.1.1.
|
||||
// This union allows old code to continue compiling without changes
|
||||
union
|
||||
{
|
||||
// deprecated name
|
||||
pRENDERDOC_IsRemoteAccessConnected IsRemoteAccessConnected;
|
||||
// current name
|
||||
pRENDERDOC_IsTargetControlConnected IsTargetControlConnected;
|
||||
};
|
||||
pRENDERDOC_LaunchReplayUI LaunchReplayUI;
|
||||
|
||||
pRENDERDOC_SetActiveWindow SetActiveWindow;
|
||||
|
||||
pRENDERDOC_StartFrameCapture StartFrameCapture;
|
||||
pRENDERDOC_IsFrameCapturing IsFrameCapturing;
|
||||
pRENDERDOC_EndFrameCapture EndFrameCapture;
|
||||
|
||||
// new function in 1.1.0
|
||||
pRENDERDOC_TriggerMultiFrameCapture TriggerMultiFrameCapture;
|
||||
|
||||
// new function in 1.2.0
|
||||
pRENDERDOC_SetCaptureFileComments SetCaptureFileComments;
|
||||
|
||||
// new function in 1.4.0
|
||||
pRENDERDOC_DiscardFrameCapture DiscardFrameCapture;
|
||||
|
||||
// new function in 1.5.0
|
||||
pRENDERDOC_ShowReplayUI ShowReplayUI;
|
||||
|
||||
// new function in 1.6.0
|
||||
pRENDERDOC_SetCaptureTitle SetCaptureTitle;
|
||||
} RENDERDOC_API_1_6_0;
|
||||
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_0;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_1;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_2;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_0;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_1;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_2;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_2_0;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_3_0;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_0;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_1;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_2;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_5_0;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// RenderDoc API entry point
|
||||
//
|
||||
// This entry point can be obtained via GetProcAddress/dlsym if RenderDoc is available.
|
||||
//
|
||||
// The name is the same as the typedef - "RENDERDOC_GetAPI"
|
||||
//
|
||||
// This function is not thread safe, and should not be called on multiple threads at once.
|
||||
// Ideally, call this once as early as possible in your application's startup, before doing
|
||||
// any API work, since some configuration functionality etc has to be done also before
|
||||
// initialising any APIs.
|
||||
//
|
||||
// Parameters:
|
||||
// version is a single value from the RENDERDOC_Version above.
|
||||
//
|
||||
// outAPIPointers will be filled out with a pointer to the corresponding struct of function
|
||||
// pointers.
|
||||
//
|
||||
// Returns:
|
||||
// 1 - if the outAPIPointers has been filled with a pointer to the API struct requested
|
||||
// 0 - if the requested version is not supported or the arguments are invalid.
|
||||
//
|
||||
typedef int(RENDERDOC_CC *pRENDERDOC_GetAPI)(RENDERDOC_Version version, void **outAPIPointers);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
2
externals/sdl3
vendored
2
externals/sdl3
vendored
|
@ -1 +1 @@
|
|||
Subproject commit f9a06c20ed85fb1d6754fc2280d6183382217910
|
||||
Subproject commit 4cc3410dce50cefce98d3cf3cf1bc8eca83b862a
|
2
externals/toml11
vendored
2
externals/toml11
vendored
|
@ -1 +1 @@
|
|||
Subproject commit b389bbc4ebf90fa2fe7651de3046fb19f661ba3c
|
||||
Subproject commit fcb1d3d7e5885edfadbbe9572991dc4b3248af58
|
2
externals/vulkan-headers
vendored
2
externals/vulkan-headers
vendored
|
@ -1 +1 @@
|
|||
Subproject commit b379292b2ab6df5771ba9870d53cf8b2c9295daf
|
||||
Subproject commit 595c8d4794410a4e64b98dc58d27c0310d7ea2fd
|
2
externals/xxhash
vendored
2
externals/xxhash
vendored
|
@ -1 +1 @@
|
|||
Subproject commit a57f6cce2698049863af8c25787084ae0489d849
|
||||
Subproject commit ee65ff988bab34a184c700e2fbe1e1c5bc27485d
|
|
@ -1,19 +1,23 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "sdl_audio.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
|
||||
#include <SDL3/SDL_audio.h>
|
||||
#include <SDL3/SDL_init.h>
|
||||
#include <SDL3/SDL_timer.h>
|
||||
#include <common/assert.h>
|
||||
#include <core/libraries/error_codes.h>
|
||||
#include "sdl_audio.h"
|
||||
|
||||
#include <mutex> // std::unique_lock
|
||||
|
||||
namespace Audio {
|
||||
|
||||
int SDLAudio::AudioOutOpen(int type, u32 samples_num, u32 freq,
|
||||
Libraries::AudioOut::OrbisAudioOutParamFormat format) {
|
||||
using Libraries::AudioOut::OrbisAudioOutParamFormat;
|
||||
std::scoped_lock lock{m_mutex};
|
||||
std::unique_lock lock{m_mutex};
|
||||
for (int id = 0; id < portsOut.size(); id++) {
|
||||
auto& port = portsOut[id];
|
||||
if (!port.isOpen) {
|
||||
|
@ -88,7 +92,7 @@ int SDLAudio::AudioOutOpen(int type, u32 samples_num, u32 freq,
|
|||
}
|
||||
|
||||
s32 SDLAudio::AudioOutOutput(s32 handle, const void* ptr) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
std::shared_lock lock{m_mutex};
|
||||
auto& port = portsOut[handle - 1];
|
||||
if (!port.isOpen) {
|
||||
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
|
||||
|
@ -100,7 +104,7 @@ s32 SDLAudio::AudioOutOutput(s32 handle, const void* ptr) {
|
|||
int result = SDL_PutAudioStreamData(port.stream, ptr,
|
||||
port.samples_num * port.sample_size * port.channels_num);
|
||||
// TODO find a correct value 8192 is estimated
|
||||
while (SDL_GetAudioStreamAvailable(port.stream) > 8192) {
|
||||
while (SDL_GetAudioStreamAvailable(port.stream) > 65536) {
|
||||
SDL_Delay(0);
|
||||
}
|
||||
|
||||
|
@ -109,7 +113,7 @@ s32 SDLAudio::AudioOutOutput(s32 handle, const void* ptr) {
|
|||
|
||||
bool SDLAudio::AudioOutSetVolume(s32 handle, s32 bitflag, s32* volume) {
|
||||
using Libraries::AudioOut::OrbisAudioOutParamFormat;
|
||||
std::scoped_lock lock{m_mutex};
|
||||
std::shared_lock lock{m_mutex};
|
||||
auto& port = portsOut[handle - 1];
|
||||
if (!port.isOpen) {
|
||||
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
|
||||
|
@ -147,7 +151,7 @@ bool SDLAudio::AudioOutSetVolume(s32 handle, s32 bitflag, s32* volume) {
|
|||
}
|
||||
|
||||
bool SDLAudio::AudioOutGetStatus(s32 handle, int* type, int* channels_num) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
std::shared_lock lock{m_mutex};
|
||||
auto& port = portsOut[handle - 1];
|
||||
*type = port.type;
|
||||
*channels_num = port.channels_num;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <SDL3/SDL_audio.h>
|
||||
#include "core/libraries/audio/audioout.h"
|
||||
|
||||
|
@ -32,7 +32,7 @@ private:
|
|||
int volume[8] = {};
|
||||
SDL_AudioStream* stream = nullptr;
|
||||
};
|
||||
std::mutex m_mutex;
|
||||
std::shared_mutex m_mutex;
|
||||
std::array<PortOut, 22> portsOut; // main up to 8 ports , BGM 1 port , voice up to 4 ports ,
|
||||
// personal up to 4 ports , padspk up to 5 ports , aux 1 port
|
||||
};
|
||||
|
|
|
@ -15,15 +15,20 @@ static u32 screenWidth = 1280;
|
|||
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 = "async";
|
||||
static std::string userName = "shadPS4";
|
||||
static bool isDebugDump = false;
|
||||
static bool isLibc = true;
|
||||
static bool isShowSplash = false;
|
||||
static bool isNullGpu = false;
|
||||
static bool shouldDumpShaders = false;
|
||||
static bool shouldDumpPM4 = false;
|
||||
static u32 vblankDivider = 1;
|
||||
static bool vkValidation = false;
|
||||
static bool vkValidationSync = false;
|
||||
static bool vkValidationGpu = false;
|
||||
static bool rdocEnable = false;
|
||||
static bool rdocMarkersEnable = false;
|
||||
// Gui
|
||||
std::string settings_install_dir = "";
|
||||
u32 main_window_geometry_x = 400;
|
||||
|
@ -41,6 +46,8 @@ u32 m_window_size_H = 720;
|
|||
std::vector<std::string> m_pkg_viewer;
|
||||
std::vector<std::string> m_elf_viewer;
|
||||
std::vector<std::string> m_recent_files;
|
||||
// Settings
|
||||
u32 m_language = 1; // english
|
||||
|
||||
bool isLleLibc() {
|
||||
return isLibc;
|
||||
|
@ -74,6 +81,10 @@ std::string getLogType() {
|
|||
return logType;
|
||||
}
|
||||
|
||||
std::string getUserName() {
|
||||
return userName;
|
||||
}
|
||||
|
||||
bool debugDump() {
|
||||
return isDebugDump;
|
||||
}
|
||||
|
@ -94,6 +105,18 @@ bool dumpPM4() {
|
|||
return shouldDumpPM4;
|
||||
}
|
||||
|
||||
bool isRdocEnabled() {
|
||||
return rdocEnable;
|
||||
}
|
||||
|
||||
bool isMarkersEnabled() {
|
||||
return rdocMarkersEnable;
|
||||
}
|
||||
|
||||
u32 vblankDiv() {
|
||||
return vblankDivider;
|
||||
}
|
||||
|
||||
bool vkValidationEnabled() {
|
||||
return vkValidation;
|
||||
}
|
||||
|
@ -102,6 +125,82 @@ bool vkValidationSyncEnabled() {
|
|||
return vkValidationSync;
|
||||
}
|
||||
|
||||
bool vkValidationGpuEnabled() {
|
||||
return vkValidationGpu;
|
||||
}
|
||||
|
||||
void setGpuId(s32 selectedGpuId) {
|
||||
gpuId = selectedGpuId;
|
||||
}
|
||||
|
||||
void setScreenWidth(u32 width) {
|
||||
screenWidth = width;
|
||||
}
|
||||
|
||||
void setScreenHeight(u32 height) {
|
||||
screenHeight = height;
|
||||
}
|
||||
|
||||
void setDebugDump(bool enable) {
|
||||
isDebugDump = enable;
|
||||
}
|
||||
|
||||
void setShowSplash(bool enable) {
|
||||
isShowSplash = enable;
|
||||
}
|
||||
|
||||
void setNullGpu(bool enable) {
|
||||
isNullGpu = enable;
|
||||
}
|
||||
|
||||
void setDumpShaders(bool enable) {
|
||||
shouldDumpShaders = enable;
|
||||
}
|
||||
|
||||
void setDumpPM4(bool enable) {
|
||||
shouldDumpPM4 = enable;
|
||||
}
|
||||
|
||||
void setVkValidation(bool enable) {
|
||||
vkValidation = enable;
|
||||
}
|
||||
|
||||
void setVkSyncValidation(bool enable) {
|
||||
vkValidationSync = enable;
|
||||
}
|
||||
|
||||
void setRdocEnabled(bool enable) {
|
||||
rdocEnable = enable;
|
||||
}
|
||||
|
||||
void setVblankDiv(u32 value) {
|
||||
vblankDivider = value;
|
||||
}
|
||||
|
||||
void setFullscreenMode(bool enable) {
|
||||
isFullscreen = enable;
|
||||
}
|
||||
|
||||
void setLanguage(u32 language) {
|
||||
m_language = language;
|
||||
}
|
||||
|
||||
void setNeoMode(bool enable) {
|
||||
isNeo = enable;
|
||||
}
|
||||
|
||||
void setLogType(std::string type) {
|
||||
logType = type;
|
||||
}
|
||||
|
||||
void setLogFilter(std::string type) {
|
||||
logFilter = type;
|
||||
}
|
||||
|
||||
void setUserName(std::string type) {
|
||||
userName = type;
|
||||
}
|
||||
|
||||
void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) {
|
||||
main_window_geometry_x = x;
|
||||
main_window_geometry_y = y;
|
||||
|
@ -197,6 +296,9 @@ std::vector<std::string> getRecentFiles() {
|
|||
return m_recent_files;
|
||||
}
|
||||
|
||||
u32 GetLanguage() {
|
||||
return m_language;
|
||||
}
|
||||
void load(const std::filesystem::path& path) {
|
||||
// If the configuration file does not exist, create it and return
|
||||
std::error_code error;
|
||||
|
@ -213,88 +315,85 @@ void load(const std::filesystem::path& path) {
|
|||
fmt::print("Got exception trying to load config file. Exception: {}\n", ex.what());
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.contains("General")) {
|
||||
auto generalResult = toml::expect<toml::value>(data.at("General"));
|
||||
if (generalResult.is_ok()) {
|
||||
auto general = generalResult.unwrap();
|
||||
const toml::value& general = data.at("General");
|
||||
|
||||
isNeo = toml::find_or<toml::boolean>(general, "isPS4Pro", false);
|
||||
isFullscreen = toml::find_or<toml::boolean>(general, "Fullscreen", true);
|
||||
logFilter = toml::find_or<toml::string>(general, "logFilter", "");
|
||||
logType = toml::find_or<toml::string>(general, "logType", "sync");
|
||||
isShowSplash = toml::find_or<toml::boolean>(general, "showSplash", true);
|
||||
}
|
||||
isNeo = toml::find_or<bool>(general, "isPS4Pro", false);
|
||||
isFullscreen = toml::find_or<bool>(general, "Fullscreen", false);
|
||||
logFilter = toml::find_or<std::string>(general, "logFilter", "");
|
||||
logType = toml::find_or<std::string>(general, "logType", "sync");
|
||||
userName = toml::find_or<std::string>(general, "userName", "shadPS4");
|
||||
isShowSplash = toml::find_or<bool>(general, "showSplash", true);
|
||||
}
|
||||
|
||||
if (data.contains("GPU")) {
|
||||
auto gpuResult = toml::expect<toml::value>(data.at("GPU"));
|
||||
if (gpuResult.is_ok()) {
|
||||
auto gpu = gpuResult.unwrap();
|
||||
const toml::value& gpu = data.at("GPU");
|
||||
|
||||
screenWidth = toml::find_or<toml::integer>(gpu, "screenWidth", screenWidth);
|
||||
screenHeight = toml::find_or<toml::integer>(gpu, "screenHeight", screenHeight);
|
||||
gpuId = toml::find_or<toml::integer>(gpu, "gpuId", 0);
|
||||
isNullGpu = toml::find_or<toml::boolean>(gpu, "nullGpu", false);
|
||||
shouldDumpShaders = toml::find_or<toml::boolean>(gpu, "dumpShaders", false);
|
||||
shouldDumpPM4 = toml::find_or<toml::boolean>(gpu, "dumpPM4", false);
|
||||
}
|
||||
screenWidth = toml::find_or<int>(gpu, "screenWidth", screenWidth);
|
||||
screenHeight = toml::find_or<int>(gpu, "screenHeight", screenHeight);
|
||||
isNullGpu = toml::find_or<bool>(gpu, "nullGpu", false);
|
||||
shouldDumpShaders = toml::find_or<bool>(gpu, "dumpShaders", false);
|
||||
shouldDumpPM4 = toml::find_or<bool>(gpu, "dumpPM4", false);
|
||||
vblankDivider = toml::find_or<int>(gpu, "vblankDivider", 1);
|
||||
}
|
||||
|
||||
if (data.contains("Vulkan")) {
|
||||
const auto vkResult = toml::expect<toml::value>(data.at("Vulkan"));
|
||||
if (vkResult.is_ok()) {
|
||||
auto vk = vkResult.unwrap();
|
||||
const toml::value& vk = data.at("Vulkan");
|
||||
|
||||
vkValidation = toml::find_or<toml::boolean>(vk, "validation", true);
|
||||
vkValidationSync = toml::find_or<toml::boolean>(vk, "validation_sync", true);
|
||||
}
|
||||
gpuId = toml::find_or<int>(vk, "gpuId", -1);
|
||||
vkValidation = toml::find_or<bool>(vk, "validation", false);
|
||||
vkValidationSync = toml::find_or<bool>(vk, "validation_sync", false);
|
||||
vkValidationGpu = toml::find_or<bool>(vk, "validation_gpu", true);
|
||||
rdocEnable = toml::find_or<bool>(vk, "rdocEnable", false);
|
||||
rdocMarkersEnable = toml::find_or<bool>(vk, "rdocMarkersEnable", false);
|
||||
}
|
||||
|
||||
if (data.contains("Debug")) {
|
||||
auto debugResult = toml::expect<toml::value>(data.at("Debug"));
|
||||
if (debugResult.is_ok()) {
|
||||
auto debug = debugResult.unwrap();
|
||||
const toml::value& debug = data.at("Debug");
|
||||
|
||||
isDebugDump = toml::find_or<toml::boolean>(debug, "DebugDump", false);
|
||||
}
|
||||
isDebugDump = toml::find_or<bool>(debug, "DebugDump", false);
|
||||
}
|
||||
|
||||
if (data.contains("LLE")) {
|
||||
auto lleResult = toml::expect<toml::value>(data.at("LLE"));
|
||||
if (lleResult.is_ok()) {
|
||||
auto lle = lleResult.unwrap();
|
||||
const toml::value& lle = data.at("LLE");
|
||||
|
||||
isLibc = toml::find_or<toml::boolean>(lle, "libc", true);
|
||||
}
|
||||
isLibc = toml::find_or<bool>(lle, "libc", true);
|
||||
}
|
||||
if (data.contains("GUI")) {
|
||||
auto guiResult = toml::expect<toml::value>(data.at("GUI"));
|
||||
if (guiResult.is_ok()) {
|
||||
auto gui = guiResult.unwrap();
|
||||
|
||||
m_icon_size = toml::find_or<toml::integer>(gui, "iconSize", 0);
|
||||
m_icon_size_grid = toml::find_or<toml::integer>(gui, "iconSizeGrid", 0);
|
||||
m_slider_pos = toml::find_or<toml::integer>(gui, "sliderPos", 0);
|
||||
m_slider_pos_grid = toml::find_or<toml::integer>(gui, "sliderPosGrid", 0);
|
||||
mw_themes = toml::find_or<toml::integer>(gui, "theme", 0);
|
||||
m_window_size_W = toml::find_or<toml::integer>(gui, "mw_width", 0);
|
||||
m_window_size_H = toml::find_or<toml::integer>(gui, "mw_height", 0);
|
||||
settings_install_dir = toml::find_or<toml::string>(gui, "installDir", "");
|
||||
main_window_geometry_x = toml::find_or<toml::integer>(gui, "geometry_x", 0);
|
||||
main_window_geometry_y = toml::find_or<toml::integer>(gui, "geometry_y", 0);
|
||||
main_window_geometry_w = toml::find_or<toml::integer>(gui, "geometry_w", 0);
|
||||
main_window_geometry_h = toml::find_or<toml::integer>(gui, "geometry_h", 0);
|
||||
m_pkg_viewer = toml::find_or<std::vector<std::string>>(gui, "pkgDirs", {});
|
||||
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<toml::integer>(gui, "gameTableMode", 0);
|
||||
}
|
||||
if (data.contains("GUI")) {
|
||||
const toml::value& gui = data.at("GUI");
|
||||
|
||||
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);
|
||||
settings_install_dir = toml::find_or<std::string>(gui, "installDir", "");
|
||||
main_window_geometry_x = toml::find_or<int>(gui, "geometry_x", 0);
|
||||
main_window_geometry_y = toml::find_or<int>(gui, "geometry_y", 0);
|
||||
main_window_geometry_w = toml::find_or<int>(gui, "geometry_w", 0);
|
||||
main_window_geometry_h = toml::find_or<int>(gui, "geometry_h", 0);
|
||||
m_pkg_viewer = toml::find_or<std::vector<std::string>>(gui, "pkgDirs", {});
|
||||
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);
|
||||
}
|
||||
|
||||
if (data.contains("Settings")) {
|
||||
const toml::value& settings = data.at("Settings");
|
||||
|
||||
m_language = toml::find_or<int>(settings, "consoleLanguage", 1);
|
||||
}
|
||||
}
|
||||
void save(const std::filesystem::path& path) {
|
||||
toml::basic_value<toml::preserve_comments> data;
|
||||
toml::value data;
|
||||
|
||||
std::error_code error;
|
||||
if (std::filesystem::exists(path, error)) {
|
||||
try {
|
||||
data = toml::parse<toml::preserve_comments>(path);
|
||||
data = toml::parse(path);
|
||||
} catch (const std::exception& ex) {
|
||||
fmt::print("Exception trying to parse config file. Exception: {}\n", ex.what());
|
||||
return;
|
||||
|
@ -311,15 +410,20 @@ void save(const std::filesystem::path& path) {
|
|||
data["General"]["Fullscreen"] = isFullscreen;
|
||||
data["General"]["logFilter"] = logFilter;
|
||||
data["General"]["logType"] = logType;
|
||||
data["General"]["userName"] = userName;
|
||||
data["General"]["showSplash"] = isShowSplash;
|
||||
data["GPU"]["gpuId"] = gpuId;
|
||||
data["GPU"]["screenWidth"] = screenWidth;
|
||||
data["GPU"]["screenHeight"] = screenHeight;
|
||||
data["GPU"]["nullGpu"] = isNullGpu;
|
||||
data["GPU"]["dumpShaders"] = shouldDumpShaders;
|
||||
data["GPU"]["dumpPM4"] = shouldDumpPM4;
|
||||
data["GPU"]["vblankDivider"] = vblankDivider;
|
||||
data["Vulkan"]["gpuId"] = gpuId;
|
||||
data["Vulkan"]["validation"] = vkValidation;
|
||||
data["Vulkan"]["validation_sync"] = vkValidationSync;
|
||||
data["Vulkan"]["validation_gpu"] = vkValidationGpu;
|
||||
data["Vulkan"]["rdocEnable"] = rdocEnable;
|
||||
data["Vulkan"]["rdocMarkersEnable"] = rdocMarkersEnable;
|
||||
data["Debug"]["DebugDump"] = isDebugDump;
|
||||
data["LLE"]["libc"] = isLibc;
|
||||
data["GUI"]["theme"] = mw_themes;
|
||||
|
@ -339,8 +443,31 @@ void save(const std::filesystem::path& path) {
|
|||
data["GUI"]["elfDirs"] = m_elf_viewer;
|
||||
data["GUI"]["recentFiles"] = m_recent_files;
|
||||
|
||||
data["Settings"]["consoleLanguage"] = m_language;
|
||||
|
||||
std::ofstream file(path, std::ios::out);
|
||||
file << data;
|
||||
file.close();
|
||||
}
|
||||
|
||||
void setDefaultValues() {
|
||||
isNeo = false;
|
||||
isFullscreen = false;
|
||||
screenWidth = 1280;
|
||||
screenHeight = 720;
|
||||
logFilter = "";
|
||||
logType = "async";
|
||||
userName = "shadPS4";
|
||||
isDebugDump = false;
|
||||
isShowSplash = false;
|
||||
isNullGpu = false;
|
||||
shouldDumpShaders = false;
|
||||
shouldDumpPM4 = false;
|
||||
vblankDivider = 1;
|
||||
vkValidation = false;
|
||||
rdocEnable = false;
|
||||
m_language = 1;
|
||||
gpuId = -1;
|
||||
}
|
||||
|
||||
} // namespace Config
|
||||
|
|
|
@ -15,6 +15,7 @@ bool isNeoMode();
|
|||
bool isFullscreenMode();
|
||||
std::string getLogFilter();
|
||||
std::string getLogType();
|
||||
std::string getUserName();
|
||||
|
||||
u32 getScreenWidth();
|
||||
u32 getScreenHeight();
|
||||
|
@ -26,9 +27,34 @@ bool showSplash();
|
|||
bool nullGpu();
|
||||
bool dumpShaders();
|
||||
bool dumpPM4();
|
||||
bool isRdocEnabled();
|
||||
bool isMarkersEnabled();
|
||||
u32 vblankDiv();
|
||||
|
||||
void setDebugDump(bool enable);
|
||||
void setShowSplash(bool enable);
|
||||
void setNullGpu(bool enable);
|
||||
void setDumpShaders(bool enable);
|
||||
void setDumpPM4(bool enable);
|
||||
void setVblankDiv(u32 value);
|
||||
void setGpuId(s32 selectedGpuId);
|
||||
void setScreenWidth(u32 width);
|
||||
void setScreenHeight(u32 height);
|
||||
void setFullscreenMode(bool enable);
|
||||
void setLanguage(u32 language);
|
||||
void setNeoMode(bool enable);
|
||||
void setUserName(std::string type);
|
||||
|
||||
void setLogType(std::string type);
|
||||
void setLogFilter(std::string type);
|
||||
|
||||
void setVkValidation(bool enable);
|
||||
void setVkSyncValidation(bool enable);
|
||||
void setRdocEnabled(bool enable);
|
||||
|
||||
bool vkValidationEnabled();
|
||||
bool vkValidationSyncEnabled();
|
||||
bool vkValidationGpuEnabled();
|
||||
|
||||
// Gui
|
||||
void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h);
|
||||
|
@ -62,4 +88,8 @@ std::vector<std::string> getPkgViewer();
|
|||
std::vector<std::string> getElfViewer();
|
||||
std::vector<std::string> getRecentFiles();
|
||||
|
||||
void setDefaultValues();
|
||||
|
||||
// settings
|
||||
u32 GetLanguage();
|
||||
}; // namespace Config
|
||||
|
|
|
@ -207,8 +207,8 @@ public:
|
|||
message_queue.EmplaceWait(entry);
|
||||
} else {
|
||||
ForEachBackend([&entry](auto& backend) { backend.Write(entry); });
|
||||
std::fflush(stdout);
|
||||
}
|
||||
std::fflush(stdout);
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
|
@ -106,6 +106,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
|
|||
SUB(Lib, DiscMap) \
|
||||
SUB(Lib, Png) \
|
||||
SUB(Lib, PlayGo) \
|
||||
SUB(Lib, Random) \
|
||||
SUB(Lib, Usbd) \
|
||||
SUB(Lib, Ajm) \
|
||||
SUB(Lib, ErrorDialog) \
|
||||
|
|
|
@ -73,6 +73,7 @@ enum class Class : u8 {
|
|||
Lib_DiscMap, ///< The LibSceDiscMap implementation.
|
||||
Lib_Png, ///< The LibScePng implementation.
|
||||
Lib_PlayGo, ///< The LibScePlayGo implementation.
|
||||
Lib_Random, ///< The libSceRandom implementation.
|
||||
Lib_Usbd, ///< The LibSceUsbd implementation.
|
||||
Lib_Ajm, ///< The LibSceAjm implementation.
|
||||
Lib_ErrorDialog, ///< The LibSceErrorDialog implementation.
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Shader {
|
||||
namespace Common {
|
||||
|
||||
template <typename T>
|
||||
requires std::is_destructible_v<T>
|
||||
|
@ -104,4 +104,4 @@ private:
|
|||
size_t new_chunk_size{};
|
||||
};
|
||||
|
||||
} // namespace Shader
|
||||
} // namespace Common
|
|
@ -72,6 +72,8 @@ static auto UserPaths = [] {
|
|||
create_path(PathType::GameDataDir, user_dir / GAMEDATA_DIR);
|
||||
create_path(PathType::TempDataDir, user_dir / TEMPDATA_DIR);
|
||||
create_path(PathType::SysModuleDir, user_dir / SYSMODULES_DIR);
|
||||
create_path(PathType::DownloadDir, user_dir / DOWNLOAD_DIR);
|
||||
create_path(PathType::CapturesDir, user_dir / CAPTURES_DIR);
|
||||
|
||||
return paths;
|
||||
}();
|
||||
|
|
|
@ -18,6 +18,8 @@ enum class PathType {
|
|||
TempDataDir, // Where game temp data is stored.
|
||||
GameDataDir, // Where game data is stored.
|
||||
SysModuleDir, // Where system modules are stored.
|
||||
DownloadDir, // Where downloads/temp files are stored.
|
||||
CapturesDir, // Where rdoc captures are stored.
|
||||
};
|
||||
|
||||
constexpr auto PORTABLE_DIR = "user";
|
||||
|
@ -31,6 +33,8 @@ constexpr auto SAVEDATA_DIR = "savedata";
|
|||
constexpr auto GAMEDATA_DIR = "data";
|
||||
constexpr auto TEMPDATA_DIR = "temp";
|
||||
constexpr auto SYSMODULES_DIR = "sys_modules";
|
||||
constexpr auto DOWNLOAD_DIR = "download";
|
||||
constexpr auto CAPTURES_DIR = "captures";
|
||||
|
||||
// Filenames
|
||||
constexpr auto LOG_FILE = "shad_log.txt";
|
||||
|
|
17
src/common/scm_rev.cpp.in
Normal file
17
src/common/scm_rev.cpp.in
Normal file
|
@ -0,0 +1,17 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/scm_rev.h"
|
||||
|
||||
#define GIT_REV "@GIT_REV@"
|
||||
#define GIT_BRANCH "@GIT_BRANCH@"
|
||||
#define GIT_DESC "@GIT_DESC@"
|
||||
|
||||
namespace Common {
|
||||
|
||||
const char g_scm_rev[] = GIT_REV;
|
||||
const char g_scm_branch[] = GIT_BRANCH;
|
||||
const char g_scm_desc[] = GIT_DESC;
|
||||
|
||||
} // namespace
|
||||
|
12
src/common/scm_rev.h
Normal file
12
src/common/scm_rev.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Common {
|
||||
|
||||
extern const char g_scm_rev[];
|
||||
extern const char g_scm_branch[];
|
||||
extern const char g_scm_desc[];
|
||||
|
||||
} // namespace Common
|
61
src/common/unique_function.h
Executable file
61
src/common/unique_function.h
Executable file
|
@ -0,0 +1,61 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace Common {
|
||||
|
||||
/// General purpose function wrapper similar to std::function.
|
||||
/// Unlike std::function, the captured values don't have to be copyable.
|
||||
/// This class can be moved but not copied.
|
||||
template <typename ResultType, typename... Args>
|
||||
class UniqueFunction {
|
||||
class CallableBase {
|
||||
public:
|
||||
virtual ~CallableBase() = default;
|
||||
virtual ResultType operator()(Args&&...) = 0;
|
||||
};
|
||||
|
||||
template <typename Functor>
|
||||
class Callable final : public CallableBase {
|
||||
public:
|
||||
Callable(Functor&& functor_) : functor{std::move(functor_)} {}
|
||||
~Callable() override = default;
|
||||
|
||||
ResultType operator()(Args&&... args) override {
|
||||
return functor(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
private:
|
||||
Functor functor;
|
||||
};
|
||||
|
||||
public:
|
||||
UniqueFunction() = default;
|
||||
|
||||
template <typename Functor>
|
||||
UniqueFunction(Functor&& functor)
|
||||
: callable{std::make_unique<Callable<Functor>>(std::move(functor))} {}
|
||||
|
||||
UniqueFunction& operator=(UniqueFunction&& rhs) noexcept = default;
|
||||
UniqueFunction(UniqueFunction&& rhs) noexcept = default;
|
||||
|
||||
UniqueFunction& operator=(const UniqueFunction&) = delete;
|
||||
UniqueFunction(const UniqueFunction&) = delete;
|
||||
|
||||
ResultType operator()(Args&&... args) const {
|
||||
return (*callable)(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept {
|
||||
return static_cast<bool>(callable);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<CallableBase> callable;
|
||||
};
|
||||
|
||||
} // namespace Common
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
namespace Common {
|
||||
|
||||
constexpr char VERSION[] = "0.1.1 WIP";
|
||||
constexpr char VERSION[] = "0.2.1 WIP";
|
||||
constexpr bool isRelease = false;
|
||||
|
||||
} // namespace Common
|
||||
|
|
|
@ -59,11 +59,11 @@ struct AddressSpace::Impl {
|
|||
static constexpr size_t MaxReductions = 10;
|
||||
|
||||
size_t reduction = 0;
|
||||
size_t virtual_size = SystemManagedSize + SystemReservedSize + UserSize;
|
||||
for (u32 i = 0; i < MaxReductions; i++) {
|
||||
req.LowestStartingAddress = reinterpret_cast<PVOID>(SYSTEM_MANAGED_MIN + reduction);
|
||||
virtual_base = static_cast<u8*>(VirtualAlloc2(
|
||||
process, NULL, SystemManagedSize + SystemReservedSize + UserSize - reduction,
|
||||
MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, ¶m, 1));
|
||||
virtual_base = static_cast<u8*>(VirtualAlloc2(process, NULL, virtual_size - reduction,
|
||||
MEM_RESERVE | MEM_RESERVE_PLACEHOLDER,
|
||||
PAGE_NOACCESS, ¶m, 1));
|
||||
if (virtual_base) {
|
||||
break;
|
||||
}
|
||||
|
@ -92,9 +92,7 @@ struct AddressSpace::Impl {
|
|||
const uintptr_t system_managed_addr = reinterpret_cast<uintptr_t>(system_managed_base);
|
||||
const uintptr_t system_reserved_addr = reinterpret_cast<uintptr_t>(system_reserved_base);
|
||||
const uintptr_t user_addr = reinterpret_cast<uintptr_t>(user_base);
|
||||
placeholders.insert({system_managed_addr, system_managed_addr + system_managed_size});
|
||||
placeholders.insert({system_reserved_addr, system_reserved_addr + system_reserved_size});
|
||||
placeholders.insert({user_addr, user_addr + user_size});
|
||||
placeholders.insert({system_managed_addr, virtual_size - reduction});
|
||||
|
||||
// Allocate backing file that represents the total physical memory.
|
||||
backing_handle =
|
||||
|
@ -456,8 +454,28 @@ void* AddressSpace::MapFile(VAddr virtual_addr, size_t size, size_t offset, u32
|
|||
#endif
|
||||
}
|
||||
|
||||
void AddressSpace::Unmap(VAddr virtual_addr, size_t size, bool has_backing) {
|
||||
return impl->Unmap(virtual_addr, size, has_backing);
|
||||
void AddressSpace::Unmap(VAddr virtual_addr, size_t size, VAddr start_in_vma, VAddr end_in_vma,
|
||||
PAddr phys_base, bool is_exec, bool has_backing) {
|
||||
#ifdef _WIN32
|
||||
// There does not appear to be comparable support for partial unmapping on Windows.
|
||||
// Unfortunately, a least one title was found to require this. The workaround is to unmap
|
||||
// the entire allocation and remap the portions outside of the requested unmapping range.
|
||||
impl->Unmap(virtual_addr, size, has_backing);
|
||||
|
||||
// TODO: Determine if any titles require partial unmapping support for flexible allocations.
|
||||
ASSERT_MSG(has_backing || (start_in_vma == 0 && end_in_vma == size),
|
||||
"Partial unmapping of flexible allocations is not supported");
|
||||
|
||||
if (start_in_vma != 0) {
|
||||
Map(virtual_addr, start_in_vma, 0, phys_base, is_exec);
|
||||
}
|
||||
|
||||
if (end_in_vma != size) {
|
||||
Map(virtual_addr + end_in_vma, size - end_in_vma, 0, phys_base + end_in_vma, is_exec);
|
||||
}
|
||||
#else
|
||||
impl->Unmap(virtual_addr + start_in_vma, end_in_vma - start_in_vma, has_backing);
|
||||
#endif
|
||||
}
|
||||
|
||||
void AddressSpace::Protect(VAddr virtual_addr, size_t size, MemoryPermission perms) {
|
||||
|
|
|
@ -22,7 +22,7 @@ constexpr VAddr CODE_BASE_OFFSET = 0x100000000ULL;
|
|||
|
||||
constexpr VAddr SYSTEM_MANAGED_MIN = 0x00000400000ULL;
|
||||
constexpr VAddr SYSTEM_MANAGED_MAX = 0x07FFFFBFFFULL;
|
||||
constexpr VAddr SYSTEM_RESERVED_MIN = 0x800000000ULL;
|
||||
constexpr VAddr SYSTEM_RESERVED_MIN = 0x07FFFFC000ULL;
|
||||
#ifdef __APPLE__
|
||||
// Can only comfortably reserve the first 0x7C0000000 of system reserved space.
|
||||
constexpr VAddr SYSTEM_RESERVED_MAX = 0xFBFFFFFFFULL;
|
||||
|
@ -34,10 +34,7 @@ constexpr VAddr USER_MAX = 0xFBFFFFFFFFULL;
|
|||
|
||||
static constexpr size_t SystemManagedSize = SYSTEM_MANAGED_MAX - SYSTEM_MANAGED_MIN + 1;
|
||||
static constexpr size_t SystemReservedSize = SYSTEM_RESERVED_MAX - SYSTEM_RESERVED_MIN + 1;
|
||||
// User area size is normally larger than this. However games are unlikely to map to high
|
||||
// regions of that area, so by default we allocate a smaller virtual address space (about 1/4th).
|
||||
// to save space on page tables.
|
||||
static constexpr size_t UserSize = 1ULL << 39;
|
||||
static constexpr size_t UserSize = 1ULL << 40;
|
||||
|
||||
/**
|
||||
* Represents the user virtual address space backed by a dmem memory block
|
||||
|
@ -94,7 +91,8 @@ public:
|
|||
void* MapFile(VAddr virtual_addr, size_t size, size_t offset, u32 prot, uintptr_t fd);
|
||||
|
||||
/// Unmaps specified virtual memory area.
|
||||
void Unmap(VAddr virtual_addr, size_t size, bool has_backing);
|
||||
void Unmap(VAddr virtual_addr, size_t size, VAddr start_in_vma, VAddr end_in_vma,
|
||||
PAddr phys_base, bool is_exec, bool has_backing);
|
||||
|
||||
void Protect(VAddr virtual_addr, size_t size, MemoryPermission perms);
|
||||
|
||||
|
|
|
@ -285,20 +285,24 @@ static void GenerateTcbAccess(const ZydisDecodedOperand* operands, Xbyak::CodeGe
|
|||
const auto slot = GetTcbKey();
|
||||
|
||||
#if defined(_WIN32)
|
||||
// The following logic is based on the wine implementation of TlsGetValue
|
||||
// https://github.com/wine-mirror/wine/blob/a27b9551/dlls/kernelbase/thread.c#L719
|
||||
// The following logic is based on the Kernel32.dll asm of TlsGetValue
|
||||
static constexpr u32 TlsSlotsOffset = 0x1480;
|
||||
static constexpr u32 TlsExpansionSlotsOffset = 0x1780;
|
||||
static constexpr u32 TlsMinimumAvailable = 64;
|
||||
|
||||
const u32 teb_offset = slot < TlsMinimumAvailable ? TlsSlotsOffset : TlsExpansionSlotsOffset;
|
||||
const u32 tls_index = slot < TlsMinimumAvailable ? slot : slot - TlsMinimumAvailable;
|
||||
|
||||
// Load the pointer to the table of TLS slots.
|
||||
c.putSeg(gs);
|
||||
c.mov(dst, ptr[reinterpret_cast<void*>(teb_offset)]);
|
||||
// Load the pointer to our buffer.
|
||||
c.mov(dst, qword[dst + tls_index * sizeof(LPVOID)]);
|
||||
if (slot < TlsMinimumAvailable) {
|
||||
// Load the pointer to TLS slots.
|
||||
c.mov(dst, ptr[reinterpret_cast<void*>(TlsSlotsOffset + slot * sizeof(LPVOID))]);
|
||||
} else {
|
||||
const u32 tls_index = slot - TlsMinimumAvailable;
|
||||
|
||||
// Load the pointer to the table of TLS expansion slots.
|
||||
c.mov(dst, ptr[reinterpret_cast<void*>(TlsExpansionSlotsOffset)]);
|
||||
// Load the pointer to our buffer.
|
||||
c.mov(dst, qword[dst + tls_index * sizeof(LPVOID)]);
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
// The following logic is based on the Darwin implementation of _os_tsd_get_direct, used by
|
||||
// pthread_getspecific https://github.com/apple/darwin-xnu/blob/main/libsyscall/os/tsd.h#L89-L96
|
||||
|
|
|
@ -350,7 +350,7 @@ bool PKG::Extract(const std::filesystem::path& filepath, const std::filesystem::
|
|||
return true;
|
||||
}
|
||||
|
||||
void PKG::ExtractFiles(const int& index) {
|
||||
void PKG::ExtractFiles(const int index) {
|
||||
int inode_number = fsTable[index].inode;
|
||||
int inode_type = fsTable[index].type;
|
||||
std::string inode_name = fsTable[index].name;
|
||||
|
|
|
@ -104,7 +104,7 @@ public:
|
|||
~PKG();
|
||||
|
||||
bool Open(const std::filesystem::path& filepath);
|
||||
void ExtractFiles(const int& index);
|
||||
void ExtractFiles(const int index);
|
||||
bool Extract(const std::filesystem::path& filepath, const std::filesystem::path& extract,
|
||||
std::string& failreason);
|
||||
|
||||
|
|
16
src/core/file_format/playgo_chunk.cpp
Normal file
16
src/core/file_format/playgo_chunk.cpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/io_file.h"
|
||||
|
||||
#include "playgo_chunk.h"
|
||||
|
||||
bool PlaygoChunk::Open(const std::filesystem::path& filepath) {
|
||||
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read);
|
||||
if (!file.IsOpen()) {
|
||||
return false;
|
||||
}
|
||||
file.Read(playgoHeader);
|
||||
|
||||
return true;
|
||||
}
|
31
src/core/file_format/playgo_chunk.h
Normal file
31
src/core/file_format/playgo_chunk.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
#include <filesystem>
|
||||
#include "common/types.h"
|
||||
|
||||
struct PlaygoHeader {
|
||||
u32 magic;
|
||||
|
||||
u16 version_major;
|
||||
u16 version_minor;
|
||||
u16 image_count;
|
||||
u16 chunk_count;
|
||||
u16 mchunk_count;
|
||||
u16 scenario_count;
|
||||
// TODO fill the rest
|
||||
};
|
||||
class PlaygoChunk {
|
||||
public:
|
||||
PlaygoChunk() = default;
|
||||
~PlaygoChunk() = default;
|
||||
|
||||
bool Open(const std::filesystem::path& filepath);
|
||||
PlaygoHeader GetPlaygoHeader() {
|
||||
return playgoHeader;
|
||||
}
|
||||
|
||||
private:
|
||||
PlaygoHeader playgoHeader;
|
||||
};
|
|
@ -6,7 +6,7 @@
|
|||
TRP::TRP() = default;
|
||||
TRP::~TRP() = default;
|
||||
|
||||
void TRP::GetNPcommID(std::filesystem::path trophyPath, int index) {
|
||||
void TRP::GetNPcommID(const std::filesystem::path& trophyPath, int index) {
|
||||
std::filesystem::path trpPath = trophyPath / "sce_sys/npbind.dat";
|
||||
Common::FS::IOFile npbindFile(trpPath, Common::FS::FileAccessMode::Read);
|
||||
if (!npbindFile.IsOpen()) {
|
||||
|
@ -27,7 +27,7 @@ static void removePadding(std::vector<u8>& vec) {
|
|||
}
|
||||
}
|
||||
|
||||
bool TRP::Extract(std::filesystem::path trophyPath) {
|
||||
bool TRP::Extract(const std::filesystem::path& trophyPath) {
|
||||
std::string title = trophyPath.filename().string();
|
||||
std::filesystem::path gameSysDir = trophyPath / "sce_sys/trophy/";
|
||||
if (!std::filesystem::exists(gameSysDir)) {
|
||||
|
|
|
@ -33,8 +33,8 @@ class TRP {
|
|||
public:
|
||||
TRP();
|
||||
~TRP();
|
||||
bool Extract(std::filesystem::path trophyPath);
|
||||
void GetNPcommID(std::filesystem::path trophyPath, int index);
|
||||
bool Extract(const std::filesystem::path& trophyPath);
|
||||
void GetNPcommID(const std::filesystem::path& trophyPath, int index);
|
||||
|
||||
private:
|
||||
Crypto crypto;
|
||||
|
|
|
@ -25,24 +25,28 @@ void MntPoints::UnmountAll() {
|
|||
m_mnt_pairs.clear();
|
||||
}
|
||||
|
||||
std::filesystem::path MntPoints::GetHostPath(const std::string& guest_directory) {
|
||||
const MntPair* mount = GetMount(guest_directory);
|
||||
std::filesystem::path MntPoints::GetHostPath(std::string_view guest_directory) {
|
||||
// Evil games like Turok2 pass double slashes e.g /app0//game.kpf
|
||||
std::string corrected_path(guest_directory);
|
||||
size_t pos = corrected_path.find("//");
|
||||
while (pos != std::string::npos) {
|
||||
corrected_path.replace(pos, 2, "/");
|
||||
pos = corrected_path.find("//", pos + 1);
|
||||
}
|
||||
|
||||
const MntPair* mount = GetMount(corrected_path);
|
||||
if (!mount) {
|
||||
return guest_directory;
|
||||
return "";
|
||||
}
|
||||
|
||||
// Nothing to do if getting the mount itself.
|
||||
if (guest_directory == mount->mount) {
|
||||
if (corrected_path == mount->mount) {
|
||||
return mount->host_path;
|
||||
}
|
||||
|
||||
// Remove device (e.g /app0) from path to retrieve relative path.
|
||||
u32 pos = mount->mount.size() + 1;
|
||||
// Evil games like Turok2 pass double slashes e.g /app0//game.kpf
|
||||
if (guest_directory[pos] == '/') {
|
||||
pos++;
|
||||
}
|
||||
const auto rel_path = std::string_view(guest_directory).substr(pos);
|
||||
pos = mount->mount.size() + 1;
|
||||
const auto rel_path = std::string_view(corrected_path).substr(pos);
|
||||
const auto host_path = mount->host_path / rel_path;
|
||||
if (!NeedsCaseInsensiveSearch) {
|
||||
return host_path;
|
||||
|
@ -50,6 +54,7 @@ std::filesystem::path MntPoints::GetHostPath(const std::string& guest_directory)
|
|||
|
||||
// If the path does not exist attempt to verify this.
|
||||
// Retrieve parent path until we find one that exists.
|
||||
std::scoped_lock lk{m_mutex};
|
||||
path_parts.clear();
|
||||
auto current_path = host_path;
|
||||
while (!std::filesystem::exists(current_path)) {
|
||||
|
@ -66,7 +71,7 @@ std::filesystem::path MntPoints::GetHostPath(const std::string& guest_directory)
|
|||
// exist in filesystem but in different case.
|
||||
auto guest_path = current_path;
|
||||
while (!path_parts.empty()) {
|
||||
const auto& part = path_parts.back();
|
||||
const auto part = path_parts.back();
|
||||
const auto add_match = [&](const auto& host_part) {
|
||||
current_path /= host_part;
|
||||
guest_path /= part;
|
||||
|
|
|
@ -31,7 +31,7 @@ public:
|
|||
void Unmount(const std::filesystem::path& host_folder, const std::string& guest_folder);
|
||||
void UnmountAll();
|
||||
|
||||
std::filesystem::path GetHostPath(const std::string& guest_directory);
|
||||
std::filesystem::path GetHostPath(std::string_view guest_directory);
|
||||
|
||||
const MntPair* GetMount(const std::string& guest_path) {
|
||||
const auto it = std::ranges::find_if(
|
||||
|
|
|
@ -198,13 +198,9 @@ int PS4_SYSV_ABI sceAppContentTemporaryDataMount() {
|
|||
|
||||
int PS4_SYSV_ABI sceAppContentTemporaryDataMount2(OrbisAppContentTemporaryDataOption option,
|
||||
OrbisAppContentMountPoint* mountPoint) {
|
||||
if (std::string_view(mountPoint->data).empty()) // causing issues with save_data.
|
||||
if (mountPoint == nullptr)
|
||||
return ORBIS_APP_CONTENT_ERROR_PARAMETER;
|
||||
auto* param_sfo = Common::Singleton<PSF>::Instance();
|
||||
std::string id(param_sfo->GetString("CONTENT_ID"), 7, 9);
|
||||
const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::TempDataDir) / id;
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
mnt->Mount(mount_dir, mountPoint->data);
|
||||
strncpy(mountPoint->data, "/temp0", 16);
|
||||
LOG_INFO(Lib_AppContent, "sceAppContentTemporaryDataMount2: option = {}, mountPoint = {}",
|
||||
option, mountPoint->data);
|
||||
return ORBIS_OK;
|
||||
|
|
|
@ -175,7 +175,6 @@ int PS4_SYSV_ABI sceAudioOutGetLastOutputTime() {
|
|||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* state) {
|
||||
|
||||
int type = 0;
|
||||
int channels_num = 0;
|
||||
|
||||
|
@ -235,8 +234,11 @@ int PS4_SYSV_ABI sceAudioOutGetSystemState() {
|
|||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAudioOutInit() {
|
||||
LOG_TRACE(Lib_AudioOut, "called");
|
||||
if (audio != nullptr) {
|
||||
return ORBIS_AUDIO_OUT_ERROR_ALREADY_INIT;
|
||||
}
|
||||
audio = std::make_unique<Audio::SDLAudio>();
|
||||
LOG_INFO(Lib_AudioOut, "called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,21 +1,34 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Generated By moduleGenerator
|
||||
#include "avplayer.h"
|
||||
|
||||
#include "avplayer_impl.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/kernel/thread_management.h"
|
||||
#include "core/libraries/libs.h"
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerAddSource() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
using namespace Kernel;
|
||||
|
||||
s32 PS4_SYSV_ABI sceAvPlayerAddSource(SceAvPlayerHandle handle, const char* filename) {
|
||||
LOG_TRACE(Lib_AvPlayer, "filename = {}", filename);
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
const auto res = handle->AddSource(filename);
|
||||
LOG_TRACE(Lib_AvPlayer, "returning {}", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerAddSourceEx() {
|
||||
s32 PS4_SYSV_ABI sceAvPlayerAddSourceEx(SceAvPlayerHandle handle, SceAvPlayerUriType uriType,
|
||||
SceAvPlayerSourceDetails* sourceDetails) {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
@ -24,122 +37,244 @@ int PS4_SYSV_ABI sceAvPlayerChangeStream() {
|
|||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerClose() {
|
||||
s32 PS4_SYSV_ABI sceAvPlayerClose(SceAvPlayerHandle handle) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called");
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
delete handle;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
u64 PS4_SYSV_ABI sceAvPlayerCurrentTime(SceAvPlayerHandle handle) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called");
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
const auto res = handle->CurrentTime();
|
||||
LOG_TRACE(Lib_AvPlayer, "returning {}", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAvPlayerDisableStream(SceAvPlayerHandle handle, u32 stream_id) {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAvPlayerEnableStream(SceAvPlayerHandle handle, u32 stream_id) {
|
||||
LOG_TRACE(Lib_AvPlayer, "stream_id = {}", stream_id);
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
const auto res = handle->EnableStream(stream_id);
|
||||
LOG_TRACE(Lib_AvPlayer, "returning {}", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool PS4_SYSV_ABI sceAvPlayerGetAudioData(SceAvPlayerHandle handle, SceAvPlayerFrameInfo* p_info) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called");
|
||||
if (handle == nullptr || p_info == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
const auto res = handle->GetAudioData(*p_info);
|
||||
LOG_TRACE(Lib_AvPlayer, "returning {}", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAvPlayerGetStreamInfo(SceAvPlayerHandle handle, u32 stream_id,
|
||||
SceAvPlayerStreamInfo* p_info) {
|
||||
LOG_TRACE(Lib_AvPlayer, "stream_id = {}", stream_id);
|
||||
if (handle == nullptr || p_info == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
const auto res = handle->GetStreamInfo(stream_id, *p_info);
|
||||
LOG_TRACE(Lib_AvPlayer, "returning {}", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool PS4_SYSV_ABI sceAvPlayerGetVideoData(SceAvPlayerHandle handle,
|
||||
SceAvPlayerFrameInfo* video_info) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called");
|
||||
if (handle == nullptr || video_info == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
const auto res = handle->GetVideoData(*video_info);
|
||||
LOG_TRACE(Lib_AvPlayer, "returning {}", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool PS4_SYSV_ABI sceAvPlayerGetVideoDataEx(SceAvPlayerHandle handle,
|
||||
SceAvPlayerFrameInfoEx* video_info) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called");
|
||||
if (handle == nullptr || video_info == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
const auto res = handle->GetVideoData(*video_info);
|
||||
LOG_TRACE(Lib_AvPlayer, "returning {}", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
SceAvPlayerHandle PS4_SYSV_ABI sceAvPlayerInit(SceAvPlayerInitData* data) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called");
|
||||
if (data == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (data->memory_replacement.allocate == nullptr ||
|
||||
data->memory_replacement.allocate_texture == nullptr ||
|
||||
data->memory_replacement.deallocate == nullptr ||
|
||||
data->memory_replacement.deallocate_texture == nullptr) {
|
||||
LOG_ERROR(Lib_AvPlayer, "All allocators are required for AVPlayer Initialisation.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new AvPlayer(*data);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAvPlayerInitEx(const SceAvPlayerInitDataEx* p_data,
|
||||
SceAvPlayerHandle* p_player) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called");
|
||||
if (p_data == nullptr || p_player == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
|
||||
if (p_data->memory_replacement.allocate == nullptr ||
|
||||
p_data->memory_replacement.allocate_texture == nullptr ||
|
||||
p_data->memory_replacement.deallocate == nullptr ||
|
||||
p_data->memory_replacement.deallocate_texture == nullptr) {
|
||||
LOG_ERROR(Lib_AvPlayer, "All allocators are required for AVPlayer Initialisation.");
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
|
||||
SceAvPlayerInitData data = {};
|
||||
data.memory_replacement = p_data->memory_replacement;
|
||||
data.file_replacement = p_data->file_replacement;
|
||||
data.event_replacement = p_data->event_replacement;
|
||||
data.default_language = p_data->default_language;
|
||||
data.num_output_video_framebuffers = p_data->num_output_video_framebuffers;
|
||||
data.auto_start = p_data->auto_start;
|
||||
|
||||
*p_player = new AvPlayer(data);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
bool PS4_SYSV_ABI sceAvPlayerIsActive(SceAvPlayerHandle handle) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called");
|
||||
if (handle == nullptr) {
|
||||
LOG_TRACE(Lib_AvPlayer, "returning ORBIS_AVPLAYER_ERROR_INVALID_PARAMS");
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
const auto res = handle->IsActive();
|
||||
LOG_TRACE(Lib_AvPlayer, "returning {}", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAvPlayerJumpToTime(SceAvPlayerHandle handle, uint64_t time) {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called, time (msec) = {}", time);
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAvPlayerPause(SceAvPlayerHandle handle) {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAvPlayerPostInit(SceAvPlayerHandle handle, SceAvPlayerPostInitData* data) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called");
|
||||
if (handle == nullptr || data == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
const auto res = handle->PostInit(*data);
|
||||
LOG_TRACE(Lib_AvPlayer, "returning {}", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAvPlayerPrintf(const char* format, ...) {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerCurrentTime() {
|
||||
s32 PS4_SYSV_ABI sceAvPlayerResume(SceAvPlayerHandle handle) {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAvPlayerSetAvSyncMode(SceAvPlayerHandle handle,
|
||||
SceAvPlayerAvSyncMode sync_mode) {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAvPlayerSetLogCallback(SceAvPlayerLogCallback log_cb, void* user_data) {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerDisableStream() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
s32 PS4_SYSV_ABI sceAvPlayerSetLooping(SceAvPlayerHandle handle, bool loop_flag) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called, looping = {}", loop_flag);
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
if (!handle->SetLooping(loop_flag)) {
|
||||
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerEnableStream() {
|
||||
s32 PS4_SYSV_ABI sceAvPlayerSetTrickSpeed(SceAvPlayerHandle handle, s32 trick_speed) {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerGetAudioData() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
s32 PS4_SYSV_ABI sceAvPlayerStart(SceAvPlayerHandle handle) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called");
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
const auto res = handle->Start();
|
||||
LOG_TRACE(Lib_AvPlayer, "returning {}", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerGetStreamInfo() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
s32 PS4_SYSV_ABI sceAvPlayerStop(SceAvPlayerHandle handle) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called");
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
const auto res = handle->Stop();
|
||||
LOG_TRACE(Lib_AvPlayer, "returning {}", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerGetVideoData() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
s32 PS4_SYSV_ABI sceAvPlayerStreamCount(SceAvPlayerHandle handle) {
|
||||
LOG_TRACE(Lib_AvPlayer, "called");
|
||||
if (handle == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
const auto res = handle->GetStreamCount();
|
||||
LOG_TRACE(Lib_AvPlayer, "returning {}", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerGetVideoDataEx() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerInit() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerInitEx() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerIsActive() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerJumpToTime() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerPause() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerPostInit() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerPrintf() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerResume() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerSetAvSyncMode() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerSetLogCallback() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerSetLooping() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerSetTrickSpeed() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerStart() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerStop() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerStreamCount() {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerVprintf() {
|
||||
s32 PS4_SYSV_ABI sceAvPlayerVprintf(const char* format, va_list args) {
|
||||
LOG_ERROR(Lib_AvPlayer, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
|
|
@ -5,39 +5,288 @@
|
|||
|
||||
#include "common/types.h"
|
||||
|
||||
#include <stdarg.h> // va_list
|
||||
#include <stddef.h> // size_t
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
int PS4_SYSV_ABI sceAvPlayerAddSource();
|
||||
int PS4_SYSV_ABI sceAvPlayerAddSourceEx();
|
||||
int PS4_SYSV_ABI sceAvPlayerChangeStream();
|
||||
int PS4_SYSV_ABI sceAvPlayerClose();
|
||||
int PS4_SYSV_ABI sceAvPlayerCurrentTime();
|
||||
int PS4_SYSV_ABI sceAvPlayerDisableStream();
|
||||
int PS4_SYSV_ABI sceAvPlayerEnableStream();
|
||||
int PS4_SYSV_ABI sceAvPlayerGetAudioData();
|
||||
int PS4_SYSV_ABI sceAvPlayerGetStreamInfo();
|
||||
int PS4_SYSV_ABI sceAvPlayerGetVideoData();
|
||||
int PS4_SYSV_ABI sceAvPlayerGetVideoDataEx();
|
||||
int PS4_SYSV_ABI sceAvPlayerInit();
|
||||
int PS4_SYSV_ABI sceAvPlayerInitEx();
|
||||
int PS4_SYSV_ABI sceAvPlayerIsActive();
|
||||
int PS4_SYSV_ABI sceAvPlayerJumpToTime();
|
||||
int PS4_SYSV_ABI sceAvPlayerPause();
|
||||
int PS4_SYSV_ABI sceAvPlayerPostInit();
|
||||
int PS4_SYSV_ABI sceAvPlayerPrintf();
|
||||
int PS4_SYSV_ABI sceAvPlayerResume();
|
||||
int PS4_SYSV_ABI sceAvPlayerSetAvSyncMode();
|
||||
int PS4_SYSV_ABI sceAvPlayerSetLogCallback();
|
||||
int PS4_SYSV_ABI sceAvPlayerSetLooping();
|
||||
int PS4_SYSV_ABI sceAvPlayerSetTrickSpeed();
|
||||
int PS4_SYSV_ABI sceAvPlayerStart();
|
||||
int PS4_SYSV_ABI sceAvPlayerStop();
|
||||
int PS4_SYSV_ABI sceAvPlayerStreamCount();
|
||||
int PS4_SYSV_ABI sceAvPlayerVprintf();
|
||||
class AvPlayer;
|
||||
|
||||
using SceAvPlayerHandle = AvPlayer*;
|
||||
|
||||
enum SceAvPlayerUriType { SCE_AVPLAYER_URI_TYPE_SOURCE = 0 };
|
||||
|
||||
struct SceAvPlayerUri {
|
||||
const char* name;
|
||||
u32 length;
|
||||
};
|
||||
|
||||
enum SceAvPlayerSourceType {
|
||||
SCE_AVPLAYER_SOURCE_TYPE_UNKNOWN = 0,
|
||||
SCE_AVPLAYER_SOURCE_TYPE_FILE_MP4 = 1,
|
||||
SCE_AVPLAYER_SOURCE_TYPE_HLS = 8
|
||||
};
|
||||
|
||||
struct SceAvPlayerSourceDetails {
|
||||
SceAvPlayerUri uri;
|
||||
u8 reserved1[64];
|
||||
SceAvPlayerSourceType source_type;
|
||||
u8 reserved2[44];
|
||||
};
|
||||
|
||||
struct SceAvPlayerAudio {
|
||||
u16 channel_count;
|
||||
u8 reserved1[2];
|
||||
u32 sample_rate;
|
||||
u32 size;
|
||||
u8 language_code[4];
|
||||
};
|
||||
|
||||
struct SceAvPlayerVideo {
|
||||
u32 width;
|
||||
u32 height;
|
||||
f32 aspect_ratio;
|
||||
u8 language_code[4];
|
||||
};
|
||||
|
||||
struct SceAvPlayerTextPosition {
|
||||
u16 top;
|
||||
u16 left;
|
||||
u16 bottom;
|
||||
u16 right;
|
||||
};
|
||||
|
||||
struct SceAvPlayerTimedText {
|
||||
u8 language_code[4];
|
||||
u16 text_size;
|
||||
u16 font_size;
|
||||
SceAvPlayerTextPosition position;
|
||||
};
|
||||
|
||||
union SceAvPlayerStreamDetails {
|
||||
u8 reserved[16];
|
||||
SceAvPlayerAudio audio;
|
||||
SceAvPlayerVideo video;
|
||||
SceAvPlayerTimedText subs;
|
||||
};
|
||||
|
||||
struct SceAvPlayerFrameInfo {
|
||||
u8* pData;
|
||||
u8 reserved[4];
|
||||
u64 timestamp;
|
||||
SceAvPlayerStreamDetails details;
|
||||
};
|
||||
|
||||
struct SceAvPlayerStreamInfo {
|
||||
u32 type;
|
||||
u8 reserved[4];
|
||||
SceAvPlayerStreamDetails details;
|
||||
u64 duration;
|
||||
u64 start_time;
|
||||
};
|
||||
|
||||
struct SceAvPlayerAudioEx {
|
||||
u16 channel_count;
|
||||
u8 reserved[2];
|
||||
u32 sample_rate;
|
||||
u32 size;
|
||||
u8 language_code[4];
|
||||
u8 reserved1[64];
|
||||
};
|
||||
|
||||
struct SceAvPlayerVideoEx {
|
||||
u32 width;
|
||||
u32 height;
|
||||
f32 aspect_ratio;
|
||||
u8 language_code[4];
|
||||
u32 framerate;
|
||||
u32 crop_left_offset;
|
||||
u32 crop_right_offset;
|
||||
u32 crop_top_offset;
|
||||
u32 crop_bottom_offset;
|
||||
u32 pitch;
|
||||
u8 luma_bit_depth;
|
||||
u8 chroma_bit_depth;
|
||||
bool video_full_range_flag;
|
||||
u8 reserved1[37];
|
||||
};
|
||||
|
||||
struct SceAvPlayerTimedTextEx {
|
||||
u8 language_code[4];
|
||||
u8 reserved[12];
|
||||
u8 reserved1[64];
|
||||
};
|
||||
|
||||
union SceAvPlayerStreamDetailsEx {
|
||||
SceAvPlayerAudioEx audio;
|
||||
SceAvPlayerVideoEx video;
|
||||
SceAvPlayerTimedTextEx subs;
|
||||
u8 reserved1[80];
|
||||
};
|
||||
|
||||
struct SceAvPlayerFrameInfoEx {
|
||||
void* pData;
|
||||
u8 reserved[4];
|
||||
u64 timestamp;
|
||||
SceAvPlayerStreamDetailsEx details;
|
||||
};
|
||||
|
||||
typedef void* PS4_SYSV_ABI (*SceAvPlayerAllocate)(void* p, u32 align, u32 size);
|
||||
typedef void PS4_SYSV_ABI (*SceAvPlayerDeallocate)(void* p, void* mem);
|
||||
typedef void* PS4_SYSV_ABI (*SceAvPlayerAllocateTexture)(void* p, u32 align, u32 size);
|
||||
typedef void PS4_SYSV_ABI (*SceAvPlayerDeallocateTexture)(void* p, void* mem);
|
||||
|
||||
struct SceAvPlayerMemAllocator {
|
||||
void* object_ptr;
|
||||
SceAvPlayerAllocate allocate;
|
||||
SceAvPlayerDeallocate deallocate;
|
||||
SceAvPlayerAllocateTexture allocate_texture;
|
||||
SceAvPlayerDeallocateTexture deallocate_texture;
|
||||
};
|
||||
|
||||
typedef s32 PS4_SYSV_ABI (*SceAvPlayerOpenFile)(void* p, const char* name);
|
||||
typedef s32 PS4_SYSV_ABI (*SceAvPlayerCloseFile)(void* p);
|
||||
typedef s32 PS4_SYSV_ABI (*SceAvPlayerReadOffsetFile)(void* p, u8* buf, u64 pos, u32 len);
|
||||
typedef u64 PS4_SYSV_ABI (*SceAvPlayerSizeFile)(void* p);
|
||||
|
||||
struct SceAvPlayerFileReplacement {
|
||||
void* object_ptr;
|
||||
SceAvPlayerOpenFile open;
|
||||
SceAvPlayerCloseFile close;
|
||||
SceAvPlayerReadOffsetFile readOffset;
|
||||
SceAvPlayerSizeFile size;
|
||||
};
|
||||
|
||||
typedef void PS4_SYSV_ABI (*SceAvPlayerEventCallback)(void* p, s32 event, s32 src_id, void* data);
|
||||
|
||||
struct SceAvPlayerEventReplacement {
|
||||
void* object_ptr;
|
||||
SceAvPlayerEventCallback event_callback;
|
||||
};
|
||||
|
||||
enum SceAvPlayerDebuglevels {
|
||||
SCE_AVPLAYER_DBG_NONE,
|
||||
SCE_AVPLAYER_DBG_INFO,
|
||||
SCE_AVPLAYER_DBG_WARNINGS,
|
||||
SCE_AVPLAYER_DBG_ALL
|
||||
};
|
||||
|
||||
struct SceAvPlayerInitData {
|
||||
SceAvPlayerMemAllocator memory_replacement;
|
||||
SceAvPlayerFileReplacement file_replacement;
|
||||
SceAvPlayerEventReplacement event_replacement;
|
||||
SceAvPlayerDebuglevels debug_level;
|
||||
u32 base_priority;
|
||||
s32 num_output_video_framebuffers;
|
||||
bool auto_start;
|
||||
u8 reserved[3];
|
||||
const char* default_language;
|
||||
};
|
||||
|
||||
struct SceAvPlayerInitDataEx {
|
||||
size_t this_size;
|
||||
SceAvPlayerMemAllocator memory_replacement;
|
||||
SceAvPlayerFileReplacement file_replacement;
|
||||
SceAvPlayerEventReplacement event_replacement;
|
||||
const char* default_language;
|
||||
SceAvPlayerDebuglevels debug_level;
|
||||
u32 audio_decoder_priority;
|
||||
u32 audio_decoder_affinity;
|
||||
u32 video_decoder_priority;
|
||||
u32 video_decoder_affinity;
|
||||
u32 demuxer_priority;
|
||||
u32 demuxer_affinity;
|
||||
u32 controller_priority;
|
||||
u32 controller_affinity;
|
||||
u32 http_streaming_priority;
|
||||
u32 http_streaming_affinity;
|
||||
u32 file_streaming_priority;
|
||||
u32 file_streaming_affinity;
|
||||
s32 num_output_video_framebuffers;
|
||||
bool auto_start;
|
||||
u8 reserved[3];
|
||||
};
|
||||
|
||||
enum SceAvPlayerStreamType {
|
||||
SCE_AVPLAYER_VIDEO,
|
||||
SCE_AVPLAYER_AUDIO,
|
||||
SCE_AVPLAYER_TIMEDTEXT,
|
||||
SCE_AVPLAYER_UNKNOWN
|
||||
};
|
||||
|
||||
enum SceAvPlayerVideoDecoderType {
|
||||
SCE_AVPLAYER_VIDEO_DECODER_TYPE_DEFAULT = 0,
|
||||
SCE_AVPLAYER_VIDEO_DECODER_TYPE_RESERVED1,
|
||||
SCE_AVPLAYER_VIDEO_DECODER_TYPE_SOFTWARE,
|
||||
SCE_AVPLAYER_VIDEO_DECODER_TYPE_SOFTWARE2
|
||||
};
|
||||
|
||||
enum SceAvPlayerAudioDecoderType {
|
||||
SCE_AVPLAYER_AUDIO_DECODER_TYPE_DEFAULT = 0,
|
||||
SCE_AVPLAYER_AUDIO_DECODER_TYPE_RESERVED1,
|
||||
SCE_AVPLAYER_AUDIO_DECODER_TYPE_RESERVED2
|
||||
};
|
||||
|
||||
struct SceAvPlayerDecoderInit {
|
||||
union {
|
||||
SceAvPlayerVideoDecoderType video_type;
|
||||
SceAvPlayerAudioDecoderType audio_type;
|
||||
u8 reserved[4];
|
||||
} decoderType;
|
||||
union {
|
||||
struct {
|
||||
s32 cpu_affinity_mask;
|
||||
s32 cpu_thread_priority;
|
||||
u8 decode_pipeline_depth;
|
||||
u8 compute_pipe_id;
|
||||
u8 compute_queue_id;
|
||||
u8 enable_interlaced;
|
||||
u8 reserved[16];
|
||||
} avcSw2;
|
||||
struct {
|
||||
u8 audio_channel_order;
|
||||
u8 reserved[27];
|
||||
} aac;
|
||||
u8 reserved[28];
|
||||
} decoderParams;
|
||||
};
|
||||
|
||||
struct SceAvPlayerHTTPCtx {
|
||||
u32 http_context_id;
|
||||
u32 ssl_context_id;
|
||||
};
|
||||
|
||||
struct SceAvPlayerPostInitData {
|
||||
u32 demux_video_buffer_size;
|
||||
SceAvPlayerDecoderInit video_decoder_init;
|
||||
SceAvPlayerDecoderInit audio_decoder_init;
|
||||
SceAvPlayerHTTPCtx http_context;
|
||||
u8 reserved[56];
|
||||
};
|
||||
|
||||
enum SceAvPlayerAvSyncMode {
|
||||
SCE_AVPLAYER_AV_SYNC_MODE_DEFAULT = 0,
|
||||
SCE_AVPLAYER_AV_SYNC_MODE_NONE
|
||||
};
|
||||
|
||||
typedef int PS4_SYSV_ABI (*SceAvPlayerLogCallback)(void* p, const char* format, va_list args);
|
||||
|
||||
enum SceAvPlayerEvents {
|
||||
SCE_AVPLAYER_STATE_STOP = 0x01,
|
||||
SCE_AVPLAYER_STATE_READY = 0x02,
|
||||
SCE_AVPLAYER_STATE_PLAY = 0x03,
|
||||
SCE_AVPLAYER_STATE_PAUSE = 0x04,
|
||||
SCE_AVPLAYER_STATE_BUFFERING = 0x05,
|
||||
SCE_AVPLAYER_TIMED_TEXT_DELIVERY = 0x10,
|
||||
SCE_AVPLAYER_WARNING_ID = 0x20,
|
||||
SCE_AVPLAYER_ENCRYPTION = 0x30,
|
||||
SCE_AVPLAYER_DRM_ERROR = 0x40
|
||||
};
|
||||
|
||||
void RegisterlibSceAvPlayer(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::AvPlayer
|
||||
|
||||
} // namespace Libraries::AvPlayer
|
||||
|
|
61
src/core/libraries/avplayer/avplayer_common.cpp
Normal file
61
src/core/libraries/avplayer/avplayer_common.cpp
Normal file
|
@ -0,0 +1,61 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "avplayer.h"
|
||||
#include "avplayer_common.h"
|
||||
|
||||
#include <algorithm> // std::equal
|
||||
#include <cctype> // std::tolower
|
||||
#include <string_view> // std::string_view
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
using namespace Kernel;
|
||||
|
||||
static bool ichar_equals(char a, char b) {
|
||||
return std::tolower(static_cast<unsigned char>(a)) ==
|
||||
std::tolower(static_cast<unsigned char>(b));
|
||||
}
|
||||
|
||||
static bool iequals(std::string_view l, std::string_view r) {
|
||||
return std::ranges::equal(l, r, ichar_equals);
|
||||
}
|
||||
|
||||
SceAvPlayerSourceType GetSourceType(std::string_view path) {
|
||||
if (path.empty()) {
|
||||
return SCE_AVPLAYER_SOURCE_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
std::string_view name = path;
|
||||
if (path.find("://") != std::string_view::npos) {
|
||||
// This path is a URI. Strip HTTP parameters from it.
|
||||
// schema://server.domain/path/file.ext/and/beyond?param=value#paragraph ->
|
||||
// -> schema://server.domain/path/to/file.ext/and/beyond
|
||||
name = path.substr(0, path.find_first_of("?#"));
|
||||
if (name.empty()) {
|
||||
return SCE_AVPLAYER_SOURCE_TYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
// schema://server.domain/path/to/file.ext/and/beyond -> .ext/and/beyond
|
||||
auto ext = name.substr(name.rfind('.'));
|
||||
if (ext.empty()) {
|
||||
return SCE_AVPLAYER_SOURCE_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
// .ext/and/beyond -> .ext
|
||||
ext = ext.substr(0, ext.find('/'));
|
||||
|
||||
if (iequals(ext, ".mp4") || iequals(ext, ".m4v") || iequals(ext, ".m3d") ||
|
||||
iequals(ext, ".m4a") || iequals(ext, ".mov")) {
|
||||
return SCE_AVPLAYER_SOURCE_TYPE_FILE_MP4;
|
||||
}
|
||||
|
||||
if (iequals(ext, ".m3u8")) {
|
||||
return SCE_AVPLAYER_SOURCE_TYPE_HLS;
|
||||
}
|
||||
|
||||
return SCE_AVPLAYER_SOURCE_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
} // namespace Libraries::AvPlayer
|
91
src/core/libraries/avplayer/avplayer_common.h
Normal file
91
src/core/libraries/avplayer/avplayer_common.h
Normal file
|
@ -0,0 +1,91 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "avplayer.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/kernel/thread_management.h"
|
||||
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <queue>
|
||||
|
||||
#define AVPLAYER_IS_ERROR(x) ((x) < 0)
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
enum class AvState {
|
||||
Initial,
|
||||
AddingSource,
|
||||
Ready,
|
||||
Play,
|
||||
Stop,
|
||||
EndOfFile,
|
||||
Pause,
|
||||
C0x08,
|
||||
Jump,
|
||||
TrickMode,
|
||||
C0x0B,
|
||||
Buffering,
|
||||
Starting,
|
||||
Error,
|
||||
};
|
||||
|
||||
enum class AvEventType {
|
||||
ChangeFlowState = 21,
|
||||
WarningId = 22,
|
||||
RevertState = 30,
|
||||
AddSource = 40,
|
||||
Error = 255,
|
||||
};
|
||||
|
||||
union AvPlayerEventData {
|
||||
u32 num_frames; // 20
|
||||
AvState state; // AvEventType::ChangeFlowState
|
||||
s32 error; // AvEventType::WarningId
|
||||
u32 attempt; // AvEventType::AddSource
|
||||
};
|
||||
|
||||
struct AvPlayerEvent {
|
||||
AvEventType event;
|
||||
AvPlayerEventData payload;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class AvPlayerQueue {
|
||||
public:
|
||||
size_t Size() {
|
||||
return m_queue.size();
|
||||
}
|
||||
|
||||
void Push(T&& value) {
|
||||
std::lock_guard guard(m_mutex);
|
||||
m_queue.emplace(std::forward<T>(value));
|
||||
}
|
||||
|
||||
std::optional<T> Pop() {
|
||||
if (Size() == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
std::lock_guard guard(m_mutex);
|
||||
auto result = std::move(m_queue.front());
|
||||
m_queue.pop();
|
||||
return result;
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
std::lock_guard guard(m_mutex);
|
||||
m_queue = {};
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex m_mutex{};
|
||||
std::queue<T> m_queue{};
|
||||
};
|
||||
|
||||
SceAvPlayerSourceType GetSourceType(std::string_view path);
|
||||
|
||||
} // namespace Libraries::AvPlayer
|
20
src/core/libraries/avplayer/avplayer_data_streamer.h
Normal file
20
src/core/libraries/avplayer/avplayer_data_streamer.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "avplayer.h"
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
struct AVIOContext;
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
class IDataStreamer {
|
||||
public:
|
||||
virtual ~IDataStreamer() = default;
|
||||
virtual AVIOContext* GetContext() = 0;
|
||||
};
|
||||
|
||||
} // namespace Libraries::AvPlayer
|
86
src/core/libraries/avplayer/avplayer_file_streamer.cpp
Normal file
86
src/core/libraries/avplayer/avplayer_file_streamer.cpp
Normal file
|
@ -0,0 +1,86 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "avplayer_file_streamer.h"
|
||||
|
||||
#include "avplayer_common.h"
|
||||
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavformat/avio.h>
|
||||
}
|
||||
|
||||
#include <algorithm> // std::max, std::min
|
||||
|
||||
#define AVPLAYER_AVIO_BUFFER_SIZE 4096
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
AvPlayerFileStreamer::AvPlayerFileStreamer(const SceAvPlayerFileReplacement& file_replacement,
|
||||
std::string_view path)
|
||||
: m_file_replacement(file_replacement) {
|
||||
const auto ptr = m_file_replacement.object_ptr;
|
||||
m_fd = m_file_replacement.open(ptr, path.data());
|
||||
ASSERT(m_fd >= 0);
|
||||
m_file_size = m_file_replacement.size(ptr);
|
||||
// avio_buffer is deallocated in `avio_context_free`
|
||||
const auto avio_buffer = reinterpret_cast<u8*>(av_malloc(AVPLAYER_AVIO_BUFFER_SIZE));
|
||||
m_avio_context =
|
||||
avio_alloc_context(avio_buffer, AVPLAYER_AVIO_BUFFER_SIZE, 0, this,
|
||||
&AvPlayerFileStreamer::ReadPacket, nullptr, &AvPlayerFileStreamer::Seek);
|
||||
}
|
||||
|
||||
AvPlayerFileStreamer::~AvPlayerFileStreamer() {
|
||||
if (m_avio_context != nullptr) {
|
||||
avio_context_free(&m_avio_context);
|
||||
}
|
||||
if (m_file_replacement.close != nullptr && m_fd >= 0) {
|
||||
const auto close = m_file_replacement.close;
|
||||
const auto ptr = m_file_replacement.object_ptr;
|
||||
close(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
s32 AvPlayerFileStreamer::ReadPacket(void* opaque, u8* buffer, s32 size) {
|
||||
const auto self = reinterpret_cast<AvPlayerFileStreamer*>(opaque);
|
||||
if (self->m_position >= self->m_file_size) {
|
||||
return AVERROR_EOF;
|
||||
}
|
||||
if (self->m_position + size > self->m_file_size) {
|
||||
size = self->m_file_size - self->m_position;
|
||||
}
|
||||
const auto read_offset = self->m_file_replacement.readOffset;
|
||||
const auto ptr = self->m_file_replacement.object_ptr;
|
||||
const auto bytes_read = read_offset(ptr, buffer, self->m_position, size);
|
||||
if (bytes_read == 0 && size != 0) {
|
||||
return AVERROR_EOF;
|
||||
}
|
||||
self->m_position += bytes_read;
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
s64 AvPlayerFileStreamer::Seek(void* opaque, s64 offset, int whence) {
|
||||
const auto self = reinterpret_cast<AvPlayerFileStreamer*>(opaque);
|
||||
if (whence & AVSEEK_SIZE) {
|
||||
return self->m_file_size;
|
||||
}
|
||||
|
||||
if (whence == SEEK_CUR) {
|
||||
self->m_position =
|
||||
std::min(u64(std::max(s64(0), s64(self->m_position) + offset)), self->m_file_size);
|
||||
return self->m_position;
|
||||
} else if (whence == SEEK_SET) {
|
||||
self->m_position = std::min(u64(std::max(s64(0), offset)), self->m_file_size);
|
||||
return self->m_position;
|
||||
} else if (whence == SEEK_END) {
|
||||
self->m_position =
|
||||
std::min(u64(std::max(s64(0), s64(self->m_file_size) + offset)), self->m_file_size);
|
||||
return self->m_position;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace Libraries::AvPlayer
|
37
src/core/libraries/avplayer/avplayer_file_streamer.h
Normal file
37
src/core/libraries/avplayer/avplayer_file_streamer.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "avplayer.h"
|
||||
#include "avplayer_data_streamer.h"
|
||||
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
struct AVIOContext;
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
class AvPlayerFileStreamer : public IDataStreamer {
|
||||
public:
|
||||
AvPlayerFileStreamer(const SceAvPlayerFileReplacement& file_replacement, std::string_view path);
|
||||
~AvPlayerFileStreamer();
|
||||
|
||||
AVIOContext* GetContext() override {
|
||||
return m_avio_context;
|
||||
}
|
||||
|
||||
private:
|
||||
static s32 ReadPacket(void* opaque, u8* buffer, s32 size);
|
||||
static s64 Seek(void* opaque, s64 buffer, int whence);
|
||||
|
||||
SceAvPlayerFileReplacement m_file_replacement;
|
||||
|
||||
int m_fd = -1;
|
||||
u64 m_position{};
|
||||
u64 m_file_size{};
|
||||
AVIOContext* m_avio_context{};
|
||||
};
|
||||
|
||||
} // namespace Libraries::AvPlayer
|
200
src/core/libraries/avplayer/avplayer_impl.cpp
Normal file
200
src/core/libraries/avplayer/avplayer_impl.cpp
Normal file
|
@ -0,0 +1,200 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "avplayer_common.h"
|
||||
#include "avplayer_file_streamer.h"
|
||||
#include "avplayer_impl.h"
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/kernel/libkernel.h"
|
||||
|
||||
using namespace Libraries::Kernel;
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
void* PS4_SYSV_ABI AvPlayer::Allocate(void* handle, u32 alignment, u32 size) {
|
||||
const auto* const self = reinterpret_cast<AvPlayer*>(handle);
|
||||
const auto allocate = self->m_init_data_original.memory_replacement.allocate;
|
||||
const auto ptr = self->m_init_data_original.memory_replacement.object_ptr;
|
||||
return allocate(ptr, alignment, size);
|
||||
}
|
||||
|
||||
void PS4_SYSV_ABI AvPlayer::Deallocate(void* handle, void* memory) {
|
||||
const auto* const self = reinterpret_cast<AvPlayer*>(handle);
|
||||
const auto deallocate = self->m_init_data_original.memory_replacement.deallocate;
|
||||
const auto ptr = self->m_init_data_original.memory_replacement.object_ptr;
|
||||
return deallocate(ptr, memory);
|
||||
}
|
||||
|
||||
void* PS4_SYSV_ABI AvPlayer::AllocateTexture(void* handle, u32 alignment, u32 size) {
|
||||
const auto* const self = reinterpret_cast<AvPlayer*>(handle);
|
||||
const auto allocate = self->m_init_data_original.memory_replacement.allocate_texture;
|
||||
const auto ptr = self->m_init_data_original.memory_replacement.object_ptr;
|
||||
return allocate(ptr, alignment, size);
|
||||
}
|
||||
|
||||
void PS4_SYSV_ABI AvPlayer::DeallocateTexture(void* handle, void* memory) {
|
||||
const auto* const self = reinterpret_cast<AvPlayer*>(handle);
|
||||
const auto deallocate = self->m_init_data_original.memory_replacement.deallocate_texture;
|
||||
const auto ptr = self->m_init_data_original.memory_replacement.object_ptr;
|
||||
return deallocate(ptr, memory);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI AvPlayer::OpenFile(void* handle, const char* filename) {
|
||||
auto const self = reinterpret_cast<AvPlayer*>(handle);
|
||||
std::lock_guard guard(self->m_file_io_mutex);
|
||||
|
||||
const auto open = self->m_init_data_original.file_replacement.open;
|
||||
const auto ptr = self->m_init_data_original.file_replacement.object_ptr;
|
||||
return open(ptr, filename);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI AvPlayer::CloseFile(void* handle) {
|
||||
auto const self = reinterpret_cast<AvPlayer*>(handle);
|
||||
std::lock_guard guard(self->m_file_io_mutex);
|
||||
|
||||
const auto close = self->m_init_data_original.file_replacement.close;
|
||||
const auto ptr = self->m_init_data_original.file_replacement.object_ptr;
|
||||
return close(ptr);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI AvPlayer::ReadOffsetFile(void* handle, u8* buffer, u64 position, u32 length) {
|
||||
auto const self = reinterpret_cast<AvPlayer*>(handle);
|
||||
std::lock_guard guard(self->m_file_io_mutex);
|
||||
|
||||
const auto read_offset = self->m_init_data_original.file_replacement.readOffset;
|
||||
const auto ptr = self->m_init_data_original.file_replacement.object_ptr;
|
||||
return read_offset(ptr, buffer, position, length);
|
||||
}
|
||||
|
||||
u64 PS4_SYSV_ABI AvPlayer::SizeFile(void* handle) {
|
||||
auto const self = reinterpret_cast<AvPlayer*>(handle);
|
||||
std::lock_guard guard(self->m_file_io_mutex);
|
||||
|
||||
const auto size = self->m_init_data_original.file_replacement.size;
|
||||
const auto ptr = self->m_init_data_original.file_replacement.object_ptr;
|
||||
return size(ptr);
|
||||
}
|
||||
|
||||
SceAvPlayerInitData AvPlayer::StubInitData(const SceAvPlayerInitData& data) {
|
||||
SceAvPlayerInitData result = data;
|
||||
result.memory_replacement.object_ptr = this;
|
||||
result.memory_replacement.allocate = &AvPlayer::Allocate;
|
||||
result.memory_replacement.deallocate = &AvPlayer::Deallocate;
|
||||
result.memory_replacement.allocate_texture = &AvPlayer::AllocateTexture;
|
||||
result.memory_replacement.deallocate_texture = &AvPlayer::DeallocateTexture;
|
||||
if (data.file_replacement.open == nullptr || data.file_replacement.close == nullptr ||
|
||||
data.file_replacement.readOffset == nullptr || data.file_replacement.size == nullptr) {
|
||||
result.file_replacement = {};
|
||||
} else {
|
||||
result.file_replacement.object_ptr = this;
|
||||
result.file_replacement.open = &AvPlayer::OpenFile;
|
||||
result.file_replacement.close = &AvPlayer::CloseFile;
|
||||
result.file_replacement.readOffset = &AvPlayer::ReadOffsetFile;
|
||||
result.file_replacement.size = &AvPlayer::SizeFile;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
AvPlayer::AvPlayer(const SceAvPlayerInitData& data)
|
||||
: m_init_data(StubInitData(data)), m_init_data_original(data),
|
||||
m_state(std::make_unique<AvPlayerState>(m_init_data)) {}
|
||||
|
||||
s32 AvPlayer::PostInit(const SceAvPlayerPostInitData& data) {
|
||||
m_post_init_data = data;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 AvPlayer::AddSource(std::string_view path) {
|
||||
if (path.empty()) {
|
||||
return ORBIS_AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
if (AVPLAYER_IS_ERROR(m_state->AddSource(path, GetSourceType(path)))) {
|
||||
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 AvPlayer::GetStreamCount() {
|
||||
if (m_state == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
const auto res = m_state->GetStreamCount();
|
||||
if (AVPLAYER_IS_ERROR(res)) {
|
||||
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
s32 AvPlayer::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) {
|
||||
if (AVPLAYER_IS_ERROR(m_state->GetStreamInfo(stream_index, info))) {
|
||||
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 AvPlayer::EnableStream(u32 stream_index) {
|
||||
if (m_state == nullptr) {
|
||||
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
if (!m_state->EnableStream(stream_index)) {
|
||||
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 AvPlayer::Start() {
|
||||
return m_state->Start();
|
||||
}
|
||||
|
||||
bool AvPlayer::GetVideoData(SceAvPlayerFrameInfo& video_info) {
|
||||
if (m_state == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return m_state->GetVideoData(video_info);
|
||||
}
|
||||
|
||||
bool AvPlayer::GetVideoData(SceAvPlayerFrameInfoEx& video_info) {
|
||||
if (m_state == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return m_state->GetVideoData(video_info);
|
||||
}
|
||||
|
||||
bool AvPlayer::GetAudioData(SceAvPlayerFrameInfo& audio_info) {
|
||||
if (m_state == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return m_state->GetAudioData(audio_info);
|
||||
}
|
||||
|
||||
bool AvPlayer::IsActive() {
|
||||
if (m_state == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return m_state->IsActive();
|
||||
}
|
||||
|
||||
u64 AvPlayer::CurrentTime() {
|
||||
if (m_state == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return m_state->CurrentTime();
|
||||
}
|
||||
|
||||
s32 AvPlayer::Stop() {
|
||||
if (m_state == nullptr || !m_state->Stop()) {
|
||||
return ORBIS_AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
bool AvPlayer::SetLooping(bool is_looping) {
|
||||
if (m_state == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return m_state->SetLooping(is_looping);
|
||||
}
|
||||
|
||||
} // namespace Libraries::AvPlayer
|
68
src/core/libraries/avplayer/avplayer_impl.h
Normal file
68
src/core/libraries/avplayer/avplayer_impl.h
Normal file
|
@ -0,0 +1,68 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "avplayer.h"
|
||||
#include "avplayer_data_streamer.h"
|
||||
#include "avplayer_state.h"
|
||||
|
||||
#include "core/libraries/kernel/thread_management.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
}
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
class AvPlayer {
|
||||
public:
|
||||
AvPlayer(const SceAvPlayerInitData& data);
|
||||
|
||||
s32 PostInit(const SceAvPlayerPostInitData& data);
|
||||
s32 AddSource(std::string_view filename);
|
||||
s32 GetStreamCount();
|
||||
s32 GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info);
|
||||
s32 EnableStream(u32 stream_index);
|
||||
s32 Start();
|
||||
bool GetAudioData(SceAvPlayerFrameInfo& audio_info);
|
||||
bool GetVideoData(SceAvPlayerFrameInfo& video_info);
|
||||
bool GetVideoData(SceAvPlayerFrameInfoEx& video_info);
|
||||
bool IsActive();
|
||||
u64 CurrentTime();
|
||||
s32 Stop();
|
||||
bool SetLooping(bool is_looping);
|
||||
|
||||
private:
|
||||
using ScePthreadMutex = Kernel::ScePthreadMutex;
|
||||
|
||||
// Memory Replacement
|
||||
static void* PS4_SYSV_ABI Allocate(void* handle, u32 alignment, u32 size);
|
||||
static void PS4_SYSV_ABI Deallocate(void* handle, void* memory);
|
||||
static void* PS4_SYSV_ABI AllocateTexture(void* handle, u32 alignment, u32 size);
|
||||
static void PS4_SYSV_ABI DeallocateTexture(void* handle, void* memory);
|
||||
|
||||
// File Replacement
|
||||
static int PS4_SYSV_ABI OpenFile(void* handle, const char* filename);
|
||||
static int PS4_SYSV_ABI CloseFile(void* handle);
|
||||
static int PS4_SYSV_ABI ReadOffsetFile(void* handle, u8* buffer, u64 position, u32 length);
|
||||
static u64 PS4_SYSV_ABI SizeFile(void* handle);
|
||||
|
||||
SceAvPlayerInitData StubInitData(const SceAvPlayerInitData& data);
|
||||
|
||||
SceAvPlayerInitData m_init_data{};
|
||||
SceAvPlayerInitData m_init_data_original{};
|
||||
SceAvPlayerPostInitData m_post_init_data{};
|
||||
std::mutex m_file_io_mutex{};
|
||||
|
||||
std::atomic_bool m_has_source{};
|
||||
std::unique_ptr<AvPlayerState> m_state{};
|
||||
};
|
||||
|
||||
} // namespace Libraries::AvPlayer
|
730
src/core/libraries/avplayer/avplayer_source.cpp
Normal file
730
src/core/libraries/avplayer/avplayer_source.cpp
Normal file
|
@ -0,0 +1,730 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "avplayer_source.h"
|
||||
|
||||
#include "avplayer_file_streamer.h"
|
||||
|
||||
#include "common/singleton.h"
|
||||
#include "core/file_sys/fs.h"
|
||||
#include "core/libraries/kernel/time_management.h"
|
||||
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavformat/avio.h>
|
||||
#include <libswresample/swresample.h>
|
||||
#include <libswscale/swscale.h>
|
||||
}
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
using namespace Kernel;
|
||||
|
||||
AvPlayerSource::AvPlayerSource(AvPlayerStateCallback& state, std::string_view path,
|
||||
const SceAvPlayerInitData& init_data,
|
||||
SceAvPlayerSourceType source_type)
|
||||
: m_state(state), m_memory_replacement(init_data.memory_replacement),
|
||||
m_num_output_video_framebuffers(
|
||||
std::min(std::max(2, init_data.num_output_video_framebuffers), 16)) {
|
||||
AVFormatContext* context = avformat_alloc_context();
|
||||
if (init_data.file_replacement.open != nullptr) {
|
||||
m_up_data_streamer =
|
||||
std::make_unique<AvPlayerFileStreamer>(init_data.file_replacement, path);
|
||||
context->pb = m_up_data_streamer->GetContext();
|
||||
ASSERT(!AVPLAYER_IS_ERROR(avformat_open_input(&context, nullptr, nullptr, nullptr)));
|
||||
} else {
|
||||
const auto mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
const auto filepath = mnt->GetHostPath(path);
|
||||
ASSERT(!AVPLAYER_IS_ERROR(
|
||||
avformat_open_input(&context, filepath.string().c_str(), nullptr, nullptr)));
|
||||
}
|
||||
m_avformat_context = AVFormatContextPtr(context, &ReleaseAVFormatContext);
|
||||
}
|
||||
|
||||
AvPlayerSource::~AvPlayerSource() {
|
||||
Stop();
|
||||
}
|
||||
|
||||
bool AvPlayerSource::FindStreamInfo() {
|
||||
if (m_avformat_context == nullptr) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not find stream info. NULL context.");
|
||||
return false;
|
||||
}
|
||||
if (m_avformat_context->nb_streams > 0) {
|
||||
return true;
|
||||
}
|
||||
return avformat_find_stream_info(m_avformat_context.get(), nullptr) == 0;
|
||||
}
|
||||
|
||||
s32 AvPlayerSource::GetStreamCount() {
|
||||
if (m_avformat_context == nullptr) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not get stream count. NULL context.");
|
||||
return -1;
|
||||
}
|
||||
LOG_INFO(Lib_AvPlayer, "Stream Count: {}", m_avformat_context->nb_streams);
|
||||
return m_avformat_context->nb_streams;
|
||||
}
|
||||
|
||||
static s32 CodecTypeToStreamType(AVMediaType codec_type) {
|
||||
switch (codec_type) {
|
||||
case AVMediaType::AVMEDIA_TYPE_VIDEO:
|
||||
return SCE_AVPLAYER_VIDEO;
|
||||
case AVMediaType::AVMEDIA_TYPE_AUDIO:
|
||||
return SCE_AVPLAYER_AUDIO;
|
||||
case AVMediaType::AVMEDIA_TYPE_SUBTITLE:
|
||||
return SCE_AVPLAYER_TIMEDTEXT;
|
||||
default:
|
||||
LOG_ERROR(Lib_AvPlayer, "Unexpected AVMediaType {}", magic_enum::enum_name(codec_type));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static f32 AVRationalToF32(const AVRational& rational) {
|
||||
return f32(rational.num) / rational.den;
|
||||
}
|
||||
|
||||
s32 AvPlayerSource::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) {
|
||||
info = {};
|
||||
if (m_avformat_context == nullptr || stream_index >= m_avformat_context->nb_streams) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not get stream {} info.", stream_index);
|
||||
return -1;
|
||||
}
|
||||
const auto p_stream = m_avformat_context->streams[stream_index];
|
||||
if (p_stream == nullptr || p_stream->codecpar == nullptr) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not get stream {} info. NULL stream.", stream_index);
|
||||
return -1;
|
||||
}
|
||||
info.type = CodecTypeToStreamType(p_stream->codecpar->codec_type);
|
||||
info.start_time = p_stream->start_time;
|
||||
info.duration = p_stream->duration;
|
||||
const auto p_lang_node = av_dict_get(p_stream->metadata, "language", nullptr, 0);
|
||||
if (p_lang_node != nullptr) {
|
||||
LOG_INFO(Lib_AvPlayer, "Stream {} language = {}", stream_index, p_lang_node->value);
|
||||
} else {
|
||||
LOG_WARNING(Lib_AvPlayer, "Stream {} language is unknown", stream_index);
|
||||
}
|
||||
switch (info.type) {
|
||||
case SCE_AVPLAYER_VIDEO:
|
||||
LOG_INFO(Lib_AvPlayer, "Stream {} is a video stream.", stream_index);
|
||||
info.details.video.aspect_ratio =
|
||||
f32(p_stream->codecpar->width) / p_stream->codecpar->height;
|
||||
info.details.video.width = p_stream->codecpar->width;
|
||||
info.details.video.height = p_stream->codecpar->height;
|
||||
if (p_lang_node != nullptr) {
|
||||
std::memcpy(info.details.video.language_code, p_lang_node->value,
|
||||
std::min(strlen(p_lang_node->value), size_t(3)));
|
||||
}
|
||||
break;
|
||||
case SCE_AVPLAYER_AUDIO:
|
||||
LOG_INFO(Lib_AvPlayer, "Stream {} is an audio stream.", stream_index);
|
||||
info.details.audio.channel_count = p_stream->codecpar->ch_layout.nb_channels;
|
||||
info.details.audio.sample_rate = p_stream->codecpar->sample_rate;
|
||||
info.details.audio.size = 0; // sceAvPlayerGetStreamInfo() is expected to set this to 0
|
||||
if (p_lang_node != nullptr) {
|
||||
std::memcpy(info.details.audio.language_code, p_lang_node->value,
|
||||
std::min(strlen(p_lang_node->value), size_t(3)));
|
||||
}
|
||||
break;
|
||||
case SCE_AVPLAYER_TIMEDTEXT:
|
||||
LOG_WARNING(Lib_AvPlayer, "Stream {} is a timedtext stream.", stream_index);
|
||||
info.details.subs.font_size = 12;
|
||||
info.details.subs.text_size = 12;
|
||||
if (p_lang_node != nullptr) {
|
||||
std::memcpy(info.details.subs.language_code, p_lang_node->value,
|
||||
std::min(strlen(p_lang_node->value), size_t(3)));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Lib_AvPlayer, "Stream {} type is unknown: {}.", stream_index, info.type);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool AvPlayerSource::EnableStream(u32 stream_index) {
|
||||
if (m_avformat_context == nullptr || stream_index >= m_avformat_context->nb_streams) {
|
||||
return false;
|
||||
}
|
||||
const auto stream = m_avformat_context->streams[stream_index];
|
||||
const auto decoder = avcodec_find_decoder(stream->codecpar->codec_id);
|
||||
if (decoder == nullptr) {
|
||||
return false;
|
||||
}
|
||||
switch (stream->codecpar->codec_type) {
|
||||
case AVMediaType::AVMEDIA_TYPE_VIDEO: {
|
||||
m_video_stream_index = stream_index;
|
||||
m_video_codec_context =
|
||||
AVCodecContextPtr(avcodec_alloc_context3(decoder), &ReleaseAVCodecContext);
|
||||
if (avcodec_parameters_to_context(m_video_codec_context.get(), stream->codecpar) < 0) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not copy stream {} avcodec parameters to context.",
|
||||
stream_index);
|
||||
return false;
|
||||
}
|
||||
if (avcodec_open2(m_video_codec_context.get(), decoder, nullptr) < 0) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not open avcodec for video stream {}.", stream_index);
|
||||
return false;
|
||||
}
|
||||
const auto width = m_video_codec_context->width;
|
||||
const auto size = (width * m_video_codec_context->height * 3) / 2;
|
||||
for (u64 index = 0; index < m_num_output_video_framebuffers; ++index) {
|
||||
m_video_buffers.Push(FrameBuffer(m_memory_replacement, 0x100, size));
|
||||
}
|
||||
LOG_INFO(Lib_AvPlayer, "Video stream {} enabled", stream_index);
|
||||
break;
|
||||
}
|
||||
case AVMediaType::AVMEDIA_TYPE_AUDIO: {
|
||||
m_audio_stream_index = stream_index;
|
||||
m_audio_codec_context =
|
||||
AVCodecContextPtr(avcodec_alloc_context3(decoder), &ReleaseAVCodecContext);
|
||||
if (avcodec_parameters_to_context(m_audio_codec_context.get(), stream->codecpar) < 0) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not copy stream {} avcodec parameters to context.",
|
||||
stream_index);
|
||||
return false;
|
||||
}
|
||||
if (avcodec_open2(m_audio_codec_context.get(), decoder, nullptr) < 0) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not open avcodec for audio stream {}.", stream_index);
|
||||
return false;
|
||||
}
|
||||
const auto num_channels = m_audio_codec_context->ch_layout.nb_channels;
|
||||
const auto align = num_channels * sizeof(u16);
|
||||
const auto size = num_channels * sizeof(u16) * 1024;
|
||||
for (u64 index = 0; index < 4; ++index) {
|
||||
m_audio_buffers.Push(FrameBuffer(m_memory_replacement, 0x100, size));
|
||||
}
|
||||
LOG_INFO(Lib_AvPlayer, "Audio stream {} enabled", stream_index);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG_WARNING(Lib_AvPlayer, "Unknown stream type {} for stream {}",
|
||||
magic_enum::enum_name(stream->codecpar->codec_type), stream_index);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AvPlayerSource::SetLooping(bool is_looping) {
|
||||
m_is_looping = is_looping;
|
||||
}
|
||||
|
||||
std::optional<bool> AvPlayerSource::HasFrames(u32 num_frames) {
|
||||
return m_video_packets.Size() > num_frames || m_is_eof;
|
||||
}
|
||||
|
||||
s32 AvPlayerSource::Start() {
|
||||
std::unique_lock lock(m_state_mutex);
|
||||
|
||||
if (m_audio_codec_context == nullptr && m_video_codec_context == nullptr) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not start playback. NULL context.");
|
||||
return -1;
|
||||
}
|
||||
m_demuxer_thread = std::jthread([this](std::stop_token stop) { this->DemuxerThread(stop); });
|
||||
m_video_decoder_thread =
|
||||
std::jthread([this](std::stop_token stop) { this->VideoDecoderThread(stop); });
|
||||
m_audio_decoder_thread =
|
||||
std::jthread([this](std::stop_token stop) { this->AudioDecoderThread(stop); });
|
||||
m_start_time = std::chrono::high_resolution_clock::now();
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool AvPlayerSource::Stop() {
|
||||
std::unique_lock lock(m_state_mutex);
|
||||
|
||||
if (!HasRunningThreads()) {
|
||||
LOG_WARNING(Lib_AvPlayer, "Could not stop playback: already stopped.");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_video_decoder_thread.request_stop();
|
||||
m_audio_decoder_thread.request_stop();
|
||||
m_demuxer_thread.request_stop();
|
||||
if (m_demuxer_thread.joinable()) {
|
||||
m_demuxer_thread.join();
|
||||
}
|
||||
if (m_video_decoder_thread.joinable()) {
|
||||
m_video_decoder_thread.join();
|
||||
}
|
||||
if (m_audio_decoder_thread.joinable()) {
|
||||
m_audio_decoder_thread.join();
|
||||
}
|
||||
if (m_current_audio_frame.has_value()) {
|
||||
m_audio_buffers.Push(std::move(m_current_audio_frame.value()));
|
||||
m_current_audio_frame.reset();
|
||||
}
|
||||
if (m_current_video_frame.has_value()) {
|
||||
m_video_buffers.Push(std::move(m_current_video_frame.value()));
|
||||
m_current_video_frame.reset();
|
||||
}
|
||||
m_stop_cv.Notify();
|
||||
|
||||
m_audio_packets.Clear();
|
||||
m_video_packets.Clear();
|
||||
m_audio_frames.Clear();
|
||||
m_video_frames.Clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AvPlayerSource::GetVideoData(SceAvPlayerFrameInfo& video_info) {
|
||||
if (!IsActive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SceAvPlayerFrameInfoEx info{};
|
||||
if (!GetVideoData(info)) {
|
||||
return false;
|
||||
}
|
||||
video_info = {};
|
||||
video_info.timestamp = u64(info.timestamp);
|
||||
video_info.pData = reinterpret_cast<u8*>(info.pData);
|
||||
video_info.details.video.aspect_ratio = info.details.video.aspect_ratio;
|
||||
video_info.details.video.width = info.details.video.width;
|
||||
video_info.details.video.height = info.details.video.height;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void CopyNV12Data(u8* dst, const AVFrame& src) {
|
||||
std::memcpy(dst, src.data[0], src.width * src.height);
|
||||
std::memcpy(dst + src.width * src.height, src.data[1], (src.width * src.height) / 2);
|
||||
}
|
||||
|
||||
bool AvPlayerSource::GetVideoData(SceAvPlayerFrameInfoEx& video_info) {
|
||||
if (!IsActive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_video_frames_cv.Wait([this] { return m_video_frames.Size() != 0 || m_is_eof; });
|
||||
|
||||
auto frame = m_video_frames.Pop();
|
||||
if (!frame.has_value()) {
|
||||
LOG_WARNING(Lib_AvPlayer, "Could get video frame. EOF reached.");
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
using namespace std::chrono;
|
||||
auto elapsed_time =
|
||||
duration_cast<milliseconds>(high_resolution_clock::now() - m_start_time).count();
|
||||
if (elapsed_time < frame->info.timestamp) {
|
||||
if (m_stop_cv.WaitFor(milliseconds(frame->info.timestamp - elapsed_time),
|
||||
[&] { return elapsed_time >= frame->info.timestamp; })) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return the buffer to the queue
|
||||
if (m_current_video_frame.has_value()) {
|
||||
m_video_buffers.Push(std::move(m_current_video_frame.value()));
|
||||
m_video_buffers_cv.Notify();
|
||||
}
|
||||
m_current_video_frame = std::move(frame->buffer);
|
||||
video_info = frame->info;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AvPlayerSource::GetAudioData(SceAvPlayerFrameInfo& audio_info) {
|
||||
if (!IsActive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_audio_frames_cv.Wait([this] { return m_audio_frames.Size() != 0 || m_is_eof; });
|
||||
|
||||
auto frame = m_audio_frames.Pop();
|
||||
if (!frame.has_value()) {
|
||||
LOG_WARNING(Lib_AvPlayer, "Could get audio frame. EOF reached.");
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
using namespace std::chrono;
|
||||
auto elapsed_time =
|
||||
duration_cast<milliseconds>(high_resolution_clock::now() - m_start_time).count();
|
||||
if (elapsed_time < frame->info.timestamp) {
|
||||
if (m_stop_cv.WaitFor(milliseconds(frame->info.timestamp - elapsed_time),
|
||||
[&] { return elapsed_time >= frame->info.timestamp; })) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return the buffer to the queue
|
||||
if (m_current_audio_frame.has_value()) {
|
||||
m_audio_buffers.Push(std::move(m_current_audio_frame.value()));
|
||||
m_audio_buffers_cv.Notify();
|
||||
}
|
||||
m_current_audio_frame = std::move(frame->buffer);
|
||||
|
||||
audio_info = {};
|
||||
audio_info.timestamp = frame->info.timestamp;
|
||||
audio_info.pData = reinterpret_cast<u8*>(frame->info.pData);
|
||||
audio_info.details.audio.size = frame->info.details.audio.size;
|
||||
audio_info.details.audio.channel_count = frame->info.details.audio.channel_count;
|
||||
return true;
|
||||
}
|
||||
|
||||
u64 AvPlayerSource::CurrentTime() {
|
||||
using namespace std::chrono;
|
||||
return duration_cast<milliseconds>(high_resolution_clock::now() - m_start_time).count();
|
||||
}
|
||||
|
||||
bool AvPlayerSource::IsActive() {
|
||||
return !m_is_eof || m_audio_packets.Size() != 0 || m_video_packets.Size() != 0 ||
|
||||
m_video_frames.Size() != 0 || m_audio_frames.Size() != 0;
|
||||
}
|
||||
|
||||
void AvPlayerSource::ReleaseAVPacket(AVPacket* packet) {
|
||||
if (packet != nullptr) {
|
||||
av_packet_free(&packet);
|
||||
}
|
||||
}
|
||||
|
||||
void AvPlayerSource::ReleaseAVFrame(AVFrame* frame) {
|
||||
if (frame != nullptr) {
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
}
|
||||
|
||||
void AvPlayerSource::ReleaseAVCodecContext(AVCodecContext* context) {
|
||||
if (context != nullptr) {
|
||||
avcodec_free_context(&context);
|
||||
}
|
||||
}
|
||||
|
||||
void AvPlayerSource::ReleaseSWRContext(SwrContext* context) {
|
||||
if (context != nullptr) {
|
||||
swr_free(&context);
|
||||
}
|
||||
}
|
||||
|
||||
void AvPlayerSource::ReleaseSWSContext(SwsContext* context) {
|
||||
if (context != nullptr) {
|
||||
sws_freeContext(context);
|
||||
}
|
||||
}
|
||||
|
||||
void AvPlayerSource::ReleaseAVFormatContext(AVFormatContext* context) {
|
||||
if (context != nullptr) {
|
||||
avformat_close_input(&context);
|
||||
}
|
||||
}
|
||||
|
||||
void AvPlayerSource::DemuxerThread(std::stop_token stop) {
|
||||
using namespace std::chrono;
|
||||
if (!m_audio_stream_index.has_value() && !m_video_stream_index.has_value()) {
|
||||
LOG_WARNING(Lib_AvPlayer, "Could not start DEMUXER thread. No streams enabled.");
|
||||
return;
|
||||
}
|
||||
LOG_INFO(Lib_AvPlayer, "Demuxer Thread started");
|
||||
|
||||
while (!stop.stop_requested()) {
|
||||
if (m_video_packets.Size() > 30 && m_audio_packets.Size() > 8) {
|
||||
std::this_thread::sleep_for(milliseconds(5));
|
||||
continue;
|
||||
}
|
||||
AVPacketPtr up_packet(av_packet_alloc(), &ReleaseAVPacket);
|
||||
const auto res = av_read_frame(m_avformat_context.get(), up_packet.get());
|
||||
if (res < 0) {
|
||||
if (res == AVERROR_EOF) {
|
||||
if (m_is_looping) {
|
||||
LOG_INFO(Lib_AvPlayer, "EOF reached in demuxer. Looping the source...");
|
||||
avio_seek(m_avformat_context->pb, 0, SEEK_SET);
|
||||
if (m_video_stream_index.has_value()) {
|
||||
const auto index = m_video_stream_index.value();
|
||||
const auto stream = m_avformat_context->streams[index];
|
||||
avformat_seek_file(m_avformat_context.get(), index, 0, 0, stream->duration,
|
||||
0);
|
||||
}
|
||||
if (m_audio_stream_index.has_value()) {
|
||||
const auto index = m_audio_stream_index.value();
|
||||
const auto stream = m_avformat_context->streams[index];
|
||||
avformat_seek_file(m_avformat_context.get(), index, 0, 0, stream->duration,
|
||||
0);
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
LOG_INFO(Lib_AvPlayer, "EOF reached in demuxer. Exiting.");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not read AV frame: error = {}", res);
|
||||
m_state.OnError();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (up_packet->stream_index == m_video_stream_index) {
|
||||
m_video_packets.Push(std::move(up_packet));
|
||||
m_video_packets_cv.Notify();
|
||||
} else if (up_packet->stream_index == m_audio_stream_index) {
|
||||
m_audio_packets.Push(std::move(up_packet));
|
||||
m_audio_packets_cv.Notify();
|
||||
}
|
||||
}
|
||||
|
||||
m_is_eof = true;
|
||||
|
||||
m_video_packets_cv.Notify();
|
||||
m_audio_packets_cv.Notify();
|
||||
m_video_frames_cv.Notify();
|
||||
m_audio_frames_cv.Notify();
|
||||
|
||||
if (m_video_decoder_thread.joinable()) {
|
||||
m_video_decoder_thread.join();
|
||||
}
|
||||
if (m_audio_decoder_thread.joinable()) {
|
||||
m_audio_decoder_thread.join();
|
||||
}
|
||||
m_state.OnEOF();
|
||||
|
||||
LOG_INFO(Lib_AvPlayer, "Demuxer Thread exited normaly");
|
||||
}
|
||||
|
||||
AvPlayerSource::AVFramePtr AvPlayerSource::ConvertVideoFrame(const AVFrame& frame) {
|
||||
auto nv12_frame = AVFramePtr{av_frame_alloc(), &ReleaseAVFrame};
|
||||
nv12_frame->pts = frame.pts;
|
||||
nv12_frame->pkt_dts = frame.pkt_dts < 0 ? 0 : frame.pkt_dts;
|
||||
nv12_frame->format = AV_PIX_FMT_NV12;
|
||||
nv12_frame->width = frame.width;
|
||||
nv12_frame->height = frame.height;
|
||||
nv12_frame->sample_aspect_ratio = frame.sample_aspect_ratio;
|
||||
|
||||
av_frame_get_buffer(nv12_frame.get(), 0);
|
||||
|
||||
if (m_sws_context == nullptr) {
|
||||
m_sws_context =
|
||||
SWSContextPtr(sws_getContext(frame.width, frame.height, AVPixelFormat(frame.format),
|
||||
frame.width, frame.height, AV_PIX_FMT_NV12,
|
||||
SWS_FAST_BILINEAR, nullptr, nullptr, nullptr),
|
||||
&ReleaseSWSContext);
|
||||
}
|
||||
const auto res = sws_scale(m_sws_context.get(), frame.data, frame.linesize, 0, frame.height,
|
||||
nv12_frame->data, nv12_frame->linesize);
|
||||
if (res < 0) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not convert to NV12: {}", av_err2str(res));
|
||||
return AVFramePtr{nullptr, &ReleaseAVFrame};
|
||||
}
|
||||
return nv12_frame;
|
||||
}
|
||||
|
||||
Frame AvPlayerSource::PrepareVideoFrame(FrameBuffer buffer, const AVFrame& frame) {
|
||||
ASSERT(frame.format == AV_PIX_FMT_NV12);
|
||||
|
||||
auto p_buffer = buffer.GetBuffer();
|
||||
CopyNV12Data(p_buffer, frame);
|
||||
|
||||
const auto pkt_dts = u64(frame.pkt_dts) * 1000;
|
||||
const auto stream = m_avformat_context->streams[m_video_stream_index.value()];
|
||||
const auto time_base = stream->time_base;
|
||||
const auto den = time_base.den;
|
||||
const auto num = time_base.num;
|
||||
const auto timestamp = (num != 0 && den > 1) ? (pkt_dts * num) / den : pkt_dts;
|
||||
|
||||
return Frame{
|
||||
.buffer = std::move(buffer),
|
||||
.info =
|
||||
{
|
||||
.pData = p_buffer,
|
||||
.timestamp = timestamp,
|
||||
.details =
|
||||
{
|
||||
.video =
|
||||
{
|
||||
.width = u32(frame.width),
|
||||
.height = u32(frame.height),
|
||||
.aspect_ratio = AVRationalToF32(frame.sample_aspect_ratio),
|
||||
.pitch = u32(frame.linesize[0]),
|
||||
.luma_bit_depth = 8,
|
||||
.chroma_bit_depth = 8,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
void AvPlayerSource::VideoDecoderThread(std::stop_token stop) {
|
||||
using namespace std::chrono;
|
||||
LOG_INFO(Lib_AvPlayer, "Video Decoder Thread started");
|
||||
while ((!m_is_eof || m_video_packets.Size() != 0) && !stop.stop_requested()) {
|
||||
if (!m_video_packets_cv.Wait(stop,
|
||||
[this] { return m_video_packets.Size() != 0 || m_is_eof; })) {
|
||||
continue;
|
||||
}
|
||||
const auto packet = m_video_packets.Pop();
|
||||
if (!packet.has_value()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto res = avcodec_send_packet(m_video_codec_context.get(), packet->get());
|
||||
if (res < 0 && res != AVERROR(EAGAIN)) {
|
||||
m_state.OnError();
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not send packet to the video codec. Error = {}",
|
||||
av_err2str(res));
|
||||
return;
|
||||
}
|
||||
while (res >= 0) {
|
||||
if (!m_video_buffers_cv.Wait(stop, [this] { return m_video_buffers.Size() != 0; })) {
|
||||
break;
|
||||
}
|
||||
if (m_video_buffers.Size() == 0) {
|
||||
continue;
|
||||
}
|
||||
auto up_frame = AVFramePtr(av_frame_alloc(), &ReleaseAVFrame);
|
||||
res = avcodec_receive_frame(m_video_codec_context.get(), up_frame.get());
|
||||
if (res < 0) {
|
||||
if (res == AVERROR_EOF) {
|
||||
LOG_INFO(Lib_AvPlayer, "EOF reached in video decoder");
|
||||
return;
|
||||
} else if (res != AVERROR(EAGAIN)) {
|
||||
LOG_ERROR(Lib_AvPlayer,
|
||||
"Could not receive frame from the video codec. Error = {}",
|
||||
av_err2str(res));
|
||||
m_state.OnError();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
auto buffer = m_video_buffers.Pop();
|
||||
if (!buffer.has_value()) {
|
||||
// Video buffers queue was cleared. This means that player was stopped.
|
||||
break;
|
||||
}
|
||||
if (up_frame->format != AV_PIX_FMT_NV12) {
|
||||
const auto nv12_frame = ConvertVideoFrame(*up_frame);
|
||||
m_video_frames.Push(PrepareVideoFrame(std::move(buffer.value()), *nv12_frame));
|
||||
} else {
|
||||
m_video_frames.Push(PrepareVideoFrame(std::move(buffer.value()), *up_frame));
|
||||
}
|
||||
m_video_frames_cv.Notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO(Lib_AvPlayer, "Video Decoder Thread exited normaly");
|
||||
}
|
||||
|
||||
AvPlayerSource::AVFramePtr AvPlayerSource::ConvertAudioFrame(const AVFrame& frame) {
|
||||
auto pcm16_frame = AVFramePtr{av_frame_alloc(), &ReleaseAVFrame};
|
||||
pcm16_frame->pts = frame.pts;
|
||||
pcm16_frame->pkt_dts = frame.pkt_dts < 0 ? 0 : frame.pkt_dts;
|
||||
pcm16_frame->format = AV_SAMPLE_FMT_S16;
|
||||
pcm16_frame->ch_layout = frame.ch_layout;
|
||||
pcm16_frame->sample_rate = frame.sample_rate;
|
||||
|
||||
if (m_swr_context == nullptr) {
|
||||
SwrContext* swr_context = nullptr;
|
||||
AVChannelLayout in_ch_layout = frame.ch_layout;
|
||||
AVChannelLayout out_ch_layout = frame.ch_layout;
|
||||
swr_alloc_set_opts2(&swr_context, &out_ch_layout, AV_SAMPLE_FMT_S16, frame.sample_rate,
|
||||
&in_ch_layout, AVSampleFormat(frame.format), frame.sample_rate, 0,
|
||||
nullptr);
|
||||
m_swr_context = SWRContextPtr(swr_context, &ReleaseSWRContext);
|
||||
swr_init(m_swr_context.get());
|
||||
}
|
||||
const auto res = swr_convert_frame(m_swr_context.get(), pcm16_frame.get(), &frame);
|
||||
if (res < 0) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not convert to NV12: {}", av_err2str(res));
|
||||
return AVFramePtr{nullptr, &ReleaseAVFrame};
|
||||
}
|
||||
return pcm16_frame;
|
||||
}
|
||||
|
||||
Frame AvPlayerSource::PrepareAudioFrame(FrameBuffer buffer, const AVFrame& frame) {
|
||||
ASSERT(frame.format == AV_SAMPLE_FMT_S16);
|
||||
ASSERT(frame.nb_samples <= 1024);
|
||||
|
||||
auto p_buffer = buffer.GetBuffer();
|
||||
const auto size = frame.ch_layout.nb_channels * frame.nb_samples * sizeof(u16);
|
||||
std::memcpy(p_buffer, frame.data[0], size);
|
||||
|
||||
const auto pkt_dts = u64(frame.pkt_dts) * 1000;
|
||||
const auto stream = m_avformat_context->streams[m_audio_stream_index.value()];
|
||||
const auto time_base = stream->time_base;
|
||||
const auto den = time_base.den;
|
||||
const auto num = time_base.num;
|
||||
const auto timestamp = (num != 0 && den > 1) ? (pkt_dts * num) / den : pkt_dts;
|
||||
|
||||
return Frame{
|
||||
.buffer = std::move(buffer),
|
||||
.info =
|
||||
{
|
||||
.pData = p_buffer,
|
||||
.timestamp = timestamp,
|
||||
.details =
|
||||
{
|
||||
.audio =
|
||||
{
|
||||
.channel_count = u16(frame.ch_layout.nb_channels),
|
||||
.size = u32(size),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
void AvPlayerSource::AudioDecoderThread(std::stop_token stop) {
|
||||
using namespace std::chrono;
|
||||
LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread started");
|
||||
while ((!m_is_eof || m_audio_packets.Size() != 0) && !stop.stop_requested()) {
|
||||
if (!m_audio_packets_cv.Wait(stop,
|
||||
[this] { return m_audio_packets.Size() != 0 || m_is_eof; })) {
|
||||
continue;
|
||||
}
|
||||
const auto packet = m_audio_packets.Pop();
|
||||
if (!packet.has_value()) {
|
||||
continue;
|
||||
}
|
||||
auto res = avcodec_send_packet(m_audio_codec_context.get(), packet->get());
|
||||
if (res < 0 && res != AVERROR(EAGAIN)) {
|
||||
m_state.OnError();
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not send packet to the audio codec. Error = {}",
|
||||
av_err2str(res));
|
||||
return;
|
||||
}
|
||||
while (res >= 0) {
|
||||
if (!m_audio_buffers_cv.Wait(stop, [this] { return m_audio_buffers.Size() != 0; })) {
|
||||
break;
|
||||
}
|
||||
if (m_audio_buffers.Size() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto up_frame = AVFramePtr(av_frame_alloc(), &ReleaseAVFrame);
|
||||
res = avcodec_receive_frame(m_audio_codec_context.get(), up_frame.get());
|
||||
if (res < 0) {
|
||||
if (res == AVERROR_EOF) {
|
||||
LOG_INFO(Lib_AvPlayer, "EOF reached in audio decoder");
|
||||
return;
|
||||
} else if (res != AVERROR(EAGAIN)) {
|
||||
m_state.OnError();
|
||||
LOG_ERROR(Lib_AvPlayer,
|
||||
"Could not receive frame from the audio codec. Error = {}",
|
||||
av_err2str(res));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
auto buffer = m_audio_buffers.Pop();
|
||||
if (!buffer.has_value()) {
|
||||
// Audio buffers queue was cleared. This means that player was stopped.
|
||||
break;
|
||||
}
|
||||
if (up_frame->format != AV_SAMPLE_FMT_S16) {
|
||||
const auto pcm16_frame = ConvertAudioFrame(*up_frame);
|
||||
m_audio_frames.Push(PrepareAudioFrame(std::move(buffer.value()), *pcm16_frame));
|
||||
} else {
|
||||
m_audio_frames.Push(PrepareAudioFrame(std::move(buffer.value()), *up_frame));
|
||||
}
|
||||
m_audio_frames_cv.Notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread exited normaly");
|
||||
}
|
||||
|
||||
bool AvPlayerSource::HasRunningThreads() const {
|
||||
return m_demuxer_thread.joinable() || m_video_decoder_thread.joinable() ||
|
||||
m_audio_decoder_thread.joinable();
|
||||
}
|
||||
|
||||
} // namespace Libraries::AvPlayer
|
219
src/core/libraries/avplayer/avplayer_source.h
Normal file
219
src/core/libraries/avplayer/avplayer_source.h
Normal file
|
@ -0,0 +1,219 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "avplayer.h"
|
||||
#include "avplayer_common.h"
|
||||
#include "avplayer_data_streamer.h"
|
||||
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/kernel/thread_management.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
struct AVCodecContext;
|
||||
struct AVFormatContext;
|
||||
struct AVFrame;
|
||||
struct AVIOContext;
|
||||
struct AVPacket;
|
||||
struct SwrContext;
|
||||
struct SwsContext;
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
class AvPlayerStateCallback {
|
||||
public:
|
||||
virtual ~AvPlayerStateCallback() = default;
|
||||
|
||||
virtual void OnWarning(u32 id) = 0;
|
||||
virtual void OnError() = 0;
|
||||
virtual void OnEOF() = 0;
|
||||
};
|
||||
|
||||
class FrameBuffer {
|
||||
public:
|
||||
FrameBuffer(const SceAvPlayerMemAllocator& memory_replacement, u32 align, u32 size) noexcept
|
||||
: m_memory_replacement(memory_replacement),
|
||||
m_data(Allocate(memory_replacement, align, size)) {
|
||||
ASSERT_MSG(m_data, "Could not allocated frame buffer.");
|
||||
}
|
||||
|
||||
~FrameBuffer() {
|
||||
if (m_data != nullptr) {
|
||||
Deallocate(m_memory_replacement, m_data);
|
||||
m_data = {};
|
||||
}
|
||||
}
|
||||
|
||||
FrameBuffer(const FrameBuffer&) noexcept = delete;
|
||||
FrameBuffer& operator=(const FrameBuffer&) noexcept = delete;
|
||||
|
||||
FrameBuffer(FrameBuffer&& r) noexcept
|
||||
: m_memory_replacement(r.m_memory_replacement), m_data(r.m_data) {
|
||||
r.m_data = nullptr;
|
||||
};
|
||||
|
||||
FrameBuffer& operator=(FrameBuffer&& r) noexcept {
|
||||
std::swap(m_data, r.m_data);
|
||||
return *this;
|
||||
}
|
||||
|
||||
u8* GetBuffer() const noexcept {
|
||||
return m_data;
|
||||
}
|
||||
|
||||
private:
|
||||
static u8* Allocate(const SceAvPlayerMemAllocator& memory_replacement, u32 align, u32 size) {
|
||||
return reinterpret_cast<u8*>(
|
||||
memory_replacement.allocate(memory_replacement.object_ptr, align, size));
|
||||
}
|
||||
|
||||
static void Deallocate(const SceAvPlayerMemAllocator& memory_replacement, void* ptr) {
|
||||
memory_replacement.deallocate(memory_replacement.object_ptr, ptr);
|
||||
}
|
||||
|
||||
const SceAvPlayerMemAllocator& m_memory_replacement;
|
||||
u8* m_data = nullptr;
|
||||
};
|
||||
|
||||
struct Frame {
|
||||
FrameBuffer buffer;
|
||||
SceAvPlayerFrameInfoEx info;
|
||||
};
|
||||
|
||||
class EventCV {
|
||||
public:
|
||||
template <class Pred>
|
||||
void Wait(Pred pred) {
|
||||
std::unique_lock lock(m_mutex);
|
||||
m_cv.wait(lock, std::move(pred));
|
||||
}
|
||||
|
||||
template <class Pred>
|
||||
bool Wait(std::stop_token stop, Pred pred) {
|
||||
std::unique_lock lock(m_mutex);
|
||||
return m_cv.wait(lock, std::move(stop), std::move(pred));
|
||||
}
|
||||
|
||||
template <class Pred, class Rep, class Period>
|
||||
bool WaitFor(std::chrono::duration<Rep, Period> timeout, Pred pred) {
|
||||
std::unique_lock lock(m_mutex);
|
||||
return m_cv.wait_for(lock, timeout, std::move(pred));
|
||||
}
|
||||
|
||||
void Notify() {
|
||||
std::unique_lock lock(m_mutex);
|
||||
m_cv.notify_all();
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex m_mutex{};
|
||||
std::condition_variable_any m_cv{};
|
||||
};
|
||||
|
||||
class AvPlayerSource {
|
||||
public:
|
||||
AvPlayerSource(AvPlayerStateCallback& state, std::string_view path,
|
||||
const SceAvPlayerInitData& init_data, SceAvPlayerSourceType source_type);
|
||||
~AvPlayerSource();
|
||||
|
||||
bool FindStreamInfo();
|
||||
s32 GetStreamCount();
|
||||
s32 GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info);
|
||||
bool EnableStream(u32 stream_index);
|
||||
void SetLooping(bool is_looping);
|
||||
std::optional<bool> HasFrames(u32 num_frames);
|
||||
s32 Start();
|
||||
bool Stop();
|
||||
bool GetAudioData(SceAvPlayerFrameInfo& audio_info);
|
||||
bool GetVideoData(SceAvPlayerFrameInfo& video_info);
|
||||
bool GetVideoData(SceAvPlayerFrameInfoEx& video_info);
|
||||
u64 CurrentTime();
|
||||
bool IsActive();
|
||||
|
||||
private:
|
||||
using ScePthread = Kernel::ScePthread;
|
||||
|
||||
static void ReleaseAVPacket(AVPacket* packet);
|
||||
static void ReleaseAVFrame(AVFrame* frame);
|
||||
static void ReleaseAVCodecContext(AVCodecContext* context);
|
||||
static void ReleaseSWRContext(SwrContext* context);
|
||||
static void ReleaseSWSContext(SwsContext* context);
|
||||
static void ReleaseAVFormatContext(AVFormatContext* context);
|
||||
|
||||
using AVPacketPtr = std::unique_ptr<AVPacket, decltype(&ReleaseAVPacket)>;
|
||||
using AVFramePtr = std::unique_ptr<AVFrame, decltype(&ReleaseAVFrame)>;
|
||||
using AVCodecContextPtr = std::unique_ptr<AVCodecContext, decltype(&ReleaseAVCodecContext)>;
|
||||
using SWRContextPtr = std::unique_ptr<SwrContext, decltype(&ReleaseSWRContext)>;
|
||||
using SWSContextPtr = std::unique_ptr<SwsContext, decltype(&ReleaseSWSContext)>;
|
||||
using AVFormatContextPtr = std::unique_ptr<AVFormatContext, decltype(&ReleaseAVFormatContext)>;
|
||||
|
||||
void DemuxerThread(std::stop_token stop);
|
||||
void VideoDecoderThread(std::stop_token stop);
|
||||
void AudioDecoderThread(std::stop_token stop);
|
||||
|
||||
bool HasRunningThreads() const;
|
||||
|
||||
AVFramePtr ConvertAudioFrame(const AVFrame& frame);
|
||||
AVFramePtr ConvertVideoFrame(const AVFrame& frame);
|
||||
|
||||
Frame PrepareAudioFrame(FrameBuffer buffer, const AVFrame& frame);
|
||||
Frame PrepareVideoFrame(FrameBuffer buffer, const AVFrame& frame);
|
||||
|
||||
AvPlayerStateCallback& m_state;
|
||||
|
||||
SceAvPlayerMemAllocator m_memory_replacement{};
|
||||
u32 m_num_output_video_framebuffers{};
|
||||
|
||||
std::atomic_bool m_is_looping = false;
|
||||
std::atomic_bool m_is_eof = false;
|
||||
|
||||
std::unique_ptr<IDataStreamer> m_up_data_streamer;
|
||||
|
||||
AvPlayerQueue<FrameBuffer> m_audio_buffers;
|
||||
AvPlayerQueue<FrameBuffer> m_video_buffers;
|
||||
|
||||
AvPlayerQueue<AVPacketPtr> m_audio_packets;
|
||||
AvPlayerQueue<AVPacketPtr> m_video_packets;
|
||||
|
||||
AvPlayerQueue<Frame> m_audio_frames;
|
||||
AvPlayerQueue<Frame> m_video_frames;
|
||||
|
||||
std::optional<FrameBuffer> m_current_video_frame;
|
||||
std::optional<FrameBuffer> m_current_audio_frame;
|
||||
|
||||
std::optional<s32> m_video_stream_index{};
|
||||
std::optional<s32> m_audio_stream_index{};
|
||||
|
||||
EventCV m_audio_packets_cv{};
|
||||
EventCV m_audio_frames_cv{};
|
||||
EventCV m_audio_buffers_cv{};
|
||||
|
||||
EventCV m_video_packets_cv{};
|
||||
EventCV m_video_frames_cv{};
|
||||
EventCV m_video_buffers_cv{};
|
||||
|
||||
EventCV m_stop_cv{};
|
||||
|
||||
std::mutex m_state_mutex{};
|
||||
std::jthread m_demuxer_thread{};
|
||||
std::jthread m_video_decoder_thread{};
|
||||
std::jthread m_audio_decoder_thread{};
|
||||
|
||||
AVFormatContextPtr m_avformat_context{nullptr, &ReleaseAVFormatContext};
|
||||
AVCodecContextPtr m_video_codec_context{nullptr, &ReleaseAVCodecContext};
|
||||
AVCodecContextPtr m_audio_codec_context{nullptr, &ReleaseAVCodecContext};
|
||||
SWRContextPtr m_swr_context{nullptr, &ReleaseSWRContext};
|
||||
SWSContextPtr m_sws_context{nullptr, &ReleaseSWSContext};
|
||||
|
||||
std::chrono::high_resolution_clock::time_point m_start_time{};
|
||||
};
|
||||
|
||||
} // namespace Libraries::AvPlayer
|
493
src/core/libraries/avplayer/avplayer_state.cpp
Normal file
493
src/core/libraries/avplayer/avplayer_state.cpp
Normal file
|
@ -0,0 +1,493 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "avplayer_file_streamer.h"
|
||||
#include "avplayer_source.h"
|
||||
#include "avplayer_state.h"
|
||||
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/kernel/time_management.h"
|
||||
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
using namespace Kernel;
|
||||
|
||||
void PS4_SYSV_ABI AvPlayerState::AutoPlayEventCallback(void* opaque, s32 event_id, s32 source_id,
|
||||
void* event_data) {
|
||||
auto const self = reinterpret_cast<AvPlayerState*>(opaque);
|
||||
|
||||
if (event_id == SCE_AVPLAYER_STATE_READY) {
|
||||
s32 video_stream_index = -1;
|
||||
s32 audio_stream_index = -1;
|
||||
s32 timedtext_stream_index = -1;
|
||||
const s32 stream_count = self->GetStreamCount();
|
||||
if (AVPLAYER_IS_ERROR(stream_count)) {
|
||||
return;
|
||||
}
|
||||
if (stream_count == 0) {
|
||||
self->Stop();
|
||||
return;
|
||||
}
|
||||
for (u32 stream_index = 0; stream_index < stream_count; ++stream_index) {
|
||||
SceAvPlayerStreamInfo info{};
|
||||
self->GetStreamInfo(stream_index, info);
|
||||
|
||||
const std::string_view default_language(
|
||||
reinterpret_cast<char*>(self->m_default_language));
|
||||
switch (info.type) {
|
||||
case SCE_AVPLAYER_VIDEO:
|
||||
if (video_stream_index == -1) {
|
||||
video_stream_index = stream_index;
|
||||
}
|
||||
if (!default_language.empty() &&
|
||||
default_language == reinterpret_cast<char*>(info.details.video.language_code)) {
|
||||
video_stream_index = stream_index;
|
||||
}
|
||||
break;
|
||||
case SCE_AVPLAYER_AUDIO:
|
||||
if (audio_stream_index == -1) {
|
||||
audio_stream_index = stream_index;
|
||||
}
|
||||
if (!default_language.empty() &&
|
||||
default_language == reinterpret_cast<char*>(info.details.video.language_code)) {
|
||||
audio_stream_index = stream_index;
|
||||
}
|
||||
break;
|
||||
case SCE_AVPLAYER_TIMEDTEXT:
|
||||
if (default_language.empty()) {
|
||||
timedtext_stream_index = stream_index;
|
||||
break;
|
||||
}
|
||||
if (default_language == reinterpret_cast<char*>(info.details.video.language_code)) {
|
||||
timedtext_stream_index = stream_index;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (video_stream_index != -1) {
|
||||
self->EnableStream(video_stream_index);
|
||||
}
|
||||
if (audio_stream_index != -1) {
|
||||
self->EnableStream(audio_stream_index);
|
||||
}
|
||||
if (timedtext_stream_index != -1) {
|
||||
self->EnableStream(timedtext_stream_index);
|
||||
}
|
||||
self->Start();
|
||||
return;
|
||||
}
|
||||
|
||||
// Pass other events to the game
|
||||
const auto callback = self->m_event_replacement.event_callback;
|
||||
const auto ptr = self->m_event_replacement.object_ptr;
|
||||
if (callback != nullptr) {
|
||||
callback(ptr, event_id, 0, event_data);
|
||||
}
|
||||
}
|
||||
|
||||
// Called inside GAME thread
|
||||
AvPlayerState::AvPlayerState(const SceAvPlayerInitData& init_data)
|
||||
: m_init_data(init_data), m_event_replacement(init_data.event_replacement) {
|
||||
if (m_event_replacement.event_callback == nullptr || init_data.auto_start) {
|
||||
m_auto_start = true;
|
||||
m_init_data.event_replacement.event_callback = &AvPlayerState::AutoPlayEventCallback;
|
||||
m_init_data.event_replacement.object_ptr = this;
|
||||
}
|
||||
if (init_data.default_language != nullptr) {
|
||||
std::memcpy(m_default_language, init_data.default_language, sizeof(m_default_language));
|
||||
}
|
||||
SetState(AvState::Initial);
|
||||
StartControllerThread();
|
||||
}
|
||||
|
||||
AvPlayerState::~AvPlayerState() {
|
||||
{
|
||||
std::unique_lock lock(m_source_mutex);
|
||||
m_up_source.reset();
|
||||
}
|
||||
if (m_controller_thread.joinable()) {
|
||||
m_controller_thread.request_stop();
|
||||
m_controller_thread.join();
|
||||
}
|
||||
m_event_queue.Clear();
|
||||
}
|
||||
|
||||
// Called inside GAME thread
|
||||
s32 AvPlayerState::AddSource(std::string_view path, SceAvPlayerSourceType source_type) {
|
||||
if (path.empty()) {
|
||||
LOG_ERROR(Lib_AvPlayer, "File path is empty.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock lock(m_source_mutex);
|
||||
if (m_up_source != nullptr) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Only one source is supported.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
m_up_source = std::make_unique<AvPlayerSource>(*this, path, m_init_data, source_type);
|
||||
}
|
||||
AddSourceEvent();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Called inside GAME thread
|
||||
s32 AvPlayerState::GetStreamCount() {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (m_up_source == nullptr) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not get stream count. No source.");
|
||||
return -1;
|
||||
}
|
||||
return m_up_source->GetStreamCount();
|
||||
}
|
||||
|
||||
// Called inside GAME thread
|
||||
s32 AvPlayerState::GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info) {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (m_up_source == nullptr) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not get stream {} info. No source.", stream_index);
|
||||
return -1;
|
||||
}
|
||||
return m_up_source->GetStreamInfo(stream_index, info);
|
||||
}
|
||||
|
||||
// Called inside GAME thread
|
||||
s32 AvPlayerState::Start() {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (m_up_source == nullptr || m_up_source->Start() < 0) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not start playback.");
|
||||
return -1;
|
||||
}
|
||||
SetState(AvState::Play);
|
||||
OnPlaybackStateChanged(AvState::Play);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AvPlayerState::AvControllerThread(std::stop_token stop) {
|
||||
using std::chrono::milliseconds;
|
||||
|
||||
while (!stop.stop_requested()) {
|
||||
if (m_event_queue.Size() != 0) {
|
||||
ProcessEvent();
|
||||
continue;
|
||||
}
|
||||
std::this_thread::sleep_for(milliseconds(5));
|
||||
UpdateBufferingState();
|
||||
}
|
||||
}
|
||||
|
||||
// Called inside GAME thread
|
||||
void AvPlayerState::AddSourceEvent() {
|
||||
SetState(AvState::AddingSource);
|
||||
m_event_queue.Push(AvPlayerEvent{
|
||||
.event = AvEventType::AddSource,
|
||||
});
|
||||
}
|
||||
|
||||
void AvPlayerState::WarningEvent(s32 id) {
|
||||
m_event_queue.Push(AvPlayerEvent{
|
||||
.event = AvEventType::WarningId,
|
||||
.payload =
|
||||
{
|
||||
.error = id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Called inside GAME thread
|
||||
void AvPlayerState::StartControllerThread() {
|
||||
m_controller_thread =
|
||||
std::jthread([this](std::stop_token stop) { this->AvControllerThread(stop); });
|
||||
}
|
||||
|
||||
// Called inside GAME thread
|
||||
bool AvPlayerState::EnableStream(u32 stream_index) {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (m_up_source == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return m_up_source->EnableStream(stream_index);
|
||||
}
|
||||
|
||||
// Called inside GAME thread
|
||||
bool AvPlayerState::Stop() {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (m_up_source == nullptr || m_current_state == AvState::Stop) {
|
||||
return false;
|
||||
}
|
||||
if (!SetState(AvState::Stop)) {
|
||||
return false;
|
||||
}
|
||||
OnPlaybackStateChanged(AvState::Stop);
|
||||
return m_up_source->Stop();
|
||||
}
|
||||
|
||||
bool AvPlayerState::GetVideoData(SceAvPlayerFrameInfo& video_info) {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (m_up_source == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return m_up_source->GetVideoData(video_info);
|
||||
}
|
||||
|
||||
bool AvPlayerState::GetVideoData(SceAvPlayerFrameInfoEx& video_info) {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (m_up_source == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return m_up_source->GetVideoData(video_info);
|
||||
}
|
||||
|
||||
bool AvPlayerState::GetAudioData(SceAvPlayerFrameInfo& audio_info) {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (m_up_source == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return m_up_source->GetAudioData(audio_info);
|
||||
}
|
||||
|
||||
bool AvPlayerState::IsActive() {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (m_up_source == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return m_current_state != AvState::Stop && m_current_state != AvState::Error &&
|
||||
m_current_state != AvState::EndOfFile && m_up_source->IsActive();
|
||||
}
|
||||
|
||||
u64 AvPlayerState::CurrentTime() {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (m_up_source == nullptr) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not get current time. No source.");
|
||||
return 0;
|
||||
}
|
||||
return m_up_source->CurrentTime();
|
||||
}
|
||||
|
||||
bool AvPlayerState::SetLooping(bool is_looping) {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (m_up_source == nullptr) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not set loop flag. No source.");
|
||||
return false;
|
||||
}
|
||||
m_up_source->SetLooping(is_looping);
|
||||
return true;
|
||||
}
|
||||
|
||||
// May be called from different threads
|
||||
void AvPlayerState::OnWarning(u32 id) {
|
||||
// Forward to CONTROLLER thread
|
||||
WarningEvent(id);
|
||||
}
|
||||
|
||||
void AvPlayerState::OnError() {
|
||||
SetState(AvState::Error);
|
||||
OnPlaybackStateChanged(AvState::Error);
|
||||
}
|
||||
|
||||
void AvPlayerState::OnEOF() {
|
||||
SetState(AvState::EndOfFile);
|
||||
}
|
||||
|
||||
// Called inside CONTROLLER thread
|
||||
void AvPlayerState::OnPlaybackStateChanged(AvState state) {
|
||||
switch (state) {
|
||||
case AvState::Ready: {
|
||||
EmitEvent(SCE_AVPLAYER_STATE_READY);
|
||||
break;
|
||||
}
|
||||
case AvState::Play: {
|
||||
EmitEvent(SCE_AVPLAYER_STATE_PLAY);
|
||||
break;
|
||||
}
|
||||
case AvState::Stop: {
|
||||
EmitEvent(SCE_AVPLAYER_STATE_STOP);
|
||||
break;
|
||||
}
|
||||
case AvState::Pause: {
|
||||
EmitEvent(SCE_AVPLAYER_STATE_PAUSE);
|
||||
break;
|
||||
}
|
||||
case AvState::Buffering: {
|
||||
EmitEvent(SCE_AVPLAYER_STATE_BUFFERING);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Called inside CONTROLLER and GAME threads
|
||||
bool AvPlayerState::SetState(AvState state) {
|
||||
std::lock_guard guard(m_state_machine_mutex);
|
||||
|
||||
if (!IsStateTransitionValid(state)) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Invalid state transition: {} -> {}",
|
||||
magic_enum::enum_name(m_current_state.load()), magic_enum::enum_name(state));
|
||||
return false;
|
||||
}
|
||||
m_previous_state.store(m_current_state);
|
||||
m_current_state.store(state);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Called inside CONTROLLER thread
|
||||
std::optional<bool> AvPlayerState::OnBufferingCheckEvent(u32 num_frames) {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (!m_up_source) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return m_up_source->HasFrames(num_frames);
|
||||
}
|
||||
|
||||
// Called inside CONTROLLER thread
|
||||
void AvPlayerState::EmitEvent(SceAvPlayerEvents event_id, void* event_data) {
|
||||
LOG_INFO(Lib_AvPlayer, "Sending event to the game: id = {}", magic_enum::enum_name(event_id));
|
||||
const auto callback = m_init_data.event_replacement.event_callback;
|
||||
if (callback) {
|
||||
const auto ptr = m_init_data.event_replacement.object_ptr;
|
||||
callback(ptr, event_id, 0, event_data);
|
||||
}
|
||||
}
|
||||
|
||||
// Called inside CONTROLLER thread
|
||||
void AvPlayerState::ProcessEvent() {
|
||||
if (m_current_state == AvState::Jump) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard guard(m_event_handler_mutex);
|
||||
|
||||
auto event = m_event_queue.Pop();
|
||||
if (!event.has_value()) {
|
||||
return;
|
||||
}
|
||||
switch (event->event) {
|
||||
case AvEventType::WarningId: {
|
||||
OnWarning(event->payload.error);
|
||||
break;
|
||||
}
|
||||
case AvEventType::RevertState: {
|
||||
SetState(m_previous_state.load());
|
||||
break;
|
||||
}
|
||||
case AvEventType::AddSource: {
|
||||
std::shared_lock lock(m_source_mutex);
|
||||
if (m_up_source->FindStreamInfo()) {
|
||||
SetState(AvState::Ready);
|
||||
OnPlaybackStateChanged(AvState::Ready);
|
||||
} else {
|
||||
OnWarning(ORBIS_AVPLAYER_ERROR_NOT_SUPPORTED);
|
||||
SetState(AvState::Error);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AvEventType::Error: {
|
||||
OnWarning(event->payload.error);
|
||||
SetState(AvState::Error);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Called inside CONTROLLER thread
|
||||
void AvPlayerState::UpdateBufferingState() {
|
||||
if (m_current_state == AvState::Buffering) {
|
||||
const auto has_frames = OnBufferingCheckEvent(10);
|
||||
if (!has_frames.has_value()) {
|
||||
return;
|
||||
}
|
||||
if (has_frames.value()) {
|
||||
const auto state =
|
||||
m_previous_state >= AvState::C0x0B ? m_previous_state.load() : AvState::Play;
|
||||
SetState(state);
|
||||
OnPlaybackStateChanged(state);
|
||||
}
|
||||
} else if (m_current_state == AvState::Play) {
|
||||
const auto has_frames = OnBufferingCheckEvent(0);
|
||||
if (!has_frames.has_value()) {
|
||||
return;
|
||||
}
|
||||
if (!has_frames.value()) {
|
||||
SetState(AvState::Buffering);
|
||||
OnPlaybackStateChanged(AvState::Buffering);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AvPlayerState::IsStateTransitionValid(AvState state) {
|
||||
switch (state) {
|
||||
case AvState::Play: {
|
||||
switch (m_current_state.load()) {
|
||||
case AvState::Stop:
|
||||
case AvState::EndOfFile:
|
||||
// case AvState::C0x08:
|
||||
case AvState::Error:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
case AvState::Pause: {
|
||||
switch (m_current_state.load()) {
|
||||
case AvState::Stop:
|
||||
case AvState::EndOfFile:
|
||||
// case AvState::C0x08:
|
||||
case AvState::Starting:
|
||||
case AvState::Error:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
case AvState::Jump: {
|
||||
switch (m_current_state.load()) {
|
||||
case AvState::Stop:
|
||||
case AvState::EndOfFile:
|
||||
// case AvState::C0x08:
|
||||
case AvState::TrickMode:
|
||||
case AvState::Starting:
|
||||
case AvState::Error:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
case AvState::TrickMode: {
|
||||
switch (m_current_state.load()) {
|
||||
case AvState::Stop:
|
||||
case AvState::EndOfFile:
|
||||
// case AvState::C0x08:
|
||||
case AvState::Jump:
|
||||
case AvState::Starting:
|
||||
case AvState::Error:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
case AvState::Buffering: {
|
||||
switch (m_current_state.load()) {
|
||||
case AvState::Stop:
|
||||
case AvState::EndOfFile:
|
||||
case AvState::Pause:
|
||||
// case AvState::C0x08:
|
||||
case AvState::Starting:
|
||||
case AvState::Error:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libraries::AvPlayer
|
88
src/core/libraries/avplayer/avplayer_state.h
Normal file
88
src/core/libraries/avplayer/avplayer_state.h
Normal file
|
@ -0,0 +1,88 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "avplayer.h"
|
||||
#include "avplayer_data_streamer.h"
|
||||
#include "avplayer_source.h"
|
||||
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "core/libraries/kernel/thread_management.h"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
|
||||
namespace Libraries::AvPlayer {
|
||||
|
||||
class Stream;
|
||||
class AvDecoder;
|
||||
|
||||
class AvPlayerState : public AvPlayerStateCallback {
|
||||
public:
|
||||
AvPlayerState(const SceAvPlayerInitData& init_data);
|
||||
~AvPlayerState();
|
||||
|
||||
s32 AddSource(std::string_view filename, SceAvPlayerSourceType source_type);
|
||||
s32 GetStreamCount();
|
||||
s32 GetStreamInfo(u32 stream_index, SceAvPlayerStreamInfo& info);
|
||||
bool EnableStream(u32 stream_index);
|
||||
s32 Start();
|
||||
bool Stop();
|
||||
bool GetAudioData(SceAvPlayerFrameInfo& audio_info);
|
||||
bool GetVideoData(SceAvPlayerFrameInfo& video_info);
|
||||
bool GetVideoData(SceAvPlayerFrameInfoEx& video_info);
|
||||
bool IsActive();
|
||||
u64 CurrentTime();
|
||||
bool SetLooping(bool is_looping);
|
||||
|
||||
private:
|
||||
using ScePthreadMutex = Kernel::ScePthreadMutex;
|
||||
using ScePthread = Kernel::ScePthread;
|
||||
|
||||
// Event Replacement
|
||||
static void PS4_SYSV_ABI AutoPlayEventCallback(void* handle, s32 event_id, s32 source_id,
|
||||
void* event_data);
|
||||
|
||||
void OnWarning(u32 id) override;
|
||||
void OnError() override;
|
||||
void OnEOF() override;
|
||||
|
||||
void OnPlaybackStateChanged(AvState state);
|
||||
std::optional<bool> OnBufferingCheckEvent(u32 num_frames);
|
||||
|
||||
void EmitEvent(SceAvPlayerEvents event_id, void* event_data = nullptr);
|
||||
bool SetState(AvState state);
|
||||
|
||||
void AvControllerThread(std::stop_token stop);
|
||||
|
||||
void AddSourceEvent();
|
||||
void WarningEvent(s32 id);
|
||||
|
||||
void StartControllerThread();
|
||||
void ProcessEvent();
|
||||
void UpdateBufferingState();
|
||||
bool IsStateTransitionValid(AvState state);
|
||||
|
||||
std::unique_ptr<AvPlayerSource> m_up_source;
|
||||
|
||||
SceAvPlayerInitData m_init_data{};
|
||||
SceAvPlayerEventReplacement m_event_replacement{};
|
||||
bool m_auto_start{};
|
||||
u8 m_default_language[4]{};
|
||||
|
||||
std::atomic<AvState> m_current_state;
|
||||
std::atomic<AvState> m_previous_state;
|
||||
u32 m_thread_priority;
|
||||
u32 m_thread_affinity;
|
||||
std::atomic_uint32_t m_some_event_result{};
|
||||
|
||||
std::shared_mutex m_source_mutex{};
|
||||
std::mutex m_state_machine_mutex{};
|
||||
std::mutex m_event_handler_mutex{};
|
||||
std::jthread m_controller_thread{};
|
||||
AvPlayerQueue<AvPlayerEvent> m_event_queue{};
|
||||
};
|
||||
|
||||
} // namespace Libraries::AvPlayer
|
|
@ -233,6 +233,9 @@ constexpr int SCE_KERNEL_ERROR_ESDKVERSION = 0x80020063;
|
|||
constexpr int SCE_KERNEL_ERROR_ESTART = 0x80020064;
|
||||
constexpr int SCE_KERNEL_ERROR_ESTOP = 0x80020065;
|
||||
|
||||
// libSceRandom error codes
|
||||
constexpr int SCE_RANDOM_ERROR_INVALID = 0x817C0016;
|
||||
|
||||
// videoOut
|
||||
constexpr int SCE_VIDEO_OUT_ERROR_INVALID_VALUE = 0x80290001; // invalid argument
|
||||
constexpr int SCE_VIDEO_OUT_ERROR_INVALID_ADDRESS = 0x80290002; // invalid addresses
|
||||
|
@ -454,5 +457,18 @@ constexpr int ORBIS_NP_TROPHY_ERROR_HANDLE_EXCEEDS_MAX = 0x80551624;
|
|||
constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_ALREADY_EXISTS = 0x80551613;
|
||||
constexpr int ORBIS_NP_TROPHY_ERROR_CONTEXT_EXCEEDS_MAX = 0x80551622;
|
||||
|
||||
// AvPlayer library
|
||||
constexpr int ORBIS_AVPLAYER_ERROR_INVALID_PARAMS = 0x806A0001;
|
||||
constexpr int ORBIS_AVPLAYER_ERROR_OPERATION_FAILED = 0x806A0002;
|
||||
constexpr int ORBIS_AVPLAYER_ERROR_NO_MEMORY = 0x806A0003;
|
||||
constexpr int ORBIS_AVPLAYER_ERROR_NOT_SUPPORTED = 0x806A0004;
|
||||
constexpr int ORBIS_AVPLAYER_ERROR_WAR_FILE_NONINTERLEAVED = 0x806A00A0;
|
||||
constexpr int ORBIS_AVPLAYER_ERROR_WAR_LOOPING_BACK = 0x806A00A1;
|
||||
constexpr int ORBIS_AVPLAYER_ERROR_WAR_JUMP_COMPLETE = 0x806A00A3;
|
||||
constexpr int ORBIS_AVPLAYER_ERROR_INFO_MARLIN_ENCRY = 0x806A00B0;
|
||||
constexpr int ORBIS_AVPLAYER_ERROR_INFO_PLAYREADY_ENCRY = 0x806A00B4;
|
||||
constexpr int ORBIS_AVPLAYER_ERROR_INFO_AES_ENCRY = 0x806A00B5;
|
||||
constexpr int ORBIS_AVPLAYER_ERROR_INFO_OTHER_ENCRY = 0x806A00BF;
|
||||
|
||||
// AppContent library
|
||||
constexpr int ORBIS_APP_CONTENT_ERROR_PARAMETER = 0x80D90002;
|
||||
|
|
|
@ -20,13 +20,12 @@
|
|||
|
||||
extern Frontend::WindowSDL* g_window;
|
||||
std::unique_ptr<Vulkan::RendererVulkan> renderer;
|
||||
std::unique_ptr<AmdGpu::Liverpool> liverpool;
|
||||
|
||||
namespace Libraries::GnmDriver {
|
||||
|
||||
using namespace AmdGpu;
|
||||
|
||||
static std::unique_ptr<AmdGpu::Liverpool> liverpool;
|
||||
|
||||
enum GnmEventIdents : u64 {
|
||||
Compute0RelMem = 0x00,
|
||||
Compute1RelMem = 0x01,
|
||||
|
@ -957,9 +956,9 @@ int PS4_SYSV_ABI sceGnmGetGpuBlockStatus() {
|
|||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceGnmGetGpuCoreClockFrequency() {
|
||||
LOG_ERROR(Lib_GnmDriver, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
u32 PS4_SYSV_ABI sceGnmGetGpuCoreClockFrequency() {
|
||||
LOG_TRACE(Lib_GnmDriver, "called");
|
||||
return Config::isNeoMode() ? 911'000'000 : 800'000'000;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceGnmGetGpuInfoStatus() {
|
||||
|
@ -1707,8 +1706,18 @@ int PS4_SYSV_ABI sceGnmSetupMipStatsReport() {
|
|||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceGnmSetVgtControl() {
|
||||
LOG_ERROR(Lib_GnmDriver, "(STUBBED) called");
|
||||
s32 PS4_SYSV_ABI sceGnmSetVgtControl(u32* cmdbuf, u32 size, u32 prim_group_sz_minus_one,
|
||||
u32 partial_vs_wave_mode, u32 wd_switch_only_on_eop_mode) {
|
||||
LOG_TRACE(Lib_GnmDriver, "called");
|
||||
|
||||
if (!cmdbuf || size != 3 || (prim_group_sz_minus_one >= 0x100) ||
|
||||
((wd_switch_only_on_eop_mode | partial_vs_wave_mode) >= 2)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const u32 reg_value =
|
||||
((partial_vs_wave_mode & 1) << 0x10) | (prim_group_sz_minus_one & 0xffffu);
|
||||
PM4CmdSetData::SetContextReg(cmdbuf, 0x2aau, reg_value); // IA_MULTI_VGT_PARAM
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
@ -2131,6 +2140,7 @@ int PS4_SYSV_ABI sceGnmSubmitDone() {
|
|||
if (!liverpool->IsGpuIdle()) {
|
||||
submission_lock = true;
|
||||
}
|
||||
liverpool->SubmitDone();
|
||||
send_init_packet = true;
|
||||
++frames_submitted;
|
||||
return ORBIS_OK;
|
||||
|
|
|
@ -85,7 +85,7 @@ int PS4_SYSV_ABI sceGnmGetDebugTimestamp();
|
|||
int PS4_SYSV_ABI sceGnmGetEqEventType();
|
||||
int PS4_SYSV_ABI sceGnmGetEqTimeStamp();
|
||||
int PS4_SYSV_ABI sceGnmGetGpuBlockStatus();
|
||||
int PS4_SYSV_ABI sceGnmGetGpuCoreClockFrequency();
|
||||
u32 PS4_SYSV_ABI sceGnmGetGpuCoreClockFrequency();
|
||||
int PS4_SYSV_ABI sceGnmGetGpuInfoStatus();
|
||||
int PS4_SYSV_ABI sceGnmGetLastWaitedAddress();
|
||||
int PS4_SYSV_ABI sceGnmGetNumTcaUnits();
|
||||
|
@ -161,7 +161,8 @@ int PS4_SYSV_ABI sceGnmSetResourceUserData();
|
|||
int PS4_SYSV_ABI sceGnmSetSpiEnableSqCounters();
|
||||
int PS4_SYSV_ABI sceGnmSetSpiEnableSqCountersForUnitInstance();
|
||||
int PS4_SYSV_ABI sceGnmSetupMipStatsReport();
|
||||
int PS4_SYSV_ABI sceGnmSetVgtControl();
|
||||
s32 PS4_SYSV_ABI sceGnmSetVgtControl(u32* cmdbuf, u32 size, u32 prim_group_sz_minus_one,
|
||||
u32 partial_vs_wave_mode, u32 wd_switch_only_on_eop_mode);
|
||||
s32 PS4_SYSV_ABI sceGnmSetVsShader(u32* cmdbuf, u32 size, const u32* vs_regs, u32 shader_modifier);
|
||||
int PS4_SYSV_ABI sceGnmSetWaveLimitMultiplier();
|
||||
int PS4_SYSV_ABI sceGnmSetWaveLimitMultipliers();
|
||||
|
|
|
@ -78,9 +78,7 @@ bool EqueueInternal::TriggerEvent(u64 ident, s16 filter, void* trigger_data) {
|
|||
std::scoped_lock lock{m_mutex};
|
||||
|
||||
for (auto& event : m_events) {
|
||||
ASSERT_MSG(event.event.filter == filter,
|
||||
"Event to trigger doesn't match to queue events");
|
||||
if (event.event.ident == ident) {
|
||||
if ((event.event.ident == ident) && (event.event.filter == filter)) {
|
||||
event.Trigger(trigger_data);
|
||||
has_found = true;
|
||||
}
|
||||
|
|
|
@ -53,6 +53,9 @@ int PS4_SYSV_ABI sceKernelOpen(const char* path, int flags, u16 mode) {
|
|||
if (std::string_view{path} == "/dev/stdout") {
|
||||
return 2002;
|
||||
}
|
||||
if (std::string_view{path} == "/dev/urandom") {
|
||||
return 2003;
|
||||
}
|
||||
u32 handle = h->CreateHandle();
|
||||
auto* file = h->GetFile(handle);
|
||||
if (directory) {
|
||||
|
@ -113,6 +116,9 @@ int PS4_SYSV_ABI sceKernelClose(int d) {
|
|||
if (d < 3) { // d probably hold an error code
|
||||
return ORBIS_KERNEL_ERROR_EPERM;
|
||||
}
|
||||
if (d == 2003) { // dev/urandom case
|
||||
return SCE_OK;
|
||||
}
|
||||
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
|
||||
auto* file = h->GetFile(d);
|
||||
if (file == nullptr) {
|
||||
|
@ -223,6 +229,13 @@ s64 PS4_SYSV_ABI posix_lseek(int d, s64 offset, int whence) {
|
|||
}
|
||||
|
||||
s64 PS4_SYSV_ABI sceKernelRead(int d, void* buf, size_t nbytes) {
|
||||
if (d == 2003) // dev urandom case
|
||||
{
|
||||
auto rbuf = static_cast<char*>(buf);
|
||||
for (size_t i = 0; i < nbytes; i++)
|
||||
rbuf[i] = std::rand() & 0xFF;
|
||||
return nbytes;
|
||||
}
|
||||
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
|
||||
auto* file = h->GetFile(d);
|
||||
if (file == nullptr) {
|
||||
|
@ -459,7 +472,30 @@ s64 PS4_SYSV_ABI sceKernelPwrite(int d, void* buf, size_t nbytes, s64 offset) {
|
|||
return file->f.WriteRaw<u8>(buf, nbytes);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelRename(const char* from, const char* to) {
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
const auto src_path = mnt->GetHostPath(from);
|
||||
if (!std::filesystem::exists(src_path)) {
|
||||
return ORBIS_KERNEL_ERROR_ENOENT;
|
||||
}
|
||||
const auto dst_path = mnt->GetHostPath(to);
|
||||
const bool src_is_dir = std::filesystem::is_directory(src_path);
|
||||
const bool dst_is_dir = std::filesystem::is_directory(dst_path);
|
||||
if (src_is_dir && !dst_is_dir) {
|
||||
return ORBIS_KERNEL_ERROR_ENOTDIR;
|
||||
}
|
||||
if (!src_is_dir && dst_is_dir) {
|
||||
return ORBIS_KERNEL_ERROR_EISDIR;
|
||||
}
|
||||
if (dst_is_dir && !std::filesystem::is_empty(dst_path)) {
|
||||
return ORBIS_KERNEL_ERROR_ENOTEMPTY;
|
||||
}
|
||||
std::filesystem::copy(src_path, dst_path, std::filesystem::copy_options::overwrite_existing);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
void fileSystemSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
|
||||
std::srand(std::time(nullptr));
|
||||
LIB_FUNCTION("1G3lF1Gg1k8", "libkernel", 1, "libkernel", 1, 1, sceKernelOpen);
|
||||
LIB_FUNCTION("wuCroIGjt2g", "libScePosix", 1, "libkernel", 1, 1, posix_open);
|
||||
LIB_FUNCTION("UK2Tl2DWUns", "libkernel", 1, "libkernel", 1, 1, sceKernelClose);
|
||||
|
@ -479,6 +515,7 @@ void fileSystemSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
|
|||
LIB_FUNCTION("kBwCPsYX-m4", "libkernel", 1, "libkernel", 1, 1, sceKernelFStat);
|
||||
LIB_FUNCTION("mqQMh1zPPT8", "libScePosix", 1, "libkernel", 1, 1, posix_fstat);
|
||||
LIB_FUNCTION("VW3TVZiM4-E", "libkernel", 1, "libkernel", 1, 1, sceKernelFtruncate);
|
||||
LIB_FUNCTION("52NcYU9+lEo", "libkernel", 1, "libkernel", 1, 1, sceKernelRename);
|
||||
|
||||
LIB_FUNCTION("E6ao34wPw+U", "libScePosix", 1, "libkernel", 1, 1, posix_stat);
|
||||
LIB_FUNCTION("+r3rMFwItV4", "libkernel", 1, "libkernel", 1, 1, sceKernelPread);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <boost/asio/io_context.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/singleton.h"
|
||||
|
@ -84,6 +85,9 @@ static PS4_SYSV_ABI void stack_chk_fail() {
|
|||
|
||||
int PS4_SYSV_ABI sceKernelMunmap(void* addr, size_t len) {
|
||||
LOG_INFO(Kernel_Vmm, "addr = {}, len = {:#x}", fmt::ptr(addr), len);
|
||||
if (len == 0) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
auto* memory = Core::Memory::Instance();
|
||||
memory->UnmapMemory(std::bit_cast<VAddr>(addr), len);
|
||||
return SCE_OK;
|
||||
|
@ -356,7 +360,6 @@ int PS4_SYSV_ABI posix_connect() {
|
|||
}
|
||||
|
||||
int PS4_SYSV_ABI _sigprocmask() {
|
||||
LOG_DEBUG(Lib_Kernel, "STUBBED");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
@ -405,6 +408,10 @@ void LibKernel_Register(Core::Loader::SymbolsResolver* sym) {
|
|||
LIB_FUNCTION("VOx8NGmHXTs", "libkernel", 1, "libkernel", 1, 1, sceKernelGetCpumode);
|
||||
LIB_FUNCTION("Xjoosiw+XPI", "libkernel", 1, "libkernel", 1, 1, sceKernelUuidCreate);
|
||||
|
||||
LIB_FUNCTION("2SKEx6bSq-4", "libkernel", 1, "libkernel", 1, 1, sceKernelBatchMap);
|
||||
LIB_FUNCTION("kBJzF8x4SyE", "libkernel", 1, "libkernel", 1, 1, sceKernelBatchMap2);
|
||||
LIB_FUNCTION("DGMG3JshrZU", "libkernel", 1, "libkernel", 1, 1, sceKernelSetVirtualRangeName);
|
||||
|
||||
// equeue
|
||||
LIB_FUNCTION("D0OdFMjp46I", "libkernel", 1, "libkernel", 1, 1, sceKernelCreateEqueue);
|
||||
LIB_FUNCTION("jpFjmgAC5AE", "libkernel", 1, "libkernel", 1, 1, sceKernelDeleteEqueue);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <bit>
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/singleton.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
|
@ -73,13 +74,22 @@ s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchE
|
|||
size_t* sizeOut) {
|
||||
LOG_WARNING(Kernel_Vmm, "called searchStart = {:#x}, searchEnd = {:#x}, alignment = {:#x}",
|
||||
searchStart, searchEnd, alignment);
|
||||
|
||||
if (searchEnd <= searchStart) {
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if (searchEnd > SCE_KERNEL_MAIN_DMEM_SIZE) {
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
auto* memory = Core::Memory::Instance();
|
||||
|
||||
PAddr physAddr;
|
||||
s32 size = memory->DirectQueryAvailable(searchStart, searchEnd, alignment, &physAddr, sizeOut);
|
||||
s32 result =
|
||||
memory->DirectQueryAvailable(searchStart, searchEnd, alignment, &physAddr, sizeOut);
|
||||
*physAddrOut = static_cast<u64>(physAddr);
|
||||
|
||||
return size;
|
||||
return result;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelVirtualQuery(const void* addr, int flags, OrbisVirtualQueryInfo* info,
|
||||
|
@ -211,9 +221,9 @@ s32 PS4_SYSV_ABI sceKernelAvailableFlexibleMemorySize(size_t* out_size) {
|
|||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
void PS4_SYSV_ABI _sceKernelRtldSetApplicationHeapAPI(void* func) {
|
||||
void PS4_SYSV_ABI _sceKernelRtldSetApplicationHeapAPI(void* func[]) {
|
||||
auto* linker = Common::Singleton<Core::Linker>::Instance();
|
||||
linker->SetHeapApiFunc(func);
|
||||
linker->SetHeapAPI(func);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, int* directMemoryTypeOut,
|
||||
|
@ -225,4 +235,77 @@ int PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, int* directMemoryTypeOut
|
|||
directMemoryEndOut);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelBatchMap(OrbisKernelBatchMapEntry* entries, int numEntries,
|
||||
int* numEntriesOut) {
|
||||
return sceKernelBatchMap2(entries, numEntries, numEntriesOut,
|
||||
MemoryFlags::SCE_KERNEL_MAP_FIXED); // 0x10, 0x410?
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceKernelMunmap(void* addr, size_t len);
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEntries,
|
||||
int* numEntriesOut, int flags) {
|
||||
int processed = 0;
|
||||
int result = 0;
|
||||
for (int i = 0; i < numEntries; i++) {
|
||||
if (entries == nullptr || entries[i].length == 0 || entries[i].operation > 4) {
|
||||
result = ORBIS_KERNEL_ERROR_EINVAL;
|
||||
break; // break and assign a value to numEntriesOut.
|
||||
}
|
||||
|
||||
if (entries[i].operation == MemoryOpTypes::ORBIS_KERNEL_MAP_OP_MAP_DIRECT) {
|
||||
result = sceKernelMapNamedDirectMemory(&entries[i].start, entries[i].length,
|
||||
entries[i].protection, flags,
|
||||
static_cast<s64>(entries[i].offset), 0, "");
|
||||
LOG_INFO(
|
||||
Kernel_Vmm,
|
||||
"BatchMap: entry = {}, operation = {}, len = {:#x}, offset = {:#x}, type = {}, "
|
||||
"result = {}",
|
||||
i, entries[i].operation, entries[i].length, entries[i].offset, (u8)entries[i].type,
|
||||
result);
|
||||
|
||||
if (result == 0)
|
||||
processed++;
|
||||
} else if (entries[i].operation == MemoryOpTypes::ORBIS_KERNEL_MAP_OP_UNMAP) {
|
||||
result = sceKernelMunmap(entries[i].start, entries[i].length);
|
||||
LOG_INFO(Kernel_Vmm, "BatchMap: entry = {}, operation = {}, len = {:#x}, result = {}",
|
||||
i, entries[i].operation, entries[i].length, result);
|
||||
|
||||
if (result == 0)
|
||||
processed++;
|
||||
} else if (entries[i].operation == MemoryOpTypes::ORBIS_KERNEL_MAP_OP_MAP_FLEXIBLE) {
|
||||
result = sceKernelMapNamedFlexibleMemory(&entries[i].start, entries[i].length,
|
||||
entries[i].protection, flags, "");
|
||||
LOG_INFO(Kernel_Vmm,
|
||||
"BatchMap: entry = {}, operation = {}, len = {:#x}, type = {}, "
|
||||
"result = {}",
|
||||
i, entries[i].operation, entries[i].length, (u8)entries[i].type, result);
|
||||
|
||||
if (result == 0)
|
||||
processed++;
|
||||
} else {
|
||||
UNREACHABLE_MSG("called: Unimplemented Operation = {}", entries[i].operation);
|
||||
}
|
||||
}
|
||||
if (numEntriesOut != NULL) { // can be zero. do not return an error code.
|
||||
*numEntriesOut = processed;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, size_t len, const char* name) {
|
||||
static constexpr size_t MaxNameSize = 32;
|
||||
if (std::strlen(name) > MaxNameSize) {
|
||||
LOG_ERROR(Kernel_Vmm, "name exceeds 32 bytes!");
|
||||
return ORBIS_KERNEL_ERROR_ENAMETOOLONG;
|
||||
}
|
||||
|
||||
if (name == nullptr) {
|
||||
LOG_ERROR(Kernel_Vmm, "name is invalid!");
|
||||
return ORBIS_KERNEL_ERROR_EFAULT;
|
||||
}
|
||||
auto* memory = Core::Memory::Instance();
|
||||
memory->NameVirtualRange(std::bit_cast<VAddr>(addr), len, name);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
} // namespace Libraries::Kernel
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include "common/bit_field.h"
|
||||
#include "common/types.h"
|
||||
|
||||
constexpr u64 SCE_KERNEL_MAIN_DMEM_SIZE = 5376_MB; // ~ 6GB
|
||||
constexpr u64 SCE_KERNEL_MAIN_DMEM_SIZE = 6_GB; // ~ 6GB
|
||||
|
||||
namespace Libraries::Kernel {
|
||||
|
||||
|
@ -31,6 +31,14 @@ enum MemoryProtection : u32 {
|
|||
SCE_KERNEL_PROT_GPU_RW = 0x30 // Permit reads/writes from the GPU
|
||||
};
|
||||
|
||||
enum MemoryOpTypes : u32 {
|
||||
ORBIS_KERNEL_MAP_OP_MAP_DIRECT = 0,
|
||||
ORBIS_KERNEL_MAP_OP_UNMAP = 1,
|
||||
ORBIS_KERNEL_MAP_OP_PROTECT = 2,
|
||||
ORBIS_KERNEL_MAP_OP_MAP_FLEXIBLE = 3,
|
||||
ORBIS_KERNEL_MAP_OP_TYPE_PROTECT = 4
|
||||
};
|
||||
|
||||
struct OrbisQueryInfo {
|
||||
uintptr_t start;
|
||||
uintptr_t end;
|
||||
|
@ -53,6 +61,16 @@ struct OrbisVirtualQueryInfo {
|
|||
std::array<char, 32> name;
|
||||
};
|
||||
|
||||
struct OrbisKernelBatchMapEntry {
|
||||
void* start;
|
||||
size_t offset;
|
||||
size_t length;
|
||||
char protection;
|
||||
char type;
|
||||
short reserved;
|
||||
int operation;
|
||||
};
|
||||
|
||||
u64 PS4_SYSV_ABI sceKernelGetDirectMemorySize();
|
||||
int PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u64 len,
|
||||
u64 alignment, int memoryType, s64* physAddrOut);
|
||||
|
@ -80,9 +98,16 @@ int PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void**
|
|||
int PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, int flags, OrbisQueryInfo* query_info,
|
||||
size_t infoSize);
|
||||
s32 PS4_SYSV_ABI sceKernelAvailableFlexibleMemorySize(size_t* sizeOut);
|
||||
void PS4_SYSV_ABI _sceKernelRtldSetApplicationHeapAPI(void* func);
|
||||
void PS4_SYSV_ABI _sceKernelRtldSetApplicationHeapAPI(void* func[]);
|
||||
int PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, int* directMemoryTypeOut,
|
||||
void** directMemoryStartOut,
|
||||
void** directMemoryEndOut);
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelBatchMap(OrbisKernelBatchMapEntry* entries, int numEntries,
|
||||
int* numEntriesOut);
|
||||
s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEntries,
|
||||
int* numEntriesOut, int flags);
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, size_t len, const char* name);
|
||||
|
||||
} // namespace Libraries::Kernel
|
||||
|
|
|
@ -394,6 +394,18 @@ int PS4_SYSV_ABI scePthreadSetaffinity(ScePthread thread, const /*SceKernelCpuma
|
|||
return result;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePthreadGetaffinity(ScePthread thread, /*SceKernelCpumask*/ u64* mask) {
|
||||
LOG_INFO(Kernel_Pthread, "called");
|
||||
|
||||
if (thread == nullptr) {
|
||||
return SCE_KERNEL_ERROR_ESRCH;
|
||||
}
|
||||
|
||||
auto result = scePthreadAttrGetaffinity(&thread->attr, mask);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ScePthreadMutex* createMutex(ScePthreadMutex* addr) {
|
||||
if (addr == nullptr || *addr != nullptr) {
|
||||
return addr;
|
||||
|
@ -409,13 +421,21 @@ ScePthreadMutex* createMutex(ScePthreadMutex* addr) {
|
|||
return addr;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePthreadMutexInit(ScePthreadMutex* mutex, const ScePthreadMutexattr* attr,
|
||||
int PS4_SYSV_ABI scePthreadMutexInit(ScePthreadMutex* mutex, const ScePthreadMutexattr* mutex_attr,
|
||||
const char* name) {
|
||||
const ScePthreadMutexattr* attr;
|
||||
|
||||
if (mutex == nullptr) {
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if (attr == nullptr) {
|
||||
if (mutex_attr == nullptr) {
|
||||
attr = g_pthread_cxt->getDefaultMutexattr();
|
||||
} else {
|
||||
if (*mutex_attr == nullptr) {
|
||||
attr = g_pthread_cxt->getDefaultMutexattr();
|
||||
} else {
|
||||
attr = mutex_attr;
|
||||
}
|
||||
}
|
||||
|
||||
*mutex = new PthreadMutexInternal{};
|
||||
|
@ -427,11 +447,7 @@ int PS4_SYSV_ABI scePthreadMutexInit(ScePthreadMutex* mutex, const ScePthreadMut
|
|||
|
||||
int result = pthread_mutex_init(&(*mutex)->pth_mutex, &(*attr)->pth_mutex_attr);
|
||||
|
||||
static auto mutex_loc = MUTEX_LOCATION("mutex");
|
||||
(*mutex)->tracy_lock = std::make_unique<tracy::LockableCtx>(&mutex_loc);
|
||||
|
||||
if (name != nullptr) {
|
||||
(*mutex)->tracy_lock->CustomName(name, std::strlen(name));
|
||||
LOG_INFO(Kernel_Pthread, "name={}, result={}", name, result);
|
||||
}
|
||||
|
||||
|
@ -457,7 +473,7 @@ int PS4_SYSV_ABI scePthreadMutexDestroy(ScePthreadMutex* mutex) {
|
|||
|
||||
int result = pthread_mutex_destroy(&(*mutex)->pth_mutex);
|
||||
|
||||
LOG_INFO(Kernel_Pthread, "name={}, result={}", (*mutex)->name, result);
|
||||
LOG_DEBUG(Kernel_Pthread, "name={}, result={}", (*mutex)->name, result);
|
||||
|
||||
delete *mutex;
|
||||
*mutex = nullptr;
|
||||
|
@ -543,15 +559,11 @@ int PS4_SYSV_ABI scePthreadMutexLock(ScePthreadMutex* mutex) {
|
|||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
(*mutex)->tracy_lock->BeforeLock();
|
||||
|
||||
int result = pthread_mutex_lock(&(*mutex)->pth_mutex);
|
||||
if (result != 0) {
|
||||
LOG_TRACE(Kernel_Pthread, "Locked name={}, result={}", (*mutex)->name, result);
|
||||
}
|
||||
|
||||
(*mutex)->tracy_lock->AfterLock();
|
||||
|
||||
switch (result) {
|
||||
case 0:
|
||||
return SCE_OK;
|
||||
|
@ -577,8 +589,6 @@ int PS4_SYSV_ABI scePthreadMutexUnlock(ScePthreadMutex* mutex) {
|
|||
LOG_TRACE(Kernel_Pthread, "Unlocking name={}, result={}", (*mutex)->name, result);
|
||||
}
|
||||
|
||||
(*mutex)->tracy_lock->AfterUnlock();
|
||||
|
||||
switch (result) {
|
||||
case 0:
|
||||
return SCE_OK;
|
||||
|
@ -723,7 +733,10 @@ int PS4_SYSV_ABI scePthreadCondDestroy(ScePthreadCond* cond) {
|
|||
}
|
||||
int result = pthread_cond_destroy(&(*cond)->cond);
|
||||
|
||||
LOG_INFO(Kernel_Pthread, "scePthreadCondDestroy, result={}", result);
|
||||
LOG_DEBUG(Kernel_Pthread, "scePthreadCondDestroy, result={}", result);
|
||||
|
||||
delete *cond;
|
||||
*cond = nullptr;
|
||||
|
||||
switch (result) {
|
||||
case 0:
|
||||
|
@ -785,14 +798,27 @@ int PS4_SYSV_ABI posix_pthread_mutex_destroy(ScePthreadMutex* mutex) {
|
|||
int PS4_SYSV_ABI posix_pthread_cond_wait(ScePthreadCond* cond, ScePthreadMutex* mutex) {
|
||||
int result = scePthreadCondWait(cond, mutex);
|
||||
if (result < 0) {
|
||||
UNREACHABLE();
|
||||
int rt = result > SCE_KERNEL_ERROR_UNKNOWN && result <= SCE_KERNEL_ERROR_ESTOP
|
||||
? result + -SCE_KERNEL_ERROR_UNKNOWN
|
||||
: POSIX_EOTHER;
|
||||
return rt;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_cond_timedwait(ScePthreadCond* cond, ScePthreadMutex* mutex,
|
||||
u64 usec) {
|
||||
int result = scePthreadCondTimedwait(cond, mutex, usec);
|
||||
if (result < 0) {
|
||||
int rt = result > SCE_KERNEL_ERROR_UNKNOWN && result <= SCE_KERNEL_ERROR_ESTOP
|
||||
? result + -SCE_KERNEL_ERROR_UNKNOWN
|
||||
: POSIX_EOTHER;
|
||||
return rt;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_cond_broadcast(ScePthreadCond* cond) {
|
||||
LOG_INFO(Kernel_Pthread,
|
||||
"posix posix_pthread_cond_broadcast redirect to scePthreadCondBroadcast");
|
||||
int result = scePthreadCondBroadcast(cond);
|
||||
if (result != 0) {
|
||||
int rt = result > SCE_KERNEL_ERROR_UNKNOWN && result <= SCE_KERNEL_ERROR_ESTOP
|
||||
|
@ -804,7 +830,6 @@ int PS4_SYSV_ABI posix_pthread_cond_broadcast(ScePthreadCond* cond) {
|
|||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_mutexattr_init(ScePthreadMutexattr* attr) {
|
||||
// LOG_INFO(Kernel_Pthread, "posix pthread_mutexattr_init redirect to scePthreadMutexattrInit");
|
||||
int result = scePthreadMutexattrInit(attr);
|
||||
if (result < 0) {
|
||||
int rt = result > SCE_KERNEL_ERROR_UNKNOWN && result <= SCE_KERNEL_ERROR_ESTOP
|
||||
|
@ -816,7 +841,6 @@ int PS4_SYSV_ABI posix_pthread_mutexattr_init(ScePthreadMutexattr* attr) {
|
|||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_mutexattr_settype(ScePthreadMutexattr* attr, int type) {
|
||||
// LOG_INFO(Kernel_Pthread, "posix pthread_mutex_init redirect to scePthreadMutexInit");
|
||||
int result = scePthreadMutexattrSettype(attr, type);
|
||||
if (result < 0) {
|
||||
int rt = result > SCE_KERNEL_ERROR_UNKNOWN && result <= SCE_KERNEL_ERROR_ESTOP
|
||||
|
@ -841,7 +865,6 @@ int PS4_SYSV_ABI posix_pthread_once(pthread_once_t* once_control, void (*init_ro
|
|||
|
||||
int PS4_SYSV_ABI posix_pthread_mutexattr_setprotocol(ScePthreadMutexattr* attr, int protocol) {
|
||||
int result = scePthreadMutexattrSetprotocol(attr, protocol);
|
||||
LOG_INFO(Kernel_Pthread, "redirect to scePthreadMutexattrSetprotocol: result = {}", result);
|
||||
if (result < 0) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
@ -1071,6 +1094,19 @@ int PS4_SYSV_ABI scePthreadAttrGetstack(ScePthreadAttr* attr, void** addr, size_
|
|||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePthreadAttrSetstack(ScePthreadAttr* attr, void* addr, size_t size) {
|
||||
if (attr == nullptr || *attr == nullptr || addr == nullptr || size < 0x4000) {
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
int result = pthread_attr_setstack(&(*attr)->pth_attr, addr, size);
|
||||
LOG_INFO(Kernel_Pthread, "scePthreadAttrSetstack: result = {}", result);
|
||||
|
||||
if (result == 0) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePthreadJoin(ScePthread thread, void** res) {
|
||||
int result = pthread_join(thread->pth, res);
|
||||
LOG_INFO(Kernel_Pthread, "scePthreadJoin result = {}", result);
|
||||
|
@ -1125,7 +1161,7 @@ int PS4_SYSV_ABI scePthreadCondWait(ScePthreadCond* cond, ScePthreadMutex* mutex
|
|||
}
|
||||
int result = pthread_cond_wait(&(*cond)->cond, &(*mutex)->pth_mutex);
|
||||
|
||||
LOG_INFO(Kernel_Pthread, "scePthreadCondWait, result={}", result);
|
||||
LOG_DEBUG(Kernel_Pthread, "scePthreadCondWait, result={}", result);
|
||||
|
||||
switch (result) {
|
||||
case 0:
|
||||
|
@ -1145,7 +1181,7 @@ int PS4_SYSV_ABI scePthreadCondattrDestroy(ScePthreadCondattr* attr) {
|
|||
}
|
||||
int result = pthread_condattr_destroy(&(*attr)->cond_attr);
|
||||
|
||||
LOG_INFO(Kernel_Pthread, "scePthreadCondattrDestroy: result = {} ", result);
|
||||
LOG_DEBUG(Kernel_Pthread, "scePthreadCondattrDestroy: result = {} ", result);
|
||||
|
||||
switch (result) {
|
||||
case 0:
|
||||
|
@ -1168,8 +1204,6 @@ int PS4_SYSV_ABI scePthreadMutexTrylock(ScePthreadMutex* mutex) {
|
|||
LOG_TRACE(Kernel_Pthread, "name={}, result={}", (*mutex)->name, result);
|
||||
}
|
||||
|
||||
(*mutex)->tracy_lock->AfterTryLock(result == 0);
|
||||
|
||||
switch (result) {
|
||||
case 0:
|
||||
return ORBIS_OK;
|
||||
|
@ -1228,6 +1262,40 @@ int PS4_SYSV_ABI posix_pthread_attr_destroy(ScePthreadAttr* attr) {
|
|||
return result;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_attr_setschedparam(ScePthreadAttr* attr,
|
||||
const SceKernelSchedParam* param) {
|
||||
int result = scePthreadAttrSetschedparam(attr, param);
|
||||
if (result < 0) {
|
||||
int rt = result > SCE_KERNEL_ERROR_UNKNOWN && result <= SCE_KERNEL_ERROR_ESTOP
|
||||
? result + -SCE_KERNEL_ERROR_UNKNOWN
|
||||
: POSIX_EOTHER;
|
||||
return rt;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_attr_setinheritsched(ScePthreadAttr* attr, int inheritSched) {
|
||||
int result = scePthreadAttrSetinheritsched(attr, inheritSched);
|
||||
if (result < 0) {
|
||||
int rt = result > SCE_KERNEL_ERROR_UNKNOWN && result <= SCE_KERNEL_ERROR_ESTOP
|
||||
? result + -SCE_KERNEL_ERROR_UNKNOWN
|
||||
: POSIX_EOTHER;
|
||||
return rt;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_setprio(ScePthread thread, int prio) {
|
||||
int result = scePthreadSetprio(thread, prio);
|
||||
if (result < 0) {
|
||||
int rt = result > SCE_KERNEL_ERROR_UNKNOWN && result <= SCE_KERNEL_ERROR_ESTOP
|
||||
? result + -SCE_KERNEL_ERROR_UNKNOWN
|
||||
: POSIX_EOTHER;
|
||||
return rt;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_attr_setdetachstate(ScePthreadAttr* attr, int detachstate) {
|
||||
// LOG_INFO(Kernel_Pthread, "posix pthread_mutexattr_init redirect to scePthreadMutexattrInit");
|
||||
int result = scePthreadAttrSetdetachstate(attr, detachstate);
|
||||
|
@ -1243,8 +1311,6 @@ int PS4_SYSV_ABI posix_pthread_attr_setdetachstate(ScePthreadAttr* attr, int det
|
|||
int PS4_SYSV_ABI posix_pthread_create_name_np(ScePthread* thread, const ScePthreadAttr* attr,
|
||||
PthreadEntryFunc start_routine, void* arg,
|
||||
const char* name) {
|
||||
LOG_INFO(Kernel_Pthread, "posix pthread_create redirect to scePthreadCreate: name = {}", name);
|
||||
|
||||
int result = scePthreadCreate(thread, attr, start_routine, arg, name);
|
||||
if (result != 0) {
|
||||
int rt = result > SCE_KERNEL_ERROR_UNKNOWN && result <= SCE_KERNEL_ERROR_ESTOP
|
||||
|
@ -1291,17 +1357,11 @@ int PS4_SYSV_ABI posix_pthread_cond_init(ScePthreadCond* cond, const ScePthreadC
|
|||
|
||||
int PS4_SYSV_ABI posix_pthread_cond_signal(ScePthreadCond* cond) {
|
||||
int result = scePthreadCondSignal(cond);
|
||||
LOG_INFO(Kernel_Pthread,
|
||||
"posix posix_pthread_cond_signal redirect to scePthreadCondSignal, result = {}",
|
||||
result);
|
||||
return result;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_cond_destroy(ScePthreadCond* cond) {
|
||||
int result = scePthreadCondDestroy(cond);
|
||||
LOG_INFO(Kernel_Pthread,
|
||||
"posix posix_pthread_cond_destroy redirect to scePthreadCondDestroy, result = {}",
|
||||
result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -1321,14 +1381,60 @@ int PS4_SYSV_ABI posix_sem_wait(sem_t* sem) {
|
|||
return sem_wait(sem);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_sem_trywait(sem_t* sem) {
|
||||
return sem_trywait(sem);
|
||||
}
|
||||
|
||||
#ifndef HAVE_SEM_TIMEDWAIT
|
||||
int sem_timedwait(sem_t* sem, const struct timespec* abstime) {
|
||||
int rc;
|
||||
while ((rc = sem_trywait(sem)) == EAGAIN) {
|
||||
struct timespec curr_time;
|
||||
clock_gettime(CLOCK_REALTIME, &curr_time);
|
||||
|
||||
s64 remaining_ns = 0;
|
||||
remaining_ns +=
|
||||
(static_cast<s64>(abstime->tv_sec) - static_cast<s64>(curr_time.tv_sec)) * 1000000000L;
|
||||
remaining_ns += static_cast<s64>(abstime->tv_nsec) - static_cast<s64>(curr_time.tv_nsec);
|
||||
|
||||
if (remaining_ns <= 0) {
|
||||
return ETIMEDOUT;
|
||||
}
|
||||
|
||||
struct timespec sleep_time;
|
||||
sleep_time.tv_sec = 0;
|
||||
if (remaining_ns < 5000000L) {
|
||||
sleep_time.tv_nsec = remaining_ns;
|
||||
} else {
|
||||
sleep_time.tv_nsec = 5000000;
|
||||
}
|
||||
|
||||
nanosleep(&sleep_time, nullptr);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
#endif
|
||||
|
||||
int PS4_SYSV_ABI posix_sem_timedwait(sem_t* sem, const timespec* t) {
|
||||
return sem_timedwait(sem, t);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_sem_post(sem_t* sem) {
|
||||
return sem_post(sem);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_sem_destroy(sem_t* sem) {
|
||||
return sem_destroy(sem);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_sem_getvalue(sem_t* sem, int* sval) {
|
||||
return sem_getvalue(sem, sval);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_attr_getstacksize(const pthread_attr_t* attr, size_t* size) {
|
||||
return pthread_attr_getstacksize(attr, size);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePthreadGetschedparam(ScePthread thread, int* policy,
|
||||
SceKernelSchedParam* param) {
|
||||
return pthread_getschedparam(thread->pth, policy, param);
|
||||
|
@ -1350,6 +1456,11 @@ int PS4_SYSV_ABI scePthreadOnce(int* once_control, void (*init_routine)(void)) {
|
|||
UNREACHABLE();
|
||||
}
|
||||
|
||||
[[noreturn]] void PS4_SYSV_ABI posix_pthread_exit(void* value_ptr) {
|
||||
pthread_exit(value_ptr);
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePthreadGetthreadid() {
|
||||
return (int)(size_t)g_pthread_self;
|
||||
}
|
||||
|
@ -1383,6 +1494,26 @@ int PS4_SYSV_ABI posix_pthread_condattr_setclock(ScePthreadCondattr* attr, clock
|
|||
return SCE_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_getschedparam(ScePthread thread, int* policy,
|
||||
SceKernelSchedParam* param) {
|
||||
return scePthreadGetschedparam(thread, policy, param);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_setschedparam(ScePthread thread, int policy,
|
||||
const SceKernelSchedParam* param) {
|
||||
return scePthreadSetschedparam(thread, policy, param);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI posix_pthread_attr_getschedpolicy(const ScePthreadAttr* attr, int* policy) {
|
||||
return scePthreadAttrGetschedpolicy(attr, policy);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePthreadRename(ScePthread thread, const char* name) {
|
||||
thread->name = name;
|
||||
LOG_INFO(Kernel_Pthread, "scePthreadRename: name = {}", thread->name);
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("lZzFeSxPl08", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_setcancelstate);
|
||||
LIB_FUNCTION("0TyVk4MSLt0", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_cond_init);
|
||||
|
@ -1393,6 +1524,7 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
|
|||
LIB_FUNCTION("WrOLvHU0yQM", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_setspecific);
|
||||
LIB_FUNCTION("4+h9EzwKF4I", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrSetschedpolicy);
|
||||
LIB_FUNCTION("-Wreprtu0Qs", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrSetdetachstate);
|
||||
LIB_FUNCTION("JaRMy+QcpeU", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrGetdetachstate);
|
||||
LIB_FUNCTION("eXbUSpEaTsA", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrSetinheritsched);
|
||||
LIB_FUNCTION("DzES9hQF4f4", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrSetschedparam);
|
||||
LIB_FUNCTION("nsYoNRywwNg", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrInit);
|
||||
|
@ -1401,11 +1533,15 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
|
|||
LIB_FUNCTION("4qGrR6eoP9Y", "libkernel", 1, "libkernel", 1, 1, scePthreadDetach);
|
||||
LIB_FUNCTION("3PtV6p3QNX4", "libkernel", 1, "libkernel", 1, 1, scePthreadEqual);
|
||||
LIB_FUNCTION("3kg7rT0NQIs", "libkernel", 1, "libkernel", 1, 1, scePthreadExit);
|
||||
LIB_FUNCTION("FJrT5LuUBAU", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_exit);
|
||||
LIB_FUNCTION("7Xl257M4VNI", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_equal);
|
||||
LIB_FUNCTION("h9CcP3J0oVM", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_join);
|
||||
LIB_FUNCTION("EI-5-jlq2dE", "libkernel", 1, "libkernel", 1, 1, scePthreadGetthreadid);
|
||||
LIB_FUNCTION("1tKyG7RlMJo", "libkernel", 1, "libkernel", 1, 1, scePthreadGetprio);
|
||||
LIB_FUNCTION("W0Hpm2X0uPE", "libkernel", 1, "libkernel", 1, 1, scePthreadSetprio);
|
||||
LIB_FUNCTION("GBUY7ywdULE", "libkernel", 1, "libkernel", 1, 1, scePthreadRename);
|
||||
LIB_FUNCTION("F+yfmduIBB8", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrSetstackaddr);
|
||||
LIB_FUNCTION("El+cQ20DynU", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrSetguardsize);
|
||||
|
||||
LIB_FUNCTION("aI+OeCz8xrQ", "libkernel", 1, "libkernel", 1, 1, scePthreadSelf);
|
||||
LIB_FUNCTION("EotR8a3ASf4", "libkernel", 1, "libkernel", 1, 1, posix_pthread_self);
|
||||
|
@ -1421,11 +1557,13 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
|
|||
LIB_FUNCTION("OxhIB8LB-PQ", "libkernel", 1, "libkernel", 1, 1, posix_pthread_create);
|
||||
LIB_FUNCTION("OxhIB8LB-PQ", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_create);
|
||||
LIB_FUNCTION("bt3CTBKmGyI", "libkernel", 1, "libkernel", 1, 1, scePthreadSetaffinity);
|
||||
LIB_FUNCTION("rcrVFJsQWRY", "libkernel", 1, "libkernel", 1, 1, scePthreadGetaffinity);
|
||||
LIB_FUNCTION("6UgtwV+0zb4", "libkernel", 1, "libkernel", 1, 1, scePthreadCreate);
|
||||
LIB_FUNCTION("T72hz6ffq08", "libkernel", 1, "libkernel", 1, 1, scePthreadYield);
|
||||
LIB_FUNCTION("B5GmVDKwpn0", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_yield);
|
||||
|
||||
LIB_FUNCTION("-quPa4SEJUw", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrGetstack);
|
||||
LIB_FUNCTION("Bvn74vj6oLo", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrSetstack);
|
||||
LIB_FUNCTION("Ru36fiTtJzA", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrGetstackaddr);
|
||||
LIB_FUNCTION("-fA+7ZlGDQs", "libkernel", 1, "libkernel", 1, 1, scePthreadAttrGetstacksize);
|
||||
LIB_FUNCTION("14bOACANTBo", "libkernel", 1, "libkernel", 1, 1, scePthreadOnce);
|
||||
|
@ -1462,6 +1600,7 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
|
|||
LIB_FUNCTION("ltCfaGr2JGE", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_destroy);
|
||||
LIB_FUNCTION("Op8TBGY5KHg", "libkernel", 1, "libkernel", 1, 1, posix_pthread_cond_wait);
|
||||
LIB_FUNCTION("Op8TBGY5KHg", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_cond_wait);
|
||||
LIB_FUNCTION("27bAgiJmOh0", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_cond_timedwait);
|
||||
LIB_FUNCTION("mkx2fVhNMsg", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_cond_broadcast);
|
||||
LIB_FUNCTION("dQHWEsJtoE4", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutexattr_init);
|
||||
LIB_FUNCTION("mDmgMOGVUqg", "libScePosix", 1, "libkernel", 1, 1,
|
||||
|
@ -1476,6 +1615,8 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
|
|||
LIB_FUNCTION("EjllaAqAPZo", "libScePosix", 1, "libkernel", 1, 1,
|
||||
posix_pthread_condattr_setclock);
|
||||
LIB_FUNCTION("Z4QosVuAsA0", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_once);
|
||||
LIB_FUNCTION("RtLRV-pBTTY", "libScePosix", 1, "libkernel", 1, 1,
|
||||
posix_pthread_attr_getschedpolicy);
|
||||
|
||||
// openorbis weird functions
|
||||
LIB_FUNCTION("7H0iTOciTLo", "libkernel", 1, "libkernel", 1, 1, posix_pthread_mutex_lock);
|
||||
|
@ -1485,15 +1626,27 @@ void pthreadSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
|
|||
LIB_FUNCTION("E+tyo3lp5Lw", "libScePosix", 1, "libkernel", 1, 1,
|
||||
posix_pthread_attr_setdetachstate);
|
||||
LIB_FUNCTION("zHchY8ft5pk", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_attr_destroy);
|
||||
LIB_FUNCTION("euKRgm0Vn2M", "libScePosix", 1, "libkernel", 1, 1,
|
||||
posix_pthread_attr_setschedparam);
|
||||
LIB_FUNCTION("7ZlAakEf0Qg", "libScePosix", 1, "libkernel", 1, 1,
|
||||
posix_pthread_attr_setinheritsched);
|
||||
LIB_FUNCTION("a2P9wYGeZvc", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_setprio);
|
||||
LIB_FUNCTION("Jmi+9w9u0E4", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_create_name_np);
|
||||
LIB_FUNCTION("OxhIB8LB-PQ", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_create);
|
||||
LIB_FUNCTION("+U1R4WtXvoc", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_detach);
|
||||
LIB_FUNCTION("CBNtXOoef-E", "libScePosix", 1, "libkernel", 1, 1, posix_sched_get_priority_max);
|
||||
LIB_FUNCTION("m0iS6jNsXds", "libScePosix", 1, "libkernel", 1, 1, posix_sched_get_priority_min);
|
||||
LIB_FUNCTION("FIs3-UQT9sg", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_getschedparam);
|
||||
LIB_FUNCTION("Xs9hdiD7sAA", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_setschedparam);
|
||||
LIB_FUNCTION("pDuPEf3m4fI", "libScePosix", 1, "libkernel", 1, 1, posix_sem_init);
|
||||
LIB_FUNCTION("YCV5dGGBcCo", "libScePosix", 1, "libkernel", 1, 1, posix_sem_wait);
|
||||
LIB_FUNCTION("WBWzsRifCEA", "libScePosix", 1, "libkernel", 1, 1, posix_sem_trywait);
|
||||
LIB_FUNCTION("w5IHyvahg-o", "libScePosix", 1, "libkernel", 1, 1, posix_sem_timedwait);
|
||||
LIB_FUNCTION("IKP8typ0QUk", "libScePosix", 1, "libkernel", 1, 1, posix_sem_post);
|
||||
LIB_FUNCTION("cDW233RAwWo", "libScePosix", 1, "libkernel", 1, 1, posix_sem_destroy);
|
||||
LIB_FUNCTION("Bq+LRV-N6Hk", "libScePosix", 1, "libkernel", 1, 1, posix_sem_getvalue);
|
||||
LIB_FUNCTION("0qOtCR-ZHck", "libScePosix", 1, "libkernel", 1, 1,
|
||||
posix_pthread_attr_getstacksize);
|
||||
// libs
|
||||
RwlockSymbolsRegister(sym);
|
||||
SemaphoreSymbolsRegister(sym);
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#include <vector>
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#include "common/debug.h"
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
|
@ -74,7 +73,6 @@ struct PthreadMutexInternal {
|
|||
u8 reserved[256];
|
||||
std::string name;
|
||||
pthread_mutex_t pth_mutex;
|
||||
std::unique_ptr<tracy::LockableCtx> tracy_lock;
|
||||
};
|
||||
|
||||
struct PthreadMutexattrInternal {
|
||||
|
@ -169,9 +167,12 @@ ScePthread PS4_SYSV_ABI scePthreadSelf();
|
|||
int PS4_SYSV_ABI scePthreadAttrSetaffinity(ScePthreadAttr* pattr,
|
||||
const /*SceKernelCpumask*/ u64 mask);
|
||||
int PS4_SYSV_ABI scePthreadSetaffinity(ScePthread thread, const /*SceKernelCpumask*/ u64 mask);
|
||||
int PS4_SYSV_ABI scePthreadGetaffinity(ScePthread thread, /*SceKernelCpumask*/ u64* mask);
|
||||
int PS4_SYSV_ABI scePthreadCreate(ScePthread* thread, const ScePthreadAttr* attr,
|
||||
PthreadEntryFunc start_routine, void* arg, const char* name);
|
||||
|
||||
int PS4_SYSV_ABI scePthreadSetprio(ScePthread thread, int prio);
|
||||
|
||||
/***
|
||||
* Mutex calls
|
||||
*/
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/kernel/thread_management.h"
|
||||
#include "core/libraries/libs.h"
|
||||
|
||||
namespace Libraries::Kernel {
|
||||
|
@ -41,7 +40,6 @@ public:
|
|||
AddWaiter(waiter);
|
||||
|
||||
// Perform the wait.
|
||||
std::exchange(lk, std::unique_lock{waiter.mutex});
|
||||
return waiter.Wait(lk, timeout);
|
||||
}
|
||||
|
||||
|
@ -59,10 +57,9 @@ public:
|
|||
it++;
|
||||
continue;
|
||||
}
|
||||
std::scoped_lock lk2{waiter.mutex};
|
||||
it = wait_list.erase(it);
|
||||
token_count -= waiter.need_count;
|
||||
waiter.cv.notify_one();
|
||||
it = wait_list.erase(it);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -84,8 +81,6 @@ public:
|
|||
|
||||
public:
|
||||
struct WaitingThread : public ListBaseHook {
|
||||
std::mutex mutex;
|
||||
std::string name;
|
||||
std::condition_variable cv;
|
||||
u32 priority;
|
||||
s32 need_count;
|
||||
|
@ -93,7 +88,6 @@ public:
|
|||
bool was_cancled{};
|
||||
|
||||
explicit WaitingThread(s32 need_count, bool is_fifo) : need_count{need_count} {
|
||||
name = scePthreadSelf()->name;
|
||||
if (is_fifo) {
|
||||
return;
|
||||
}
|
||||
|
@ -177,10 +171,16 @@ s32 PS4_SYSV_ABI sceKernelCreateSema(OrbisKernelSema* sem, const char* pName, u3
|
|||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelWaitSema(OrbisKernelSema sem, s32 needCount, u32* pTimeout) {
|
||||
if (!sem) {
|
||||
return ORBIS_KERNEL_ERROR_ESRCH;
|
||||
}
|
||||
return sem->Wait(true, needCount, pTimeout);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelSignalSema(OrbisKernelSema sem, s32 signalCount) {
|
||||
if (!sem) {
|
||||
return ORBIS_KERNEL_ERROR_ESRCH;
|
||||
}
|
||||
if (!sem->Signal(signalCount)) {
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
@ -188,10 +188,16 @@ s32 PS4_SYSV_ABI sceKernelSignalSema(OrbisKernelSema sem, s32 signalCount) {
|
|||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelPollSema(OrbisKernelSema sem, s32 needCount) {
|
||||
if (!sem) {
|
||||
return ORBIS_KERNEL_ERROR_ESRCH;
|
||||
}
|
||||
return sem->Wait(false, needCount, nullptr);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceKernelCancelSema(OrbisKernelSema sem, s32 setCount, s32* pNumWaitThreads) {
|
||||
if (!sem) {
|
||||
return ORBIS_KERNEL_ERROR_ESRCH;
|
||||
}
|
||||
return sem->Cancel(setCount, pNumWaitThreads);
|
||||
}
|
||||
|
||||
|
|
|
@ -214,6 +214,22 @@ int PS4_SYSV_ABI posix_clock_getres(u32 clock_id, OrbisKernelTimespec* res) {
|
|||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceKernelConvertLocaltimeToUtc(time_t param_1, int64_t param_2, time_t* seconds,
|
||||
OrbisKernelTimezone* timezone, int* dst_seconds) {
|
||||
LOG_INFO(Kernel, "called");
|
||||
if (timezone) {
|
||||
sceKernelGettimezone(timezone);
|
||||
param_1 -= (timezone->tz_minuteswest + timezone->tz_dsttime) * 60;
|
||||
if (seconds)
|
||||
*seconds = param_1;
|
||||
if (dst_seconds)
|
||||
*dst_seconds = timezone->tz_dsttime * 60;
|
||||
} else {
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
void timeSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
|
||||
clock = std::make_unique<Common::NativeClock>();
|
||||
initial_ptc = clock->GetUptime();
|
||||
|
@ -239,6 +255,7 @@ void timeSymbolsRegister(Core::Loader::SymbolsResolver* sym) {
|
|||
LIB_FUNCTION("lLMT9vJAck0", "libkernel", 1, "libkernel", 1, 1, posix_clock_gettime);
|
||||
LIB_FUNCTION("lLMT9vJAck0", "libScePosix", 1, "libkernel", 1, 1, posix_clock_gettime);
|
||||
LIB_FUNCTION("smIj7eqzZE8", "libScePosix", 1, "libkernel", 1, 1, posix_clock_getres);
|
||||
LIB_FUNCTION("0NTHN1NKONI", "libkernel", 1, "libkernel", 1, 1, sceKernelConvertLocaltimeToUtc);
|
||||
}
|
||||
|
||||
} // namespace Libraries::Kernel
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "core/libraries/np_trophy/np_trophy.h"
|
||||
#include "core/libraries/pad/pad.h"
|
||||
#include "core/libraries/playgo/playgo.h"
|
||||
#include "core/libraries/random/random.h"
|
||||
#include "core/libraries/rtc/rtc.h"
|
||||
#include "core/libraries/save_data/savedata.h"
|
||||
#include "core/libraries/screenshot/screenshot.h"
|
||||
|
@ -43,8 +44,8 @@ namespace Libraries {
|
|||
void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
|
||||
LOG_INFO(Lib_Kernel, "Initializing HLE libraries");
|
||||
Libraries::Kernel::LibKernel_Register(sym);
|
||||
Libraries::VideoOut::RegisterLib(sym);
|
||||
Libraries::GnmDriver::RegisterlibSceGnmDriver(sym);
|
||||
Libraries::VideoOut::RegisterLib(sym);
|
||||
if (!Config::isLleLibc()) {
|
||||
Libraries::LibC::libcSymbolsRegister(sym);
|
||||
}
|
||||
|
@ -71,6 +72,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
|
|||
Libraries::AppContent::RegisterlibSceAppContent(sym);
|
||||
Libraries::PngDec::RegisterlibScePngDec(sym);
|
||||
Libraries::PlayGo::RegisterlibScePlayGo(sym);
|
||||
Libraries::Random::RegisterlibSceRandom(sym);
|
||||
Libraries::Usbd::RegisterlibSceUsbd(sym);
|
||||
Libraries::Pad::RegisterlibScePad(sym);
|
||||
Libraries::Ajm::RegisterlibSceAjm(sym);
|
||||
|
|
|
@ -559,7 +559,7 @@ int PS4_SYSV_ABI sceNetEpollDestroy() {
|
|||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetEpollWait() {
|
||||
LOG_ERROR(Lib_Net, "(STUBBED) called");
|
||||
LOG_TRACE(Lib_Net, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ int PS4_SYSV_ABI sceNetCtlUnregisterCallbackV6() {
|
|||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNetCtlCheckCallback() {
|
||||
LOG_ERROR(Lib_NetCtl, "(STUBBED) called");
|
||||
LOG_TRACE(Lib_NetCtl, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Generated By moduleGenerator
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.h"
|
||||
|
@ -870,7 +871,7 @@ int PS4_SYSV_ABI sceNpAsmTerminate() {
|
|||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpCheckCallback() {
|
||||
LOG_ERROR(Lib_NpManager, "(STUBBED) called");
|
||||
LOG_TRACE(Lib_NpManager, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
@ -974,8 +975,11 @@ int PS4_SYSV_ABI sceNpGetGamePresenceStatusA() {
|
|||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpGetNpId() {
|
||||
LOG_ERROR(Lib_NpManager, "(STUBBED) called");
|
||||
int PS4_SYSV_ABI sceNpGetNpId(OrbisUserServiceUserId userId, OrbisNpId* npId) {
|
||||
LOG_ERROR(Lib_NpManager, "(DUMMY) called");
|
||||
|
||||
std::string name = Config::getUserName();
|
||||
strcpy(npId->handle.data, name.c_str());
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
@ -3510,4 +3514,4 @@ void RegisterlibSceNpManager(Core::Loader::SymbolsResolver* sym) {
|
|||
sceNpUnregisterStateCallbackForToolkit);
|
||||
};
|
||||
|
||||
} // namespace Libraries::NpManager
|
||||
} // namespace Libraries::NpManager
|
||||
|
|
|
@ -11,6 +11,22 @@ class SymbolsResolver;
|
|||
|
||||
namespace Libraries::NpManager {
|
||||
|
||||
constexpr int ORBIS_NP_ONLINEID_MAX_LENGTH = 16;
|
||||
|
||||
typedef int OrbisUserServiceUserId;
|
||||
|
||||
struct OrbisNpOnlineId {
|
||||
char data[ORBIS_NP_ONLINEID_MAX_LENGTH];
|
||||
char term;
|
||||
char dummy[3];
|
||||
};
|
||||
|
||||
struct OrbisNpId {
|
||||
OrbisNpOnlineId handle;
|
||||
u8 opt[8];
|
||||
u8 reserved[8];
|
||||
};
|
||||
|
||||
int PS4_SYSV_ABI Func_EF4378573542A508();
|
||||
int PS4_SYSV_ABI _sceNpIpcCreateMemoryFromKernel();
|
||||
int PS4_SYSV_ABI _sceNpIpcCreateMemoryFromPool();
|
||||
|
@ -204,7 +220,7 @@ int PS4_SYSV_ABI sceNpGetAccountLanguage2();
|
|||
int PS4_SYSV_ABI sceNpGetAccountLanguageA();
|
||||
int PS4_SYSV_ABI sceNpGetGamePresenceStatus();
|
||||
int PS4_SYSV_ABI sceNpGetGamePresenceStatusA();
|
||||
int PS4_SYSV_ABI sceNpGetNpId();
|
||||
int PS4_SYSV_ABI sceNpGetNpId(OrbisUserServiceUserId userId, OrbisNpId* npId);
|
||||
int PS4_SYSV_ABI sceNpGetNpReachabilityState();
|
||||
int PS4_SYSV_ABI sceNpGetOnlineId();
|
||||
int PS4_SYSV_ABI sceNpGetParentalControlInfo();
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Generated By moduleGenerator
|
||||
#include <common/assert.h>
|
||||
#include <common/singleton.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
|
@ -85,7 +86,18 @@ int PS4_SYSV_ABI scePadGetCapability() {
|
|||
|
||||
int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerInformation* pInfo) {
|
||||
LOG_INFO(Lib_Pad, "called handle = {}", handle);
|
||||
std::memset(pInfo, 0, sizeof(OrbisPadControllerInformation));
|
||||
if (handle < 0) {
|
||||
pInfo->touchPadInfo.pixelDensity = 1;
|
||||
pInfo->touchPadInfo.resolution.x = 1920;
|
||||
pInfo->touchPadInfo.resolution.y = 950;
|
||||
pInfo->stickInfo.deadZoneLeft = 2;
|
||||
pInfo->stickInfo.deadZoneRight = 2;
|
||||
pInfo->connectionType = ORBIS_PAD_PORT_TYPE_STANDARD;
|
||||
pInfo->connectedCount = 1;
|
||||
pInfo->connected = false;
|
||||
pInfo->deviceClass = ORBIS_PAD_DEVICE_CLASS_STANDARD;
|
||||
return SCE_OK;
|
||||
}
|
||||
pInfo->touchPadInfo.pixelDensity = 1;
|
||||
pInfo->touchPadInfo.resolution.x = 1920;
|
||||
pInfo->touchPadInfo.resolution.y = 950;
|
||||
|
@ -239,7 +251,6 @@ int PS4_SYSV_ABI scePadOutputReport() {
|
|||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) {
|
||||
std::memset(pData, 0, sizeof(OrbisPadData));
|
||||
int connected_count = 0;
|
||||
bool connected = false;
|
||||
Input::State states[64];
|
||||
|
@ -306,11 +317,9 @@ int PS4_SYSV_ABI scePadReadHistory() {
|
|||
|
||||
int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) {
|
||||
auto* controller = Common::Singleton<Input::GameController>::Instance();
|
||||
|
||||
int connectedCount = 0;
|
||||
bool isConnected = false;
|
||||
Input::State state;
|
||||
std::memset(pData, 0, sizeof(OrbisPadData));
|
||||
controller->ReadState(&state, &isConnected, &connectedCount);
|
||||
pData->buttons = state.buttonsState;
|
||||
pData->leftStick.x = state.axes[static_cast<int>(Input::Axis::LeftX)];
|
||||
|
@ -322,7 +331,7 @@ int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) {
|
|||
pData->orientation.x = 0;
|
||||
pData->orientation.y = 0;
|
||||
pData->orientation.z = 0;
|
||||
pData->orientation.w = 0;
|
||||
pData->orientation.w = 1;
|
||||
pData->acceleration.x = 0.0f;
|
||||
pData->acceleration.y = 0.0f;
|
||||
pData->acceleration.z = 0.0f;
|
||||
|
@ -410,8 +419,14 @@ int PS4_SYSV_ABI scePadSetForceIntercepted() {
|
|||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pParam) {
|
||||
LOG_ERROR(Lib_Pad, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
if (pParam != nullptr) {
|
||||
LOG_INFO(Lib_Pad, "scePadSetLightBar called handle = {} rgb = {} {} {}", handle, pParam->r,
|
||||
pParam->g, pParam->b);
|
||||
auto* controller = Common::Singleton<Input::GameController>::Instance();
|
||||
controller->SetLightBarRGB(pParam->r, pParam->g, pParam->b);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
return ORBIS_PAD_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadSetLightBarBaseBrightness() {
|
||||
|
@ -470,8 +485,14 @@ int PS4_SYSV_ABI scePadSetUserColor() {
|
|||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadSetVibration(s32 handle, const OrbisPadVibrationParam* pParam) {
|
||||
LOG_ERROR(Lib_Pad, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
if (pParam != nullptr) {
|
||||
LOG_INFO(Lib_Pad, "scePadSetVibration called handle = {} data = {} , {}", handle,
|
||||
pParam->smallMotor, pParam->largeMotor);
|
||||
auto* controller = Common::Singleton<Input::GameController>::Instance();
|
||||
controller->SetVibration(pParam->smallMotor, pParam->largeMotor);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
return ORBIS_PAD_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadSetVibrationForce() {
|
||||
|
@ -665,4 +686,4 @@ void RegisterlibScePad(Core::Loader::SymbolsResolver* sym) {
|
|||
LIB_FUNCTION("7xA+hFtvBCA", "libScePad", 1, "libScePad", 1, 1, Func_EF103E845B6F0420);
|
||||
};
|
||||
|
||||
} // namespace Libraries::Pad
|
||||
} // namespace Libraries::Pad
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <core/file_format/playgo_chunk.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/singleton.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
|
@ -8,8 +9,6 @@
|
|||
#include "playgo.h"
|
||||
|
||||
namespace Libraries::PlayGo {
|
||||
// this lib is used to play as the game is being installed.
|
||||
// can be skipped by just returning and assigning the correct values.
|
||||
|
||||
s32 PS4_SYSV_ABI sceDbgPlayGoRequestNextChunk() {
|
||||
LOG_ERROR(Lib_PlayGo, "(STUBBED)called");
|
||||
|
@ -52,9 +51,17 @@ s32 PS4_SYSV_ABI scePlayGoGetLocus(OrbisPlayGoHandle handle, const OrbisPlayGoCh
|
|||
uint32_t numberOfEntries, OrbisPlayGoLocus* outLoci) {
|
||||
LOG_ERROR(Lib_PlayGo, "(STUBBED)called handle = {}, chunkIds = {}, numberOfEntries = {}",
|
||||
handle, *chunkIds, numberOfEntries);
|
||||
// assign all now so that scePlayGoGetLocus is not called again for every single entry
|
||||
std::fill(outLoci, outLoci + numberOfEntries,
|
||||
OrbisPlayGoLocusValue::ORBIS_PLAYGO_LOCUS_LOCAL_FAST);
|
||||
|
||||
auto* playgo = Common::Singleton<PlaygoChunk>::Instance();
|
||||
|
||||
for (uint32_t i = 0; i < numberOfEntries; i++) {
|
||||
if (chunkIds[i] <= playgo->GetPlaygoHeader().mchunk_count) {
|
||||
outLoci[i] = OrbisPlayGoLocusValue::ORBIS_PLAYGO_LOCUS_LOCAL_FAST;
|
||||
} else {
|
||||
outLoci[i] = ORBIS_PLAYGO_LOCUS_NOT_DOWNLOADED;
|
||||
return ORBIS_PLAYGO_ERROR_BAD_CHUNK_ID;
|
||||
}
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
@ -64,13 +71,14 @@ s32 PS4_SYSV_ABI scePlayGoGetProgress(OrbisPlayGoHandle handle, const OrbisPlayG
|
|||
handle, *chunkIds, numberOfEntries);
|
||||
outProgress->progressSize = 0x10000; // todo?
|
||||
outProgress->totalSize = 0x10000;
|
||||
return 0;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI scePlayGoGetToDoList(OrbisPlayGoHandle handle, OrbisPlayGoToDo* outTodoList,
|
||||
u32 numberOfEntries, u32* outEntries) {
|
||||
LOG_ERROR(Lib_PlayGo, "(STUBBED)called");
|
||||
if (handle != shadMagic)
|
||||
LOG_ERROR(Lib_PlayGo, "(STUBBED)called handle = {} numberOfEntries = {}", handle,
|
||||
numberOfEntries);
|
||||
if (handle != 1)
|
||||
return ORBIS_PLAYGO_ERROR_BAD_HANDLE;
|
||||
if (outTodoList == nullptr)
|
||||
return ORBIS_PLAYGO_ERROR_BAD_POINTER;
|
||||
|
@ -88,7 +96,7 @@ s32 PS4_SYSV_ABI scePlayGoInitialize(OrbisPlayGoInitParams* param) {
|
|||
}
|
||||
|
||||
s32 PS4_SYSV_ABI scePlayGoOpen(OrbisPlayGoHandle* outHandle, const void* param) {
|
||||
*outHandle = shadMagic;
|
||||
*outHandle = 1;
|
||||
LOG_INFO(Lib_PlayGo, "(STUBBED)called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
@ -141,4 +149,4 @@ void RegisterlibScePlayGo(Core::Loader::SymbolsResolver* sym) {
|
|||
LIB_FUNCTION("MPe0EeBGM-E", "libScePlayGo", 1, "libScePlayGo", 1, 0, scePlayGoTerminate);
|
||||
};
|
||||
|
||||
} // namespace Libraries::PlayGo
|
||||
} // namespace Libraries::PlayGo
|
||||
|
|
27
src/core/libraries/random/random.cpp
Normal file
27
src/core/libraries/random/random.cpp
Normal file
|
@ -0,0 +1,27 @@
|
|||
// 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 "random.h"
|
||||
|
||||
namespace Libraries::Random {
|
||||
|
||||
s32 PS4_SYSV_ABI sceRandomGetRandomNumber(u8* buf, size_t size) {
|
||||
LOG_TRACE(Lib_Random, "called");
|
||||
if (size > SCE_RANDOM_MAX_SIZE) {
|
||||
return SCE_RANDOM_ERROR_INVALID;
|
||||
}
|
||||
|
||||
for (auto i = 0; i < size; ++i) {
|
||||
buf[i] = std::rand() & 0xFF;
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
void RegisterlibSceRandom(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("PI7jIZj4pcE", "libSceRandom", 1, "libSceRandom", 1, 1, sceRandomGetRandomNumber);
|
||||
};
|
||||
|
||||
} // namespace Libraries::Random
|
17
src/core/libraries/random/random.h
Normal file
17
src/core/libraries/random/random.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
// 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::Random {
|
||||
constexpr int32_t SCE_RANDOM_MAX_SIZE = 64;
|
||||
|
||||
s32 PS4_SYSV_ABI sceRandomGetRandomNumber(u8* buf, size_t size);
|
||||
|
||||
void RegisterlibSceRandom(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::Random
|
|
@ -15,7 +15,7 @@
|
|||
#include "error_codes.h"
|
||||
|
||||
namespace Libraries::SaveData {
|
||||
|
||||
bool is_rw_mode = false;
|
||||
static constexpr std::string_view g_mount_point = "/savedata0"; // temp mount point (todo)
|
||||
std::string game_serial;
|
||||
|
||||
|
@ -180,27 +180,31 @@ int PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond*
|
|||
OrbisSaveDataDirNameSearchResult* result) {
|
||||
if (cond == nullptr)
|
||||
return ORBIS_SAVE_DATA_ERROR_PARAMETER;
|
||||
LOG_ERROR(Lib_SaveData, "TODO sceSaveDataDirNameSearch: Add params");
|
||||
LOG_INFO(Lib_SaveData, "called");
|
||||
const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) /
|
||||
std::to_string(cond->userId) / game_serial;
|
||||
if (!mount_dir.empty() && std::filesystem::exists(mount_dir)) {
|
||||
if (cond->dirName == nullptr) { // look for all dirs if no dir is provided.
|
||||
if (cond->dirName == nullptr || std::string_view(cond->dirName->data)
|
||||
.empty()) { // look for all dirs if no dir is provided.
|
||||
for (int i = 0; const auto& entry : std::filesystem::directory_iterator(mount_dir)) {
|
||||
if (std::filesystem::is_directory(entry.path())) {
|
||||
i++;
|
||||
result->dirNamesNum = 0; // why is it 1024? is it max?
|
||||
if (std::filesystem::is_directory(entry.path()) &&
|
||||
entry.path().filename().string() != "sdmemory") {
|
||||
// sceSaveDataDirNameSearch does not search for dataMemory1/2 dirs.
|
||||
// copy dir name to be used by sceSaveDataMount in read mode.
|
||||
strncpy(result->dirNames[i].data, entry.path().filename().string().c_str(), 32);
|
||||
result->hitNum = i + 1;
|
||||
result->dirNamesNum = i + 1; // to confirm
|
||||
result->setNum = i + 1; // to confirm
|
||||
i++;
|
||||
result->hitNum = i;
|
||||
result->dirNamesNum = i;
|
||||
result->setNum = i;
|
||||
}
|
||||
}
|
||||
} else { // Need a game to test.
|
||||
LOG_ERROR(Lib_SaveData, "Check Me. sceSaveDataDirNameSearch: dirName = {}",
|
||||
cond->dirName->data);
|
||||
strncpy(result->dirNames[0].data, cond->dirName->data, 32);
|
||||
result->hitNum = 1;
|
||||
result->dirNamesNum = 1; // to confirm
|
||||
result->setNum = 1; // to confirm
|
||||
result->dirNamesNum = 1;
|
||||
result->setNum = 1;
|
||||
}
|
||||
} else {
|
||||
result->hitNum = 0;
|
||||
|
@ -303,8 +307,51 @@ int PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountPoi
|
|||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSaveDataGetParam() {
|
||||
LOG_ERROR(Lib_SaveData, "(STUBBED) called");
|
||||
int PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint,
|
||||
const OrbisSaveDataParamType paramType, void* paramBuf,
|
||||
const size_t paramBufSize, size_t* gotSize) {
|
||||
|
||||
if (mountPoint == nullptr)
|
||||
return ORBIS_SAVE_DATA_ERROR_PARAMETER;
|
||||
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
const auto mount_dir = mnt->GetHostPath(mountPoint->data);
|
||||
Common::FS::IOFile file(mount_dir / "param.txt", Common::FS::FileAccessMode::Read);
|
||||
OrbisSaveDataParam params;
|
||||
file.Read(params);
|
||||
|
||||
LOG_INFO(Lib_SaveData, "called");
|
||||
|
||||
switch (paramType) {
|
||||
case ORBIS_SAVE_DATA_PARAM_TYPE_ALL: {
|
||||
memcpy(paramBuf, ¶ms, sizeof(OrbisSaveDataParam));
|
||||
*gotSize = sizeof(OrbisSaveDataParam);
|
||||
} break;
|
||||
case ORBIS_SAVE_DATA_PARAM_TYPE_TITLE: {
|
||||
std::memcpy(paramBuf, ¶ms.title, ORBIS_SAVE_DATA_TITLE_MAXSIZE);
|
||||
*gotSize = ORBIS_SAVE_DATA_TITLE_MAXSIZE;
|
||||
} break;
|
||||
case ORBIS_SAVE_DATA_PARAM_TYPE_SUB_TITLE: {
|
||||
std::memcpy(paramBuf, ¶ms.subTitle, ORBIS_SAVE_DATA_SUBTITLE_MAXSIZE);
|
||||
*gotSize = ORBIS_SAVE_DATA_SUBTITLE_MAXSIZE;
|
||||
} break;
|
||||
case ORBIS_SAVE_DATA_PARAM_TYPE_DETAIL: {
|
||||
std::memcpy(paramBuf, ¶ms.detail, ORBIS_SAVE_DATA_DETAIL_MAXSIZE);
|
||||
*gotSize = ORBIS_SAVE_DATA_DETAIL_MAXSIZE;
|
||||
} break;
|
||||
case ORBIS_SAVE_DATA_PARAM_TYPE_USER_PARAM: {
|
||||
std::memcpy(paramBuf, ¶ms.userParam, sizeof(u32));
|
||||
*gotSize = sizeof(u32);
|
||||
} break;
|
||||
case ORBIS_SAVE_DATA_PARAM_TYPE_MTIME: {
|
||||
std::memcpy(paramBuf, ¶ms.mtime, sizeof(time_t));
|
||||
*gotSize = sizeof(time_t);
|
||||
} break;
|
||||
default: {
|
||||
UNREACHABLE_MSG("Unknown Param = {}", paramType);
|
||||
} break;
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
@ -321,7 +368,7 @@ int PS4_SYSV_ABI sceSaveDataGetSaveDataCount() {
|
|||
int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const u32 userId, void* buf, const size_t bufSize,
|
||||
const int64_t offset) {
|
||||
const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) /
|
||||
std::to_string(userId) / game_serial / "save_mem1.sav";
|
||||
std::to_string(userId) / game_serial / "sdmemory/save_mem1.sav";
|
||||
|
||||
Common::FS::IOFile file(mount_dir, Common::FS::FileAccessMode::Read);
|
||||
if (!file.IsOpen()) {
|
||||
|
@ -336,7 +383,7 @@ int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const u32 userId, void* buf, const
|
|||
|
||||
int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam) {
|
||||
const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) /
|
||||
std::to_string(getParam->userId) / game_serial;
|
||||
std::to_string(getParam->userId) / game_serial / "sdmemory";
|
||||
if (getParam == nullptr)
|
||||
return ORBIS_SAVE_DATA_ERROR_PARAMETER;
|
||||
if (getParam->data != nullptr) {
|
||||
|
@ -443,6 +490,7 @@ s32 saveDataMount(u32 user_id, char* dir_name, u32 mount_mode,
|
|||
case ORBIS_SAVE_DATA_MOUNT_MODE_RDWR:
|
||||
case ORBIS_SAVE_DATA_MOUNT_MODE_RDWR | ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF:
|
||||
case ORBIS_SAVE_DATA_MOUNT_MODE_RDONLY | ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF: {
|
||||
is_rw_mode = (mount_mode == ORBIS_SAVE_DATA_MOUNT_MODE_RDWR) ? true : false;
|
||||
if (!std::filesystem::exists(mount_dir)) {
|
||||
return ORBIS_SAVE_DATA_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
@ -460,10 +508,6 @@ s32 saveDataMount(u32 user_id, char* dir_name, u32 mount_mode,
|
|||
case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE | ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF |
|
||||
ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON: {
|
||||
if (std::filesystem::exists(mount_dir)) {
|
||||
g_mount_point.copy(mount_result->mount_point.data, 16);
|
||||
mnt->Mount(mount_dir, mount_result->mount_point.data);
|
||||
mount_result->required_blocks = 0;
|
||||
mount_result->mount_status = 0;
|
||||
return ORBIS_SAVE_DATA_ERROR_EXISTS;
|
||||
}
|
||||
if (std::filesystem::create_directories(mount_dir)) {
|
||||
|
@ -483,7 +527,7 @@ s32 saveDataMount(u32 user_id, char* dir_name, u32 mount_mode,
|
|||
mount_result->mount_status = 1;
|
||||
} break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
UNREACHABLE_MSG("Unknown mount mode = {}", mount_mode);
|
||||
}
|
||||
mount_result->required_blocks = 0;
|
||||
|
||||
|
@ -583,15 +627,46 @@ int PS4_SYSV_ABI sceSaveDataSetEventInfo() {
|
|||
int PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint,
|
||||
OrbisSaveDataParamType paramType, const void* paramBuf,
|
||||
size_t paramBufSize) {
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
const auto mount_dir = mnt->GetHostPath(mountPoint->data);
|
||||
LOG_INFO(Lib_SaveData, "called = {}, mountPoint->data = {}", mount_dir.string(),
|
||||
mountPoint->data);
|
||||
if (paramBuf == nullptr)
|
||||
return ORBIS_SAVE_DATA_ERROR_PARAMETER;
|
||||
|
||||
if (paramBuf != nullptr) {
|
||||
Common::FS::IOFile file(mount_dir / "param.txt", Common::FS::FileAccessMode::Write);
|
||||
file.WriteRaw<u8>(paramBuf, paramBufSize);
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
const auto mount_dir = mnt->GetHostPath(mountPoint->data) / "param.txt";
|
||||
OrbisSaveDataParam params;
|
||||
if (std::filesystem::exists(mount_dir)) {
|
||||
Common::FS::IOFile file(mount_dir, Common::FS::FileAccessMode::Read);
|
||||
file.ReadRaw<u8>(¶ms, sizeof(OrbisSaveDataParam));
|
||||
}
|
||||
|
||||
LOG_INFO(Lib_SaveData, "called");
|
||||
|
||||
switch (paramType) {
|
||||
case ORBIS_SAVE_DATA_PARAM_TYPE_ALL: {
|
||||
memcpy(¶ms, paramBuf, sizeof(OrbisSaveDataParam));
|
||||
} break;
|
||||
case ORBIS_SAVE_DATA_PARAM_TYPE_TITLE: {
|
||||
strncpy(params.title, static_cast<const char*>(paramBuf), paramBufSize);
|
||||
} break;
|
||||
case ORBIS_SAVE_DATA_PARAM_TYPE_SUB_TITLE: {
|
||||
strncpy(params.subTitle, static_cast<const char*>(paramBuf), paramBufSize);
|
||||
} break;
|
||||
case ORBIS_SAVE_DATA_PARAM_TYPE_DETAIL: {
|
||||
strncpy(params.detail, static_cast<const char*>(paramBuf), paramBufSize);
|
||||
} break;
|
||||
case ORBIS_SAVE_DATA_PARAM_TYPE_USER_PARAM: {
|
||||
params.userParam = *(static_cast<const u32*>(paramBuf));
|
||||
} break;
|
||||
case ORBIS_SAVE_DATA_PARAM_TYPE_MTIME: {
|
||||
params.mtime = *(static_cast<const time_t*>(paramBuf));
|
||||
} break;
|
||||
default: {
|
||||
UNREACHABLE_MSG("Unknown Param = {}", paramType);
|
||||
}
|
||||
}
|
||||
|
||||
Common::FS::IOFile file(mount_dir, Common::FS::FileAccessMode::Write);
|
||||
file.WriteRaw<u8>(¶ms, sizeof(OrbisSaveDataParam));
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
@ -604,11 +679,11 @@ int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(const u32 userId, const void* buf,
|
|||
const size_t bufSize, const int64_t offset) {
|
||||
LOG_INFO(Lib_SaveData, "called");
|
||||
const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) /
|
||||
std::to_string(userId) / game_serial / "save_mem1.sav";
|
||||
std::to_string(userId) / game_serial / "sdmemory/save_mem1.sav";
|
||||
|
||||
Common::FS::IOFile file(mount_dir, Common::FS::FileAccessMode::Write);
|
||||
file.Seek(offset);
|
||||
file.WriteRaw<u8>((void*)buf, bufSize);
|
||||
file.WriteRaw<u8>(buf, bufSize);
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
@ -616,13 +691,13 @@ int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(const u32 userId, const void* buf,
|
|||
int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam) {
|
||||
LOG_INFO(Lib_SaveData, "called: dataNum = {}, slotId= {}", setParam->dataNum, setParam->slotId);
|
||||
const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) /
|
||||
std::to_string(setParam->userId) / game_serial;
|
||||
std::to_string(setParam->userId) / game_serial / "sdmemory";
|
||||
if (setParam->data != nullptr) {
|
||||
Common::FS::IOFile file(mount_dir / "save_mem2.sav", Common::FS::FileAccessMode::Write);
|
||||
if (!file.IsOpen())
|
||||
return -1;
|
||||
file.Seek(setParam->data->offset);
|
||||
file.WriteRaw<u8>((void*)setParam->data->buf, setParam->data->bufSize);
|
||||
file.WriteRaw<u8>(setParam->data->buf, setParam->data->bufSize);
|
||||
}
|
||||
|
||||
if (setParam->param != nullptr) {
|
||||
|
@ -632,7 +707,7 @@ int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* se
|
|||
|
||||
if (setParam->icon != nullptr) {
|
||||
Common::FS::IOFile file(mount_dir / "save_icon.png", Common::FS::FileAccessMode::Write);
|
||||
file.WriteRaw<u8>((void*)setParam->icon->buf, setParam->icon->bufSize);
|
||||
file.WriteRaw<u8>(setParam->icon->buf, setParam->icon->bufSize);
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
|
@ -644,7 +719,7 @@ int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(u32 userId, size_t memorySize,
|
|||
LOG_INFO(Lib_SaveData, "called:userId = {}, memorySize = {}", userId, memorySize);
|
||||
|
||||
const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) /
|
||||
std::to_string(userId) / game_serial;
|
||||
std::to_string(userId) / game_serial / "sdmemory";
|
||||
|
||||
if (std::filesystem::exists(mount_dir)) {
|
||||
return ORBIS_SAVE_DATA_ERROR_EXISTS;
|
||||
|
@ -663,7 +738,7 @@ int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2
|
|||
LOG_INFO(Lib_SaveData, "called");
|
||||
// if (setupParam->option == 1) { // check this later.
|
||||
const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) /
|
||||
std::to_string(setupParam->userId) / game_serial;
|
||||
std::to_string(setupParam->userId) / game_serial / "sdmemory";
|
||||
if (std::filesystem::exists(mount_dir) &&
|
||||
std::filesystem::exists(mount_dir / "save_mem2.sav")) {
|
||||
Common::FS::IOFile file(mount_dir / "save_mem2.sav", Common::FS::FileAccessMode::Read);
|
||||
|
@ -717,16 +792,17 @@ int PS4_SYSV_ABI sceSaveDataTransferringMount() {
|
|||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint) {
|
||||
if (std::string(mountPoint->data).empty()) {
|
||||
LOG_INFO(Lib_SaveData, "mountPoint = {}", mountPoint->data);
|
||||
if (std::string_view(mountPoint->data).empty()) {
|
||||
return ORBIS_SAVE_DATA_ERROR_NOT_MOUNTED;
|
||||
}
|
||||
const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) /
|
||||
std::to_string(1) / game_serial / mountPoint->data;
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
|
||||
const auto& guest_path = mnt->GetHostPath(mountPoint->data);
|
||||
if (guest_path.empty())
|
||||
return ORBIS_SAVE_DATA_ERROR_NOT_MOUNTED;
|
||||
mnt->Unmount(mount_dir, mountPoint->data);
|
||||
LOG_INFO(Lib_SaveData, "mountPoint = {}", std::string(mountPoint->data));
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
@ -736,23 +812,33 @@ int PS4_SYSV_ABI sceSaveDataUmountSys() {
|
|||
}
|
||||
|
||||
int PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint) {
|
||||
LOG_ERROR(Lib_SaveData, "called = {}", std::string(mountPoint->data));
|
||||
LOG_INFO(Lib_SaveData, "called mount = {}, is_rw_mode = {}", std::string(mountPoint->data),
|
||||
is_rw_mode);
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
const auto mount_dir = mnt->GetHostPath(mountPoint->data);
|
||||
if (!std::filesystem::exists(mount_dir)) {
|
||||
return ORBIS_SAVE_DATA_ERROR_NOT_FOUND;
|
||||
}
|
||||
// leave disabled for now. and just unmount.
|
||||
|
||||
std::filesystem::create_directories(mount_dir.parent_path() / "backup");
|
||||
/* if (is_rw_mode) { // backup is done only when mount mode is ReadWrite.
|
||||
auto backup_path = mount_dir;
|
||||
std::string save_data_dir = (mount_dir.string() + "_backup");
|
||||
backup_path.replace_filename(save_data_dir);
|
||||
|
||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(mount_dir)) {
|
||||
const auto& path = entry.path();
|
||||
const auto target_path = mount_dir.parent_path() / "backup";
|
||||
if (std::filesystem::is_regular_file(path)) {
|
||||
std::filesystem::copy(path, target_path,
|
||||
std::filesystem::copy_options::overwrite_existing);
|
||||
std::filesystem::create_directories(backup_path);
|
||||
|
||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(mount_dir)) {
|
||||
const auto& path = entry.path();
|
||||
if (std::filesystem::is_regular_file(path)) {
|
||||
std::filesystem::copy(path, save_data_dir,
|
||||
std::filesystem::copy_options::overwrite_existing);
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
const auto& guest_path = mnt->GetHostPath(mountPoint->data);
|
||||
if (guest_path.empty())
|
||||
return ORBIS_SAVE_DATA_ERROR_NOT_MOUNTED;
|
||||
|
||||
mnt->Unmount(mount_dir, mountPoint->data);
|
||||
return ORBIS_OK;
|
||||
|
|
|
@ -242,6 +242,13 @@ struct OrbisSaveDataMemorySync {
|
|||
u8 reserved[28];
|
||||
};
|
||||
|
||||
constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_ALL = 0;
|
||||
constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_TITLE = 1;
|
||||
constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_SUB_TITLE = 2;
|
||||
constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_DETAIL = 3;
|
||||
constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_USER_PARAM = 4;
|
||||
constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_MTIME = 5;
|
||||
|
||||
int PS4_SYSV_ABI sceSaveDataAbort();
|
||||
int PS4_SYSV_ABI sceSaveDataBackup();
|
||||
int PS4_SYSV_ABI sceSaveDataBindPsnAccount();
|
||||
|
@ -291,7 +298,9 @@ int PS4_SYSV_ABI sceSaveDataGetFormat();
|
|||
int PS4_SYSV_ABI sceSaveDataGetMountedSaveDataCount();
|
||||
int PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountPoint,
|
||||
OrbisSaveDataMountInfo* info);
|
||||
int PS4_SYSV_ABI sceSaveDataGetParam();
|
||||
int PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint,
|
||||
const OrbisSaveDataParamType paramType, void* paramBuf,
|
||||
const size_t paramBufSize, size_t* gotSize);
|
||||
int PS4_SYSV_ABI sceSaveDataGetProgress();
|
||||
int PS4_SYSV_ABI sceSaveDataGetSaveDataCount();
|
||||
int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const u32 userId, void* buf, const size_t bufSize,
|
||||
|
|
|
@ -1898,7 +1898,7 @@ s32 PS4_SYSV_ABI sceSystemServiceParamGetInt(int param_id, int* value) {
|
|||
}
|
||||
switch (param_id) {
|
||||
case ORBIS_SYSTEM_SERVICE_PARAM_ID_LANG:
|
||||
*value = ORBIS_SYSTEM_PARAM_LANG_ENGLISH_US;
|
||||
*value = Config::GetLanguage();
|
||||
break;
|
||||
case ORBIS_SYSTEM_SERVICE_PARAM_ID_DATE_FORMAT:
|
||||
*value = ORBIS_SYSTEM_PARAM_DATE_FORMAT_DDMMYYYY;
|
||||
|
|
|
@ -3,14 +3,17 @@
|
|||
|
||||
#include <pthread.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/config.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/kernel/time_management.h"
|
||||
#include "core/libraries/videoout/driver.h"
|
||||
#include "core/platform.h"
|
||||
|
||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||
|
||||
extern std::unique_ptr<Vulkan::RendererVulkan> renderer;
|
||||
extern std::unique_ptr<AmdGpu::Liverpool> liverpool;
|
||||
|
||||
namespace Libraries::VideoOut {
|
||||
|
||||
|
@ -41,20 +44,18 @@ VideoOutDriver::VideoOutDriver(u32 width, u32 height) {
|
|||
main_port.resolution.fullHeight = height;
|
||||
main_port.resolution.paneWidth = width;
|
||||
main_port.resolution.paneHeight = height;
|
||||
present_thread = std::jthread([&](std::stop_token token) { PresentThread(token); });
|
||||
}
|
||||
|
||||
VideoOutDriver::~VideoOutDriver() = default;
|
||||
|
||||
int VideoOutDriver::Open(const ServiceThreadParams* params) {
|
||||
std::scoped_lock lock{mutex};
|
||||
|
||||
if (main_port.is_open) {
|
||||
return ORBIS_VIDEO_OUT_ERROR_RESOURCE_BUSY;
|
||||
}
|
||||
|
||||
int handle = 1;
|
||||
main_port.is_open = true;
|
||||
return handle;
|
||||
liverpool->SetVoPort(&main_port);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void VideoOutDriver::Close(s32 handle) {
|
||||
|
@ -158,41 +159,37 @@ int VideoOutDriver::UnregisterBuffers(VideoOutPort* port, s32 attributeIndex) {
|
|||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
void VideoOutDriver::Flip(std::chrono::microseconds timeout) {
|
||||
Request req;
|
||||
{
|
||||
std::unique_lock lock{mutex};
|
||||
submit_cond.wait_for(lock, timeout, [&] { return !requests.empty(); });
|
||||
if (requests.empty()) {
|
||||
renderer->ShowSplash();
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the request.
|
||||
req = requests.front();
|
||||
requests.pop();
|
||||
std::chrono::microseconds VideoOutDriver::Flip(const Request& req) {
|
||||
if (!req) {
|
||||
return std::chrono::microseconds{0};
|
||||
}
|
||||
|
||||
const auto start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
// Whatever the game is rendering show splash if it is active
|
||||
if (!renderer->ShowSplash(req.frame)) {
|
||||
// Present the frame.
|
||||
renderer->Present(req.frame);
|
||||
}
|
||||
|
||||
std::scoped_lock lock{mutex};
|
||||
|
||||
// Update flip status.
|
||||
auto& flip_status = req.port->flip_status;
|
||||
flip_status.count++;
|
||||
flip_status.processTime = Libraries::Kernel::sceKernelGetProcessTime();
|
||||
flip_status.tsc = Libraries::Kernel::sceKernelReadTsc();
|
||||
flip_status.submitTsc = Libraries::Kernel::sceKernelReadTsc();
|
||||
flip_status.flipArg = req.flip_arg;
|
||||
flip_status.currentBuffer = req.index;
|
||||
flip_status.flipPendingNum = static_cast<int>(requests.size());
|
||||
auto* port = req.port;
|
||||
{
|
||||
std::unique_lock lock{port->port_mutex};
|
||||
auto& flip_status = port->flip_status;
|
||||
flip_status.count++;
|
||||
flip_status.processTime = Libraries::Kernel::sceKernelGetProcessTime();
|
||||
flip_status.tsc = Libraries::Kernel::sceKernelReadTsc();
|
||||
flip_status.flipArg = req.flip_arg;
|
||||
flip_status.currentBuffer = req.index;
|
||||
if (req.eop) {
|
||||
--flip_status.gcQueueNum;
|
||||
}
|
||||
--flip_status.flipPendingNum;
|
||||
}
|
||||
|
||||
// Trigger flip events for the port.
|
||||
for (auto& event : req.port->flip_events) {
|
||||
for (auto& event : port->flip_events) {
|
||||
if (event != nullptr) {
|
||||
event->TriggerEvent(SCE_VIDEO_OUT_EVENT_FLIP, Kernel::SceKernelEvent::Filter::VideoOut,
|
||||
reinterpret_cast<void*>(req.flip_arg));
|
||||
|
@ -201,57 +198,109 @@ void VideoOutDriver::Flip(std::chrono::microseconds timeout) {
|
|||
|
||||
// Reset flip label
|
||||
if (req.index != -1) {
|
||||
req.port->buffer_labels[req.index] = 0;
|
||||
port->buffer_labels[req.index] = 0;
|
||||
port->SignalVoLabel();
|
||||
}
|
||||
|
||||
const auto end = std::chrono::high_resolution_clock::now();
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(end - start);
|
||||
}
|
||||
|
||||
bool VideoOutDriver::SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg,
|
||||
bool is_eop /*= false*/) {
|
||||
std::scoped_lock lock{mutex};
|
||||
{
|
||||
std::unique_lock lock{port->port_mutex};
|
||||
if (index != -1 && port->flip_status.flipPendingNum >= port->NumRegisteredBuffers()) {
|
||||
LOG_ERROR(Lib_VideoOut, "Flip queue is full");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_eop) {
|
||||
++port->flip_status.gcQueueNum;
|
||||
}
|
||||
++port->flip_status.flipPendingNum; // integral GPU and CPU pending flips counter
|
||||
port->flip_status.submitTsc = Libraries::Kernel::sceKernelReadTsc();
|
||||
}
|
||||
|
||||
if (!is_eop) {
|
||||
// Before processing the flip we need to ask GPU thread to flush command list as at this
|
||||
// point VO surface is ready to be presented, and we will need have an actual state of
|
||||
// Vulkan image at the time of frame presentation.
|
||||
liverpool->SendCommand([=, this]() {
|
||||
renderer->FlushDraw();
|
||||
SubmitFlipInternal(port, index, flip_arg, is_eop);
|
||||
});
|
||||
} else {
|
||||
SubmitFlipInternal(port, index, flip_arg, is_eop);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VideoOutDriver::SubmitFlipInternal(VideoOutPort* port, s32 index, s64 flip_arg,
|
||||
bool is_eop /*= false*/) {
|
||||
Vulkan::Frame* frame;
|
||||
if (index == -1) {
|
||||
frame = renderer->PrepareBlankFrame();
|
||||
frame = renderer->PrepareBlankFrame(is_eop);
|
||||
} else {
|
||||
const auto& buffer = port->buffer_slots[index];
|
||||
const auto& group = port->groups[buffer.group_index];
|
||||
frame = renderer->PrepareFrame(group, buffer.address_left);
|
||||
}
|
||||
|
||||
if (index != -1 && requests.size() >= port->NumRegisteredBuffers()) {
|
||||
LOG_ERROR(Lib_VideoOut, "Flip queue is full");
|
||||
return false;
|
||||
frame = renderer->PrepareFrame(group, buffer.address_left, is_eop);
|
||||
}
|
||||
|
||||
std::scoped_lock lock{mutex};
|
||||
requests.push({
|
||||
.frame = frame,
|
||||
.port = port,
|
||||
.index = index,
|
||||
.flip_arg = flip_arg,
|
||||
.submit_tsc = Libraries::Kernel::sceKernelReadTsc(),
|
||||
.eop = is_eop,
|
||||
});
|
||||
|
||||
port->flip_status.flipPendingNum = static_cast<int>(requests.size());
|
||||
port->flip_status.gcQueueNum = 0;
|
||||
submit_cond.notify_one();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VideoOutDriver::Vblank() {
|
||||
std::scoped_lock lock{mutex};
|
||||
void VideoOutDriver::PresentThread(std::stop_token token) {
|
||||
static constexpr std::chrono::milliseconds VblankPeriod{16};
|
||||
Common::SetCurrentThreadName("PresentThread");
|
||||
|
||||
auto& vblank_status = main_port.vblank_status;
|
||||
vblank_status.count++;
|
||||
vblank_status.processTime = Libraries::Kernel::sceKernelGetProcessTime();
|
||||
vblank_status.tsc = Libraries::Kernel::sceKernelReadTsc();
|
||||
const auto receive_request = [this] -> Request {
|
||||
std::scoped_lock lk{mutex};
|
||||
if (!requests.empty()) {
|
||||
const auto request = requests.front();
|
||||
requests.pop();
|
||||
return request;
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
// Trigger flip events for the port.
|
||||
for (auto& event : main_port.vblank_events) {
|
||||
if (event != nullptr) {
|
||||
event->TriggerEvent(SCE_VIDEO_OUT_EVENT_VBLANK,
|
||||
Kernel::SceKernelEvent::Filter::VideoOut, nullptr);
|
||||
auto vblank_period = VblankPeriod / Config::vblankDiv();
|
||||
auto delay = std::chrono::microseconds{0};
|
||||
while (!token.stop_requested()) {
|
||||
// Sleep for most of the vblank duration.
|
||||
std::this_thread::sleep_for(vblank_period - delay);
|
||||
|
||||
// Check if it's time to take a request.
|
||||
auto& vblank_status = main_port.vblank_status;
|
||||
if (vblank_status.count % (main_port.flip_rate + 1) == 0) {
|
||||
const auto request = receive_request();
|
||||
delay = Flip(request);
|
||||
FRAME_END;
|
||||
}
|
||||
|
||||
{
|
||||
// Needs lock here as can be concurrently read by `sceVideoOutGetVblankStatus`
|
||||
std::unique_lock lock{main_port.vo_mutex};
|
||||
vblank_status.count++;
|
||||
vblank_status.processTime = Libraries::Kernel::sceKernelGetProcessTime();
|
||||
vblank_status.tsc = Libraries::Kernel::sceKernelReadTsc();
|
||||
main_port.vblank_cv.notify_all();
|
||||
}
|
||||
|
||||
// Trigger flip events for the port.
|
||||
for (auto& event : main_port.vblank_events) {
|
||||
if (event != nullptr) {
|
||||
event->TriggerEvent(SCE_VIDEO_OUT_EVENT_VBLANK,
|
||||
Kernel::SceKernelEvent::Filter::VideoOut, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,13 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "core/libraries/videoout/video_out.h"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include "core/libraries/videoout/video_out.h"
|
||||
|
||||
namespace Vulkan {
|
||||
struct Frame;
|
||||
|
@ -25,6 +28,10 @@ struct VideoOutPort {
|
|||
SceVideoOutVblankStatus vblank_status;
|
||||
std::vector<Kernel::SceKernelEqueue> flip_events;
|
||||
std::vector<Kernel::SceKernelEqueue> vblank_events;
|
||||
std::mutex vo_mutex;
|
||||
std::mutex port_mutex;
|
||||
std::condition_variable vo_cv;
|
||||
std::condition_variable vblank_cv;
|
||||
int flip_rate = 0;
|
||||
|
||||
s32 FindFreeGroup() const {
|
||||
|
@ -35,6 +42,22 @@ struct VideoOutPort {
|
|||
return index;
|
||||
}
|
||||
|
||||
bool IsVoLabel(const u64* address) const {
|
||||
const u64* start = &buffer_labels[0];
|
||||
const u64* end = &buffer_labels[MaxDisplayBuffers - 1];
|
||||
return address >= start && address <= end;
|
||||
}
|
||||
|
||||
void WaitVoLabel(auto&& pred) {
|
||||
std::unique_lock lk{vo_mutex};
|
||||
vo_cv.wait(lk, pred);
|
||||
}
|
||||
|
||||
void SignalVoLabel() {
|
||||
std::scoped_lock lk{vo_mutex};
|
||||
vo_cv.notify_one();
|
||||
}
|
||||
|
||||
[[nodiscard]] int NumRegisteredBuffers() const {
|
||||
return std::count_if(buffer_slots.cbegin(), buffer_slots.cend(),
|
||||
[](auto& buffer) { return buffer.group_index != -1; });
|
||||
|
@ -63,27 +86,29 @@ public:
|
|||
const BufferAttribute* attribute);
|
||||
int UnregisterBuffers(VideoOutPort* port, s32 attributeIndex);
|
||||
|
||||
void Flip(std::chrono::microseconds timeout);
|
||||
bool SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg, bool is_eop = false);
|
||||
|
||||
void Vblank();
|
||||
|
||||
private:
|
||||
struct Request {
|
||||
Vulkan::Frame* frame;
|
||||
VideoOutPort* port;
|
||||
s32 index;
|
||||
s64 flip_arg;
|
||||
u64 submit_tsc;
|
||||
bool eop;
|
||||
|
||||
operator bool() const noexcept {
|
||||
return frame != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
std::chrono::microseconds Flip(const Request& req);
|
||||
void SubmitFlipInternal(VideoOutPort* port, s32 index, s64 flip_arg, bool is_eop = false);
|
||||
void PresentThread(std::stop_token token);
|
||||
|
||||
std::mutex mutex;
|
||||
VideoOutPort main_port{};
|
||||
std::condition_variable_any submit_cond;
|
||||
std::condition_variable done_cond;
|
||||
std::jthread present_thread;
|
||||
std::queue<Request> requests;
|
||||
bool is_neo{};
|
||||
};
|
||||
|
||||
} // namespace Libraries::VideoOut
|
||||
|
|
|
@ -113,7 +113,9 @@ s32 PS4_SYSV_ABI sceVideoOutSetFlipRate(s32 handle, s32 rate) {
|
|||
|
||||
s32 PS4_SYSV_ABI sceVideoOutIsFlipPending(s32 handle) {
|
||||
LOG_INFO(Lib_VideoOut, "called");
|
||||
s32 pending = driver->GetPort(handle)->flip_status.flipPendingNum;
|
||||
auto* port = driver->GetPort(handle);
|
||||
std::unique_lock lock{port->port_mutex};
|
||||
s32 pending = port->flip_status.flipPendingNum;
|
||||
return pending;
|
||||
}
|
||||
|
||||
|
@ -161,6 +163,7 @@ s32 PS4_SYSV_ABI sceVideoOutGetFlipStatus(s32 handle, FlipStatus* status) {
|
|||
return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
std::unique_lock lock{port->port_mutex};
|
||||
*status = port->flip_status;
|
||||
|
||||
LOG_INFO(Lib_VideoOut,
|
||||
|
@ -183,6 +186,7 @@ s32 PS4_SYSV_ABI sceVideoOutGetVblankStatus(int handle, SceVideoOutVblankStatus*
|
|||
return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
std::unique_lock lock{port->vo_mutex};
|
||||
*status = port->vblank_status;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
@ -196,7 +200,6 @@ s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutio
|
|||
s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 index,
|
||||
const void* param) {
|
||||
LOG_INFO(Lib_VideoOut, "called");
|
||||
ASSERT(userId == UserService::ORBIS_USER_SERVICE_USER_ID_SYSTEM || userId == 0);
|
||||
ASSERT(busType == SCE_VIDEO_OUT_BUS_TYPE_MAIN);
|
||||
|
||||
if (index != 0) {
|
||||
|
@ -229,14 +232,6 @@ s32 PS4_SYSV_ABI sceVideoOutUnregisterBuffers(s32 handle, s32 attributeIndex) {
|
|||
return driver->UnregisterBuffers(port, attributeIndex);
|
||||
}
|
||||
|
||||
void Flip(std::chrono::microseconds micros) {
|
||||
return driver->Flip(micros);
|
||||
}
|
||||
|
||||
void Vblank() {
|
||||
return driver->Vblank();
|
||||
}
|
||||
|
||||
void sceVideoOutGetBufferLabelAddress(s32 handle, uintptr_t* label_addr) {
|
||||
auto* port = driver->GetPort(handle);
|
||||
ASSERT(port);
|
||||
|
@ -266,6 +261,18 @@ s32 PS4_SYSV_ABI sceVideoOutGetDeviceCapabilityInfo(
|
|||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceVideoOutWaitVblank(s32 handle) {
|
||||
auto* port = driver->GetPort(handle);
|
||||
if (!port) {
|
||||
return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
std::unique_lock lock{port->vo_mutex};
|
||||
const auto prev_counter = port->vblank_status.count;
|
||||
port->vblank_cv.wait(lock, [&]() { return prev_counter != port->vblank_status.count; });
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
driver = std::make_unique<VideoOutDriver>(Config::getScreenWidth(), Config::getScreenHeight());
|
||||
|
||||
|
@ -294,6 +301,7 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
|||
sceVideoOutGetVblankStatus);
|
||||
LIB_FUNCTION("kGVLc3htQE8", "libSceVideoOut", 1, "libSceVideoOut", 0, 0,
|
||||
sceVideoOutGetDeviceCapabilityInfo);
|
||||
LIB_FUNCTION("j6RaAUlaLv0", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutWaitVblank);
|
||||
|
||||
// openOrbis appears to have libSceVideoOut_v1 module libSceVideoOut_v1.1
|
||||
LIB_FUNCTION("Up36PTk687E", "libSceVideoOut", 1, "libSceVideoOut", 1, 1, sceVideoOutOpen);
|
||||
|
|
|
@ -92,11 +92,12 @@ void PS4_SYSV_ABI sceVideoOutSetBufferAttribute(BufferAttribute* attribute, Pixe
|
|||
u32 tilingMode, u32 aspectRatio, u32 width,
|
||||
u32 height, u32 pitchInPixel);
|
||||
s32 PS4_SYSV_ABI sceVideoOutAddFlipEvent(Kernel::SceKernelEqueue eq, s32 handle, void* udata);
|
||||
s32 PS4_SYSV_ABI sceVideoOutAddVBlankEvent(Kernel::SceKernelEqueue eq, s32 handle, void* udata);
|
||||
s32 PS4_SYSV_ABI sceVideoOutAddVblankEvent(Kernel::SceKernelEqueue eq, s32 handle, void* udata);
|
||||
s32 PS4_SYSV_ABI sceVideoOutRegisterBuffers(s32 handle, s32 startIndex, void* const* addresses,
|
||||
s32 bufferNum, const BufferAttribute* attribute);
|
||||
s32 PS4_SYSV_ABI sceVideoOutSetFlipRate(s32 handle, s32 rate);
|
||||
s32 PS4_SYSV_ABI sceVideoOutIsFlipPending(s32 handle);
|
||||
s32 PS4_SYSV_ABI sceVideoOutWaitVblank(s32 handle);
|
||||
s32 PS4_SYSV_ABI sceVideoOutSubmitFlip(s32 handle, s32 bufferIndex, s32 flipMode, s64 flipArg);
|
||||
s32 PS4_SYSV_ABI sceVideoOutGetFlipStatus(s32 handle, FlipStatus* status);
|
||||
s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutionStatus* status);
|
||||
|
@ -104,9 +105,6 @@ s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 i
|
|||
const void* param);
|
||||
s32 PS4_SYSV_ABI sceVideoOutClose(s32 handle);
|
||||
|
||||
void Flip(std::chrono::microseconds micros);
|
||||
void Vblank();
|
||||
|
||||
// Internal system functions
|
||||
void sceVideoOutGetBufferLabelAddress(s32 handle, uintptr_t* label_addr);
|
||||
s32 sceVideoOutSubmitEopFlip(s32 handle, u32 buf_id, u32 mode, u32 arg, void** unk);
|
||||
|
|
|
@ -305,7 +305,8 @@ void* Linker::TlsGetAddr(u64 module_index, u64 offset) {
|
|||
// Module was just loaded by above code. Allocate TLS block for it.
|
||||
Module* module = m_modules[module_index - 1].get();
|
||||
const u32 init_image_size = module->tls.init_image_size;
|
||||
u8* dest = reinterpret_cast<u8*>(heap_api_func(module->tls.image_size));
|
||||
// TODO: Determine if Windows will crash from this
|
||||
u8* dest = reinterpret_cast<u8*>(heap_api->heap_malloc(module->tls.image_size));
|
||||
const u8* src = reinterpret_cast<const u8*>(module->tls.image_virtual_addr);
|
||||
std::memcpy(dest, src, init_image_size);
|
||||
std::memset(dest + init_image_size, 0, module->tls.image_size - init_image_size);
|
||||
|
@ -320,21 +321,38 @@ void Linker::InitTlsForThread(bool is_primary) {
|
|||
static constexpr size_t TlsAllocAlign = 0x20;
|
||||
const size_t total_tls_size = Common::AlignUp(static_tls_size, TlsAllocAlign) + TcbSize;
|
||||
|
||||
// If sceKernelMapNamedFlexibleMemory is being called from libkernel and addr = 0
|
||||
// it automatically places mappings in system reserved area instead of managed.
|
||||
static constexpr VAddr KernelAllocBase = 0x880000000ULL;
|
||||
|
||||
// The kernel module has a few different paths for TLS allocation.
|
||||
// For SDK < 1.7 it allocates both main and secondary thread blocks using libc mspace/malloc.
|
||||
// In games compiled with newer SDK, the main thread gets mapped from flexible memory,
|
||||
// with addr = 0, so system managed area. Here we will only implement the latter.
|
||||
void* addr_out{};
|
||||
void* addr_out{reinterpret_cast<void*>(KernelAllocBase)};
|
||||
if (is_primary) {
|
||||
const size_t tls_aligned = Common::AlignUp(total_tls_size, 16_KB);
|
||||
const int ret = Libraries::Kernel::sceKernelMapNamedFlexibleMemory(
|
||||
&addr_out, tls_aligned, 3, 0, "SceKernelPrimaryTcbTls");
|
||||
ASSERT_MSG(ret == 0, "Unable to allocate TLS+TCB for the primary thread");
|
||||
} else {
|
||||
if (heap_api_func) {
|
||||
addr_out = heap_api_func(total_tls_size);
|
||||
if (heap_api) {
|
||||
#ifndef WIN32
|
||||
addr_out = heap_api->heap_malloc(total_tls_size);
|
||||
} else {
|
||||
addr_out = std::malloc(total_tls_size);
|
||||
#else
|
||||
// TODO: Windows tls malloc replacement, refer to rtld_tls_block_malloc
|
||||
LOG_ERROR(Core_Linker, "TLS user malloc called, using std::malloc");
|
||||
addr_out = std::malloc(total_tls_size);
|
||||
if (!addr_out) {
|
||||
auto pth_id = pthread_self();
|
||||
auto handle = pthread_gethandle(pth_id);
|
||||
ASSERT_MSG(addr_out,
|
||||
"Cannot allocate TLS block defined for handle=%x, index=%d size=%d",
|
||||
handle, pth_id, total_tls_size);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,21 @@ struct EntryParams {
|
|||
const char* argv[3];
|
||||
};
|
||||
|
||||
using HeapApiFunc = PS4_SYSV_ABI void* (*)(size_t);
|
||||
struct HeapAPI {
|
||||
PS4_SYSV_ABI void* (*heap_malloc)(size_t);
|
||||
PS4_SYSV_ABI void (*heap_free)(void*);
|
||||
PS4_SYSV_ABI void* (*heap_calloc)(size_t, size_t);
|
||||
PS4_SYSV_ABI void* (*heap_realloc)(void*, size_t);
|
||||
PS4_SYSV_ABI void* (*heap_memalign)(size_t, size_t);
|
||||
PS4_SYSV_ABI int (*heap_posix_memalign)(void**, size_t, size_t);
|
||||
// NOTE: Fields below may be inaccurate
|
||||
PS4_SYSV_ABI int (*heap_reallocalign)(void);
|
||||
PS4_SYSV_ABI void (*heap_malloc_stats)(void);
|
||||
PS4_SYSV_ABI int (*heap_malloc_stats_fast)(void);
|
||||
PS4_SYSV_ABI size_t (*heap_malloc_usable_size)(void*);
|
||||
};
|
||||
|
||||
using AppHeapAPI = HeapAPI*;
|
||||
|
||||
class Linker {
|
||||
public:
|
||||
|
@ -75,8 +89,8 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
void SetHeapApiFunc(void* func) {
|
||||
heap_api_func = *reinterpret_cast<HeapApiFunc*>(func);
|
||||
void SetHeapAPI(void* func[]) {
|
||||
heap_api = reinterpret_cast<AppHeapAPI>(func);
|
||||
}
|
||||
|
||||
void AdvanceGenerationCounter() noexcept {
|
||||
|
@ -104,7 +118,7 @@ private:
|
|||
size_t static_tls_size{};
|
||||
u32 max_tls_index{};
|
||||
u32 num_static_modules{};
|
||||
HeapApiFunc heap_api_func{};
|
||||
AppHeapAPI heap_api{};
|
||||
std::vector<std::unique_ptr<Module>> m_modules;
|
||||
Loader::SymbolsResolver m_hle_symbols{};
|
||||
};
|
||||
|
|
|
@ -4,11 +4,10 @@
|
|||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/kernel/memory_management.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
#include "video_core/renderer_vulkan/vk_rasterizer.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
|
@ -55,7 +54,7 @@ PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size,
|
|||
free_addr = alignment > 0 ? Common::AlignUp(free_addr, alignment) : free_addr;
|
||||
|
||||
// Add the allocated region to the list and commit its pages.
|
||||
auto& area = AddDmemAllocation(free_addr, size);
|
||||
auto& area = CarveDmemArea(free_addr, size)->second;
|
||||
area.memory_type = memory_type;
|
||||
area.is_free = false;
|
||||
return free_addr;
|
||||
|
@ -64,9 +63,8 @@ PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size,
|
|||
void MemoryManager::Free(PAddr phys_addr, size_t size) {
|
||||
std::scoped_lock lk{mutex};
|
||||
|
||||
const auto dmem_area = FindDmemArea(phys_addr);
|
||||
ASSERT(dmem_area != dmem_map.end() && dmem_area->second.base == phys_addr &&
|
||||
dmem_area->second.size == size);
|
||||
auto dmem_area = CarveDmemArea(phys_addr, size);
|
||||
ASSERT(dmem_area != dmem_map.end() && dmem_area->second.size >= size);
|
||||
|
||||
// Release any dmem mappings that reference this physical block.
|
||||
std::vector<std::pair<VAddr, u64>> remove_list;
|
||||
|
@ -75,10 +73,11 @@ void MemoryManager::Free(PAddr phys_addr, size_t size) {
|
|||
continue;
|
||||
}
|
||||
if (mapping.phys_base <= phys_addr && phys_addr < mapping.phys_base + mapping.size) {
|
||||
LOG_INFO(Kernel_Vmm, "Unmaping direct mapping {:#x} with size {:#x}", addr,
|
||||
mapping.size);
|
||||
auto vma_segment_start_addr = phys_addr - mapping.phys_base + addr;
|
||||
LOG_INFO(Kernel_Vmm, "Unmaping direct mapping {:#x} with size {:#x}",
|
||||
vma_segment_start_addr, size);
|
||||
// Unmaping might erase from vma_map. We can't do it here.
|
||||
remove_list.emplace_back(addr, mapping.size);
|
||||
remove_list.emplace_back(vma_segment_start_addr, size);
|
||||
}
|
||||
}
|
||||
for (const auto& [addr, size] : remove_list) {
|
||||
|
@ -100,29 +99,30 @@ int MemoryManager::Reserve(void** out_addr, VAddr virtual_addr, size_t size, Mem
|
|||
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)) {
|
||||
const auto& vma = FindVMA(mapped_addr)->second;
|
||||
// If the VMA is mapped, unmap the region first.
|
||||
if (vma.IsMapped()) {
|
||||
UnmapMemory(mapped_addr, size);
|
||||
}
|
||||
const size_t remaining_size = vma.base + vma.size - mapped_addr;
|
||||
ASSERT_MSG(vma.type == VMAType::Free && remaining_size >= size);
|
||||
}
|
||||
|
||||
// Find the first free area starting with provided virtual address.
|
||||
if (False(flags & MemoryMapFlags::Fixed)) {
|
||||
auto it = FindVMA(mapped_addr);
|
||||
// If the VMA is free and contains the requested mapping we are done.
|
||||
if (it->second.type == VMAType::Free && it->second.Contains(virtual_addr, size)) {
|
||||
mapped_addr = virtual_addr;
|
||||
} else {
|
||||
// Search for the first free VMA that fits our mapping.
|
||||
while (it->second.type != VMAType::Free || it->second.size < size) {
|
||||
it++;
|
||||
}
|
||||
ASSERT(it != vma_map.end());
|
||||
const auto& vma = it->second;
|
||||
mapped_addr = alignment > 0 ? Common::AlignUp(vma.base, alignment) : vma.base;
|
||||
}
|
||||
mapped_addr = SearchFree(mapped_addr, size, alignment);
|
||||
}
|
||||
|
||||
// Add virtual memory area
|
||||
auto& new_vma = AddMapping(mapped_addr, size);
|
||||
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 = "";
|
||||
new_vma.type = VMAType::Reserved;
|
||||
MergeAdjacent(vma_map, new_vma_handle);
|
||||
|
||||
*out_addr = std::bit_cast<void*>(mapped_addr);
|
||||
return ORBIS_OK;
|
||||
|
@ -132,6 +132,9 @@ int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, M
|
|||
MemoryMapFlags flags, VMAType type, std::string_view name,
|
||||
bool is_exec, PAddr phys_addr, u64 alignment) {
|
||||
std::scoped_lock lk{mutex};
|
||||
|
||||
// Certain games perform flexible mappings on loop to determine
|
||||
// the available flexible memory size. Questionable but we need to handle this.
|
||||
if (type == VMAType::Flexible && flexible_usage + size > total_flexible_size) {
|
||||
return SCE_KERNEL_ERROR_ENOMEM;
|
||||
}
|
||||
|
@ -140,91 +143,64 @@ int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, M
|
|||
// flag so we will take the branch that searches for free (or reserved) mappings.
|
||||
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;
|
||||
SCOPE_EXIT {
|
||||
auto& new_vma = AddMapping(mapped_addr, size);
|
||||
new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce);
|
||||
new_vma.prot = prot;
|
||||
new_vma.name = name;
|
||||
new_vma.type = type;
|
||||
|
||||
if (type == VMAType::Direct) {
|
||||
new_vma.phys_base = phys_addr;
|
||||
MapVulkanMemory(mapped_addr, size);
|
||||
}
|
||||
if (type == VMAType::Flexible) {
|
||||
flexible_usage += size;
|
||||
}
|
||||
};
|
||||
|
||||
// Fixed mapping means the virtual address must exactly match the provided one.
|
||||
if (True(flags & MemoryMapFlags::Fixed) && True(flags & MemoryMapFlags::NoOverwrite)) {
|
||||
if (True(flags & MemoryMapFlags::Fixed)) {
|
||||
// This should return SCE_KERNEL_ERROR_ENOMEM but shouldn't normally happen.
|
||||
const auto& vma = FindVMA(mapped_addr)->second;
|
||||
const size_t remaining_size = vma.base + vma.size - mapped_addr;
|
||||
ASSERT_MSG(vma.type == VMAType::Free && remaining_size >= size);
|
||||
ASSERT_MSG(!vma.IsMapped() && remaining_size >= size);
|
||||
}
|
||||
|
||||
// Find the first free area starting with provided virtual address.
|
||||
if (False(flags & MemoryMapFlags::Fixed)) {
|
||||
auto it = FindVMA(mapped_addr);
|
||||
// If the VMA is free and contains the requested mapping we are done.
|
||||
if (it->second.type == VMAType::Free && it->second.Contains(virtual_addr, size)) {
|
||||
mapped_addr = virtual_addr;
|
||||
} else {
|
||||
// Search for the first free VMA that fits our mapping.
|
||||
while (it->second.type != VMAType::Free || it->second.size < size) {
|
||||
it++;
|
||||
}
|
||||
ASSERT(it != vma_map.end());
|
||||
const auto& vma = it->second;
|
||||
mapped_addr = alignment > 0 ? Common::AlignUp(vma.base, alignment) : vma.base;
|
||||
}
|
||||
mapped_addr = SearchFree(mapped_addr, size, alignment);
|
||||
}
|
||||
|
||||
// Perform the mapping.
|
||||
*out_addr = impl.Map(mapped_addr, size, alignment, phys_addr, is_exec);
|
||||
TRACK_ALLOC(*out_addr, size, "VMEM");
|
||||
|
||||
auto& new_vma = CarveVMA(mapped_addr, size)->second;
|
||||
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;
|
||||
rasterizer->MapMemory(mapped_addr, size);
|
||||
}
|
||||
if (type == VMAType::Flexible) {
|
||||
flexible_usage += size;
|
||||
}
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, size_t size, MemoryProt prot,
|
||||
MemoryMapFlags flags, uintptr_t fd, size_t offset) {
|
||||
if (virtual_addr == 0) {
|
||||
virtual_addr = impl.SystemManagedVirtualBase();
|
||||
} else {
|
||||
LOG_INFO(Kernel_Vmm, "Virtual addr {:#x} with size {:#x}", virtual_addr, size);
|
||||
}
|
||||
|
||||
VAddr mapped_addr = 0;
|
||||
VAddr mapped_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr;
|
||||
const size_t size_aligned = Common::AlignUp(size, 16_KB);
|
||||
|
||||
// Find first free area to map the file.
|
||||
if (False(flags & MemoryMapFlags::Fixed)) {
|
||||
auto it = FindVMA(virtual_addr);
|
||||
while (it->second.type != VMAType::Free || it->second.size < size_aligned) {
|
||||
it++;
|
||||
}
|
||||
ASSERT(it != vma_map.end());
|
||||
|
||||
mapped_addr = it->second.base;
|
||||
mapped_addr = SearchFree(mapped_addr, size_aligned, 1);
|
||||
}
|
||||
|
||||
if (True(flags & MemoryMapFlags::Fixed)) {
|
||||
const auto& vma = FindVMA(virtual_addr)->second;
|
||||
const size_t remaining_size = vma.base + vma.size - virtual_addr;
|
||||
ASSERT_MSG((vma.type == VMAType::Free || vma.type == VMAType::Reserved) &&
|
||||
remaining_size >= size);
|
||||
|
||||
mapped_addr = virtual_addr;
|
||||
ASSERT_MSG(!vma.IsMapped() && remaining_size >= size);
|
||||
}
|
||||
|
||||
// Map the file.
|
||||
impl.MapFile(mapped_addr, size, offset, std::bit_cast<u32>(prot), fd);
|
||||
|
||||
// Add virtual memory area
|
||||
auto& new_vma = AddMapping(mapped_addr, size_aligned);
|
||||
auto& new_vma = CarveVMA(mapped_addr, size_aligned)->second;
|
||||
new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce);
|
||||
new_vma.prot = prot;
|
||||
new_vma.name = "File";
|
||||
|
@ -238,29 +214,38 @@ int MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, size_t size, Mem
|
|||
void MemoryManager::UnmapMemory(VAddr virtual_addr, size_t size) {
|
||||
std::scoped_lock lk{mutex};
|
||||
|
||||
// TODO: Partial unmaps are technically supported by the guest.
|
||||
const auto it = vma_map.find(virtual_addr);
|
||||
ASSERT_MSG(it != vma_map.end() && it->first == virtual_addr,
|
||||
"Attempting to unmap partially mapped range");
|
||||
const auto it = FindVMA(virtual_addr);
|
||||
const auto& vma_base = it->second;
|
||||
ASSERT_MSG(vma_base.Contains(virtual_addr, size),
|
||||
"Existing mapping does not contain requested unmap range");
|
||||
|
||||
const auto type = it->second.type;
|
||||
const auto vma_base_addr = vma_base.base;
|
||||
const auto vma_base_size = vma_base.size;
|
||||
const auto phys_base = vma_base.phys_base;
|
||||
const bool is_exec = vma_base.is_exec;
|
||||
const auto start_in_vma = virtual_addr - vma_base_addr;
|
||||
const auto type = vma_base.type;
|
||||
const bool has_backing = type == VMAType::Direct || type == VMAType::File;
|
||||
if (type == VMAType::Direct) {
|
||||
UnmapVulkanMemory(virtual_addr, size);
|
||||
rasterizer->UnmapMemory(virtual_addr, size);
|
||||
}
|
||||
if (type == VMAType::Flexible) {
|
||||
flexible_usage -= size;
|
||||
}
|
||||
|
||||
// Mark region as free and attempt to coalesce it with neighbours.
|
||||
auto& vma = it->second;
|
||||
const auto new_it = CarveVMA(virtual_addr, size);
|
||||
auto& vma = new_it->second;
|
||||
vma.type = VMAType::Free;
|
||||
vma.prot = MemoryProt::NoAccess;
|
||||
vma.phys_base = 0;
|
||||
MergeAdjacent(vma_map, it);
|
||||
vma.disallow_merge = false;
|
||||
vma.name = "";
|
||||
MergeAdjacent(vma_map, new_it);
|
||||
|
||||
// Unmap the memory region.
|
||||
impl.Unmap(virtual_addr, size, has_backing);
|
||||
impl.Unmap(vma_base_addr, vma_base_size, start_in_vma, start_in_vma + size, phys_base, is_exec,
|
||||
has_backing);
|
||||
TRACK_FREE(virtual_addr, "VMEM");
|
||||
}
|
||||
|
||||
|
@ -284,7 +269,7 @@ int MemoryManager::QueryProtection(VAddr addr, void** start, void** end, u32* pr
|
|||
}
|
||||
|
||||
int MemoryManager::VirtualQuery(VAddr addr, int flags,
|
||||
Libraries::Kernel::OrbisVirtualQueryInfo* info) {
|
||||
::Libraries::Kernel::OrbisVirtualQueryInfo* info) {
|
||||
std::scoped_lock lk{mutex};
|
||||
|
||||
auto it = FindVMA(addr);
|
||||
|
@ -302,6 +287,7 @@ int MemoryManager::VirtualQuery(VAddr addr, int flags,
|
|||
info->is_flexible.Assign(vma.type == VMAType::Flexible);
|
||||
info->is_direct.Assign(vma.type == VMAType::Direct);
|
||||
info->is_commited.Assign(vma.type != VMAType::Free);
|
||||
vma.name.copy(info->name.data(), std::min(info->name.size(), vma.name.size()));
|
||||
if (vma.type == VMAType::Direct) {
|
||||
const auto dmem_it = FindDmemArea(vma.phys_base);
|
||||
ASSERT(dmem_it != dmem_map.end());
|
||||
|
@ -313,7 +299,7 @@ int MemoryManager::VirtualQuery(VAddr addr, int flags,
|
|||
}
|
||||
|
||||
int MemoryManager::DirectMemoryQuery(PAddr addr, bool find_next,
|
||||
Libraries::Kernel::OrbisQueryInfo* out_info) {
|
||||
::Libraries::Kernel::OrbisQueryInfo* out_info) {
|
||||
std::scoped_lock lk{mutex};
|
||||
|
||||
auto dmem_area = FindDmemArea(addr);
|
||||
|
@ -353,21 +339,53 @@ int MemoryManager::DirectQueryAvailable(PAddr search_start, PAddr search_end, si
|
|||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
std::pair<vk::Buffer, size_t> MemoryManager::GetVulkanBuffer(VAddr addr) {
|
||||
auto it = mapped_memories.upper_bound(addr);
|
||||
it = std::prev(it);
|
||||
ASSERT(it != mapped_memories.end() && it->first <= addr);
|
||||
return std::make_pair(*it->second.buffer, addr - it->first);
|
||||
void MemoryManager::NameVirtualRange(VAddr virtual_addr, size_t size, std::string_view name) {
|
||||
auto it = FindVMA(virtual_addr);
|
||||
|
||||
ASSERT_MSG(it->second.Contains(virtual_addr, size),
|
||||
"Range provided is not fully containted in vma");
|
||||
it->second.name = name;
|
||||
}
|
||||
VAddr MemoryManager::SearchFree(VAddr virtual_addr, size_t size, u32 alignment) {
|
||||
// If the requested address is below the mapped range, start search from the lowest address
|
||||
auto min_search_address = impl.SystemManagedVirtualBase();
|
||||
if (virtual_addr < min_search_address) {
|
||||
virtual_addr = min_search_address;
|
||||
}
|
||||
|
||||
auto it = FindVMA(virtual_addr);
|
||||
ASSERT_MSG(it != vma_map.end(), "Specified mapping address was not found!");
|
||||
|
||||
// If the VMA is free and contains the requested mapping we are done.
|
||||
if (it->second.IsFree() && it->second.Contains(virtual_addr, size)) {
|
||||
return virtual_addr;
|
||||
}
|
||||
// Search for the first free VMA that fits our mapping.
|
||||
const auto is_suitable = [&] {
|
||||
if (!it->second.IsFree()) {
|
||||
return false;
|
||||
}
|
||||
const auto& vma = it->second;
|
||||
virtual_addr = Common::AlignUp(vma.base, alignment);
|
||||
// Sometimes the alignment itself might be larger than the VMA.
|
||||
if (virtual_addr > vma.base + vma.size) {
|
||||
return false;
|
||||
}
|
||||
const size_t remaining_size = vma.base + vma.size - virtual_addr;
|
||||
return remaining_size >= size;
|
||||
};
|
||||
while (!is_suitable()) {
|
||||
it++;
|
||||
}
|
||||
return virtual_addr;
|
||||
}
|
||||
|
||||
VirtualMemoryArea& MemoryManager::AddMapping(VAddr virtual_addr, size_t size) {
|
||||
MemoryManager::VMAHandle MemoryManager::CarveVMA(VAddr virtual_addr, size_t size) {
|
||||
auto vma_handle = FindVMA(virtual_addr);
|
||||
ASSERT_MSG(vma_handle != vma_map.end(), "Virtual address not in vm_map");
|
||||
|
||||
const VirtualMemoryArea& vma = vma_handle->second;
|
||||
ASSERT_MSG((vma.type == VMAType::Free || vma.type == VMAType::Reserved) &&
|
||||
vma.base <= virtual_addr,
|
||||
"Adding a mapping to already mapped region");
|
||||
ASSERT_MSG(vma.base <= virtual_addr, "Adding a mapping to already mapped region");
|
||||
|
||||
const VAddr start_in_vma = virtual_addr - vma.base;
|
||||
const VAddr end_in_vma = start_in_vma + size;
|
||||
|
@ -382,16 +400,15 @@ VirtualMemoryArea& MemoryManager::AddMapping(VAddr virtual_addr, size_t size) {
|
|||
vma_handle = Split(vma_handle, start_in_vma);
|
||||
}
|
||||
|
||||
return vma_handle->second;
|
||||
return vma_handle;
|
||||
}
|
||||
|
||||
DirectMemoryArea& MemoryManager::AddDmemAllocation(PAddr addr, size_t size) {
|
||||
MemoryManager::DMemHandle MemoryManager::CarveDmemArea(PAddr addr, size_t size) {
|
||||
auto dmem_handle = FindDmemArea(addr);
|
||||
ASSERT_MSG(dmem_handle != dmem_map.end(), "Physical address not in dmem_map");
|
||||
|
||||
const DirectMemoryArea& area = dmem_handle->second;
|
||||
ASSERT_MSG(area.is_free && area.base <= addr,
|
||||
"Adding an allocation to already allocated region");
|
||||
ASSERT_MSG(area.base <= addr, "Adding an allocation to already allocated region");
|
||||
|
||||
const PAddr start_in_area = addr - area.base;
|
||||
const PAddr end_in_vma = start_in_area + size;
|
||||
|
@ -406,7 +423,7 @@ DirectMemoryArea& MemoryManager::AddDmemAllocation(PAddr addr, size_t size) {
|
|||
dmem_handle = Split(dmem_handle, start_in_area);
|
||||
}
|
||||
|
||||
return dmem_handle->second;
|
||||
return dmem_handle;
|
||||
}
|
||||
|
||||
MemoryManager::VMAHandle MemoryManager::Split(VMAHandle vma_handle, size_t offset_in_vma) {
|
||||
|
@ -436,85 +453,6 @@ MemoryManager::DMemHandle MemoryManager::Split(DMemHandle dmem_handle, size_t of
|
|||
return dmem_map.emplace_hint(std::next(dmem_handle), new_area.base, new_area);
|
||||
};
|
||||
|
||||
void MemoryManager::MapVulkanMemory(VAddr addr, size_t size) {
|
||||
return;
|
||||
const vk::Device device = instance->GetDevice();
|
||||
const auto memory_props = instance->GetPhysicalDevice().getMemoryProperties();
|
||||
void* host_pointer = reinterpret_cast<void*>(addr);
|
||||
const auto host_mem_props = device.getMemoryHostPointerPropertiesEXT(
|
||||
vk::ExternalMemoryHandleTypeFlagBits::eHostAllocationEXT, host_pointer);
|
||||
ASSERT(host_mem_props.memoryTypeBits != 0);
|
||||
|
||||
int mapped_memory_type = -1;
|
||||
auto find_mem_type_with_flag = [&](const vk::MemoryPropertyFlags flags) {
|
||||
u32 host_mem_types = host_mem_props.memoryTypeBits;
|
||||
while (host_mem_types != 0) {
|
||||
// Try to find a cached memory type
|
||||
mapped_memory_type = std::countr_zero(host_mem_types);
|
||||
host_mem_types -= (1 << mapped_memory_type);
|
||||
|
||||
if ((memory_props.memoryTypes[mapped_memory_type].propertyFlags & flags) == flags) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mapped_memory_type = -1;
|
||||
};
|
||||
|
||||
// First try to find a memory that is both coherent and cached
|
||||
find_mem_type_with_flag(vk::MemoryPropertyFlagBits::eHostCoherent |
|
||||
vk::MemoryPropertyFlagBits::eHostCached);
|
||||
if (mapped_memory_type == -1)
|
||||
// Then only coherent (lower performance)
|
||||
find_mem_type_with_flag(vk::MemoryPropertyFlagBits::eHostCoherent);
|
||||
|
||||
if (mapped_memory_type == -1) {
|
||||
LOG_CRITICAL(Render_Vulkan, "No coherent memory available for memory mapping");
|
||||
mapped_memory_type = std::countr_zero(host_mem_props.memoryTypeBits);
|
||||
}
|
||||
|
||||
const vk::StructureChain alloc_info = {
|
||||
vk::MemoryAllocateInfo{
|
||||
.allocationSize = size,
|
||||
.memoryTypeIndex = static_cast<uint32_t>(mapped_memory_type),
|
||||
},
|
||||
vk::ImportMemoryHostPointerInfoEXT{
|
||||
.handleType = vk::ExternalMemoryHandleTypeFlagBits::eHostAllocationEXT,
|
||||
.pHostPointer = host_pointer,
|
||||
},
|
||||
};
|
||||
|
||||
const auto [it, new_memory] = mapped_memories.try_emplace(addr);
|
||||
ASSERT_MSG(new_memory, "Attempting to remap already mapped vulkan memory");
|
||||
|
||||
auto& memory = it->second;
|
||||
memory.backing = device.allocateMemoryUnique(alloc_info.get());
|
||||
|
||||
constexpr vk::BufferUsageFlags MapFlags =
|
||||
vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eVertexBuffer |
|
||||
vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eTransferDst |
|
||||
vk::BufferUsageFlagBits::eUniformBuffer | vk::BufferUsageFlagBits::eStorageBuffer;
|
||||
|
||||
const vk::StructureChain buffer_info = {
|
||||
vk::BufferCreateInfo{
|
||||
.size = size,
|
||||
.usage = MapFlags,
|
||||
.sharingMode = vk::SharingMode::eExclusive,
|
||||
},
|
||||
vk::ExternalMemoryBufferCreateInfoKHR{
|
||||
.handleTypes = vk::ExternalMemoryHandleTypeFlagBits::eHostAllocationEXT,
|
||||
}};
|
||||
memory.buffer = device.createBufferUnique(buffer_info.get());
|
||||
device.bindBufferMemory(*memory.buffer, *memory.backing, 0);
|
||||
}
|
||||
|
||||
void MemoryManager::UnmapVulkanMemory(VAddr addr, size_t size) {
|
||||
return;
|
||||
const auto it = mapped_memories.find(addr);
|
||||
ASSERT(it != mapped_memories.end() && it->second.buffer_size == size);
|
||||
mapped_memories.erase(it);
|
||||
}
|
||||
|
||||
int MemoryManager::GetDirectMemoryType(PAddr addr, int* directMemoryTypeOut,
|
||||
void** directMemoryStartOut, void** directMemoryEndOut) {
|
||||
std::scoped_lock lk{mutex};
|
||||
|
|
|
@ -3,20 +3,17 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <boost/icl/split_interval_map.hpp>
|
||||
#include "common/enum.h"
|
||||
#include "common/singleton.h"
|
||||
#include "common/types.h"
|
||||
#include "core/address_space.h"
|
||||
#include "core/libraries/kernel/memory_management.h"
|
||||
#include "video_core/renderer_vulkan/vk_common.h"
|
||||
|
||||
namespace Vulkan {
|
||||
class Instance;
|
||||
class Rasterizer;
|
||||
}
|
||||
|
||||
namespace Libraries::Kernel {
|
||||
|
@ -87,9 +84,18 @@ struct VirtualMemoryArea {
|
|||
bool disallow_merge = false;
|
||||
std::string name = "";
|
||||
uintptr_t fd = 0;
|
||||
bool is_exec = false;
|
||||
|
||||
bool Contains(VAddr addr, size_t size) const {
|
||||
return addr >= base && (addr + size) < (base + this->size);
|
||||
return addr >= base && (addr + size) <= (base + this->size);
|
||||
}
|
||||
|
||||
bool IsFree() const noexcept {
|
||||
return type == VMAType::Free;
|
||||
}
|
||||
|
||||
bool IsMapped() const noexcept {
|
||||
return type != VMAType::Free && type != VMAType::Reserved;
|
||||
}
|
||||
|
||||
bool CanMergeWith(const VirtualMemoryArea& next) const {
|
||||
|
@ -120,8 +126,8 @@ public:
|
|||
explicit MemoryManager();
|
||||
~MemoryManager();
|
||||
|
||||
void SetInstance(const Vulkan::Instance* instance_) {
|
||||
instance = instance_;
|
||||
void SetRasterizer(Vulkan::Rasterizer* rasterizer_) {
|
||||
rasterizer = rasterizer_;
|
||||
}
|
||||
|
||||
void SetTotalFlexibleSize(u64 size) {
|
||||
|
@ -132,9 +138,7 @@ public:
|
|||
return total_flexible_size - flexible_usage;
|
||||
}
|
||||
|
||||
/// Returns the offset of the mapped virtual system managed memory base from where it usually
|
||||
/// would be mapped.
|
||||
[[nodiscard]] VAddr SystemReservedVirtualBase() noexcept {
|
||||
VAddr SystemReservedVirtualBase() noexcept {
|
||||
return impl.SystemReservedVirtualBase();
|
||||
}
|
||||
|
||||
|
@ -164,11 +168,11 @@ public:
|
|||
int DirectQueryAvailable(PAddr search_start, PAddr search_end, size_t alignment,
|
||||
PAddr* phys_addr_out, size_t* size_out);
|
||||
|
||||
std::pair<vk::Buffer, size_t> GetVulkanBuffer(VAddr addr);
|
||||
|
||||
int GetDirectMemoryType(PAddr addr, int* directMemoryTypeOut, void** directMemoryStartOut,
|
||||
void** directMemoryEndOut);
|
||||
|
||||
void NameVirtualRange(VAddr virtual_addr, size_t size, std::string_view name);
|
||||
|
||||
private:
|
||||
VMAHandle FindVMA(VAddr target) {
|
||||
return std::prev(vma_map.upper_bound(target));
|
||||
|
@ -198,18 +202,16 @@ private:
|
|||
return iter;
|
||||
}
|
||||
|
||||
VirtualMemoryArea& AddMapping(VAddr virtual_addr, size_t size);
|
||||
VAddr SearchFree(VAddr virtual_addr, size_t size, u32 alignment = 0);
|
||||
|
||||
DirectMemoryArea& AddDmemAllocation(PAddr addr, size_t size);
|
||||
VMAHandle CarveVMA(VAddr virtual_addr, size_t size);
|
||||
|
||||
DMemHandle CarveDmemArea(PAddr addr, size_t size);
|
||||
|
||||
VMAHandle Split(VMAHandle vma_handle, size_t offset_in_vma);
|
||||
|
||||
DMemHandle Split(DMemHandle dmem_handle, size_t offset_in_area);
|
||||
|
||||
void MapVulkanMemory(VAddr addr, size_t size);
|
||||
|
||||
void UnmapVulkanMemory(VAddr addr, size_t size);
|
||||
|
||||
private:
|
||||
AddressSpace impl;
|
||||
DMemMap dmem_map;
|
||||
|
@ -217,14 +219,7 @@ private:
|
|||
std::recursive_mutex mutex;
|
||||
size_t total_flexible_size = 448_MB;
|
||||
size_t flexible_usage{};
|
||||
|
||||
struct MappedMemory {
|
||||
vk::UniqueBuffer buffer;
|
||||
vk::UniqueDeviceMemory backing;
|
||||
size_t buffer_size;
|
||||
};
|
||||
std::map<VAddr, MappedMemory> mapped_memories;
|
||||
const Vulkan::Instance* instance{};
|
||||
Vulkan::Rasterizer* rasterizer{};
|
||||
};
|
||||
|
||||
using Memory = Common::Singleton<MemoryManager>;
|
||||
|
|
|
@ -88,6 +88,7 @@ void Module::LoadModuleToMemory(u32& max_tls_index) {
|
|||
aligned_base_size + TrampolineSize, MemoryProt::CpuReadWrite,
|
||||
MemoryMapFlags::Fixed, VMAType::Code, name, true);
|
||||
LoadOffset += CODE_BASE_INCR * (1 + aligned_base_size / CODE_BASE_INCR);
|
||||
LOG_INFO(Core_Linker, "Loading module {} to {}", name, fmt::ptr(*out_addr));
|
||||
|
||||
// Initialize trampoline generator.
|
||||
void* trampoline_addr = std::bit_cast<void*>(base_virtual_addr + aligned_base_size);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue