Compare commits

..

30 Commits

Author SHA1 Message Date
2fe2dd1482 Merge branch 'master' into vk-fixes 2024-02-23 17:11:16 -08:00
4f9fc88bb3 apt: Improve accuracy of applet slot states on system applet launch. (#7456) 2024-02-23 16:18:16 -08:00
d857743075 Downgrade blend factor crash to warning (#7459)
* pica_to_vk: Downgrade assert to warning

* pica_to_gl: Downgrade unreachable to warning
2024-02-22 15:43:44 -08:00
b5042a5257 Core: update kernel config memory to latest 11.17 (#7460) 2024-02-22 15:43:33 -08:00
e524542a40 vk_texture_runtime: Use boost-static_vector (#7455)
* vk_texture_runtime: Use boost-`static_vector` for image init-barriers

Uses `static_vector` rather than `std::array`+`u32` when passing input
parameters into the initialization barriers.

* vk_texture_runtime: Use boost-`static_vector` for framebuffer attachments

* vk_texture_runtime: Use boost-`static_vector` for surface uploads
2024-02-22 02:35:57 +02:00
3a4ebb1413 file_util: Make sure portable user path is absolute. (#7448) 2024-02-18 15:21:53 -08:00
cbe8987036 ci: Update action versions. (#7449) 2024-02-18 08:23:15 -08:00
da5aa70fc9 android: Port yuzu system info logging (#7431) 2024-02-17 20:10:10 -08:00
749a721aa2 externals: disable system cpp-httplib if it is a shared object (#7446)
Co-authored-by: Castor216 <davidjamescastor215@proton.me>
2024-02-17 06:39:38 -08:00
bb003c2bd4 audio_core\hle\source.cpp: Improve accuracy of SourceStatus (#7432) 2024-02-17 02:12:54 +01:00
7638f87f74 Port several small multiplayer PRs from yuzu (#7419)
* yuzu: Use displayed port on direct connect

* Color player counts in the multiplayer public lobby list

- Full lobbies have their player count displayed in red.
- Lobbies with one slot left have their player count displayed in orange.
- Empty lobbies have their player count grayed out.

* Add hotkeys for multiplayer actions

Default shortcuts were chosen as to be intuitive (use the first letter
of the action, or the second word's first letter) and work on all
types of keyboards. The hotkeys can be used while playing a game too,
as they are application-wide.

* Persist filters in multiplayer public lobby list

After connecting to a room, the chosen filter text, "Games I Own",
"Hide Empty Rooms" and "Hide Full Rooms" values are persisted
to configuration so they are preserved across restarts.

This makes it easier to rejoin a room if you regularly play the same
game, or after a crash.

* citra_qt/lobby: Fix multiplayer player count color in dark theme

Co-Authored-By: Kevnkkm <56404895+kevnkkm@users.noreply.github.com>

* Address review comments

---------

Co-authored-by: Narr the Reg <juangerman-13@hotmail.com>
Co-authored-by: Hugo Locurcio <hugo.locurcio@hugo.pro>
Co-authored-by: Kevnkkm <56404895+kevnkkm@users.noreply.github.com>
2024-02-16 04:34:10 -08:00
aa6809e2a8 renderer_vulkan: Use no more than target supported version. (#7439) 2024-02-15 19:38:32 -08:00
5e02be75a3 renderer_vulkan: Use getToolPropertiesEXT instead of getToolProperties (#7434)
getToolProperties is not available until Vulkan 1.3; we need to use the EXT version.
2024-02-13 21:43:09 -08:00
0c9037f075 renderer_vulkan: Rewrite descriptor management
* Switch to batched vkUpdateDescriptorSets from cached descriptor sets with templates
2024-02-12 00:08:47 +02:00
4a63fc2ca2 renderer_vulkan: Rename renderpass cache to render manager
* It is no longer just a cache
2024-02-11 12:32:18 +02:00
9f5c8d0e2f renderer_vulkan: Remove vulkan prefix in SetObjectName 2024-02-11 12:32:08 +02:00
2bcbfeb861 vk_master_semaphore: Remove waitable atomic
* These are buggy on some platforms and regular condition_variables are faster most of the time
2024-02-11 12:31:59 +02:00
b9c9beeee5 android: add basic support for google game dashboard (#7430)
This adds support for the Performance and Battery Saver modes in the Game Dashboard mostly found on Google Pixel devices.
This does not yet define the specifics for the performance modes but does provide the initial basic support.

Co-authored-by: Emma <153868115+gaypotatoemma@users.noreply.github.com>
2024-02-10 17:24:10 -08:00
de993dcfbd service: Stub mcu::HWC (#7428) 2024-02-09 14:09:05 -08:00
3c9157b1ec fix ASAN error in sdl_impl.cpp (#7427) 2024-02-09 14:08:15 -08:00
0c40c10022 Update Android Deps (#7383) 2024-02-09 07:24:55 -05:00
2766118e33 http: Implement various missing commands (#7415) 2024-02-08 11:01:46 -08:00
06b26691ba soc: Pass accurate sockaddr length to socket functions. (#7426) 2024-02-08 11:01:38 -08:00
d41ce64f7b Add ipv6 socket support (#7418)
* Add IPV6 socket support

* Suggestions
2024-02-07 19:22:44 -08:00
1165a708d5 .tx/config: Use language mappings for android "tx pull" (#7422)
The language names we are using in the android resources differ from those on Transifex.

We need to manually specify mappings for them, so Transifex is able to place the files in the correct folders.
2024-02-07 05:41:29 -08:00
19784355f9 build: Improve support for Windows cross-compilation. (#7389)
* build: Improve support for Windows cross-compilation.

* build: Move linuxdeploy download to bundle target execution time.
2024-02-05 10:09:50 -08:00
aa6a29d7e1 AudioCore/HLE/source: Partially implement last_buffer_id (#7397)
* AudioCore/HLE/source: Partially implement last_buffer_id

shared_memory.h: fix typo

* tests\audio_core\hle\source.cpp: Add test cases to verify last_buffer_id
2024-02-05 09:54:13 -08:00
106364e01e video_core: Use source3 when GPU_PREVIOUS is used in first stage (#7411) 2024-02-05 09:53:54 -08:00
d5a1bd07f3 glsl_shader_gen: Increase z=0 epsillon (#7408) 2024-02-05 09:53:41 -08:00
8afa27718c dumpkeys: Add seeddb.bin to output files. (#7417) 2024-02-05 09:14:14 -08:00
287 changed files with 8699 additions and 9443 deletions

View File

@ -12,13 +12,13 @@ jobs:
if: ${{ !github.head_ref }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Pack
run: ./.ci/source.sh
- name: Upload
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: source
path: artifacts/
@ -37,11 +37,11 @@ jobs:
OS: linux
TARGET: ${{ matrix.target }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up cache
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
@ -53,7 +53,7 @@ jobs:
run: ./.ci/pack.sh
if: ${{ matrix.target == 'appimage' }}
- name: Upload
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: ${{ matrix.target == 'appimage' }}
with:
name: ${{ env.OS }}-${{ env.TARGET }}
@ -70,11 +70,11 @@ jobs:
OS: macos
TARGET: ${{ matrix.target }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up cache
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
@ -87,7 +87,7 @@ jobs:
- name: Prepare outputs for caching
run: mv build/bundle $OS-$TARGET
- name: Cache outputs for universal build
uses: actions/cache/save@v3
uses: actions/cache/save@v4
with:
path: ${{ env.OS }}-${{ env.TARGET }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
@ -98,15 +98,15 @@ jobs:
OS: macos
TARGET: universal
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Download x86_64 build from cache
uses: actions/cache/restore@v3
uses: actions/cache/restore@v4
with:
path: ${{ env.OS }}-x86_64
key: ${{ runner.os }}-x86_64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
fail-on-cache-miss: true
- name: Download ARM64 build from cache
uses: actions/cache/restore@v3
uses: actions/cache/restore@v4
with:
path: ${{ env.OS }}-arm64
key: ${{ runner.os }}-arm64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
@ -118,7 +118,7 @@ jobs:
- name: Pack
run: ./.ci/pack.sh
- name: Upload
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: artifacts/
@ -137,11 +137,11 @@ jobs:
OS: windows
TARGET: ${{ matrix.target }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up cache
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
@ -179,7 +179,7 @@ jobs:
- name: Pack
run: ./.ci/pack.sh
- name: Upload
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: artifacts/
@ -192,11 +192,11 @@ jobs:
OS: android
TARGET: universal
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up cache
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
@ -228,7 +228,7 @@ jobs:
env:
UNPACKED: 1
- name: Upload
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: src/android/app/artifacts/
@ -242,11 +242,11 @@ jobs:
OS: ios
TARGET: arm64
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up cache
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ runner.os }}-ios-${{ github.sha }}
@ -261,7 +261,7 @@ jobs:
needs: [windows, linux, macos-universal, android, source]
if: ${{ startsWith(github.ref, 'refs/tags/') }}
steps:
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
- name: Create release
uses: actions/create-release@v1
env:

View File

@ -13,7 +13,7 @@ jobs:
image: citraemu/build-environments:linux-fresh
options: -u 1001
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Build

View File

@ -20,11 +20,11 @@ jobs:
if: ${{ github.event.inputs.nightly != 'false' && github.repository == 'citra-emu/citra' }}
steps:
# this checkout is required to make sure the GitHub Actions scripts are available
- uses: actions/checkout@v3
- uses: actions/checkout@v4
name: Pre-checkout
with:
submodules: false
- uses: actions/github-script@v6
- uses: actions/github-script@v7
id: check-changes
name: 'Check for new changes'
env:
@ -38,7 +38,7 @@ jobs:
return checkBaseChanges(github, context);
- run: npm install execa@5
if: ${{ steps.check-changes.outputs.result == 'true' }}
- uses: actions/checkout@v3
- uses: actions/checkout@v4
name: Checkout
if: ${{ steps.check-changes.outputs.result == 'true' }}
with:
@ -46,7 +46,7 @@ jobs:
fetch-depth: 0
submodules: true
token: ${{ secrets.ALT_GITHUB_TOKEN }}
- uses: actions/github-script@v6
- uses: actions/github-script@v7
name: 'Update and tag new commits'
if: ${{ steps.check-changes.outputs.result == 'true' }}
env:
@ -62,11 +62,11 @@ jobs:
if: ${{ github.event.inputs.canary != 'false' && github.repository == 'citra-emu/citra' }}
steps:
# this checkout is required to make sure the GitHub Actions scripts are available
- uses: actions/checkout@v3
- uses: actions/checkout@v4
name: Pre-checkout
with:
submodules: false
- uses: actions/github-script@v6
- uses: actions/github-script@v7
id: check-changes
name: 'Check for new changes'
env:
@ -79,7 +79,7 @@ jobs:
return checkCanaryChanges(github, context);
- run: npm install execa@5
if: ${{ steps.check-changes.outputs.result == 'true' }}
- uses: actions/checkout@v3
- uses: actions/checkout@v4
name: Checkout
if: ${{ steps.check-changes.outputs.result == 'true' }}
with:
@ -87,7 +87,7 @@ jobs:
fetch-depth: 0
submodules: true
token: ${{ secrets.ALT_GITHUB_TOKEN }}
- uses: actions/github-script@v6
- uses: actions/github-script@v7
name: 'Check and merge canary changes'
if: ${{ steps.check-changes.outputs.result == 'true' }}
env:

View File

@ -10,7 +10,7 @@ jobs:
container: citraemu/build-environments:linux-fresh
if: ${{ github.repository == 'citra-emu/citra' }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0

View File

@ -85,8 +85,6 @@ option(ENABLE_VULKAN "Enables the Vulkan renderer" ON)
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
CMAKE_DEPENDENT_OPTION(CITRA_ENABLE_BUNDLE_TARGET "Enable the distribution bundling target." ON "NOT ANDROID AND NOT IOS" OFF)
# Compile options
CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ${IS_DEBUG_BUILD} "MINGW" OFF)
option(ENABLE_LTO "Enable link time optimization" ${DEFAULT_ENABLE_LTO})
@ -249,6 +247,26 @@ if (ENABLE_QT)
if (ENABLE_QT_TRANSLATION)
find_package(Qt6 REQUIRED COMPONENTS LinguistTools)
endif()
if (NOT DEFINED QT_TARGET_PATH)
# Determine the location of the compile target's Qt.
get_target_property(qtcore_path Qt6::Core LOCATION_Release)
string(FIND "${qtcore_path}" "/bin/" qtcore_path_bin_pos REVERSE)
string(FIND "${qtcore_path}" "/lib/" qtcore_path_lib_pos REVERSE)
if (qtcore_path_bin_pos GREATER qtcore_path_lib_pos)
string(SUBSTRING "${qtcore_path}" 0 ${qtcore_path_bin_pos} QT_TARGET_PATH)
else()
string(SUBSTRING "${qtcore_path}" 0 ${qtcore_path_lib_pos} QT_TARGET_PATH)
endif()
endif()
if (NOT DEFINED QT_HOST_PATH)
# Use the same for host Qt if none is defined.
set(QT_HOST_PATH "${QT_TARGET_PATH}")
endif()
message(STATUS "Using target Qt at ${QT_TARGET_PATH}")
message(STATUS "Using host Qt at ${QT_HOST_PATH}")
endif()
# Use system tsl::robin_map if available (otherwise we fallback to version bundled with dynarmic)
@ -424,7 +442,8 @@ else()
endif()
# Create target for outputting distributable bundles.
if (CITRA_ENABLE_BUNDLE_TARGET)
# Not supported for mobile platforms as distributables are built differently.
if (NOT ANDROID AND NOT IOS)
include(BundleTarget)
if (ENABLE_SDL2_FRONTEND)
bundle_target(citra)

View File

@ -2,37 +2,104 @@
if (BUNDLE_TARGET_EXECUTE)
# --- Bundling method logic ---
function(symlink_safe_copy from to)
if (WIN32)
# Use cmake copy for maximum compatibility.
execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${from}" "${to}"
RESULT_VARIABLE cp_result)
else()
# Use native copy to turn symlinks into normal files.
execute_process(COMMAND cp -L "${from}" "${to}"
RESULT_VARIABLE cp_result)
endif()
if (NOT cp_result EQUAL "0")
message(FATAL_ERROR "cp \"${from}\" \"${to}\" failed: ${cp_result}")
endif()
endfunction()
function(bundle_qt executable_path)
if (WIN32)
# Perform standalone bundling first to copy over all used libraries, as windeployqt does not do this.
bundle_standalone("${executable_path}" "${EXECUTABLE_PATH}" "${BUNDLE_LIBRARY_PATHS}")
get_filename_component(executable_parent_dir "${executable_path}" DIRECTORY)
find_program(windeployqt_executable windeployqt6)
# Create a qt.conf file pointing to the app directory.
# This ensures Qt can find its plugins.
file(WRITE "${executable_parent_dir}/qt.conf" "[Paths]\nprefix = .")
file(WRITE "${executable_parent_dir}/qt.conf" "[Paths]\nPrefix = .")
find_program(windeployqt_executable windeployqt6 PATHS "${QT_HOST_PATH}/bin")
find_program(qtpaths_executable qtpaths6 PATHS "${QT_HOST_PATH}/bin")
# TODO: Hack around windeployqt's poor cross-compilation support by
# TODO: making a local copy with a prefix pointing to the target Qt.
if (NOT "${QT_HOST_PATH}" STREQUAL "${QT_TARGET_PATH}")
set(windeployqt_dir "${BINARY_PATH}/windeployqt_copy")
file(MAKE_DIRECTORY "${windeployqt_dir}")
symlink_safe_copy("${windeployqt_executable}" "${windeployqt_dir}/windeployqt.exe")
symlink_safe_copy("${qtpaths_executable}" "${windeployqt_dir}/qtpaths.exe")
symlink_safe_copy("${QT_HOST_PATH}/bin/Qt6Core.dll" "${windeployqt_dir}")
if (EXISTS "${QT_TARGET_PATH}/share")
# Unix-style Qt; we need to wire up the paths manually.
file(WRITE "${windeployqt_dir}/qt.conf" "\
[Paths]\n
Prefix = ${QT_TARGET_PATH}\n \
ArchData = ${QT_TARGET_PATH}/share/qt6\n \
Binaries = ${QT_TARGET_PATH}/bin\n \
Data = ${QT_TARGET_PATH}/share/qt6\n \
Documentation = ${QT_TARGET_PATH}/share/qt6/doc\n \
Headers = ${QT_TARGET_PATH}/include/qt6\n \
Libraries = ${QT_TARGET_PATH}/lib\n \
LibraryExecutables = ${QT_TARGET_PATH}/share/qt6/bin\n \
Plugins = ${QT_TARGET_PATH}/share/qt6/plugins\n \
QmlImports = ${QT_TARGET_PATH}/share/qt6/qml\n \
Translations = ${QT_TARGET_PATH}/share/qt6/translations\n \
")
else()
# Windows-style Qt; the defaults should suffice.
file(WRITE "${windeployqt_dir}/qt.conf" "[Paths]\nPrefix = ${QT_TARGET_PATH}")
endif()
set(windeployqt_executable "${windeployqt_dir}/windeployqt.exe")
set(qtpaths_executable "${windeployqt_dir}/qtpaths.exe")
endif()
message(STATUS "Executing windeployqt for executable ${executable_path}")
execute_process(COMMAND "${windeployqt_executable}" "${executable_path}"
--qtpaths "${qtpaths_executable}"
--no-compiler-runtime --no-system-d3d-compiler --no-opengl-sw --no-translations
--plugindir "${executable_parent_dir}/plugins")
--plugindir "${executable_parent_dir}/plugins"
RESULT_VARIABLE windeployqt_result)
if (NOT windeployqt_result EQUAL "0")
message(FATAL_ERROR "windeployqt failed: ${windeployqt_result}")
endif()
# Remove the FFmpeg multimedia plugin as we don't include FFmpeg.
# We want to use the Windows media plugin instead, which is also included.
file(REMOVE "${executable_parent_dir}/plugins/multimedia/ffmpegmediaplugin.dll")
elseif (APPLE)
get_filename_component(executable_name "${executable_path}" NAME_WE)
find_program(MACDEPLOYQT_EXECUTABLE macdeployqt6)
find_program(macdeployqt_executable macdeployqt6 PATHS "${QT_HOST_PATH}/bin")
message(STATUS "Executing macdeployqt for executable ${executable_path}")
message(STATUS "Executing macdeployqt at \"${macdeployqt_executable}\" for executable \"${executable_path}\"")
execute_process(
COMMAND "${MACDEPLOYQT_EXECUTABLE}"
COMMAND "${macdeployqt_executable}"
"${executable_path}"
"-executable=${executable_path}/Contents/MacOS/${executable_name}"
-always-overwrite)
-always-overwrite
RESULT_VARIABLE macdeployqt_result)
if (NOT macdeployqt_result EQUAL "0")
message(FATAL_ERROR "macdeployqt failed: ${macdeployqt_result}")
endif()
# Bundling libraries can rewrite path information and break code signatures of system libraries.
# Perform an ad-hoc re-signing on the whole app bundle to fix this.
execute_process(COMMAND codesign --deep -fs - "${executable_path}")
execute_process(COMMAND codesign --deep -fs - "${executable_path}"
RESULT_VARIABLE codesign_result)
if (NOT codesign_result EQUAL "0")
message(FATAL_ERROR "codesign failed: ${codesign_result}")
endif()
else()
message(FATAL_ERROR "Unsupported OS for Qt bundling.")
endif()
@ -44,9 +111,9 @@ if (BUNDLE_TARGET_EXECUTE)
if (enable_qt)
# Find qmake to make sure the plugin uses the right version of Qt.
find_program(QMAKE_EXECUTABLE qmake6)
find_program(qmake_executable qmake6 PATHS "${QT_HOST_PATH}/bin")
set(extra_linuxdeploy_env "QMAKE=${QMAKE_EXECUTABLE}")
set(extra_linuxdeploy_env "QMAKE=${qmake_executable}")
set(extra_linuxdeploy_args --plugin qt)
endif()
@ -59,7 +126,11 @@ if (BUNDLE_TARGET_EXECUTE)
--executable "${executable_path}"
--icon-file "${source_path}/dist/citra.svg"
--desktop-file "${source_path}/dist/${executable_name}.desktop"
--appdir "${appdir_path}")
--appdir "${appdir_path}"
RESULT_VARIABLE linuxdeploy_appdir_result)
if (NOT linuxdeploy_appdir_result EQUAL "0")
message(FATAL_ERROR "linuxdeploy failed to create AppDir: ${linuxdeploy_appdir_result}")
endif()
if (enable_qt)
set(qt_hook_file "${appdir_path}/apprun-hooks/linuxdeploy-plugin-qt-hook.sh")
@ -82,7 +153,11 @@ if (BUNDLE_TARGET_EXECUTE)
"OUTPUT=${bundle_dir}/${executable_name}.AppImage"
"${linuxdeploy_executable}"
--output appimage
--appdir "${appdir_path}")
--appdir "${appdir_path}"
RESULT_VARIABLE linuxdeploy_appimage_result)
if (NOT linuxdeploy_appimage_result EQUAL "0")
message(FATAL_ERROR "linuxdeploy failed to create AppImage: ${linuxdeploy_appimage_result}")
endif()
endfunction()
function(bundle_standalone executable_path original_executable_path bundle_library_paths)
@ -109,16 +184,23 @@ if (BUNDLE_TARGET_EXECUTE)
file(MAKE_DIRECTORY ${lib_dir})
foreach (lib_file IN LISTS resolved_deps)
message(STATUS "Bundling library ${lib_file}")
# Use native copy to turn symlinks into normal files.
execute_process(COMMAND cp -L "${lib_file}" "${lib_dir}")
symlink_safe_copy("${lib_file}" "${lib_dir}")
endforeach()
endif()
# Add libs directory to executable rpath where applicable.
if (APPLE)
execute_process(COMMAND install_name_tool -add_rpath "@loader_path/libs" "${executable_path}")
execute_process(COMMAND install_name_tool -add_rpath "@loader_path/libs" "${executable_path}"
RESULT_VARIABLE install_name_tool_result)
if (NOT install_name_tool_result EQUAL "0")
message(FATAL_ERROR "install_name_tool failed: ${install_name_tool_result}")
endif()
elseif (UNIX)
execute_process(COMMAND patchelf --set-rpath '$ORIGIN/../libs' "${executable_path}")
execute_process(COMMAND patchelf --set-rpath '$ORIGIN/../libs' "${executable_path}"
RESULT_VARIABLE patchelf_result)
if (NOT patchelf_result EQUAL "0")
message(FATAL_ERROR "patchelf failed: ${patchelf_result}")
endif()
endif()
endfunction()
@ -127,7 +209,7 @@ if (BUNDLE_TARGET_EXECUTE)
set(bundle_dir ${BINARY_PATH}/bundle)
# On Linux, always bundle an AppImage.
if (DEFINED LINUXDEPLOY)
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
if (IN_PLACE)
message(FATAL_ERROR "Cannot bundle for Linux in-place.")
endif()
@ -146,14 +228,12 @@ if (BUNDLE_TARGET_EXECUTE)
if (BUNDLE_QT)
bundle_qt("${bundled_executable_path}")
endif()
if (WIN32 OR NOT BUNDLE_QT)
else()
bundle_standalone("${bundled_executable_path}" "${EXECUTABLE_PATH}" "${BUNDLE_LIBRARY_PATHS}")
endif()
endif()
else()
# --- Bundling target creation logic ---
elseif (BUNDLE_TARGET_DOWNLOAD_LINUXDEPLOY)
# --- linuxdeploy download logic ---
# Downloads and extracts a linuxdeploy component.
function(download_linuxdeploy_component base_dir name executable_name)
@ -161,7 +241,7 @@ else()
if (NOT EXISTS "${executable_file}")
message(STATUS "Downloading ${executable_name}")
file(DOWNLOAD
"https://github.com/linuxdeploy/${name}/releases/download/continuous/${executable_name}"
"https://github.com/${name}/releases/download/continuous/${executable_name}"
"${executable_file}" SHOW_PROGRESS)
file(CHMOD "${executable_file}" PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE)
@ -170,7 +250,11 @@ else()
message(STATUS "Extracting ${executable_name}")
execute_process(
COMMAND "${executable_file}" --appimage-extract
WORKING_DIRECTORY "${base_dir}")
WORKING_DIRECTORY "${base_dir}"
RESULT_VARIABLE extract_result)
if (NOT extract_result EQUAL "0")
message(FATAL_ERROR "AppImage extract failed: ${extract_result}")
endif()
else()
message(STATUS "Copying ${executable_name}")
file(COPY "${executable_file}" DESTINATION "${base_dir}/squashfs-root/usr/bin/")
@ -178,89 +262,102 @@ else()
endif()
endfunction()
# Download plugins first so they don't overwrite linuxdeploy's AppRun file.
download_linuxdeploy_component("${LINUXDEPLOY_PATH}" "linuxdeploy/linuxdeploy-plugin-qt" "linuxdeploy-plugin-qt-${LINUXDEPLOY_ARCH}.AppImage")
download_linuxdeploy_component("${LINUXDEPLOY_PATH}" "darealshinji/linuxdeploy-plugin-checkrt" "linuxdeploy-plugin-checkrt.sh")
download_linuxdeploy_component("${LINUXDEPLOY_PATH}" "linuxdeploy/linuxdeploy" "linuxdeploy-${LINUXDEPLOY_ARCH}.AppImage")
else()
# --- Bundling target creation logic ---
# Creates the base bundle target with common files and pre-bundle steps.
function(create_base_bundle_target)
message(STATUS "Creating base bundle target")
add_custom_target(bundle)
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bundle/")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bundle/dist/")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/dist/icon.png" "${CMAKE_BINARY_DIR}/bundle/dist/citra.png")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/license.txt" "${CMAKE_BINARY_DIR}/bundle/")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/README.md" "${CMAKE_BINARY_DIR}/bundle/")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/dist/scripting" "${CMAKE_BINARY_DIR}/bundle/scripting")
# On Linux, add a command to prepare linuxdeploy and any required plugins before any bundling occurs.
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND}
"-DBUNDLE_TARGET_DOWNLOAD_LINUXDEPLOY=1"
"-DLINUXDEPLOY_PATH=${CMAKE_BINARY_DIR}/externals/linuxdeploy"
"-DLINUXDEPLOY_ARCH=${CMAKE_HOST_SYSTEM_PROCESSOR}"
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
endif()
endfunction()
# Adds a target to the bundle target, packing in required libraries.
# If in_place is true, the bundling will be done in-place as part of the specified target.
function(bundle_target_internal target_name in_place)
# Create base bundle target if it does not exist.
if (NOT in_place AND NOT TARGET bundle)
message(STATUS "Creating base bundle target")
add_custom_target(bundle)
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bundle/")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bundle/dist/")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/dist/icon.png" "${CMAKE_BINARY_DIR}/bundle/dist/citra.png")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/license.txt" "${CMAKE_BINARY_DIR}/bundle/")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/README.md" "${CMAKE_BINARY_DIR}/bundle/")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/dist/scripting" "${CMAKE_BINARY_DIR}/bundle/scripting")
create_base_bundle_target()
endif()
set(BUNDLE_EXECUTABLE_PATH "$<TARGET_FILE:${target_name}>")
set(bundle_executable_path "$<TARGET_FILE:${target_name}>")
if (target_name MATCHES ".*qt")
set(BUNDLE_QT ON)
set(bundle_qt ON)
if (APPLE)
# For Qt targets on Apple, expect an app bundle.
set(BUNDLE_EXECUTABLE_PATH "$<TARGET_BUNDLE_DIR:${target_name}>")
set(bundle_executable_path "$<TARGET_BUNDLE_DIR:${target_name}>")
endif()
else()
set(BUNDLE_QT OFF)
set(bundle_qt OFF)
endif()
# Build a list of library search paths from prefix paths.
foreach(prefix_path IN LISTS CMAKE_PREFIX_PATH CMAKE_SYSTEM_PREFIX_PATH)
foreach(prefix_path IN LISTS CMAKE_FIND_ROOT_PATH CMAKE_PREFIX_PATH CMAKE_SYSTEM_PREFIX_PATH)
if (WIN32)
list(APPEND BUNDLE_LIBRARY_PATHS "${prefix_path}/bin")
list(APPEND bundle_library_paths "${prefix_path}/bin")
endif()
list(APPEND BUNDLE_LIBRARY_PATHS "${prefix_path}/lib")
list(APPEND bundle_library_paths "${prefix_path}/lib")
endforeach()
foreach(library_path IN LISTS CMAKE_SYSTEM_LIBRARY_PATH)
list(APPEND BUNDLE_LIBRARY_PATHS "${library_path}")
list(APPEND bundle_library_paths "${library_path}")
endforeach()
# On Linux, prepare linuxdeploy and any required plugins.
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(LINUXDEPLOY_BASE "${CMAKE_BINARY_DIR}/externals/linuxdeploy")
# Download plugins first so they don't overwrite linuxdeploy's AppRun file.
download_linuxdeploy_component("${LINUXDEPLOY_BASE}" "linuxdeploy-plugin-qt" "linuxdeploy-plugin-qt-x86_64.AppImage")
download_linuxdeploy_component("${LINUXDEPLOY_BASE}" "linuxdeploy-plugin-checkrt" "linuxdeploy-plugin-checkrt-x86_64.sh")
download_linuxdeploy_component("${LINUXDEPLOY_BASE}" "linuxdeploy" "linuxdeploy-x86_64.AppImage")
set(EXTRA_BUNDLE_ARGS "-DLINUXDEPLOY=${LINUXDEPLOY_BASE}/squashfs-root/AppRun")
endif()
if (in_place)
message(STATUS "Adding in-place bundling to ${target_name}")
set(DEST_TARGET ${target_name})
set(dest_target ${target_name})
else()
message(STATUS "Adding ${target_name} to bundle target")
set(DEST_TARGET bundle)
set(dest_target bundle)
add_dependencies(bundle ${target_name})
endif()
add_custom_command(TARGET ${DEST_TARGET} POST_BUILD
add_custom_command(TARGET ${dest_target} POST_BUILD
COMMAND ${CMAKE_COMMAND}
"-DCMAKE_PREFIX_PATH=\"${CMAKE_PREFIX_PATH}\""
"-DQT_HOST_PATH=\"${QT_HOST_PATH}\""
"-DQT_TARGET_PATH=\"${QT_TARGET_PATH}\""
"-DBUNDLE_TARGET_EXECUTE=1"
"-DTARGET=${target_name}"
"-DSOURCE_PATH=${CMAKE_SOURCE_DIR}"
"-DBINARY_PATH=${CMAKE_BINARY_DIR}"
"-DEXECUTABLE_PATH=${BUNDLE_EXECUTABLE_PATH}"
"-DBUNDLE_LIBRARY_PATHS=\"${BUNDLE_LIBRARY_PATHS}\""
"-DBUNDLE_QT=${BUNDLE_QT}"
"-DEXECUTABLE_PATH=${bundle_executable_path}"
"-DBUNDLE_LIBRARY_PATHS=\"${bundle_library_paths}\""
"-DBUNDLE_QT=${bundle_qt}"
"-DIN_PLACE=${in_place}"
${EXTRA_BUNDLE_ARGS}
"-DLINUXDEPLOY=${CMAKE_BINARY_DIR}/externals/linuxdeploy/squashfs-root/AppRun"
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
endfunction()

View File

@ -1,21 +1,20 @@
set(CURRENT_MODULE_DIR ${CMAKE_CURRENT_LIST_DIR})
# This function downloads Qt using aqt. The path of the downloaded content will be added to the CMAKE_PREFIX_PATH.
# Params:
# target: Qt dependency to install. Specify a version number to download Qt, or "tools_(name)" for a specific build tool.
function(download_qt target)
# Determines parameters based on the host and target for downloading the right Qt binaries.
function(determine_qt_parameters target host_out type_out arch_out arch_path_out host_type_out host_arch_out host_arch_path_out)
if (target MATCHES "tools_.*")
set(DOWNLOAD_QT_TOOL ON)
set(tool ON)
else()
set(DOWNLOAD_QT_TOOL OFF)
set(tool OFF)
endif()
# Determine installation parameters for OS, architecture, and compiler
if (WIN32)
set(host "windows")
set(type "desktop")
if (NOT DOWNLOAD_QT_TOOL)
if (NOT tool)
if (MINGW)
set(arch "win64_mingw")
set(arch_path "mingw_64")
@ -28,21 +27,35 @@ function(download_qt target)
message(FATAL_ERROR "Unsupported bundled Qt architecture. Enable USE_SYSTEM_QT and provide your own.")
endif()
set(arch "win64_${arch_path}")
# In case we're cross-compiling, prepare to also fetch the correct host Qt tools.
if (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "AMD64")
set(host_arch_path "msvc2019_64")
elseif (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "ARM64")
# TODO: msvc2019_arm64 doesn't include some of the required tools for some reason,
# TODO: so until it does, just use msvc2019_64 under x86_64 emulation.
# set(host_arch_path "msvc2019_arm64")
set(host_arch_path "msvc2019_64")
endif()
set(host_arch "win64_${host_arch_path}")
else()
message(FATAL_ERROR "Unsupported bundled Qt toolchain. Enable USE_SYSTEM_QT and provide your own.")
endif()
endif()
elseif (APPLE)
set(host "mac")
if (IOS AND NOT DOWNLOAD_QT_TOOL)
set(type "desktop")
set(arch "clang_64")
set(arch_path "macos")
if (IOS AND NOT tool)
set(host_type "${type}")
set(host_arch "${arch}")
set(host_arch_path "${arch_path}")
set(type "ios")
set(arch "ios")
set(arch_path "ios")
set(host_arch_path "macos")
else()
set(type "desktop")
set(arch "clang_64")
set(arch_path "macos")
endif()
else()
set(host "linux")
@ -51,38 +64,64 @@ function(download_qt target)
set(arch_path "linux")
endif()
get_external_prefix(qt base_path)
file(MAKE_DIRECTORY "${base_path}")
set(${host_out} "${host}" PARENT_SCOPE)
set(${type_out} "${type}" PARENT_SCOPE)
set(${arch_out} "${arch}" PARENT_SCOPE)
set(${arch_path_out} "${arch_path}" PARENT_SCOPE)
if (DEFINED host_type)
set(${host_type_out} "${host_type}" PARENT_SCOPE)
else()
set(${host_type_out} "${type}" PARENT_SCOPE)
endif()
if (DEFINED host_arch)
set(${host_arch_out} "${host_arch}" PARENT_SCOPE)
else()
set(${host_arch_out} "${arch}" PARENT_SCOPE)
endif()
if (DEFINED host_arch_path)
set(${host_arch_path_out} "${host_arch_path}" PARENT_SCOPE)
else()
set(${host_arch_path_out} "${arch_path}" PARENT_SCOPE)
endif()
endfunction()
# Download Qt binaries for a specifc configuration.
function(download_qt_configuration prefix_out target host type arch arch_path base_path)
if (target MATCHES "tools_.*")
set(tool ON)
else()
set(tool OFF)
endif()
set(install_args -c "${CURRENT_MODULE_DIR}/aqt_config.ini")
if (DOWNLOAD_QT_TOOL)
if (tool)
set(prefix "${base_path}/Tools")
set(install_args ${install_args} install-tool --outputdir ${base_path} ${host} desktop ${target})
else()
set(prefix "${base_path}/${target}/${arch_path}")
if (host_arch_path)
set(host_flag "--autodesktop")
set(host_prefix "${base_path}/${target}/${host_arch_path}")
endif()
set(install_args ${install_args} install-qt --outputdir ${base_path} ${host} ${type} ${target} ${arch} ${host_flag}
-m qtmultimedia --archives qttranslations qttools qtsvg qtbase)
set(install_args ${install_args} install-qt --outputdir ${base_path} ${host} ${type} ${target} ${arch}
-m qtmultimedia --archives qttranslations qttools qtsvg qtbase)
endif()
if (NOT EXISTS "${prefix}")
message(STATUS "Downloading binaries for Qt...")
message(STATUS "Downloading Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path}")
set(AQT_PREBUILD_BASE_URL "https://github.com/miurahr/aqtinstall/releases/download/v3.1.9")
if (WIN32)
set(aqt_path "${base_path}/aqt.exe")
file(DOWNLOAD
${AQT_PREBUILD_BASE_URL}/aqt.exe
${aqt_path} SHOW_PROGRESS)
if (NOT EXISTS "${aqt_path}")
file(DOWNLOAD
${AQT_PREBUILD_BASE_URL}/aqt.exe
${aqt_path} SHOW_PROGRESS)
endif()
execute_process(COMMAND ${aqt_path} ${install_args}
WORKING_DIRECTORY ${base_path})
elseif (APPLE)
set(aqt_path "${base_path}/aqt-macos")
file(DOWNLOAD
${AQT_PREBUILD_BASE_URL}/aqt-macos
${aqt_path} SHOW_PROGRESS)
if (NOT EXISTS "${aqt_path}")
file(DOWNLOAD
${AQT_PREBUILD_BASE_URL}/aqt-macos
${aqt_path} SHOW_PROGRESS)
endif()
execute_process(COMMAND chmod +x ${aqt_path})
execute_process(COMMAND ${aqt_path} ${install_args}
WORKING_DIRECTORY ${base_path})
@ -96,18 +135,38 @@ function(download_qt target)
execute_process(COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${aqt_install_path} python3 -m aqt ${install_args}
WORKING_DIRECTORY ${base_path})
endif()
message(STATUS "Downloaded Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path} to ${prefix}")
endif()
message(STATUS "Using downloaded Qt binaries at ${prefix}")
set(${prefix_out} "${prefix}" PARENT_SCOPE)
endfunction()
# Add the Qt prefix path so CMake can locate it.
# This function downloads Qt using aqt.
# The path of the downloaded content will be added to the CMAKE_PREFIX_PATH.
# QT_TARGET_PATH is set to the Qt for the compile target platform.
# QT_HOST_PATH is set to a host-compatible Qt, for running tools.
# Params:
# target: Qt dependency to install. Specify a version number to download Qt, or "tools_(name)" for a specific build tool.
function(download_qt target)
determine_qt_parameters("${target}" host type arch arch_path host_type host_arch host_arch_path)
get_external_prefix(qt base_path)
file(MAKE_DIRECTORY "${base_path}")
download_qt_configuration(prefix "${target}" "${host}" "${type}" "${arch}" "${arch_path}" "${base_path}")
if (DEFINED host_arch_path AND NOT "${host_arch_path}" STREQUAL "${arch_path}")
download_qt_configuration(host_prefix "${target}" "${host}" "${host_type}" "${host_arch}" "${host_arch_path}" "${base_path}")
else()
set(host_prefix "${prefix}")
endif()
set(QT_TARGET_PATH "${prefix}" CACHE STRING "")
set(QT_HOST_PATH "${host_prefix}" CACHE STRING "")
# Add the target Qt prefix path so CMake can locate it.
list(APPEND CMAKE_PREFIX_PATH "${prefix}")
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE)
if (DEFINED host_prefix)
message(STATUS "Using downloaded host Qt binaries at ${host_prefix}")
set(QT_HOST_PATH "${host_prefix}" CACHE STRING "")
endif()
endfunction()
function(download_moltenvk)

View File

@ -287,5 +287,13 @@ dumptxt -p $[OUT] "nfcSecret1Seed=$[NFC_SEED_1]"
dumptxt -p $[OUT] "nfcSecret1HmacKey=$[NFC_HMAC_KEY_1]"
dumptxt -p $[OUT] "nfcIv=$[NFC_IV]"
# Dump seeddb.bin as well
set SEEDDB_IN "0:/gm9/out/seeddb.bin"
set SEEDDB_OUT "0:/gm9/seeddb.bin"
sdump -w seeddb.bin
cp -w $[SEEDDB_IN] $[SEEDDB_OUT]
@Exit

View File

@ -6,5 +6,5 @@ Usage:
1. Copy "DumpKeys.gm9" into the "gm9/scripts/" directory on your SD card.
2. Launch GodMode9, press the HOME button, select Scripts, and select "DumpKeys" from the list of scripts that appears.
3. Wait for the script to complete and return you to the GodMode9 main menu.
4. Power off your system and copy the "gm9/aes_keys.txt" file off of your SD card into "(Citra directory)/sysdata/".
4. Power off your system and copy the "gm9/aes_keys.txt" and "gm9/seeddb.bin" files off of your SD card into "(Citra directory)/sysdata/".

View File

@ -11,3 +11,4 @@ type = QT
file_filter = ../../src/android/app/src/main/res/values-<lang>/strings.xml
source_file = ../../src/android/app/src/main/res/values/strings.xml
type = ANDROID
lang_map = es_ES:es, hu_HU:hu, ru_RU:ru, pt_BR:pt, zh_CN:zh

View File

@ -57,6 +57,12 @@ if(USE_SYSTEM_CRYPTOPP)
add_library(cryptopp INTERFACE)
target_link_libraries(cryptopp INTERFACE cryptopp::cryptopp)
else()
if (WIN32 AND NOT MSVC AND "arm64" IN_LIST ARCHITECTURE)
# TODO: CryptoPP ARM64 ASM does not seem to support Windows unless compiled with MSVC.
# TODO: See https://github.com/weidai11/cryptopp/issues/1260
set(CRYPTOPP_DISABLE_ASM ON CACHE BOOL "")
endif()
set(CRYPTOPP_BUILD_DOCUMENTATION OFF CACHE BOOL "")
set(CRYPTOPP_BUILD_TESTING OFF CACHE BOOL "")
set(CRYPTOPP_INSTALL OFF CACHE BOOL "")
@ -235,6 +241,18 @@ endif()
# DiscordRPC
if (USE_DISCORD_PRESENCE)
# rapidjson used by discord-rpc is old and doesn't correctly detect endianness for some platforms.
include(TestBigEndian)
test_big_endian(RAPIDJSON_BIG_ENDIAN)
if(RAPIDJSON_BIG_ENDIAN)
add_compile_definitions(RAPIDJSON_ENDIAN=1)
else()
add_compile_definitions(RAPIDJSON_ENDIAN=0)
endif()
# Apply a dummy CLANG_FORMAT_SUFFIX to disable discord-rpc's unnecessary automatic clang-format.
set(CLANG_FORMAT_SUFFIX "dummy")
add_subdirectory(discord-rpc EXCLUDE_FROM_ALL)
target_include_directories(discord-rpc INTERFACE ./discord-rpc/include)
endif()
@ -276,11 +294,20 @@ endif()
add_library(httplib INTERFACE)
if(USE_SYSTEM_CPP_HTTPLIB)
find_package(CppHttp 0.14.1)
if(CppHttp_FOUND)
target_link_libraries(httplib INTERFACE httplib::httplib)
else()
message(STATUS "Cpp-httplib not found or not suitable version! Falling back to bundled...")
# Detect if system cpphttplib is a shared library
# this breaks building as Citra relies on functions that are moved
# into the shared object.
get_target_property(HTTP_LIBS httplib::httplib INTERFACE_LINK_LIBRARIES)
if(HTTP_LIBS)
message(WARNING "Shared cpp-http (${HTTP_LIBS}) not supported. Falling back to bundled...")
target_include_directories(httplib SYSTEM INTERFACE ./httplib)
else()
if(CppHttp_FOUND)
target_link_libraries(httplib INTERFACE httplib::httplib)
else()
message(STATUS "Cpp-httplib not found or not suitable version! Falling back to bundled...")
target_include_directories(httplib SYSTEM INTERFACE ./httplib)
endif()
endif()
else()
target_include_directories(httplib SYSTEM INTERFACE ./httplib)

View File

@ -10,7 +10,7 @@ plugins {
id("org.jetbrains.kotlin.android")
id("de.undercouch.download") version "5.5.0"
id("kotlin-parcelize")
kotlin("plugin.serialization") version "1.8.21"
kotlin("plugin.serialization") version "1.9.22"
id("androidx.navigation.safeargs.kotlin")
}
@ -173,23 +173,23 @@ android {
dependencies {
implementation("androidx.recyclerview:recyclerview:1.3.2")
implementation("androidx.activity:activity-ktx:1.8.0")
implementation("androidx.activity:activity-ktx:1.8.2")
implementation("androidx.fragment:fragment-ktx:1.6.2")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.documentfile:documentfile:1.0.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
implementation("androidx.slidingpanelayout:slidingpanelayout:1.2.0")
implementation("com.google.android.material:material:1.9.0")
implementation("androidx.core:core-splashscreen:1.0.1")
implementation("androidx.work:work-runtime:2.8.1")
implementation("androidx.work:work-runtime:2.9.0")
implementation("org.ini4j:ini4j:0.5.4")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("androidx.navigation:navigation-fragment-ktx:2.7.5")
implementation("androidx.navigation:navigation-ui-ktx:2.7.5")
implementation("androidx.navigation:navigation-fragment-ktx:2.7.6")
implementation("androidx.navigation:navigation-ui-ktx:2.7.6")
implementation("info.debatty:java-string-similarity:2.0.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
implementation("androidx.preference:preference-ktx:1.2.1")
implementation("io.coil-kt:coil:2.2.2")
implementation("io.coil-kt:coil:2.5.0")
}
// Download Vulkan Validation Layers from the KhronosGroup GitHub.

View File

@ -42,6 +42,9 @@
android:banner="@mipmap/ic_launcher"
android:requestLegacyExternalStorage="true">
<meta-data android:name="android.game_mode_config"
android:resource="@xml/game_mode_config" />
<activity
android:name="org.citra.citra_emu.ui.main.MainActivity"
android:theme="@style/Theme.Citra.Splash.Main"

View File

@ -9,10 +9,13 @@ import android.app.Application
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import org.citra.citra_emu.utils.DirectoryInitialization
import org.citra.citra_emu.utils.DocumentsTree
import org.citra.citra_emu.utils.GpuDriverHelper
import org.citra.citra_emu.utils.PermissionsHandler
import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.utils.MemoryUtil
class CitraApplication : Application() {
private fun createNotificationChannel() {
@ -53,9 +56,20 @@ class CitraApplication : Application() {
}
NativeLibrary.logDeviceInfo()
logDeviceInfo()
createNotificationChannel()
}
fun logDeviceInfo() {
Log.info("Device Manufacturer - ${Build.MANUFACTURER}")
Log.info("Device Model - ${Build.MODEL}")
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
Log.info("SoC Manufacturer - ${Build.SOC_MANUFACTURER}")
Log.info("SoC Model - ${Build.SOC_MODEL}")
}
Log.info("Total System Memory - ${MemoryUtil.getDeviceRAM()}")
}
companion object {
private var application: CitraApplication? = null

View File

@ -413,12 +413,12 @@ object NativeLibrary {
}
fun setEmulationActivity(emulationActivity: EmulationActivity?) {
Log.verbose("[NativeLibrary] Registering EmulationActivity.")
Log.debug("[NativeLibrary] Registering EmulationActivity.")
sEmulationActivity = WeakReference(emulationActivity)
}
fun clearEmulationActivity() {
Log.verbose("[NativeLibrary] Unregistering EmulationActivity.")
Log.debug("[NativeLibrary] Unregistering EmulationActivity.")
sEmulationActivity.clear()
}

View File

@ -94,14 +94,14 @@ object DirectoryInitialization {
val dataPath = PermissionsHandler.citraDirectory
if (dataPath.toString().isNotEmpty()) {
userPath = dataPath.toString()
Log.debug("[DirectoryInitialization] User Dir: $userPath")
android.util.Log.d("[Citra Frontend]", "[DirectoryInitialization] User Dir: $userPath")
return true
}
return false
}
private fun copyAsset(asset: String, output: File, overwrite: Boolean, context: Context) {
Log.verbose("[DirectoryInitialization] Copying File $asset to $output")
Log.debug("[DirectoryInitialization] Copying File $asset to $output")
try {
if (!output.exists() || overwrite) {
val inputStream = context.assets.open(asset)
@ -121,7 +121,7 @@ object DirectoryInitialization {
overwrite: Boolean,
context: Context
) {
Log.verbose("[DirectoryInitialization] Copying Folder $assetFolder to $outputFolder")
Log.debug("[DirectoryInitialization] Copying Folder $assetFolder to $outputFolder")
try {
var createdFolder = false
for (file in context.assets.list(assetFolder)!!) {

View File

@ -4,34 +4,17 @@
package org.citra.citra_emu.utils
import android.util.Log
import org.citra.citra_emu.BuildConfig
/**
* Contains methods that call through to [android.util.Log], but
* with the same TAG automatically provided. Also no-ops VERBOSE and DEBUG log
* levels in release builds.
*/
object Log {
// Tracks whether we should share the old log or the current log
var gameLaunched = false
private const val TAG = "Citra Frontend"
fun verbose(message: String?) {
if (BuildConfig.DEBUG) {
Log.v(TAG, message!!)
}
}
external fun debug(message: String)
fun debug(message: String?) {
if (BuildConfig.DEBUG) {
Log.d(TAG, message!!)
}
}
external fun warning(message: String)
fun info(message: String?) = Log.i(TAG, message!!)
external fun info(message: String)
fun warning(message: String?) = Log.w(TAG, message!!)
external fun error(message: String)
fun error(message: String?) = Log.e(TAG, message!!)
external fun critical(message: String)
}

View File

@ -0,0 +1,108 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.citra.citra_emu.utils
import android.app.ActivityManager
import android.content.Context
import android.os.Build
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.R
import java.util.Locale
import kotlin.math.ceil
object MemoryUtil {
private val context get() = CitraApplication.appContext
private val Float.hundredths: String
get() = String.format(Locale.ROOT, "%.2f", this)
const val Kb: Float = 1024F
const val Mb = Kb * 1024
const val Gb = Mb * 1024
const val Tb = Gb * 1024
const val Pb = Tb * 1024
const val Eb = Pb * 1024
fun bytesToSizeUnit(size: Float, roundUp: Boolean = false): String =
when {
size < Kb -> {
context.getString(
R.string.memory_formatted,
size.hundredths,
context.getString(R.string.memory_byte_shorthand)
)
}
size < Mb -> {
context.getString(
R.string.memory_formatted,
if (roundUp) ceil(size / Kb) else (size / Kb).hundredths,
context.getString(R.string.memory_kilobyte)
)
}
size < Gb -> {
context.getString(
R.string.memory_formatted,
if (roundUp) ceil(size / Mb) else (size / Mb).hundredths,
context.getString(R.string.memory_megabyte)
)
}
size < Tb -> {
context.getString(
R.string.memory_formatted,
if (roundUp) ceil(size / Gb) else (size / Gb).hundredths,
context.getString(R.string.memory_gigabyte)
)
}
size < Pb -> {
context.getString(
R.string.memory_formatted,
if (roundUp) ceil(size / Tb) else (size / Tb).hundredths,
context.getString(R.string.memory_terabyte)
)
}
size < Eb -> {
context.getString(
R.string.memory_formatted,
if (roundUp) ceil(size / Pb) else (size / Pb).hundredths,
context.getString(R.string.memory_petabyte)
)
}
else -> {
context.getString(
R.string.memory_formatted,
if (roundUp) ceil(size / Eb) else (size / Eb).hundredths,
context.getString(R.string.memory_exabyte)
)
}
}
val totalMemory: Float
get() {
val memInfo = ActivityManager.MemoryInfo()
with(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager) {
getMemoryInfo(memInfo)
}
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
memInfo.advertisedMem.toFloat()
} else {
memInfo.totalMem.toFloat()
}
}
fun isLessThan(minimum: Int, size: Float): Boolean =
when (size) {
Kb -> totalMemory < Mb && totalMemory < minimum
Mb -> totalMemory < Gb && (totalMemory / Mb) < minimum
Gb -> totalMemory < Tb && (totalMemory / Gb) < minimum
Tb -> totalMemory < Pb && (totalMemory / Tb) < minimum
Pb -> totalMemory < Eb && (totalMemory / Pb) < minimum
Eb -> totalMemory / Eb < minimum
else -> totalMemory < Kb && totalMemory < minimum
}
// Devices are unlikely to have 0.5GB increments of memory so we'll just round up to account for
// the potential error created by memInfo.totalMem
fun getDeviceRAM(): String = bytesToSizeUnit(totalMemory, true)
}

View File

@ -28,6 +28,7 @@ add_library(citra-android SHARED
ndk_motion.cpp
ndk_motion.h
system_save_game.cpp
native_log.cpp
)
target_link_libraries(citra-android PRIVATE audio_core citra_common citra_core input_common network)

View File

@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <common/logging/log.h>
#include <jni.h>
#include "android_common/android_common.h"
extern "C" {
void Java_org_citra_citra_1emu_utils_Log_debug(JNIEnv* env, jobject obj, jstring jmessage) {
LOG_DEBUG(Frontend, "{}", GetJString(env, jmessage));
}
void Java_org_citra_citra_1emu_utils_Log_warning(JNIEnv* env, jobject obj, jstring jmessage) {
LOG_WARNING(Frontend, "{}", GetJString(env, jmessage));
}
void Java_org_citra_citra_1emu_utils_Log_info(JNIEnv* env, jobject obj, jstring jmessage) {
LOG_INFO(Frontend, "{}", GetJString(env, jmessage));
}
void Java_org_citra_citra_1emu_utils_Log_error(JNIEnv* env, jobject obj, jstring jmessage) {
LOG_ERROR(Frontend, "{}", GetJString(env, jmessage));
}
void Java_org_citra_citra_1emu_utils_Log_critical(JNIEnv* env, jobject obj, jstring jmessage) {
LOG_CRITICAL(Frontend, "{}", GetJString(env, jmessage));
}
} // extern "C"

View File

@ -442,6 +442,17 @@
<string name="cia_install_error_encrypted">\"%s\" must be decrypted before being used with Citra.\n A real 3DS is required</string>
<string name="cia_install_error_unknown">An unknown error occurred while installing \"%s\".\n Please see the log for more details</string>
<!-- Memory Sizes -->
<string name="memory_formatted">%1$s %2$s</string>
<string name="memory_byte">Byte</string>
<string name="memory_byte_shorthand">B</string>
<string name="memory_kilobyte">KB</string>
<string name="memory_megabyte">MB</string>
<string name="memory_gigabyte">GB</string>
<string name="memory_terabyte">TB</string>
<string name="memory_petabyte">PB</string>
<string name="memory_exabyte">EB</string>
<!-- Theme Modes -->
<string name="change_theme_mode">Change Theme Mode</string>
<string name="theme_mode_follow_system">Follow System</string>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<game-mode-config
xmlns:android="http://schemas.android.com/apk/res/android"
android:supportsBatteryGameMode="true"
android:supportsPerformanceGameMode="true"
android:allowGameDownscaling="false"
android:allowGameFpsOverride="false"/>

View File

@ -4,10 +4,10 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.1.2" apply false
id("com.android.library") version "8.1.2" apply false
id("org.jetbrains.kotlin.android") version "1.8.21" apply false
id("org.jetbrains.kotlin.plugin.serialization") version "1.8.21"
id("com.android.application") version "8.2.1" apply false
id("com.android.library") version "8.2.1" apply false
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
id("org.jetbrains.kotlin.plugin.serialization") version "1.9.22"
}
tasks.register("clean").configure {
@ -19,6 +19,6 @@ buildscript {
google()
}
dependencies {
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.7.5")
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.7.6")
}
}

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip

View File

@ -316,7 +316,7 @@ struct SourceStatus {
u16_le sync_count; ///< Is set by the DSP to the value of SourceConfiguration::sync_count
u32_dsp buffer_position; ///< Number of samples into the current buffer
u16_le current_buffer_id; ///< Updated when a buffer finishes playing
INSERT_PADDING_DSPWORDS(1);
u16_le last_buffer_id; ///< Updated when all buffers in the queue finish playing
};
Status status[num_sources];

View File

@ -298,9 +298,9 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config,
b.buffer_id,
state.mono_or_stereo,
state.format,
true,
{}, // 0 in u32_dsp
false,
true, // from_queue
0, // play_position
false, // has_played
});
}
LOG_TRACE(Audio_DSP, "enqueuing queued {} addr={:#010x} len={} id={}", i,
@ -321,16 +321,19 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config,
void Source::GenerateFrame() {
current_frame.fill({});
if (state.current_buffer.empty() && !DequeueBuffer()) {
if (state.current_buffer.empty()) {
// TODO(SachinV): Should dequeue happen at the end of the frame generation?
if (DequeueBuffer()) {
return;
}
state.enabled = false;
state.buffer_update = true;
state.last_buffer_id = state.current_buffer_id;
state.current_buffer_id = 0;
return;
}
std::size_t frame_position = 0;
state.current_sample_number = state.next_sample_number;
while (frame_position < current_frame.size()) {
if (state.current_buffer.empty() && !DequeueBuffer()) {
break;
@ -357,7 +360,7 @@ void Source::GenerateFrame() {
}
// TODO(jroweboy): Keep track of frame_position independently so that it doesn't lose precision
// over time
state.next_sample_number += static_cast<u32>(frame_position * state.rate_multiplier);
state.current_sample_number += static_cast<u32>(frame_position * state.rate_multiplier);
state.filters.ProcessFrame(current_frame);
}
@ -408,9 +411,9 @@ bool Source::DequeueBuffer() {
// the first playthrough starts at play_position, loops start at the beginning of the buffer
state.current_sample_number = (!buf.has_played) ? buf.play_position : 0;
state.next_sample_number = state.current_sample_number;
state.current_buffer_physical_address = buf.physical_address;
state.current_buffer_id = buf.buffer_id;
state.last_buffer_id = 0;
state.buffer_update = buf.from_queue && !buf.has_played;
if (buf.is_looping) {
@ -418,8 +421,17 @@ bool Source::DequeueBuffer() {
state.input_queue.push(buf);
}
LOG_TRACE(Audio_DSP, "source_id={} buffer_id={} from_queue={} current_buffer.size()={}",
source_id, buf.buffer_id, buf.from_queue, state.current_buffer.size());
// Because our interpolation consumes samples instead of using an index,
// let's just consume the samples up to the current sample number.
state.current_buffer.erase(
state.current_buffer.begin(),
std::next(state.current_buffer.begin(), state.current_sample_number));
LOG_TRACE(Audio_DSP,
"source_id={} buffer_id={} from_queue={} current_buffer.size()={}, "
"buf.has_played={}, buf.play_position={}",
source_id, buf.buffer_id, buf.from_queue, state.current_buffer.size(), buf.has_played,
buf.play_position);
return true;
}
@ -432,9 +444,10 @@ SourceStatus::Status Source::GetCurrentStatus() {
ret.is_enabled = state.enabled;
ret.current_buffer_id_dirty = state.buffer_update ? 1 : 0;
state.buffer_update = false;
ret.current_buffer_id = state.current_buffer_id;
ret.buffer_position = state.current_sample_number;
ret.sync_count = state.sync_count;
ret.buffer_position = state.current_sample_number;
ret.current_buffer_id = state.current_buffer_id;
ret.last_buffer_id = state.last_buffer_id;
return ret;
}

View File

@ -87,8 +87,8 @@ private:
Format format;
bool from_queue;
u32_dsp play_position; // = 0;
bool has_played; // = false;
u32 play_position; // = 0;
bool has_played; // = false;
private:
template <class Archive>
@ -136,14 +136,14 @@ private:
// Current buffer
u32 current_sample_number = 0;
u32 next_sample_number = 0;
PAddr current_buffer_physical_address = 0;
AudioInterp::StereoBuffer16 current_buffer = {};
// buffer_id state
bool buffer_update = false;
u32 current_buffer_id = 0;
u16 last_buffer_id = 0;
u16 current_buffer_id = 0;
// Decoding state
@ -170,7 +170,6 @@ private:
ar& mono_or_stereo;
ar& format;
ar& current_sample_number;
ar& next_sample_number;
ar& current_buffer_physical_address;
ar& current_buffer;
ar& buffer_update;

View File

@ -54,7 +54,7 @@ const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config:
// This must be in alphabetical order according to action name as it must have the same order as
// UISetting::values.shortcuts, which is alphabetically ordered.
// clang-format off
const std::array<UISettings::Shortcut, 30> Config::default_hotkeys {{
const std::array<UISettings::Shortcut, 35> Config::default_hotkeys {{
{QStringLiteral("Advance Frame"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}},
{QStringLiteral("Audio Mute/Unmute"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}},
{QStringLiteral("Audio Volume Down"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WindowShortcut}},
@ -71,6 +71,11 @@ const std::array<UISettings::Shortcut, 30> Config::default_hotkeys {{
{QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::WidgetWithChildrenShortcut}},
{QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WidgetWithChildrenShortcut}},
{QStringLiteral("Load from Newest Slot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+V"), Qt::WindowShortcut}},
{QStringLiteral("Multiplayer Browse Public Game Lobby"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+B"), Qt::ApplicationShortcut}},
{QStringLiteral("Multiplayer Create Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+N"), Qt::ApplicationShortcut}},
{QStringLiteral("Multiplayer Direct Connect to Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Shift"), Qt::ApplicationShortcut}},
{QStringLiteral("Multiplayer Leave Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+L"), Qt::ApplicationShortcut}},
{QStringLiteral("Multiplayer Show Current Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+R"), Qt::ApplicationShortcut}},
{QStringLiteral("Remove Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F3"), Qt::ApplicationShortcut}},
{QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
{QStringLiteral("Rotate Screens Upright"), QStringLiteral("Main Window"), {QStringLiteral("F8"), Qt::WindowShortcut}},
@ -557,6 +562,15 @@ void Config::ReadMultiplayerValues() {
UISettings::values.game_id = ReadSetting(QStringLiteral("game_id"), 0).toULongLong();
UISettings::values.room_description =
ReadSetting(QStringLiteral("room_description"), QString{}).toString();
UISettings::values.multiplayer_filter_text =
ReadSetting(QStringLiteral("multiplayer_filter_text"), QString{}).toString();
UISettings::values.multiplayer_filter_games_owned =
ReadSetting(QStringLiteral("multiplayer_filter_games_owned"), false).toBool();
UISettings::values.multiplayer_filter_hide_empty =
ReadSetting(QStringLiteral("multiplayer_filter_hide_empty"), false).toBool();
UISettings::values.multiplayer_filter_hide_full =
ReadSetting(QStringLiteral("multiplayer_filter_hide_full"), false).toBool();
// Read ban list back
int size = qt_config->beginReadArray(QStringLiteral("username_ban_list"));
UISettings::values.ban_list.first.resize(size);
@ -1074,6 +1088,15 @@ void Config::SaveMultiplayerValues() {
WriteSetting(QStringLiteral("game_id"), UISettings::values.game_id, 0);
WriteSetting(QStringLiteral("room_description"), UISettings::values.room_description,
QString{});
WriteSetting(QStringLiteral("multiplayer_filter_text"),
UISettings::values.multiplayer_filter_text, QString{});
WriteSetting(QStringLiteral("multiplayer_filter_games_owned"),
UISettings::values.multiplayer_filter_games_owned, false);
WriteSetting(QStringLiteral("multiplayer_filter_hide_empty"),
UISettings::values.multiplayer_filter_hide_empty, false);
WriteSetting(QStringLiteral("multiplayer_filter_hide_full"),
UISettings::values.multiplayer_filter_hide_full, false);
// Write ban list
qt_config->beginWriteArray(QStringLiteral("username_ban_list"));
for (std::size_t i = 0; i < UISettings::values.ban_list.first.size(); ++i) {

View File

@ -26,7 +26,7 @@ public:
static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs;
static const std::array<UISettings::Shortcut, 30> default_hotkeys;
static const std::array<UISettings::Shortcut, 35> default_hotkeys;
private:
void Initialize(const std::string& config_name);

View File

@ -6,13 +6,13 @@
#include "citra_qt/debugger/wait_tree.h"
#include "citra_qt/uisettings.h"
#include "common/assert.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_mutex.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_semaphore.h"
#include "core/hle/kernel/k_synchronization_object.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/k_timer.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/mutex.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/semaphore.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/timer.h"
#include "core/hle/kernel/wait_object.h"
namespace {
@ -98,7 +98,7 @@ QString WaitTreeText::GetText() const {
return text;
}
WaitTreeWaitObject::WaitTreeWaitObject(const Kernel::KSynchronizationObject& o) : object(o) {}
WaitTreeWaitObject::WaitTreeWaitObject(const Kernel::WaitObject& o) : object(o) {}
bool WaitTreeExpandableItem::IsExpandable() const {
return true;
@ -106,24 +106,23 @@ bool WaitTreeExpandableItem::IsExpandable() const {
QString WaitTreeWaitObject::GetText() const {
return tr("[%1]%2 %3")
.arg(/*object.GetObjectId()*/ 0)
.arg(object.GetObjectId())
.arg(QString::fromStdString(object.GetTypeName()),
QString::fromStdString(/*object.GetName()*/ "name"));
QString::fromStdString(object.GetName()));
}
std::unique_ptr<WaitTreeWaitObject> WaitTreeWaitObject::make(
const Kernel::KSynchronizationObject& object) {
switch (object.GetTypeObj().GetClassToken()) {
case Kernel::ClassTokenType::KEvent:
return std::make_unique<WaitTreeEvent>(static_cast<const Kernel::KEvent&>(object));
case Kernel::ClassTokenType::KMutex:
return std::make_unique<WaitTreeMutex>(static_cast<const Kernel::KMutex&>(object));
case Kernel::ClassTokenType::KSemaphore:
return std::make_unique<WaitTreeSemaphore>(static_cast<const Kernel::KSemaphore&>(object));
case Kernel::ClassTokenType::KTimer:
return std::make_unique<WaitTreeTimer>(static_cast<const Kernel::KTimer&>(object));
case Kernel::ClassTokenType::KThread:
return std::make_unique<WaitTreeThread>(static_cast<const Kernel::KThread&>(object));
std::unique_ptr<WaitTreeWaitObject> WaitTreeWaitObject::make(const Kernel::WaitObject& object) {
switch (object.GetHandleType()) {
case Kernel::HandleType::Event:
return std::make_unique<WaitTreeEvent>(static_cast<const Kernel::Event&>(object));
case Kernel::HandleType::Mutex:
return std::make_unique<WaitTreeMutex>(static_cast<const Kernel::Mutex&>(object));
case Kernel::HandleType::Semaphore:
return std::make_unique<WaitTreeSemaphore>(static_cast<const Kernel::Semaphore&>(object));
case Kernel::HandleType::Timer:
return std::make_unique<WaitTreeTimer>(static_cast<const Kernel::Timer&>(object));
case Kernel::HandleType::Thread:
return std::make_unique<WaitTreeThread>(static_cast<const Kernel::Thread&>(object));
default:
return std::make_unique<WaitTreeWaitObject>(object);
}
@ -154,7 +153,7 @@ QString WaitTreeWaitObject::GetResetTypeQString(Kernel::ResetType reset_type) {
return {};
}
WaitTreeObjectList::WaitTreeObjectList(const std::vector<Kernel::KSynchronizationObject*>& list,
WaitTreeObjectList::WaitTreeObjectList(const std::vector<std::shared_ptr<Kernel::WaitObject>>& list,
bool w_all)
: object_list(list), wait_all(w_all) {}
@ -171,12 +170,12 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeObjectList::GetChildren() con
return list;
}
WaitTreeThread::WaitTreeThread(const Kernel::KThread& thread) : WaitTreeWaitObject(thread) {}
WaitTreeThread::WaitTreeThread(const Kernel::Thread& thread) : WaitTreeWaitObject(thread) {}
QString WaitTreeThread::GetText() const {
const auto& thread = static_cast<const Kernel::KThread&>(object);
const auto& thread = static_cast<const Kernel::Thread&>(object);
QString status;
switch (thread.GetStatus()) {
switch (thread.status) {
case Kernel::ThreadStatus::Running:
status = tr("running");
break;
@ -184,7 +183,7 @@ QString WaitTreeThread::GetText() const {
status = tr("ready");
break;
case Kernel::ThreadStatus::WaitArb:
status = tr("waiting for address 0x%1").arg(thread.m_wait_address, 8, 16, QLatin1Char('0'));
status = tr("waiting for address 0x%1").arg(thread.wait_address, 8, 16, QLatin1Char('0'));
break;
case Kernel::ThreadStatus::WaitSleep:
status = tr("sleeping");
@ -206,18 +205,17 @@ QString WaitTreeThread::GetText() const {
status = tr("dead");
break;
}
const auto& context = thread.GetContext();
QString pc_info = tr(" PC = 0x%1 LR = 0x%2")
.arg(context.GetProgramCounter(), 8, 16, QLatin1Char('0'))
.arg(context.GetLinkRegister(), 8, 16, QLatin1Char('0'));
.arg(thread.context.GetProgramCounter(), 8, 16, QLatin1Char('0'))
.arg(thread.context.GetLinkRegister(), 8, 16, QLatin1Char('0'));
return QStringLiteral("%1%2 (%3) ").arg(WaitTreeWaitObject::GetText(), pc_info, status);
}
QColor WaitTreeThread::GetColor() const {
const std::size_t color_index = IsDarkTheme() ? 1 : 0;
const auto& thread = static_cast<const Kernel::KThread&>(object);
switch (thread.GetStatus()) {
const auto& thread = static_cast<const Kernel::Thread&>(object);
switch (thread.status) {
case Kernel::ThreadStatus::Running:
return QColor(WaitTreeColors[0][color_index]);
case Kernel::ThreadStatus::Ready:
@ -244,10 +242,10 @@ QColor WaitTreeThread::GetColor() const {
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeWaitObject::GetChildren());
const auto& thread = static_cast<const Kernel::KThread&>(object);
const auto& thread = static_cast<const Kernel::Thread&>(object);
QString processor;
switch (thread.m_processor_id) {
switch (thread.processor_id) {
case Kernel::ThreadProcessorId::ThreadProcessorIdDefault:
processor = tr("default");
break;
@ -261,88 +259,86 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
processor = tr("SysCore");
break;
default:
processor = tr("Unknown processor %1").arg(thread.m_processor_id);
processor = tr("Unknown processor %1").arg(thread.processor_id);
break;
}
list.push_back(
std::make_unique<WaitTreeText>(tr("object id = %1").arg(/*thread.GetObjectId()*/ 1)));
list.push_back(std::make_unique<WaitTreeText>(tr("object id = %1").arg(thread.GetObjectId())));
list.push_back(std::make_unique<WaitTreeText>(tr("processor = %1").arg(processor)));
list.push_back(std::make_unique<WaitTreeText>(tr("thread id = %1").arg(thread.GetThreadId())));
if (auto process = thread.GetOwner()) {
list.push_back(std::make_unique<WaitTreeText>(
tr("process = %1 (%2)")
.arg(QString::fromStdString(/*process->GetName()*/ ""))
.arg(process->process_id)));
if (auto process = thread.owner_process.lock()) {
list.push_back(
std::make_unique<WaitTreeText>(tr("process = %1 (%2)")
.arg(QString::fromStdString(process->GetName()))
.arg(process->process_id)));
}
list.push_back(std::make_unique<WaitTreeText>(tr("priority = %1(current) / %2(normal)")
.arg(thread.GetCurrentPriority())
.arg(thread.m_nominal_priority)));
.arg(thread.current_priority)
.arg(thread.nominal_priority)));
list.push_back(std::make_unique<WaitTreeText>(
tr("last running ticks = %1").arg(thread.m_last_running_ticks)));
tr("last running ticks = %1").arg(thread.last_running_ticks)));
if (thread.m_held_mutexes.empty()) {
if (thread.held_mutexes.empty()) {
list.push_back(std::make_unique<WaitTreeText>(tr("not holding mutex")));
} else {
list.push_back(std::make_unique<WaitTreeMutexList>(thread.m_held_mutexes));
list.push_back(std::make_unique<WaitTreeMutexList>(thread.held_mutexes));
}
if (thread.GetStatus() == Kernel::ThreadStatus::WaitSynchAny ||
thread.GetStatus() == Kernel::ThreadStatus::WaitSynchAll ||
thread.GetStatus() == Kernel::ThreadStatus::WaitHleEvent) {
list.push_back(std::make_unique<WaitTreeObjectList>(thread.m_wait_objects,
if (thread.status == Kernel::ThreadStatus::WaitSynchAny ||
thread.status == Kernel::ThreadStatus::WaitSynchAll ||
thread.status == Kernel::ThreadStatus::WaitHleEvent) {
list.push_back(std::make_unique<WaitTreeObjectList>(thread.wait_objects,
thread.IsSleepingOnWaitAll()));
}
return list;
}
WaitTreeEvent::WaitTreeEvent(const Kernel::KEvent& object) : WaitTreeWaitObject(object) {}
WaitTreeEvent::WaitTreeEvent(const Kernel::Event& object) : WaitTreeWaitObject(object) {}
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeEvent::GetChildren() const {
std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeWaitObject::GetChildren());
list.push_back(std::make_unique<WaitTreeText>(
tr("reset type = %1")
.arg(GetResetTypeQString(static_cast<const Kernel::KEvent&>(object).GetResetType()))));
.arg(GetResetTypeQString(static_cast<const Kernel::Event&>(object).GetResetType()))));
return list;
}
WaitTreeMutex::WaitTreeMutex(const Kernel::KMutex& object) : WaitTreeWaitObject(object) {}
WaitTreeMutex::WaitTreeMutex(const Kernel::Mutex& object) : WaitTreeWaitObject(object) {}
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutex::GetChildren() const {
std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeWaitObject::GetChildren());
const auto& mutex = static_cast<const Kernel::KMutex&>(object);
if (mutex.m_lock_count) {
list.push_back(std::make_unique<WaitTreeText>(
tr("locked %1 times by thread:").arg(mutex.m_lock_count)));
list.push_back(std::make_unique<WaitTreeThread>(*mutex.m_holding_thread));
const auto& mutex = static_cast<const Kernel::Mutex&>(object);
if (mutex.lock_count) {
list.push_back(
std::make_unique<WaitTreeText>(tr("locked %1 times by thread:").arg(mutex.lock_count)));
list.push_back(std::make_unique<WaitTreeThread>(*mutex.holding_thread));
} else {
list.push_back(std::make_unique<WaitTreeText>(tr("free")));
}
return list;
}
WaitTreeSemaphore::WaitTreeSemaphore(const Kernel::KSemaphore& object)
WaitTreeSemaphore::WaitTreeSemaphore(const Kernel::Semaphore& object)
: WaitTreeWaitObject(object) {}
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeSemaphore::GetChildren() const {
std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeWaitObject::GetChildren());
const auto& semaphore = static_cast<const Kernel::KSemaphore&>(object);
list.push_back(std::make_unique<WaitTreeText>(
tr("available count = %1").arg(semaphore.GetAvailableCount())));
const auto& semaphore = static_cast<const Kernel::Semaphore&>(object);
list.push_back(
std::make_unique<WaitTreeText>(tr("max count = %1").arg(semaphore.GetMaxCount())));
std::make_unique<WaitTreeText>(tr("available count = %1").arg(semaphore.available_count)));
list.push_back(std::make_unique<WaitTreeText>(tr("max count = %1").arg(semaphore.max_count)));
return list;
}
WaitTreeTimer::WaitTreeTimer(const Kernel::KTimer& object) : WaitTreeWaitObject(object) {}
WaitTreeTimer::WaitTreeTimer(const Kernel::Timer& object) : WaitTreeWaitObject(object) {}
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeTimer::GetChildren() const {
std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeWaitObject::GetChildren());
const auto& timer = static_cast<const Kernel::KTimer&>(object);
const auto& timer = static_cast<const Kernel::Timer&>(object);
list.push_back(std::make_unique<WaitTreeText>(
tr("reset type = %1").arg(GetResetTypeQString(timer.GetResetType()))));
@ -353,7 +349,8 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeTimer::GetChildren() const {
return list;
}
WaitTreeMutexList::WaitTreeMutexList(const boost::container::flat_set<Kernel::KMutex*>& list)
WaitTreeMutexList::WaitTreeMutexList(
const boost::container::flat_set<std::shared_ptr<Kernel::Mutex>>& list)
: mutex_list(list) {}
QString WaitTreeMutexList::GetText() const {
@ -367,7 +364,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutexList::GetChildren() cons
return list;
}
WaitTreeThreadList::WaitTreeThreadList(const std::vector<Kernel::KThread*>& list)
WaitTreeThreadList::WaitTreeThreadList(const std::vector<std::shared_ptr<Kernel::Thread>>& list)
: thread_list(list) {}
QString WaitTreeThreadList::GetText() const {

View File

@ -10,16 +10,17 @@
#include <QTreeView>
#include <boost/container/flat_set.hpp>
#include "core/core.h"
#include "core/hle/kernel/object.h"
class EmuThread;
namespace Kernel {
class KSynchronizationObject;
class KEvent;
class KMutex;
class KSemaphore;
class KThread;
class KTimer;
class WaitObject;
class Event;
class Mutex;
class Semaphore;
class Thread;
class Timer;
} // namespace Kernel
namespace Core {
@ -72,13 +73,13 @@ public:
class WaitTreeWaitObject : public WaitTreeExpandableItem {
Q_OBJECT
public:
explicit WaitTreeWaitObject(const Kernel::KSynchronizationObject& object);
static std::unique_ptr<WaitTreeWaitObject> make(const Kernel::KSynchronizationObject& object);
explicit WaitTreeWaitObject(const Kernel::WaitObject& object);
static std::unique_ptr<WaitTreeWaitObject> make(const Kernel::WaitObject& object);
QString GetText() const override;
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
protected:
const Kernel::KSynchronizationObject& object;
const Kernel::WaitObject& object;
static QString GetResetTypeQString(Kernel::ResetType reset_type);
};
@ -86,19 +87,19 @@ protected:
class WaitTreeObjectList : public WaitTreeExpandableItem {
Q_OBJECT
public:
WaitTreeObjectList(const std::vector<Kernel::KSynchronizationObject*>& list, bool wait_all);
WaitTreeObjectList(const std::vector<std::shared_ptr<Kernel::WaitObject>>& list, bool wait_all);
QString GetText() const override;
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
private:
const std::vector<Kernel::KSynchronizationObject*>& object_list;
const std::vector<std::shared_ptr<Kernel::WaitObject>>& object_list;
bool wait_all;
};
class WaitTreeThread : public WaitTreeWaitObject {
Q_OBJECT
public:
explicit WaitTreeThread(const Kernel::KThread& thread);
explicit WaitTreeThread(const Kernel::Thread& thread);
QString GetText() const override;
QColor GetColor() const override;
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
@ -107,52 +108,53 @@ public:
class WaitTreeEvent : public WaitTreeWaitObject {
Q_OBJECT
public:
explicit WaitTreeEvent(const Kernel::KEvent& object);
explicit WaitTreeEvent(const Kernel::Event& object);
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
};
class WaitTreeMutex : public WaitTreeWaitObject {
Q_OBJECT
public:
explicit WaitTreeMutex(const Kernel::KMutex& object);
explicit WaitTreeMutex(const Kernel::Mutex& object);
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
};
class WaitTreeSemaphore : public WaitTreeWaitObject {
Q_OBJECT
public:
explicit WaitTreeSemaphore(const Kernel::KSemaphore& object);
explicit WaitTreeSemaphore(const Kernel::Semaphore& object);
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
};
class WaitTreeTimer : public WaitTreeWaitObject {
Q_OBJECT
public:
explicit WaitTreeTimer(const Kernel::KTimer& object);
explicit WaitTreeTimer(const Kernel::Timer& object);
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
};
class WaitTreeMutexList : public WaitTreeExpandableItem {
Q_OBJECT
public:
explicit WaitTreeMutexList(const boost::container::flat_set<Kernel::KMutex*>& list);
explicit WaitTreeMutexList(
const boost::container::flat_set<std::shared_ptr<Kernel::Mutex>>& list);
QString GetText() const override;
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
private:
const boost::container::flat_set<Kernel::KMutex*>& mutex_list;
const boost::container::flat_set<std::shared_ptr<Kernel::Mutex>>& mutex_list;
};
class WaitTreeThreadList : public WaitTreeExpandableItem {
Q_OBJECT
public:
explicit WaitTreeThreadList(const std::vector<Kernel::KThread*>& list);
explicit WaitTreeThreadList(const std::vector<std::shared_ptr<Kernel::Thread>>& list);
QString GetText() const override;
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
private:
const std::vector<Kernel::KThread*>& thread_list;
const std::vector<std::shared_ptr<Kernel::Thread>>& thread_list;
};
class WaitTreeModel : public QAbstractItemModel {

View File

@ -647,6 +647,13 @@ void GMainWindow::InitializeHotkeys() {
link_action_shortcut(ui->action_Advance_Frame, QStringLiteral("Advance Frame"));
link_action_shortcut(ui->action_Load_from_Newest_Slot, QStringLiteral("Load from Newest Slot"));
link_action_shortcut(ui->action_Save_to_Oldest_Slot, QStringLiteral("Save to Oldest Slot"));
link_action_shortcut(ui->action_View_Lobby,
QStringLiteral("Multiplayer Browse Public Game Lobby"));
link_action_shortcut(ui->action_Start_Room, QStringLiteral("Multiplayer Create Room"));
link_action_shortcut(ui->action_Connect_To_Room,
QStringLiteral("Multiplayer Direct Connect to Room"));
link_action_shortcut(ui->action_Show_Room, QStringLiteral("Multiplayer Show Current Room"));
link_action_shortcut(ui->action_Leave_Room, QStringLiteral("Multiplayer Leave Room"));
const auto add_secondary_window_hotkey = [this](QKeySequence hotkey, const char* slot) {
// This action will fire specifically when secondary_window is in focus
@ -3190,8 +3197,10 @@ int main(int argc, char* argv[]) {
QApplication::setHighDpiScaleFactorRoundingPolicy(rounding_policy);
#ifdef __APPLE__
std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + "..";
chdir(bin_path.c_str());
auto bundle_dir = FileUtil::GetBundleDirectory();
if (bundle_dir) {
FileUtil::SetCurrentDir(bundle_dir.value() + "..");
}
#endif
#ifdef ENABLE_OPENGL

View File

@ -80,9 +80,8 @@ void DirectConnectWindow::Connect() {
// Store settings
UISettings::values.nickname = ui->nickname->text();
UISettings::values.ip = ui->ip->text();
UISettings::values.port = (ui->port->isModified() && !ui->port->text().isEmpty())
? ui->port->text()
: UISettings::values.port;
UISettings::values.port =
!ui->port->text().isEmpty() ? ui->port->text() : UISettings::values.port;
// attempt to connect in a different thread
QFuture<void> f = QtConcurrent::run([&] {

View File

@ -63,10 +63,10 @@ Lobby::Lobby(Core::System& system_, QWidget* parent, QStandardItemModel* list,
// UI Buttons
connect(ui->refresh_list, &QPushButton::clicked, this, &Lobby::RefreshLobby);
connect(ui->search, &QLineEdit::textChanged, proxy, &LobbyFilterProxyModel::SetFilterSearch);
connect(ui->games_owned, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterOwned);
connect(ui->hide_empty, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterEmpty);
connect(ui->hide_full, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterFull);
connect(ui->search, &QLineEdit::textChanged, proxy, &LobbyFilterProxyModel::SetFilterSearch);
connect(ui->room_list, &QTreeView::doubleClicked, this, &Lobby::OnJoinRoom);
connect(ui->room_list, &QTreeView::clicked, this, &Lobby::OnExpandRoom);
@ -74,6 +74,12 @@ Lobby::Lobby(Core::System& system_, QWidget* parent, QStandardItemModel* list,
connect(&room_list_watcher, &QFutureWatcher<AnnounceMultiplayerRoom::RoomList>::finished, this,
&Lobby::OnRefreshLobby);
// Load persistent filters after events are connected to make sure they apply
ui->search->setText(UISettings::values.multiplayer_filter_text);
ui->games_owned->setChecked(UISettings::values.multiplayer_filter_games_owned);
ui->hide_empty->setChecked(UISettings::values.multiplayer_filter_hide_empty);
ui->hide_full->setChecked(UISettings::values.multiplayer_filter_hide_full);
// manually start a refresh when the window is opening
// TODO(jroweboy): if this refresh is slow for people with bad internet, then don't do it as
// part of the constructor, but offload the refresh until after the window shown. perhaps emit a
@ -180,6 +186,10 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
UISettings::values.nickname = ui->nickname->text();
UISettings::values.ip = proxy->data(connection_index, LobbyItemHost::HostIPRole).toString();
UISettings::values.port = proxy->data(connection_index, LobbyItemHost::HostPortRole).toString();
UISettings::values.multiplayer_filter_text = ui->search->text();
UISettings::values.multiplayer_filter_games_owned = ui->games_owned->isChecked();
UISettings::values.multiplayer_filter_hide_empty = ui->hide_empty->isChecked();
UISettings::values.multiplayer_filter_hide_full = ui->hide_full->isChecked();
}
void Lobby::ResetModel() {

View File

@ -188,12 +188,37 @@ public:
}
QVariant data(int role) const override {
if (role != Qt::DisplayRole) {
switch (role) {
case Qt::DisplayRole: {
auto members = data(MemberListRole).toList();
return QStringLiteral("%1 / %2").arg(QString::number(members.size()),
data(MaxPlayerRole).toString());
}
case Qt::ForegroundRole: {
auto members = data(MemberListRole).toList();
auto max_players = data(MaxPlayerRole).toInt();
const QColor room_full_color(255, 48, 32);
const QColor room_almost_full_color(255, 140, 32);
const QColor room_has_players_color(32, 160, 32);
const QColor room_empty_color(128, 128, 128);
if (members.size() >= max_players) {
return QBrush(room_full_color);
} else if (members.size() == (max_players - 1)) {
return QBrush(room_almost_full_color);
} else if (members.size() == 0) {
return QBrush(room_empty_color);
} else if (members.size() > 0 && members.size() < (max_players - 1)) {
return QBrush(room_has_players_color);
}
// FIXME: How to return a value that tells Qt not to modify the
// text color from the default (as if Qt::ForegroundRole wasn't overridden)?
return QBrush(nullptr);
}
default:
return LobbyItem::data(role);
}
auto members = data(MemberListRole).toList();
return QStringLiteral("%1 / %2").arg(QString::number(members.size()),
data(MaxPlayerRole).toString());
}
bool operator<(const QStandardItem& other) const override {

View File

@ -138,6 +138,11 @@ struct Values {
QString room_description;
std::pair<std::vector<std::string>, std::vector<std::string>> ban_list;
QString multiplayer_filter_text;
bool multiplayer_filter_games_owned;
bool multiplayer_filter_hide_empty;
bool multiplayer_filter_hide_full;
// logging
Settings::Setting<bool> show_console{false, "showConsole"};
};

View File

@ -88,7 +88,6 @@ add_library(citra_common STATIC
file_util.cpp
file_util.h
hash.h
intrusive_list.h
literals.h
logging/backend.cpp
logging/backend.h
@ -110,7 +109,6 @@ add_library(citra_common STATIC
microprofileui.h
param_package.cpp
param_package.h
parent_of_member.h
polyfill_thread.h
precompiled_headers.h
quaternion.h

View File

@ -21,10 +21,4 @@ template <typename T>
return static_cast<T>(value - value % size);
}
template <typename T>
requires std::is_unsigned_v<T>
[[nodiscard]] constexpr bool Is4KBAligned(T value) {
return (value & 0xFFF) == 0;
}
} // namespace Common

View File

@ -49,14 +49,6 @@ __declspec(dllimport) void __stdcall DebugBreak(void);
#define locale_t _locale_t
#endif // _MSC_VER
#define CITRA_NON_COPYABLE(cls) \
cls(const cls&) = delete; \
cls& operator=(const cls&) = delete
#define CITRA_NON_MOVEABLE(cls) \
cls(cls&&) = delete; \
cls& operator=(cls&&) = delete
#define DECLARE_ENUM_FLAG_OPERATORS(type) \
[[nodiscard]] constexpr type operator|(type a, type b) noexcept { \
using T = std::underlying_type_t<type>; \

View File

@ -13,7 +13,6 @@
#endif
// The user data dir
#define ROOT_DIR "."
#define USERDATA_DIR "user"
#ifdef USER_DIR
#define EMU_DATA_DIR USER_DIR

View File

@ -634,6 +634,10 @@ std::optional<std::string> GetCurrentDir() {
std::string strDir = dir;
#endif
free(dir);
if (!strDir.ends_with(DIR_SEP)) {
strDir += DIR_SEP;
}
return strDir;
} // namespace FileUtil
@ -646,17 +650,36 @@ bool SetCurrentDir(const std::string& directory) {
}
#if defined(__APPLE__)
std::string GetBundleDirectory() {
CFURLRef BundleRef;
char AppBundlePath[MAXPATHLEN];
std::optional<std::string> GetBundleDirectory() {
// Get the main bundle for the app
BundleRef = CFBundleCopyBundleURL(CFBundleGetMainBundle());
CFStringRef BundlePath = CFURLCopyFileSystemPath(BundleRef, kCFURLPOSIXPathStyle);
CFStringGetFileSystemRepresentation(BundlePath, AppBundlePath, sizeof(AppBundlePath));
CFRelease(BundleRef);
CFRelease(BundlePath);
CFBundleRef bundle_ref = CFBundleGetMainBundle();
if (!bundle_ref) {
return {};
}
return AppBundlePath;
CFURLRef bundle_url_ref = CFBundleCopyBundleURL(bundle_ref);
if (!bundle_url_ref) {
return {};
}
SCOPE_EXIT({ CFRelease(bundle_url_ref); });
CFStringRef bundle_path_ref = CFURLCopyFileSystemPath(bundle_url_ref, kCFURLPOSIXPathStyle);
if (!bundle_path_ref) {
return {};
}
SCOPE_EXIT({ CFRelease(bundle_path_ref); });
char app_bundle_path[MAXPATHLEN];
if (!CFStringGetFileSystemRepresentation(bundle_path_ref, app_bundle_path,
sizeof(app_bundle_path))) {
return {};
}
std::string path_str(app_bundle_path);
if (!path_str.ends_with(DIR_SEP)) {
path_str += DIR_SEP;
}
return path_str;
}
#endif
@ -732,22 +755,6 @@ static const std::string& GetHomeDirectory() {
}
#endif
std::string GetSysDirectory() {
std::string sysDir;
#if defined(__APPLE__)
sysDir = GetBundleDirectory();
sysDir += DIR_SEP;
sysDir += SYSDATA_DIR;
#else
sysDir = SYSDATA_DIR;
#endif
sysDir += DIR_SEP;
LOG_DEBUG(Common_Filesystem, "Setting to {}:", sysDir);
return sysDir;
}
namespace {
std::unordered_map<UserPath, std::string> g_paths;
std::unordered_map<UserPath, std::string> g_default_paths;
@ -777,8 +784,10 @@ void SetUserPath(const std::string& path) {
g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
#else
if (FileUtil::Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) {
user_path = ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP;
auto current_dir = FileUtil::GetCurrentDir();
if (current_dir.has_value() &&
FileUtil::Exists(current_dir.value() + USERDATA_DIR DIR_SEP)) {
user_path = current_dir.value() + USERDATA_DIR DIR_SEP;
g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
} else {

View File

@ -193,11 +193,8 @@ void SetCurrentRomPath(const std::string& path);
// Update the Global Path with the new value
void UpdateUserPath(UserPath path, const std::string& filename);
// Returns the path to where the sys file are
[[nodiscard]] std::string GetSysDirectory();
#ifdef __APPLE__
[[nodiscard]] std::string GetBundleDirectory();
[[nodiscard]] std::optional<std::string> GetBundleDirectory();
#endif
#ifdef _WIN32

View File

@ -1,631 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/common_funcs.h"
#include "common/parent_of_member.h"
namespace Common {
// Forward declare implementation class for Node.
namespace impl {
class IntrusiveListImpl;
}
class IntrusiveListNode {
CITRA_NON_COPYABLE(IntrusiveListNode);
private:
friend class impl::IntrusiveListImpl;
IntrusiveListNode* m_prev;
IntrusiveListNode* m_next;
public:
constexpr IntrusiveListNode() : m_prev(this), m_next(this) {}
constexpr bool IsLinked() const {
return m_next != this;
}
private:
constexpr void LinkPrev(IntrusiveListNode* node) {
// We can't link an already linked node.
ASSERT(!node->IsLinked());
this->SplicePrev(node, node);
}
constexpr void SplicePrev(IntrusiveListNode* first, IntrusiveListNode* last) {
// Splice a range into the list.
auto last_prev = last->m_prev;
first->m_prev = m_prev;
last_prev->m_next = this;
m_prev->m_next = first;
m_prev = last_prev;
}
constexpr void LinkNext(IntrusiveListNode* node) {
// We can't link an already linked node.
ASSERT(!node->IsLinked());
return this->SpliceNext(node, node);
}
constexpr void SpliceNext(IntrusiveListNode* first, IntrusiveListNode* last) {
// Splice a range into the list.
auto last_prev = last->m_prev;
first->m_prev = this;
last_prev->m_next = m_next;
m_next->m_prev = last_prev;
m_next = first;
}
constexpr void Unlink() {
this->Unlink(m_next);
}
constexpr void Unlink(IntrusiveListNode* last) {
// Unlink a node from a next node.
auto last_prev = last->m_prev;
m_prev->m_next = last;
last->m_prev = m_prev;
last_prev->m_next = this;
m_prev = last_prev;
}
constexpr IntrusiveListNode* GetPrev() {
return m_prev;
}
constexpr const IntrusiveListNode* GetPrev() const {
return m_prev;
}
constexpr IntrusiveListNode* GetNext() {
return m_next;
}
constexpr const IntrusiveListNode* GetNext() const {
return m_next;
}
};
// DEPRECATED: static_assert(std::is_literal_type<IntrusiveListNode>::value);
namespace impl {
class IntrusiveListImpl {
CITRA_NON_COPYABLE(IntrusiveListImpl);
private:
IntrusiveListNode m_root_node;
public:
template <bool Const>
class Iterator;
using value_type = IntrusiveListNode;
using size_type = size_t;
using difference_type = ptrdiff_t;
using pointer = value_type*;
using const_pointer = const value_type*;
using reference = value_type&;
using const_reference = const value_type&;
using iterator = Iterator<false>;
using const_iterator = Iterator<true>;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
template <bool Const>
class Iterator {
public:
using iterator_category = std::bidirectional_iterator_tag;
using value_type = typename IntrusiveListImpl::value_type;
using difference_type = typename IntrusiveListImpl::difference_type;
using pointer =
std::conditional_t<Const, IntrusiveListImpl::const_pointer, IntrusiveListImpl::pointer>;
using reference = std::conditional_t<Const, IntrusiveListImpl::const_reference,
IntrusiveListImpl::reference>;
private:
pointer m_node;
public:
constexpr explicit Iterator(pointer n) : m_node(n) {}
constexpr bool operator==(const Iterator& rhs) const {
return m_node == rhs.m_node;
}
constexpr pointer operator->() const {
return m_node;
}
constexpr reference operator*() const {
return *m_node;
}
constexpr Iterator& operator++() {
m_node = m_node->m_next;
return *this;
}
constexpr Iterator& operator--() {
m_node = m_node->m_prev;
return *this;
}
constexpr Iterator operator++(int) {
const Iterator it{*this};
++(*this);
return it;
}
constexpr Iterator operator--(int) {
const Iterator it{*this};
--(*this);
return it;
}
constexpr operator Iterator<true>() const {
return Iterator<true>(m_node);
}
constexpr Iterator<false> GetNonConstIterator() const {
return Iterator<false>(const_cast<IntrusiveListImpl::pointer>(m_node));
}
};
public:
constexpr IntrusiveListImpl() : m_root_node() {}
// Iterator accessors.
constexpr iterator begin() {
return iterator(m_root_node.GetNext());
}
constexpr const_iterator begin() const {
return const_iterator(m_root_node.GetNext());
}
constexpr iterator end() {
return iterator(std::addressof(m_root_node));
}
constexpr const_iterator end() const {
return const_iterator(std::addressof(m_root_node));
}
constexpr iterator iterator_to(reference v) {
// Only allow iterator_to for values in lists.
ASSERT(v.IsLinked());
return iterator(std::addressof(v));
}
constexpr const_iterator iterator_to(const_reference v) const {
// Only allow iterator_to for values in lists.
ASSERT(v.IsLinked());
return const_iterator(std::addressof(v));
}
// Content management.
constexpr bool empty() const {
return !m_root_node.IsLinked();
}
constexpr size_type size() const {
return static_cast<size_type>(std::distance(this->begin(), this->end()));
}
constexpr reference back() {
return *m_root_node.GetPrev();
}
constexpr const_reference back() const {
return *m_root_node.GetPrev();
}
constexpr reference front() {
return *m_root_node.GetNext();
}
constexpr const_reference front() const {
return *m_root_node.GetNext();
}
constexpr void push_back(reference node) {
m_root_node.LinkPrev(std::addressof(node));
}
constexpr void push_front(reference node) {
m_root_node.LinkNext(std::addressof(node));
}
constexpr void pop_back() {
m_root_node.GetPrev()->Unlink();
}
constexpr void pop_front() {
m_root_node.GetNext()->Unlink();
}
constexpr iterator insert(const_iterator pos, reference node) {
pos.GetNonConstIterator()->LinkPrev(std::addressof(node));
return iterator(std::addressof(node));
}
constexpr void splice(const_iterator pos, IntrusiveListImpl& o) {
splice_impl(pos, o.begin(), o.end());
}
constexpr void splice(const_iterator pos, IntrusiveListImpl&, const_iterator first) {
const_iterator last(first);
std::advance(last, 1);
splice_impl(pos, first, last);
}
constexpr void splice(const_iterator pos, IntrusiveListImpl&, const_iterator first,
const_iterator last) {
splice_impl(pos, first, last);
}
constexpr iterator erase(const_iterator pos) {
if (pos == this->end()) {
return this->end();
}
iterator it(pos.GetNonConstIterator());
(it++)->Unlink();
return it;
}
constexpr void clear() {
while (!this->empty()) {
this->pop_front();
}
}
private:
constexpr void splice_impl(const_iterator _pos, const_iterator _first, const_iterator _last) {
if (_first == _last) {
return;
}
iterator pos(_pos.GetNonConstIterator());
iterator first(_first.GetNonConstIterator());
iterator last(_last.GetNonConstIterator());
first->Unlink(std::addressof(*last));
pos->SplicePrev(std::addressof(*first), std::addressof(*first));
}
};
} // namespace impl
template <class T, class Traits>
class IntrusiveList {
CITRA_NON_COPYABLE(IntrusiveList);
private:
impl::IntrusiveListImpl m_impl;
public:
template <bool Const>
class Iterator;
using value_type = T;
using size_type = size_t;
using difference_type = ptrdiff_t;
using pointer = value_type*;
using const_pointer = const value_type*;
using reference = value_type&;
using const_reference = const value_type&;
using iterator = Iterator<false>;
using const_iterator = Iterator<true>;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
template <bool Const>
class Iterator {
public:
friend class Common::IntrusiveList<T, Traits>;
using ImplIterator =
std::conditional_t<Const, Common::impl::IntrusiveListImpl::const_iterator,
Common::impl::IntrusiveListImpl::iterator>;
using iterator_category = std::bidirectional_iterator_tag;
using value_type = typename IntrusiveList::value_type;
using difference_type = typename IntrusiveList::difference_type;
using pointer =
std::conditional_t<Const, IntrusiveList::const_pointer, IntrusiveList::pointer>;
using reference =
std::conditional_t<Const, IntrusiveList::const_reference, IntrusiveList::reference>;
private:
ImplIterator m_iterator;
private:
constexpr explicit Iterator(ImplIterator it) : m_iterator(it) {}
constexpr ImplIterator GetImplIterator() const {
return m_iterator;
}
public:
constexpr bool operator==(const Iterator& rhs) const {
return m_iterator == rhs.m_iterator;
}
constexpr pointer operator->() const {
return std::addressof(Traits::GetParent(*m_iterator));
}
constexpr reference operator*() const {
return Traits::GetParent(*m_iterator);
}
constexpr Iterator& operator++() {
++m_iterator;
return *this;
}
constexpr Iterator& operator--() {
--m_iterator;
return *this;
}
constexpr Iterator operator++(int) {
const Iterator it{*this};
++m_iterator;
return it;
}
constexpr Iterator operator--(int) {
const Iterator it{*this};
--m_iterator;
return it;
}
constexpr operator Iterator<true>() const {
return Iterator<true>(m_iterator);
}
};
private:
static constexpr IntrusiveListNode& GetNode(reference ref) {
return Traits::GetNode(ref);
}
static constexpr IntrusiveListNode const& GetNode(const_reference ref) {
return Traits::GetNode(ref);
}
static constexpr reference GetParent(IntrusiveListNode& node) {
return Traits::GetParent(node);
}
static constexpr const_reference GetParent(IntrusiveListNode const& node) {
return Traits::GetParent(node);
}
public:
constexpr IntrusiveList() : m_impl() {}
// Iterator accessors.
constexpr iterator begin() {
return iterator(m_impl.begin());
}
constexpr const_iterator begin() const {
return const_iterator(m_impl.begin());
}
constexpr iterator end() {
return iterator(m_impl.end());
}
constexpr const_iterator end() const {
return const_iterator(m_impl.end());
}
constexpr const_iterator cbegin() const {
return this->begin();
}
constexpr const_iterator cend() const {
return this->end();
}
constexpr reverse_iterator rbegin() {
return reverse_iterator(this->end());
}
constexpr const_reverse_iterator rbegin() const {
return const_reverse_iterator(this->end());
}
constexpr reverse_iterator rend() {
return reverse_iterator(this->begin());
}
constexpr const_reverse_iterator rend() const {
return const_reverse_iterator(this->begin());
}
constexpr const_reverse_iterator crbegin() const {
return this->rbegin();
}
constexpr const_reverse_iterator crend() const {
return this->rend();
}
constexpr iterator iterator_to(reference v) {
return iterator(m_impl.iterator_to(GetNode(v)));
}
constexpr const_iterator iterator_to(const_reference v) const {
return const_iterator(m_impl.iterator_to(GetNode(v)));
}
// Content management.
constexpr bool empty() const {
return m_impl.empty();
}
constexpr size_type size() const {
return m_impl.size();
}
constexpr reference back() {
return GetParent(m_impl.back());
}
constexpr const_reference back() const {
return GetParent(m_impl.back());
}
constexpr reference front() {
return GetParent(m_impl.front());
}
constexpr const_reference front() const {
return GetParent(m_impl.front());
}
constexpr void push_back(reference ref) {
m_impl.push_back(GetNode(ref));
}
constexpr void push_front(reference ref) {
m_impl.push_front(GetNode(ref));
}
constexpr void pop_back() {
m_impl.pop_back();
}
constexpr void pop_front() {
m_impl.pop_front();
}
constexpr iterator insert(const_iterator pos, reference ref) {
return iterator(m_impl.insert(pos.GetImplIterator(), GetNode(ref)));
}
constexpr void splice(const_iterator pos, IntrusiveList& o) {
m_impl.splice(pos.GetImplIterator(), o.m_impl);
}
constexpr void splice(const_iterator pos, IntrusiveList& o, const_iterator first) {
m_impl.splice(pos.GetImplIterator(), o.m_impl, first.GetImplIterator());
}
constexpr void splice(const_iterator pos, IntrusiveList& o, const_iterator first,
const_iterator last) {
m_impl.splice(pos.GetImplIterator(), o.m_impl, first.GetImplIterator(),
last.GetImplIterator());
}
constexpr iterator erase(const_iterator pos) {
return iterator(m_impl.erase(pos.GetImplIterator()));
}
constexpr void clear() {
m_impl.clear();
}
};
template <auto T, class Derived = Common::impl::GetParentType<T>>
class IntrusiveListMemberTraits;
template <class Parent, IntrusiveListNode Parent::*Member, class Derived>
class IntrusiveListMemberTraits<Member, Derived> {
public:
using ListType = IntrusiveList<Derived, IntrusiveListMemberTraits>;
private:
friend class IntrusiveList<Derived, IntrusiveListMemberTraits>;
static constexpr IntrusiveListNode& GetNode(Derived& parent) {
return parent.*Member;
}
static constexpr IntrusiveListNode const& GetNode(Derived const& parent) {
return parent.*Member;
}
static Derived& GetParent(IntrusiveListNode& node) {
return Common::GetParentReference<Member, Derived>(std::addressof(node));
}
static Derived const& GetParent(IntrusiveListNode const& node) {
return Common::GetParentReference<Member, Derived>(std::addressof(node));
}
};
template <auto T, class Derived = Common::impl::GetParentType<T>>
class IntrusiveListMemberTraitsByNonConstexprOffsetOf;
template <class Parent, IntrusiveListNode Parent::*Member, class Derived>
class IntrusiveListMemberTraitsByNonConstexprOffsetOf<Member, Derived> {
public:
using ListType = IntrusiveList<Derived, IntrusiveListMemberTraitsByNonConstexprOffsetOf>;
private:
friend class IntrusiveList<Derived, IntrusiveListMemberTraitsByNonConstexprOffsetOf>;
static constexpr IntrusiveListNode& GetNode(Derived& parent) {
return parent.*Member;
}
static constexpr IntrusiveListNode const& GetNode(Derived const& parent) {
return parent.*Member;
}
static Derived& GetParent(IntrusiveListNode& node) {
return *reinterpret_cast<Derived*>(reinterpret_cast<char*>(std::addressof(node)) -
GetOffset());
}
static Derived const& GetParent(IntrusiveListNode const& node) {
return *reinterpret_cast<const Derived*>(
reinterpret_cast<const char*>(std::addressof(node)) - GetOffset());
}
static uintptr_t GetOffset() {
return reinterpret_cast<uintptr_t>(std::addressof(reinterpret_cast<Derived*>(0)->*Member));
}
};
template <class Derived>
class IntrusiveListBaseNode : public IntrusiveListNode {};
template <class Derived>
class IntrusiveListBaseTraits {
public:
using ListType = IntrusiveList<Derived, IntrusiveListBaseTraits>;
private:
friend class IntrusiveList<Derived, IntrusiveListBaseTraits>;
static constexpr IntrusiveListNode& GetNode(Derived& parent) {
return static_cast<IntrusiveListNode&>(
static_cast<IntrusiveListBaseNode<Derived>&>(parent));
}
static constexpr IntrusiveListNode const& GetNode(Derived const& parent) {
return static_cast<const IntrusiveListNode&>(
static_cast<const IntrusiveListBaseNode<Derived>&>(parent));
}
static constexpr Derived& GetParent(IntrusiveListNode& node) {
return static_cast<Derived&>(static_cast<IntrusiveListBaseNode<Derived>&>(node));
}
static constexpr Derived const& GetParent(IntrusiveListNode const& node) {
return static_cast<const Derived&>(
static_cast<const IntrusiveListBaseNode<Derived>&>(node));
}
};
} // namespace Common

View File

@ -1,190 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <type_traits>
#include "common/assert.h"
namespace Common {
namespace detail {
template <typename T, size_t Size, size_t Align>
struct TypedStorageImpl {
alignas(Align) u8 storage_[Size];
};
} // namespace detail
template <typename T>
using TypedStorage = detail::TypedStorageImpl<T, sizeof(T), alignof(T)>;
template <typename T>
static constexpr T* GetPointer(TypedStorage<T>& ts) {
return static_cast<T*>(static_cast<void*>(std::addressof(ts.storage_)));
}
template <typename T>
static constexpr const T* GetPointer(const TypedStorage<T>& ts) {
return static_cast<const T*>(static_cast<const void*>(std::addressof(ts.storage_)));
}
namespace impl {
template <size_t MaxDepth>
struct OffsetOfUnionHolder {
template <typename ParentType, typename MemberType, size_t Offset>
union UnionImpl {
using PaddingMember = char;
static constexpr size_t GetOffset() {
return Offset;
}
#pragma pack(push, 1)
struct {
PaddingMember padding[Offset];
MemberType members[(sizeof(ParentType) / sizeof(MemberType)) + 1];
} data;
#pragma pack(pop)
UnionImpl<ParentType, MemberType, Offset + 1> next_union;
};
template <typename ParentType, typename MemberType>
union UnionImpl<ParentType, MemberType, 0> {
static constexpr size_t GetOffset() {
return 0;
}
struct {
MemberType members[(sizeof(ParentType) / sizeof(MemberType)) + 1];
} data;
UnionImpl<ParentType, MemberType, 1> next_union;
};
template <typename ParentType, typename MemberType>
union UnionImpl<ParentType, MemberType, MaxDepth> {};
};
template <typename ParentType, typename MemberType>
struct OffsetOfCalculator {
using UnionHolder =
typename OffsetOfUnionHolder<sizeof(MemberType)>::template UnionImpl<ParentType, MemberType,
0>;
union Union {
char c{};
UnionHolder first_union;
TypedStorage<ParentType> parent;
constexpr Union() : c() {}
};
static constexpr Union U = {};
static constexpr const MemberType* GetNextAddress(const MemberType* start,
const MemberType* target) {
while (start < target) {
start++;
}
return start;
}
static constexpr std::ptrdiff_t GetDifference(const MemberType* start,
const MemberType* target) {
return (target - start) * sizeof(MemberType);
}
template <typename CurUnion>
static constexpr std::ptrdiff_t OffsetOfImpl(MemberType ParentType::*member,
CurUnion& cur_union) {
constexpr size_t Offset = CurUnion::GetOffset();
const auto target = std::addressof(GetPointer(U.parent)->*member);
const auto start = std::addressof(cur_union.data.members[0]);
const auto next = GetNextAddress(start, target);
if (next != target) {
if constexpr (Offset < sizeof(MemberType) - 1) {
return OffsetOfImpl(member, cur_union.next_union);
} else {
UNREACHABLE();
}
}
return static_cast<ptrdiff_t>(static_cast<size_t>(next - start) * sizeof(MemberType) +
Offset);
}
static constexpr std::ptrdiff_t OffsetOf(MemberType ParentType::*member) {
return OffsetOfImpl(member, U.first_union);
}
};
template <typename T>
struct GetMemberPointerTraits;
template <typename P, typename M>
struct GetMemberPointerTraits<M P::*> {
using Parent = P;
using Member = M;
};
template <auto MemberPtr>
using GetParentType = typename GetMemberPointerTraits<decltype(MemberPtr)>::Parent;
template <auto MemberPtr>
using GetMemberType = typename GetMemberPointerTraits<decltype(MemberPtr)>::Member;
template <auto MemberPtr, typename RealParentType = GetParentType<MemberPtr>>
constexpr std::ptrdiff_t OffsetOf() {
using DeducedParentType = GetParentType<MemberPtr>;
using MemberType = GetMemberType<MemberPtr>;
static_assert(std::is_base_of<DeducedParentType, RealParentType>::value ||
std::is_same<RealParentType, DeducedParentType>::value);
return OffsetOfCalculator<RealParentType, MemberType>::OffsetOf(MemberPtr);
};
} // namespace impl
template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>>
constexpr RealParentType& GetParentReference(impl::GetMemberType<MemberPtr>* member) {
std::ptrdiff_t Offset = impl::OffsetOf<MemberPtr, RealParentType>();
return *static_cast<RealParentType*>(
static_cast<void*>(static_cast<uint8_t*>(static_cast<void*>(member)) - Offset));
}
template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>>
constexpr RealParentType const& GetParentReference(impl::GetMemberType<MemberPtr> const* member) {
std::ptrdiff_t Offset = impl::OffsetOf<MemberPtr, RealParentType>();
return *static_cast<const RealParentType*>(static_cast<const void*>(
static_cast<const uint8_t*>(static_cast<const void*>(member)) - Offset));
}
template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>>
constexpr RealParentType* GetParentPointer(impl::GetMemberType<MemberPtr>* member) {
return std::addressof(GetParentReference<MemberPtr, RealParentType>(member));
}
template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>>
constexpr RealParentType const* GetParentPointer(impl::GetMemberType<MemberPtr> const* member) {
return std::addressof(GetParentReference<MemberPtr, RealParentType>(member));
}
template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>>
constexpr RealParentType& GetParentReference(impl::GetMemberType<MemberPtr>& member) {
return GetParentReference<MemberPtr, RealParentType>(std::addressof(member));
}
template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>>
constexpr RealParentType const& GetParentReference(impl::GetMemberType<MemberPtr> const& member) {
return GetParentReference<MemberPtr, RealParentType>(std::addressof(member));
}
template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>>
constexpr RealParentType* GetParentPointer(impl::GetMemberType<MemberPtr>& member) {
return std::addressof(GetParentReference<MemberPtr, RealParentType>(member));
}
template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>>
constexpr RealParentType const* GetParentPointer(impl::GetMemberType<MemberPtr> const& member) {
return std::addressof(GetParentReference<MemberPtr, RealParentType>(member));
}
} // namespace Common

View File

@ -128,70 +128,60 @@ add_library(citra_core STATIC
hle/applets/swkbd.h
hle/ipc.h
hle/ipc_helpers.h
hle/kernel/address_arbiter.cpp
hle/kernel/address_arbiter.h
hle/kernel/client_port.cpp
hle/kernel/client_port.h
hle/kernel/client_session.cpp
hle/kernel/client_session.h
hle/kernel/config_mem.cpp
hle/kernel/config_mem.h
hle/kernel/errors.h
hle/kernel/event.cpp
hle/kernel/event.h
hle/kernel/handle_table.cpp
hle/kernel/handle_table.h
hle/kernel/hle_ipc.cpp
hle/kernel/hle_ipc.h
hle/kernel/ipc.cpp
hle/kernel/ipc.h
hle/kernel/ipc_debugger/recorder.cpp
hle/kernel/ipc_debugger/recorder.h
hle/kernel/k_address_arbiter.cpp
hle/kernel/k_address_arbiter.h
hle/kernel/k_auto_object.cpp
hle/kernel/k_auto_object.h
hle/kernel/k_auto_object_container.cpp
hle/kernel/k_auto_object_container.h
hle/kernel/k_client_port.cpp
hle/kernel/k_client_port.h
hle/kernel/k_client_session.cpp
hle/kernel/k_client_session.h
hle/kernel/k_code_set.h
hle/kernel/k_event.cpp
hle/kernel/k_event.h
hle/kernel/k_handle_table.cpp
hle/kernel/k_handle_table.h
hle/kernel/k_linked_list.h
hle/kernel/k_mutex.cpp
hle/kernel/k_mutex.h
hle/kernel/k_object_name.cpp
hle/kernel/k_object_name.h
hle/kernel/k_port.cpp
hle/kernel/k_port.h
hle/kernel/k_process.cpp
hle/kernel/k_process.h
hle/kernel/k_resource_limit.cpp
hle/kernel/k_resource_limit.h
hle/kernel/k_scoped_resource_reservation.h
hle/kernel/k_semaphore.cpp
hle/kernel/k_semaphore.h
hle/kernel/k_server_port.cpp
hle/kernel/k_server_port.h
hle/kernel/k_server_session.cpp
hle/kernel/k_server_session.h
hle/kernel/k_session.cpp
hle/kernel/k_session.h
hle/kernel/k_shared_memory.cpp
hle/kernel/k_shared_memory.h
hle/kernel/k_slab_heap.h
hle/kernel/k_synchronization_object.cpp
hle/kernel/k_synchronization_object.h
hle/kernel/k_thread.cpp
hle/kernel/k_thread.h
hle/kernel/k_timer.cpp
hle/kernel/k_timer.h
hle/kernel/kernel.cpp
hle/kernel/kernel.h
hle/kernel/memory.cpp
hle/kernel/memory.h
hle/kernel/mutex.cpp
hle/kernel/mutex.h
hle/kernel/object.cpp
hle/kernel/object.h
hle/kernel/process.cpp
hle/kernel/process.h
hle/kernel/resource_limit.cpp
hle/kernel/resource_limit.h
hle/kernel/semaphore.cpp
hle/kernel/semaphore.h
hle/kernel/server_port.cpp
hle/kernel/server_port.h
hle/kernel/server_session.cpp
hle/kernel/server_session.h
hle/kernel/session.h
hle/kernel/session.cpp
hle/kernel/shared_memory.cpp
hle/kernel/shared_memory.h
hle/kernel/shared_page.cpp
hle/kernel/shared_page.h
hle/kernel/svc.cpp
hle/kernel/svc.h
hle/kernel/svc_wrapper.h
hle/kernel/thread.cpp
hle/kernel/thread.h
hle/kernel/timer.cpp
hle/kernel/timer.h
hle/kernel/vm_manager.cpp
hle/kernel/vm_manager.h
hle/kernel/wait_object.cpp
hle/kernel/wait_object.h
hle/mii.h
hle/mii.cpp
hle/result.h
@ -333,12 +323,14 @@ add_library(citra_core STATIC
hle/service/ir/ir_u.h
hle/service/ir/ir_user.cpp
hle/service/ir/ir_user.h
hle/service/kernel_helpers.cpp
hle/service/kernel_helpers.h
hle/service/ldr_ro/cro_helper.cpp
hle/service/ldr_ro/cro_helper.h
hle/service/ldr_ro/ldr_ro.cpp
hle/service/ldr_ro/ldr_ro.h
hle/service/mcu/mcu_hwc.cpp
hle/service/mcu/mcu_hwc.h
hle/service/mcu/mcu.cpp
hle/service/mcu/mcu.h
hle/service/mic/mic_u.cpp
hle/service/mic/mic_u.h
hle/service/mvd/mvd.cpp

View File

@ -292,8 +292,8 @@ void ARM_Dynarmic::SetPageTable(const std::shared_ptr<Memory::PageTable>& page_t
}
void ARM_Dynarmic::ServeBreak() {
Kernel::KThread* thread = system.Kernel().GetCurrentThreadManager().GetCurrentThread();
SaveContext(thread->GetContext());
Kernel::Thread* thread = system.Kernel().GetCurrentThreadManager().GetCurrentThread();
SaveContext(thread->context);
GDBStub::Break();
GDBStub::SendTrap(thread, 5);
}

View File

@ -609,8 +609,8 @@ void ARMul_State::ServeBreak() {
DEBUG_ASSERT(Reg[15] == last_bkpt.address);
}
Kernel::KThread* thread = system.Kernel().GetCurrentThreadManager().GetCurrentThread();
system.GetRunningCore().SaveContext(thread->GetContext());
Kernel::Thread* thread = system.Kernel().GetCurrentThreadManager().GetCurrentThread();
system.GetRunningCore().SaveContext(thread->context);
if (last_bkpt_hit || GDBStub::IsMemoryBreak() || GDBStub::GetCpuStepFlag()) {
last_bkpt_hit = false;

View File

@ -27,9 +27,9 @@
#include "core/frontend/image_interface.h"
#include "core/gdbstub/gdbstub.h"
#include "core/global.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/service/apt/applet_manager.h"
#include "core/hle/service/apt/apt.h"
#include "core/hle/service/cam/cam.h"
@ -83,9 +83,9 @@ System::ResultStatus System::RunLoop(bool tight_loop) {
}
if (GDBStub::IsServerEnabled()) {
Kernel::KThread* thread = kernel->GetCurrentThreadManager().GetCurrentThread();
Kernel::Thread* thread = kernel->GetCurrentThreadManager().GetCurrentThread();
if (thread && running_core) {
running_core->SaveContext(thread->GetContext());
running_core->SaveContext(thread->context);
}
GDBStub::HandlePacket(*this);
@ -311,8 +311,8 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
}
telemetry_session->AddInitialInfo(*app_loader);
Kernel::Process* process;
const Loader::ResultStatus load_result{app_loader->Load(std::addressof(process))};
std::shared_ptr<Kernel::Process> process;
const Loader::ResultStatus load_result{app_loader->Load(process)};
if (Loader::ResultStatus::Success != load_result) {
LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", load_result);
System::Shutdown();

View File

@ -7,7 +7,7 @@
#include "common/archives.h"
#include "core/file_sys/archive_other_savedata.h"
#include "core/file_sys/errors.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/fs/archive.h"
SERIALIZE_EXPORT_IMPL(FileSys::ArchiveFactory_OtherSaveDataPermitted)

View File

@ -6,7 +6,7 @@
#include "common/archives.h"
#include "core/core.h"
#include "core/file_sys/archive_savedata.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/process.h"
SERIALIZE_EXPORT_IMPL(FileSys::ArchiveFactory_SaveData)

View File

@ -11,7 +11,7 @@
#include "core/file_sys/archive_selfncch.h"
#include "core/file_sys/errors.h"
#include "core/file_sys/ivfc_archive.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/process.h"
SERIALIZE_EXPORT_IMPL(FileSys::ArchiveFactory_SelfNCCH)

View File

@ -121,7 +121,7 @@ Loader::ResultStatus FileSys::Plugin3GXLoader::Load(
if (!compatible_TID.empty() &&
std::find(compatible_TID.begin(), compatible_TID.end(),
static_cast<u32>(process.codeset.program_id)) == compatible_TID.end()) {
static_cast<u32>(process.codeset->program_id)) == compatible_TID.end()) {
LOG_ERROR(Service_PLGLDR,
"Failed to load 3GX plugin. Not compatible with loaded process: {}",
plg_context.plugin_path);
@ -291,7 +291,7 @@ void FileSys::Plugin3GXLoader::MapBootloader(Kernel::Process& process, Kernel::K
u32 exe_checksum, bool no_flash) {
u32_le game_instructions[2];
kernel.memory.ReadBlock(process, process.codeset.CodeSegment().addr, game_instructions,
kernel.memory.ReadBlock(process, process.codeset->CodeSegment().addr, game_instructions,
sizeof(u32) * 2);
std::array<u32_le, g_plugin_loader_bootloader.size() / sizeof(u32)> bootloader;
@ -307,7 +307,7 @@ void FileSys::Plugin3GXLoader::MapBootloader(Kernel::Process& process, Kernel::K
*it = game_instructions[1];
} break;
case 0xDEAD0002: {
*it = process.codeset.CodeSegment().addr;
*it = process.codeset->CodeSegment().addr;
} break;
case 0xDEAD0003: {
for (u32 i = 0;
@ -361,6 +361,6 @@ void FileSys::Plugin3GXLoader::MapBootloader(Kernel::Process& process, Kernel::K
game_instructions[0] = 0xE51FF004; // ldr pc, [pc, #-4]
game_instructions[1] = _3GX_exe_load_addr - bootloader_memory_size;
kernel.memory.WriteBlock(process, process.codeset.CodeSegment().addr, game_instructions,
kernel.memory.WriteBlock(process, process.codeset->CodeSegment().addr, game_instructions,
sizeof(u32) * 2);
}

View File

@ -25,7 +25,7 @@
#include "common/common_types.h"
#include "common/swap.h"
#include "core/file_sys/archive_backend.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/plgldr/plgldr.h"
namespace Loader {

View File

@ -35,7 +35,7 @@
#include "core/core.h"
#include "core/gdbstub/gdbstub.h"
#include "core/gdbstub/hio.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/process.h"
#include "core/memory.h"
namespace GDBStub {
@ -128,7 +128,7 @@ u32 command_length;
u32 latest_signal = 0;
bool memory_break = false;
static Kernel::KThread* current_thread = nullptr;
static Kernel::Thread* current_thread = nullptr;
// Binding to a port within the reserved ports range (0-1023) requires root permissions,
// so default to a port outside of that range.
@ -159,76 +159,72 @@ BreakpointMap breakpoints_read;
BreakpointMap breakpoints_write;
} // Anonymous namespace
static Kernel::KThread* FindThreadById(int id) {
static Kernel::Thread* FindThreadById(int id) {
u32 num_cores = Core::GetNumCores();
for (u32 i = 0; i < num_cores; ++i) {
const auto& threads =
Core::System::GetInstance().Kernel().GetThreadManager(i).GetThreadList();
for (auto& thread : threads) {
if (thread->GetThreadId() == static_cast<u32>(id)) {
return thread;
return thread.get();
}
}
}
return nullptr;
}
static u32 RegRead(std::size_t id, Kernel::KThread* thread = nullptr) {
static u32 RegRead(std::size_t id, Kernel::Thread* thread = nullptr) {
if (!thread) {
return 0;
}
const auto& context = thread->GetContext();
if (id <= PC_REGISTER) {
return context.cpu_registers[id];
return thread->context.cpu_registers[id];
} else if (id == CPSR_REGISTER) {
return context.cpsr;
return thread->context.cpsr;
} else {
return 0;
}
}
static void RegWrite(std::size_t id, u32 val, Kernel::KThread* thread = nullptr) {
static void RegWrite(std::size_t id, u32 val, Kernel::Thread* thread = nullptr) {
if (!thread) {
return;
}
auto& context = thread->GetContext();
if (id <= PC_REGISTER) {
context.cpu_registers[id] = val;
thread->context.cpu_registers[id] = val;
} else if (id == CPSR_REGISTER) {
context.cpsr = val;
thread->context.cpsr = val;
}
}
static u64 FpuRead(std::size_t id, Kernel::KThread* thread = nullptr) {
static u64 FpuRead(std::size_t id, Kernel::Thread* thread = nullptr) {
if (!thread) {
return 0;
}
const auto& context = thread->GetContext();
if (id >= D0_REGISTER && id < FPSCR_REGISTER) {
u64 ret = context.fpu_registers[2 * (id - D0_REGISTER)];
ret |= static_cast<u64>(context.fpu_registers[2 * (id - D0_REGISTER) + 1]) << 32;
u64 ret = thread->context.fpu_registers[2 * (id - D0_REGISTER)];
ret |= static_cast<u64>(thread->context.fpu_registers[2 * (id - D0_REGISTER) + 1]) << 32;
return ret;
} else if (id == FPSCR_REGISTER) {
return context.fpscr;
return thread->context.fpscr;
} else {
return 0;
}
}
static void FpuWrite(std::size_t id, u64 val, Kernel::KThread* thread = nullptr) {
static void FpuWrite(std::size_t id, u64 val, Kernel::Thread* thread = nullptr) {
if (!thread) {
return;
}
auto& context = thread->GetContext();
if (id >= D0_REGISTER && id < FPSCR_REGISTER) {
context.fpu_registers[2 * (id - D0_REGISTER)] = static_cast<u32>(val);
context.fpu_registers[2 * (id - D0_REGISTER) + 1] = static_cast<u32>(val >> 32);
thread->context.fpu_registers[2 * (id - D0_REGISTER)] = static_cast<u32>(val);
thread->context.fpu_registers[2 * (id - D0_REGISTER) + 1] = static_cast<u32>(val >> 32);
} else if (id == FPSCR_REGISTER) {
context.fpscr = static_cast<u32>(val);
thread->context.fpscr = static_cast<u32>(val);
}
}
@ -610,7 +606,7 @@ static void HandleThreadAlive() {
*
* @param signal Signal to be sent to client.
*/
static void SendSignal(Kernel::KThread* thread, u32 signal, bool full = true) {
static void SendSignal(Kernel::Thread* thread, u32 signal, bool full = true) {
if (gdbserver_socket == -1) {
return;
}
@ -789,7 +785,7 @@ static void WriteRegister() {
return SendReply("E01");
}
Core::GetRunningCore().LoadContext(current_thread->GetContext());
Core::GetRunningCore().LoadContext(current_thread->context);
SendReply("OK");
}
@ -819,7 +815,7 @@ static void WriteRegisters() {
}
}
Core::GetRunningCore().LoadContext(current_thread->GetContext());
Core::GetRunningCore().LoadContext(current_thread->context);
SendReply("OK");
}
@ -894,7 +890,7 @@ void Break(bool is_memory_break) {
static void Step() {
if (command_length > 1) {
RegWrite(PC_REGISTER, GdbHexToInt(command_buffer + 1), current_thread);
Core::GetRunningCore().LoadContext(current_thread->GetContext());
Core::GetRunningCore().LoadContext(current_thread->context);
}
step_loop = true;
halt_loop = true;
@ -1270,7 +1266,7 @@ void SetCpuStepFlag(bool is_step) {
step_loop = is_step;
}
void SendTrap(Kernel::KThread* thread, int trap) {
void SendTrap(Kernel::Thread* thread, int trap) {
if (!send_trap) {
return;
}

View File

@ -8,7 +8,7 @@
#include <span>
#include "common/common_types.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/thread.h"
namespace Core {
class System;
@ -118,7 +118,7 @@ void SetCpuStepFlag(bool is_step);
* @param thread Sending thread.
* @param trap Trap no.
*/
void SendTrap(Kernel::KThread* thread, int trap);
void SendTrap(Kernel::Thread* thread, int trap);
/**
* Send reply to gdb client.

View File

@ -47,10 +47,10 @@ void Applet::SendParameter(const Service::APT::MessageParameter& parameter) {
}
}
void Applet::CloseApplet(Kernel::KAutoObject* object, const std::vector<u8>& buffer) {
void Applet::CloseApplet(std::shared_ptr<Kernel::Object> object, const std::vector<u8>& buffer) {
if (auto locked = manager.lock()) {
locked->PrepareToCloseLibraryApplet(true, false, false);
locked->CloseLibraryApplet(object, buffer);
locked->CloseLibraryApplet(std::move(object), buffer);
} else {
LOG_ERROR(Service_APT, "called after destructing applet manager");
}

View File

@ -8,10 +8,6 @@
#include "core/hle/result.h"
#include "core/hle/service/apt/applet_manager.h"
namespace Core {
class System;
}
namespace HLE::Applets {
class Applet {
@ -43,8 +39,7 @@ public:
protected:
Applet(Core::System& system, Service::APT::AppletId id, Service::APT::AppletId parent,
bool preload, std::weak_ptr<Service::APT::AppletManager> manager)
: system(system), id(id), parent(parent), preload(preload), service_context(system),
manager(std::move(manager)) {}
: system(system), id(id), parent(parent), preload(preload), manager(std::move(manager)) {}
/**
* Handles a parameter from the application.
@ -67,11 +62,11 @@ protected:
virtual Result Finalize() = 0;
Core::System& system;
Service::APT::AppletId id; ///< Id of this Applet
Service::APT::AppletId parent; ///< Id of this Applet's parent
bool preload; ///< Whether the Applet is being preloaded.
std::shared_ptr<std::vector<u8>> heap_memory; ///< Heap memory for this Applet
Service::KernelHelpers::ServiceContext service_context;
/// Whether this applet is running.
bool is_running = true;
@ -80,7 +75,7 @@ protected:
bool is_active = false;
void SendParameter(const Service::APT::MessageParameter& parameter);
void CloseApplet(Kernel::KAutoObject* object, const std::vector<u8>& buffer);
void CloseApplet(std::shared_ptr<Kernel::Object> object, const std::vector<u8>& buffer);
private:
std::weak_ptr<Service::APT::AppletManager> manager;

View File

@ -5,7 +5,6 @@
#include "common/string_util.h"
#include "core/core.h"
#include "core/hle/applets/erreula.h"
#include "core/hle/kernel/k_shared_memory.h"
#include "core/hle/service/apt/apt.h"
namespace HLE::Applets {
@ -29,7 +28,7 @@ Result ErrEula::ReceiveParameterImpl(const Service::APT::MessageParameter& param
// TODO: allocated memory never released
using Kernel::MemoryPermission;
// Create a SharedMemory that directly points to this heap block.
framebuffer_memory = service_context.CreateSharedMemoryForApplet(
framebuffer_memory = system.Kernel().CreateSharedMemoryForApplet(
0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite,
"ErrEula Memory");

View File

@ -5,10 +5,7 @@
#pragma once
#include "core/hle/applets/applet.h"
namespace Kernel {
class KSharedMemory;
}
#include "core/hle/kernel/shared_memory.h"
namespace HLE::Applets {
@ -27,7 +24,7 @@ private:
/// This SharedMemory will be created when we receive the LibAppJustStarted message.
/// It holds the framebuffer info retrieved by the application with
/// GSPGPU::ImportDisplayCaptureInfo
Kernel::KSharedMemory* framebuffer_memory;
std::shared_ptr<Kernel::SharedMemory> framebuffer_memory;
/// Parameter received by the applet on start.
std::vector<u8> startup_param;

View File

@ -11,8 +11,8 @@
#include "core/core.h"
#include "core/frontend/applets/mii_selector.h"
#include "core/hle/applets/mii_selector.h"
#include "core/hle/kernel/k_shared_memory.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/result.h"
namespace HLE::Applets {
@ -35,7 +35,7 @@ Result MiiSelector::ReceiveParameterImpl(const Service::APT::MessageParameter& p
using Kernel::MemoryPermission;
// Create a SharedMemory that directly points to this heap block.
framebuffer_memory = service_context.CreateSharedMemoryForApplet(
framebuffer_memory = system.Kernel().CreateSharedMemoryForApplet(
0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite,
"MiiSelector Memory");

View File

@ -8,6 +8,7 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/hle/applets/applet.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/mii.h"
#include "core/hle/result.h"
#include "core/hle/service/apt/apt.h"
@ -17,10 +18,6 @@ class MiiSelector;
struct MiiSelectorConfig;
} // namespace Frontend
namespace Kernel {
class KSharedMemory;
}
namespace HLE::Applets {
struct MiiConfig {
@ -82,7 +79,7 @@ private:
/// This SharedMemory will be created when we receive the LibAppJustStarted message.
/// It holds the framebuffer info retrieved by the application with
/// GSPGPU::ImportDisplayCaptureInfo
Kernel::KSharedMemory* framebuffer_memory;
std::shared_ptr<Kernel::SharedMemory> framebuffer_memory;
MiiConfig config;

View File

@ -2,8 +2,10 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/string_util.h"
#include "core/core.h"
#include "core/hle/applets/mint.h"
#include "core/hle/kernel/k_shared_memory.h"
#include "core/hle/service/apt/apt.h"
namespace HLE::Applets {
@ -26,7 +28,7 @@ Result Mint::ReceiveParameterImpl(const Service::APT::MessageParameter& paramete
// TODO: allocated memory never released
using Kernel::MemoryPermission;
// Create a SharedMemory that directly points to this heap block.
framebuffer_memory = service_context.CreateSharedMemoryForApplet(
framebuffer_memory = system.Kernel().CreateSharedMemoryForApplet(
0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite,
"Mint Memory");

View File

@ -5,10 +5,7 @@
#pragma once
#include "core/hle/applets/applet.h"
namespace Kernel {
class KSharedMemory;
}
#include "core/hle/kernel/shared_memory.h"
namespace HLE::Applets {
@ -27,7 +24,7 @@ private:
/// This SharedMemory will be created when we receive the Request message.
/// It holds the framebuffer info retrieved by the application with
/// GSPGPU::ImportDisplayCaptureInfo
Kernel::KSharedMemory* framebuffer_memory;
std::shared_ptr<Kernel::SharedMemory> framebuffer_memory;
/// Parameter received by the applet on start.
std::vector<u8> startup_param;

View File

@ -10,9 +10,12 @@
#include "common/string_util.h"
#include "core/core.h"
#include "core/hle/applets/swkbd.h"
#include "core/hle/kernel/k_shared_memory.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/result.h"
#include "core/hle/service/gsp/gsp.h"
#include "core/hle/service/hid/hid.h"
#include "core/memory.h"
namespace HLE::Applets {
@ -29,7 +32,7 @@ Result SoftwareKeyboard::ReceiveParameterImpl(Service::APT::MessageParameter con
using Kernel::MemoryPermission;
// Create a SharedMemory that directly points to this heap block.
framebuffer_memory = service_context.CreateSharedMemoryForApplet(
framebuffer_memory = system.Kernel().CreateSharedMemoryForApplet(
0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite,
"SoftwareKeyboard Memory");
@ -91,7 +94,7 @@ Result SoftwareKeyboard::Start(Service::APT::MessageParameter const& parameter)
"The size of the parameter (SoftwareKeyboardConfig) is wrong");
std::memcpy(&config, parameter.buffer.data(), parameter.buffer.size());
text_memory = parameter.object->DynamicCast<Kernel::KSharedMemory*>();
text_memory = std::static_pointer_cast<Kernel::SharedMemory, Kernel::Object>(parameter.object);
DrawScreenKeyboard();

View File

@ -9,6 +9,7 @@
#include "common/common_types.h"
#include "core/frontend/applets/swkbd.h"
#include "core/hle/applets/applet.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/result.h"
#include "core/hle/service/apt/apt.h"
@ -194,10 +195,10 @@ private:
/// This SharedMemory will be created when we receive the LibAppJustStarted message.
/// It holds the framebuffer info retrieved by the application with
/// GSPGPU::ImportDisplayCaptureInfo
Kernel::KSharedMemory* framebuffer_memory;
std::shared_ptr<Kernel::SharedMemory> framebuffer_memory;
/// SharedMemory where the output text will be stored
Kernel::KSharedMemory* text_memory;
std::shared_ptr<Kernel::SharedMemory> text_memory;
/// Configuration of this instance of the SoftwareKeyboard, as received from the application
SoftwareKeyboardConfig config;

View File

@ -6,6 +6,8 @@
#include "common/common_types.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/thread.h"
#include "core/memory.h"
namespace IPC {

View File

@ -87,11 +87,11 @@ public:
void PushRaw(const T& value);
// TODO : ensure that translate params are added after all regular params
template <typename... T>
void PushCopyObjects(T*... pointers);
template <typename... O>
void PushCopyObjects(std::shared_ptr<O>... pointers);
template <typename... T>
void PushMoveObjects(T*... pointers);
template <typename... O>
void PushMoveObjects(std::shared_ptr<O>... pointers);
void PushStaticBuffer(std::vector<u8> buffer, u8 buffer_id);
@ -183,14 +183,14 @@ inline void RequestBuilder::PushMoveHLEHandles(H... handles) {
Push(static_cast<u32>(handles)...);
}
template <typename... T>
inline void RequestBuilder::PushCopyObjects(T*... pointers) {
PushCopyHLEHandles(context->AddOutgoingHandle(pointers)...);
template <typename... O>
inline void RequestBuilder::PushCopyObjects(std::shared_ptr<O>... pointers) {
PushCopyHLEHandles(context->AddOutgoingHandle(std::move(pointers))...);
}
template <typename... T>
inline void RequestBuilder::PushMoveObjects(T*... pointers) {
PushMoveHLEHandles(context->AddOutgoingHandle(pointers)...);
template <typename... O>
inline void RequestBuilder::PushMoveObjects(std::shared_ptr<O>... pointers) {
PushMoveHLEHandles(context->AddOutgoingHandle(std::move(pointers))...);
}
inline void RequestBuilder::PushStaticBuffer(std::vector<u8> buffer, u8 buffer_id) {
@ -241,11 +241,11 @@ public:
}
/// Equivalent to calling `PopGenericObjects<1>()[0]`.
Kernel::KAutoObject* PopGenericObject();
std::shared_ptr<Kernel::Object> PopGenericObject();
/// Equivalent to calling `std::get<0>(PopObjects<T>())`.
template <typename T>
T* PopObject();
std::shared_ptr<T> PopObject();
/**
* Pop a descriptor containing `N` handles and resolves them to Kernel::Object pointers. If a
@ -255,7 +255,7 @@ public:
* call to read 2 single-handle descriptors.
*/
template <unsigned int N>
std::array<Kernel::KAutoObject*, N> PopGenericObjects();
std::array<std::shared_ptr<Kernel::Object>, N> PopGenericObjects();
/**
* Resolves handles to Kernel::Objects as in PopGenericsObjects(), but then also casts them to
@ -263,11 +263,11 @@ public:
* not match, null is returned instead.
*/
template <typename... T>
std::tuple<T*...> PopObjects();
std::tuple<std::shared_ptr<T>...> PopObjects();
/// Convenience wrapper around PopObjects() which assigns the handles to the passed references.
template <typename... T>
void PopObjects(T**... pointers) {
void PopObjects(std::shared_ptr<T>&... pointers) {
std::tie(pointers...) = PopObjects<T...>();
}
@ -401,20 +401,20 @@ std::array<u32, N> RequestParser::PopHLEHandles() {
return handles;
}
inline Kernel::KAutoObject* RequestParser::PopGenericObject() {
inline std::shared_ptr<Kernel::Object> RequestParser::PopGenericObject() {
auto [handle] = PopHLEHandles<1>();
return context->GetIncomingHandle(handle);
}
template <typename T>
T* RequestParser::PopObject() {
return PopGenericObject()->DynamicCast<T*>();
std::shared_ptr<T> RequestParser::PopObject() {
return Kernel::DynamicObjectCast<T>(PopGenericObject());
}
template <u32 N>
inline std::array<Kernel::KAutoObject*, N> RequestParser::PopGenericObjects() {
template <unsigned int N>
inline std::array<std::shared_ptr<Kernel::Object>, N> RequestParser::PopGenericObjects() {
std::array<u32, N> handles = PopHLEHandles<N>();
std::array<Kernel::KAutoObject*, N> pointers;
std::array<std::shared_ptr<Kernel::Object>, N> pointers;
for (int i = 0; i < N; ++i) {
pointers[i] = context->GetIncomingHandle(handles[i]);
}
@ -423,14 +423,15 @@ inline std::array<Kernel::KAutoObject*, N> RequestParser::PopGenericObjects() {
namespace detail {
template <typename... T, std::size_t... I>
std::tuple<T*...> PopObjectsHelper(std::array<Kernel::KAutoObject*, sizeof...(T)>& pointers,
std::index_sequence<I...>) {
return std::make_tuple((pointers[I]->template DynamicCast<T*>())...);
std::tuple<std::shared_ptr<T>...> PopObjectsHelper(
std::array<std::shared_ptr<Kernel::Object>, sizeof...(T)>&& pointers,
std::index_sequence<I...>) {
return std::make_tuple(Kernel::DynamicObjectCast<T>(std::move(pointers[I]))...);
}
} // namespace detail
template <typename... T>
inline std::tuple<T*...> RequestParser::PopObjects() {
inline std::tuple<std::shared_ptr<T>...> RequestParser::PopObjects() {
return detail::PopObjectsHelper<T...>(PopGenericObjects<sizeof...(T)>(),
std::index_sequence_for<T...>{});
}

View File

@ -0,0 +1,220 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/vector.hpp>
#include "common/archives.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/hle/kernel/address_arbiter.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/thread.h"
#include "core/memory.h"
SERIALIZE_EXPORT_IMPL(Kernel::AddressArbiter)
SERIALIZE_EXPORT_IMPL(Kernel::AddressArbiter::Callback)
namespace Kernel {
void AddressArbiter::WaitThread(std::shared_ptr<Thread> thread, VAddr wait_address) {
thread->wait_address = wait_address;
thread->status = ThreadStatus::WaitArb;
waiting_threads.emplace_back(std::move(thread));
}
u64 AddressArbiter::ResumeAllThreads(VAddr address) {
// Determine which threads are waiting on this address, those should be woken up.
auto itr = std::stable_partition(waiting_threads.begin(), waiting_threads.end(),
[address](const auto& thread) {
ASSERT_MSG(thread->status == ThreadStatus::WaitArb,
"Inconsistent AddressArbiter state");
return thread->wait_address != address;
});
// Wake up all the found threads
const u64 num_threads = std::distance(itr, waiting_threads.end());
std::for_each(itr, waiting_threads.end(), [](auto& thread) { thread->ResumeFromWait(); });
// Remove the woken up threads from the wait list.
waiting_threads.erase(itr, waiting_threads.end());
return num_threads;
}
bool AddressArbiter::ResumeHighestPriorityThread(VAddr address) {
// Determine which threads are waiting on this address, those should be considered for wakeup.
auto matches_start = std::stable_partition(
waiting_threads.begin(), waiting_threads.end(), [address](const auto& thread) {
ASSERT_MSG(thread->status == ThreadStatus::WaitArb,
"Inconsistent AddressArbiter state");
return thread->wait_address != address;
});
// Iterate through threads, find highest priority thread that is waiting to be arbitrated.
// Note: The real kernel will pick the first thread in the list if more than one have the
// same highest priority value. Lower priority values mean higher priority.
auto itr = std::min_element(matches_start, waiting_threads.end(),
[](const auto& lhs, const auto& rhs) {
return lhs->current_priority < rhs->current_priority;
});
if (itr == waiting_threads.end()) {
return false;
}
auto thread = *itr;
thread->ResumeFromWait();
waiting_threads.erase(itr);
return true;
}
AddressArbiter::AddressArbiter(KernelSystem& kernel)
: Object(kernel), kernel(kernel), timeout_callback(std::make_shared<Callback>(*this)) {}
AddressArbiter::~AddressArbiter() {
if (resource_limit) {
resource_limit->Release(ResourceLimitType::AddressArbiter, 1);
}
}
std::shared_ptr<AddressArbiter> KernelSystem::CreateAddressArbiter(std::string name) {
auto address_arbiter = std::make_shared<AddressArbiter>(*this);
address_arbiter->name = std::move(name);
return address_arbiter;
}
class AddressArbiter::Callback : public WakeupCallback {
public:
explicit Callback(AddressArbiter& _parent) : parent(_parent) {}
AddressArbiter& parent;
void WakeUp(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
std::shared_ptr<WaitObject> object) override {
parent.WakeUp(reason, std::move(thread), std::move(object));
}
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<WakeupCallback>(*this);
}
friend class boost::serialization::access;
};
void AddressArbiter::WakeUp(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
std::shared_ptr<WaitObject> object) {
ASSERT(reason == ThreadWakeupReason::Timeout);
// Remove the newly-awakened thread from the Arbiter's waiting list.
waiting_threads.erase(std::remove(waiting_threads.begin(), waiting_threads.end(), thread),
waiting_threads.end());
};
Result AddressArbiter::ArbitrateAddress(std::shared_ptr<Thread> thread, ArbitrationType type,
VAddr address, s32 value, u64 nanoseconds) {
switch (type) {
// Signal thread(s) waiting for arbitrate address...
case ArbitrationType::Signal: {
u64 num_threads{};
// Negative value means resume all threads
if (value < 0) {
num_threads = ResumeAllThreads(address);
} else {
// Resume first N threads
for (s32 i = 0; i < value; i++) {
num_threads += ResumeHighestPriorityThread(address);
}
}
// Prevents lag from low priority threads that spam svcArbitrateAddress and wake no threads
// The tick count is taken directly from official HOS kernel. The priority value is one less
// than official kernel as the affected FMV threads dont meet the priority threshold of 50.
// TODO: Revisit this when scheduler is rewritten and adjust if there isn't a problem there.
if (num_threads == 0 && thread->current_priority >= 49) {
kernel.current_cpu->GetTimer().AddTicks(1614u);
}
break;
}
// Wait current thread (acquire the arbiter)...
case ArbitrationType::WaitIfLessThan:
if ((s32)kernel.memory.Read32(address) < value) {
WaitThread(std::move(thread), address);
}
break;
case ArbitrationType::WaitIfLessThanWithTimeout:
if ((s32)kernel.memory.Read32(address) < value) {
thread->wakeup_callback = timeout_callback;
thread->WakeAfterDelay(nanoseconds);
WaitThread(std::move(thread), address);
}
break;
case ArbitrationType::DecrementAndWaitIfLessThan: {
s32 memory_value = kernel.memory.Read32(address);
if (memory_value < value) {
// Only change the memory value if the thread should wait
kernel.memory.Write32(address, (s32)memory_value - 1);
WaitThread(std::move(thread), address);
}
break;
}
case ArbitrationType::DecrementAndWaitIfLessThanWithTimeout: {
s32 memory_value = kernel.memory.Read32(address);
if (memory_value < value) {
// Only change the memory value if the thread should wait
kernel.memory.Write32(address, (s32)memory_value - 1);
thread->wakeup_callback = timeout_callback;
thread->WakeAfterDelay(nanoseconds);
WaitThread(std::move(thread), address);
}
break;
}
default:
LOG_ERROR(Kernel, "unknown type={}", type);
return ResultInvalidEnumValueFnd;
}
// The calls that use a timeout seem to always return a Timeout error even if they did not put
// the thread to sleep
if (type == ArbitrationType::WaitIfLessThanWithTimeout ||
type == ArbitrationType::DecrementAndWaitIfLessThanWithTimeout) {
return ResultTimeout;
}
return ResultSuccess;
}
template <class Archive>
void AddressArbiter::serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<Object>(*this);
ar& name;
ar& waiting_threads;
ar& timeout_callback;
ar& resource_limit;
}
SERIALIZE_IMPL(AddressArbiter)
} // namespace Kernel
namespace boost::serialization {
template <class Archive>
void save_construct_data(Archive& ar, const Kernel::AddressArbiter::Callback* t,
const unsigned int) {
ar << Kernel::SharedFrom(&t->parent);
}
template <class Archive>
void load_construct_data(Archive& ar, Kernel::AddressArbiter::Callback* t, const unsigned int) {
std::shared_ptr<Kernel::AddressArbiter> parent;
ar >> parent;
::new (t) Kernel::AddressArbiter::Callback(*parent);
}
} // namespace boost::serialization

View File

@ -0,0 +1,88 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <vector>
#include <boost/serialization/export.hpp>
#include "common/common_types.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/result.h"
// Address arbiters are an underlying kernel synchronization object that can be created/used via
// supervisor calls (SVCs). They function as sort of a global lock. Typically, games/other CTR
// applications use them as an underlying mechanism to implement thread-safe barriers, events, and
// semaphores.
namespace Kernel {
class Thread;
class ResourceLimit;
enum class ArbitrationType : u32 {
Signal,
WaitIfLessThan,
DecrementAndWaitIfLessThan,
WaitIfLessThanWithTimeout,
DecrementAndWaitIfLessThanWithTimeout,
};
class AddressArbiter final : public Object, public WakeupCallback {
public:
explicit AddressArbiter(KernelSystem& kernel);
~AddressArbiter() override;
std::string GetTypeName() const override {
return "Arbiter";
}
std::string GetName() const override {
return name;
}
static constexpr HandleType HANDLE_TYPE = HandleType::AddressArbiter;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
std::shared_ptr<ResourceLimit> resource_limit;
std::string name; ///< Name of address arbiter object (optional)
Result ArbitrateAddress(std::shared_ptr<Thread> thread, ArbitrationType type, VAddr address,
s32 value, u64 nanoseconds);
class Callback;
private:
KernelSystem& kernel;
/// Puts the thread to wait on the specified arbitration address under this address arbiter.
void WaitThread(std::shared_ptr<Thread> thread, VAddr wait_address);
/// Resume all threads found to be waiting on the address under this address arbiter
u64 ResumeAllThreads(VAddr address);
/// Resume one thread found to be waiting on the address under this address arbiter and return
/// the resumed thread.
bool ResumeHighestPriorityThread(VAddr address);
/// Threads waiting for the address arbiter to be signaled.
std::vector<std::shared_ptr<Thread>> waiting_threads;
std::shared_ptr<Callback> timeout_callback;
void WakeUp(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
std::shared_ptr<WaitObject> object) override;
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::AddressArbiter)
BOOST_CLASS_EXPORT_KEY(Kernel::AddressArbiter::Callback)
CONSTRUCT_KERNEL_OBJECT(Kernel::AddressArbiter)

View File

@ -0,0 +1,63 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/string.hpp>
#include "common/archives.h"
#include "common/assert.h"
#include "core/global.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/server_port.h"
#include "core/hle/kernel/server_session.h"
SERIALIZE_EXPORT_IMPL(Kernel::ClientPort)
namespace Kernel {
ClientPort::ClientPort(KernelSystem& kernel) : Object(kernel), kernel(kernel) {}
ClientPort::~ClientPort() = default;
Result ClientPort::Connect(std::shared_ptr<ClientSession>* out_client_session) {
// Note: Threads do not wait for the server endpoint to call
// AcceptSession before returning from this call.
R_UNLESS(active_sessions < max_sessions, ResultMaxConnectionsReached);
active_sessions++;
// Create a new session pair, let the created sessions inherit the parent port's HLE handler.
auto [server, client] = kernel.CreateSessionPair(server_port->GetName(), SharedFrom(this));
if (server_port->hle_handler) {
server_port->hle_handler->ClientConnected(server);
} else {
server_port->pending_sessions.push_back(server);
}
// Wake the threads waiting on the ServerPort
server_port->WakeupAllWaitingThreads();
*out_client_session = client;
return ResultSuccess;
}
void ClientPort::ConnectionClosed() {
ASSERT(active_sessions > 0);
--active_sessions;
}
template <class Archive>
void ClientPort::serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<Object>(*this);
ar& server_port;
ar& max_sessions;
ar& active_sessions;
ar& name;
}
SERIALIZE_IMPL(ClientPort)
} // namespace Kernel

View File

@ -0,0 +1,73 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <string>
#include <boost/serialization/export.hpp>
#include "common/common_types.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/server_port.h"
#include "core/hle/result.h"
namespace Kernel {
class ClientSession;
class ClientPort final : public Object {
public:
explicit ClientPort(KernelSystem& kernel);
~ClientPort() override;
friend class ServerPort;
std::string GetTypeName() const override {
return "ClientPort";
}
std::string GetName() const override {
return name;
}
static constexpr HandleType HANDLE_TYPE = HandleType::ClientPort;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
std::shared_ptr<ServerPort> GetServerPort() const {
return server_port;
}
/**
* Creates a new Session pair, adds the created ServerSession to the associated ServerPort's
* list of pending sessions, and signals the ServerPort, causing any threads
* waiting on it to awake.
* @returns ClientSession The client endpoint of the created Session pair, or error code.
*/
Result Connect(std::shared_ptr<ClientSession>* out_client_session);
/**
* Signifies that a previously active connection has been closed,
* decreasing the total number of active connections to this port.
*/
void ConnectionClosed();
private:
KernelSystem& kernel;
std::shared_ptr<ServerPort> server_port; ///< ServerPort associated with this client port.
u32 max_sessions = 0; ///< Maximum number of simultaneous sessions the port can have
u32 active_sessions = 0; ///< Number of currently open sessions to this port
std::string name; ///< Name of client port (optional)
friend class KernelSystem;
private:
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::ClientPort)
CONSTRUCT_KERNEL_OBJECT(Kernel::ClientPort)

View File

@ -0,0 +1,67 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/string.hpp>
#include "common/archives.h"
#include "common/assert.h"
#include "core/hle/kernel/client_session.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/session.h"
#include "core/hle/kernel/thread.h"
SERIALIZE_EXPORT_IMPL(Kernel::ClientSession)
namespace Kernel {
ClientSession::ClientSession(KernelSystem& kernel) : Object(kernel) {}
ClientSession::~ClientSession() {
// This destructor will be called automatically when the last ClientSession handle is closed by
// the emulated application.
// Local references to ServerSession and SessionRequestHandler are necessary to guarantee they
// will be kept alive until after ClientDisconnected() returns.
std::shared_ptr<ServerSession> server = SharedFrom(parent->server);
if (server) {
std::shared_ptr<SessionRequestHandler> hle_handler = server->hle_handler;
if (hle_handler)
hle_handler->ClientDisconnected(server);
// Clean up the list of client threads with pending requests, they are unneeded now that the
// client endpoint is closed.
server->pending_requesting_threads.clear();
server->currently_handling = nullptr;
}
parent->client = nullptr;
if (server) {
// Notify any threads waiting on the ServerSession that the endpoint has been closed. Note
// that this call has to happen after `Session::client` has been set to nullptr to let the
// ServerSession know that the client endpoint has been closed.
server->WakeupAllWaitingThreads();
}
}
Result ClientSession::SendSyncRequest(std::shared_ptr<Thread> thread) {
// Keep ServerSession alive until we're done working with it.
std::shared_ptr<ServerSession> server = SharedFrom(parent->server);
R_UNLESS(server, ResultSessionClosed);
// Signal the server session that new data is available
return server->HandleSyncRequest(std::move(thread));
}
template <class Archive>
void ClientSession::serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<Object>(*this);
ar& name;
ar& parent;
}
SERIALIZE_IMPL(ClientSession)
} // namespace Kernel

View File

@ -0,0 +1,60 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <string>
#include <boost/serialization/export.hpp>
#include "common/common_types.h"
#include "core/hle/kernel/object.h"
#include "core/hle/result.h"
namespace Kernel {
class Session;
class Thread;
class ClientSession final : public Object {
public:
explicit ClientSession(KernelSystem& kernel);
~ClientSession() override;
friend class KernelSystem;
std::string GetTypeName() const override {
return "ClientSession";
}
std::string GetName() const override {
return name;
}
static constexpr HandleType HANDLE_TYPE = HandleType::ClientSession;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
/**
* Sends an SyncRequest from the current emulated thread.
* @param thread Thread that initiated the request.
* @return Result of the operation.
*/
Result SendSyncRequest(std::shared_ptr<Thread> thread);
std::string name; ///< Name of client port (optional)
/// The parent session, which links to the server endpoint.
std::shared_ptr<Session> parent;
private:
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::ClientSession)
CONSTRUCT_KERNEL_OBJECT(Kernel::ClientSession)

View File

@ -14,18 +14,18 @@ namespace ConfigMem {
Handler::Handler() {
std::memset(&config_mem, 0, sizeof(config_mem));
// Values extracted from firmware 11.2.0-35E
config_mem.kernel_version_min = 0x34;
// Values extracted from firmware 11.17.0-50E
config_mem.kernel_version_min = 0x3a;
config_mem.kernel_version_maj = 0x2;
config_mem.ns_tid = 0x0004013000008002;
config_mem.sys_core_ver = 0x2;
config_mem.unit_info = 0x1; // Bit 0 set for Retail
config_mem.prev_firm = 0x1;
config_mem.ctr_sdk_ver = 0x0000F297;
config_mem.firm_version_min = 0x34;
config_mem.ctr_sdk_ver = 0x0000F450;
config_mem.firm_version_min = 0x3a;
config_mem.firm_version_maj = 0x2;
config_mem.firm_sys_core_ver = 0x2;
config_mem.firm_ctr_sdk_ver = 0x0000F297;
config_mem.firm_ctr_sdk_ver = 0x0000F450;
}
ConfigMemDef& Handler::GetConfigMem() {

View File

@ -16,7 +16,6 @@ enum {
OutOfEvents = 15,
OutOfTimers = 16,
OutOfHandles = 19,
ProcessNotFound = 24,
SessionClosedByRemote = 26,
PortNameTooLong = 30,
WrongLockingThread = 31,
@ -110,8 +109,5 @@ constexpr Result ResultTimeout(ErrorDescription::Timeout, ErrorModule::OS,
constexpr Result ResultNoPendingSessions(ErrCodes::NoPendingSessions, ErrorModule::OS,
ErrorSummary::WouldBlock,
ErrorLevel::Permanent); // 0xD8401823
constexpr Result ResultProcessNotFound(ErrCodes::ProcessNotFound, ErrorModule::OS,
ErrorSummary::WrongArgument,
ErrorLevel::Permanent); // 0xD9001818
} // namespace Kernel

View File

@ -0,0 +1,73 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/string.hpp>
#include "common/archives.h"
#include "common/assert.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/thread.h"
SERIALIZE_EXPORT_IMPL(Kernel::Event)
namespace Kernel {
Event::Event(KernelSystem& kernel) : WaitObject(kernel) {}
Event::~Event() {
if (resource_limit) {
resource_limit->Release(ResourceLimitType::Event, 1);
}
}
std::shared_ptr<Event> KernelSystem::CreateEvent(ResetType reset_type, std::string name) {
auto event = std::make_shared<Event>(*this);
event->signaled = false;
event->reset_type = reset_type;
event->name = std::move(name);
return event;
}
bool Event::ShouldWait(const Thread* thread) const {
return !signaled;
}
void Event::Acquire(Thread* thread) {
ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
if (reset_type == ResetType::OneShot) {
signaled = false;
}
}
void Event::Signal() {
signaled = true;
WakeupAllWaitingThreads();
}
void Event::Clear() {
signaled = false;
}
void Event::WakeupAllWaitingThreads() {
WaitObject::WakeupAllWaitingThreads();
if (reset_type == ResetType::Pulse) {
signaled = false;
}
}
template <class Archive>
void Event::serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<WaitObject>(*this);
ar& reset_type;
ar& signaled;
ar& name;
ar& resource_limit;
}
SERIALIZE_IMPL(Event)
} // namespace Kernel

View File

@ -0,0 +1,64 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <boost/serialization/export.hpp>
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/wait_object.h"
namespace Kernel {
class Event final : public WaitObject {
public:
explicit Event(KernelSystem& kernel);
~Event() override;
std::string GetTypeName() const override {
return "Event";
}
std::string GetName() const override {
return name;
}
void SetName(const std::string& name_) {
name = name_;
}
static constexpr HandleType HANDLE_TYPE = HandleType::Event;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
ResetType GetResetType() const {
return reset_type;
}
bool ShouldWait(const Thread* thread) const override;
void Acquire(Thread* thread) override;
void WakeupAllWaitingThreads() override;
void Signal();
void Clear();
std::shared_ptr<ResourceLimit> resource_limit;
private:
ResetType reset_type; ///< Current ResetType
bool signaled; ///< Whether the event has already been signaled
std::string name; ///< Name of event (optional)
friend class KernelSystem;
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::Event)
CONSTRUCT_KERNEL_OBJECT(Kernel::Event)

View File

@ -0,0 +1,111 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <utility>
#include <boost/serialization/array.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include "common/archives.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/thread.h"
SERIALIZE_EXPORT_IMPL(Kernel::HandleTable)
namespace Kernel {
namespace {
constexpr u16 GetSlot(Handle handle) {
return handle >> 15;
}
constexpr u16 GetGeneration(Handle handle) {
return handle & 0x7FFF;
}
} // Anonymous namespace
HandleTable::HandleTable(KernelSystem& kernel) : kernel(kernel) {
next_generation = 1;
Clear();
}
HandleTable::~HandleTable() = default;
Result HandleTable::Create(Handle* out_handle, std::shared_ptr<Object> obj) {
DEBUG_ASSERT(obj != nullptr);
u16 slot = next_free_slot;
R_UNLESS(slot < generations.size(), ResultOutOfHandles);
next_free_slot = generations[slot];
u16 generation = next_generation++;
// Overflow count so it fits in the 15 bits dedicated to the generation in the handle.
// CTR-OS doesn't use generation 0, so skip straight to 1.
if (next_generation >= (1 << 15)) {
next_generation = 1;
}
generations[slot] = generation;
objects[slot] = std::move(obj);
*out_handle = generation | (slot << 15);
return ResultSuccess;
}
Result HandleTable::Duplicate(Handle* out_handle, Handle handle) {
std::shared_ptr<Object> object = GetGeneric(handle);
R_UNLESS(object, ResultInvalidHandle);
return Create(out_handle, std::move(object));
}
Result HandleTable::Close(Handle handle) {
R_UNLESS(IsValid(handle), ResultInvalidHandle);
const u16 slot = GetSlot(handle);
objects[slot] = nullptr;
generations[slot] = next_free_slot;
next_free_slot = slot;
return ResultSuccess;
}
bool HandleTable::IsValid(Handle handle) const {
const u16 slot = GetSlot(handle);
const u16 generation = GetGeneration(handle);
return slot < MAX_COUNT && objects[slot] != nullptr && generations[slot] == generation;
}
std::shared_ptr<Object> HandleTable::GetGeneric(Handle handle) const {
if (handle == CurrentThread) {
return SharedFrom(kernel.GetCurrentThreadManager().GetCurrentThread());
} else if (handle == CurrentProcess) {
return kernel.GetCurrentProcess();
}
if (!IsValid(handle)) {
return nullptr;
}
return objects[GetSlot(handle)];
}
void HandleTable::Clear() {
for (u16 i = 0; i < MAX_COUNT; ++i) {
generations[i] = i + 1;
objects[i] = nullptr;
}
next_free_slot = 0;
}
template <class Archive>
void HandleTable::serialize(Archive& ar, const unsigned int) {
ar& objects;
ar& generations;
ar& next_generation;
ar& next_free_slot;
}
SERIALIZE_IMPL(HandleTable)
} // namespace Kernel

View File

@ -0,0 +1,129 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <cstddef>
#include <memory>
#include <boost/serialization/export.hpp>
#include "common/common_types.h"
#include "core/hle/kernel/object.h"
#include "core/hle/result.h"
namespace Kernel {
enum KernelHandle : Handle {
CurrentThread = 0xFFFF8000,
CurrentProcess = 0xFFFF8001,
};
/**
* This class allows the creation of Handles, which are references to objects that can be tested
* for validity and looked up. Here they are used to pass references to kernel objects to/from the
* emulated process. it has been designed so that it follows the same handle format and has
* approximately the same restrictions as the handle manager in the CTR-OS.
*
* Handles contain two sub-fields: a slot index (bits 31:15) and a generation value (bits 14:0).
* The slot index is used to index into the arrays in this class to access the data corresponding
* to the Handle.
*
* To prevent accidental use of a freed Handle whose slot has already been reused, a global counter
* is kept and incremented every time a Handle is created. This is the Handle's "generation". The
* value of the counter is stored into the Handle as well as in the handle table (in the
* "generations" array). When looking up a handle, the Handle's generation must match with the
* value stored on the class, otherwise the Handle is considered invalid.
*
* To find free slots when allocating a Handle without needing to scan the entire object array, the
* generations field of unallocated slots is re-purposed as a linked list of indices to free slots.
* When a Handle is created, an index is popped off the list and used for the new Handle. When it
* is destroyed, it is again pushed onto the list to be re-used by the next allocation. It is
* likely that this allocation strategy differs from the one used in CTR-OS, but this hasn't been
* verified and isn't likely to cause any problems.
*/
class HandleTable final : NonCopyable {
public:
explicit HandleTable(KernelSystem& kernel);
~HandleTable();
/**
* Allocates a handle for the given object.
* @return The created Handle or one of the following errors:
* - `ResultOutOfHandles`: the maximum number of handles has been exceeded.
*/
Result Create(Handle* out_handle, std::shared_ptr<Object> obj);
/**
* Returns a new handle that points to the same object as the passed in handle.
* @return The duplicated Handle or one of the following errors:
* - `ResultInvalidHandle`: an invalid handle was passed in.
* - Any errors returned by `Create()`.
*/
Result Duplicate(Handle* out_handle, Handle handle);
/**
* Closes a handle, removing it from the table and decreasing the object's ref-count.
* @return `ResultSuccess` or one of the following errors:
* - `ResultInvalidHandle`: an invalid handle was passed in.
*/
Result Close(Handle handle);
/// Checks if a handle is valid and points to an existing object.
bool IsValid(Handle handle) const;
/**
* Looks up a handle.
* @return Pointer to the looked-up object, or `nullptr` if the handle is not valid.
*/
std::shared_ptr<Object> GetGeneric(Handle handle) const;
/**
* Looks up a handle while verifying its type.
* @return Pointer to the looked-up object, or `nullptr` if the handle is not valid or its
* type differs from the requested one.
*/
template <class T>
std::shared_ptr<T> Get(Handle handle) const {
return DynamicObjectCast<T>(GetGeneric(handle));
}
/// Closes all handles held in this table.
void Clear();
private:
/**
* This is the maximum limit of handles allowed per process in CTR-OS. It can be further
* reduced by ExHeader values, but this is not emulated here.
*/
static const std::size_t MAX_COUNT = 4096;
/// Stores the Object referenced by the handle or null if the slot is empty.
std::array<std::shared_ptr<Object>, MAX_COUNT> objects;
/**
* The value of `next_generation` when the handle was created, used to check for validity. For
* empty slots, contains the index of the next free slot in the list.
*/
std::array<u16, MAX_COUNT> generations;
/**
* Global counter of the number of created handles. Stored in `generations` when a handle is
* created, and wraps around to 1 when it hits 0x8000.
*/
u16 next_generation;
/// Head of the free slots linked list.
u16 next_free_slot;
KernelSystem& kernel;
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
};
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::HandleTable)
CONSTRUCT_KERNEL_OBJECT(Kernel::HandleTable)

View File

@ -12,12 +12,12 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "core/core.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/ipc_debugger/recorder.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_handle_table.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
SERIALIZE_EXPORT_IMPL(Kernel::SessionRequestHandler)
SERIALIZE_EXPORT_IMPL(Kernel::SessionRequestHandler::SessionDataBase)
@ -33,13 +33,15 @@ public:
ThreadCallback(std::shared_ptr<HLERequestContext> context_,
std::shared_ptr<HLERequestContext::WakeupCallback> callback_)
: callback(std::move(callback_)), context(std::move(context_)) {}
void WakeUp(ThreadWakeupReason reason, KThread* thread, KSynchronizationObject* object) {
ASSERT(thread->m_status == ThreadStatus::WaitHleEvent);
void WakeUp(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
std::shared_ptr<WaitObject> object) {
ASSERT(thread->status == ThreadStatus::WaitHleEvent);
if (callback) {
callback->WakeUp(thread, *context, reason);
}
Process* process = thread->GetOwner();
auto process = thread->owner_process.lock();
ASSERT(process);
// We must copy the entire command buffer *plus* the entire static buffers area, since
// the translation might need to read from it in order to retrieve the StaticBuffer
@ -68,16 +70,16 @@ private:
friend class boost::serialization::access;
};
SessionRequestHandler::SessionInfo::SessionInfo(KServerSession* session_,
SessionRequestHandler::SessionInfo::SessionInfo(std::shared_ptr<ServerSession> session,
std::unique_ptr<SessionDataBase> data)
: session(session_), data(std::move(data)) {}
: session(std::move(session)), data(std::move(data)) {}
void SessionRequestHandler::ClientConnected(KServerSession* server_session) {
void SessionRequestHandler::ClientConnected(std::shared_ptr<ServerSession> server_session) {
server_session->SetHleHandler(shared_from_this());
connected_sessions.emplace_back(server_session, MakeSessionData());
connected_sessions.emplace_back(std::move(server_session), MakeSessionData());
}
void SessionRequestHandler::ClientDisconnected(KServerSession* server_session) {
void SessionRequestHandler::ClientDisconnected(std::shared_ptr<ServerSession> server_session) {
server_session->SetHleHandler(nullptr);
connected_sessions.erase(
std::remove_if(connected_sessions.begin(), connected_sessions.end(),
@ -102,46 +104,40 @@ void SessionRequestHandler::SessionInfo::serialize(Archive& ar, const unsigned i
}
SERIALIZE_IMPL(SessionRequestHandler::SessionInfo)
KEvent* HLERequestContext::SleepClientThread(const std::string& reason,
std::chrono::nanoseconds timeout,
std::shared_ptr<WakeupCallback> callback) {
std::shared_ptr<Event> HLERequestContext::SleepClientThread(
const std::string& reason, std::chrono::nanoseconds timeout,
std::shared_ptr<WakeupCallback> callback) {
// Put the client thread to sleep until the wait event is signaled or the timeout expires.
thread->m_wakeup_callback = std::make_shared<ThreadCallback>(shared_from_this(), callback);
thread->wakeup_callback = std::make_shared<ThreadCallback>(shared_from_this(), callback);
// Create pause event.
auto* event = KEvent::Create(kernel);
event->Initialize(nullptr, ResetType::OneShot);
event->SetName("HLE Pause Event: " + reason);
KEvent::Register(kernel, event);
// Add the event to the list of objects the thread is waiting for.
thread->m_status = ThreadStatus::WaitHleEvent;
thread->m_wait_objects = {event};
auto event = kernel.CreateEvent(Kernel::ResetType::OneShot, "HLE Pause Event: " + reason);
thread->status = ThreadStatus::WaitHleEvent;
thread->wait_objects = {event};
event->AddWaitingThread(thread);
if (timeout.count() > 0) {
if (timeout.count() > 0)
thread->WakeAfterDelay(timeout.count());
}
return event;
}
HLERequestContext::HLERequestContext() : kernel(Core::Global<KernelSystem>()) {}
HLERequestContext::HLERequestContext(KernelSystem& kernel, KServerSession* session, KThread* thread)
: kernel(kernel), session(session), thread(thread) {
HLERequestContext::HLERequestContext(KernelSystem& kernel, std::shared_ptr<ServerSession> session,
std::shared_ptr<Thread> thread)
: kernel(kernel), session(std::move(session)), thread(thread) {
cmd_buf[0] = 0;
}
HLERequestContext::~HLERequestContext() = default;
KAutoObject* HLERequestContext::GetIncomingHandle(u32 id_from_cmdbuf) const {
std::shared_ptr<Object> HLERequestContext::GetIncomingHandle(u32 id_from_cmdbuf) const {
ASSERT(id_from_cmdbuf < request_handles.size());
return request_handles[id_from_cmdbuf];
}
u32 HLERequestContext::AddOutgoingHandle(KAutoObject* object) {
request_handles.push_back(object);
u32 HLERequestContext::AddOutgoingHandle(std::shared_ptr<Object> object) {
request_handles.push_back(std::move(object));
return static_cast<u32>(request_handles.size() - 1);
}
@ -158,7 +154,8 @@ void HLERequestContext::AddStaticBuffer(u8 buffer_id, std::vector<u8> data) {
}
Result HLERequestContext::PopulateFromIncomingCommandBuffer(const u32_le* src_cmdbuf,
Process* src_process) {
std::shared_ptr<Process> src_process_) {
auto& src_process = *src_process_;
IPC::Header header{src_cmdbuf[0]};
std::size_t untranslated_size = 1u + header.normal_params_size;
@ -182,32 +179,25 @@ Result HLERequestContext::PopulateFromIncomingCommandBuffer(const u32_le* src_cm
switch (IPC::GetDescriptorType(descriptor)) {
case IPC::DescriptorType::CopyHandle:
case IPC::DescriptorType::MoveHandle: {
const u32 num_handles = IPC::HandleNumberFromDesc(descriptor);
auto& src_handle_table = src_process->handle_table;
u32 num_handles = IPC::HandleNumberFromDesc(descriptor);
ASSERT(i + num_handles <= command_size); // TODO(yuriks): Return error
for (u32 j = 0; j < num_handles; ++j) {
const Handle handle = src_cmdbuf[i];
if (!handle) {
cmd_buf[i++] = AddOutgoingHandle(nullptr);
continue;
Handle handle = src_cmdbuf[i];
std::shared_ptr<Object> object = nullptr;
if (handle != 0) {
object = src_process.handle_table.GetGeneric(handle);
ASSERT(object != nullptr); // TODO(yuriks): Return error
if (descriptor == IPC::DescriptorType::MoveHandle) {
src_process.handle_table.Close(handle);
}
}
// Get object from the handle table.
KScopedAutoObject object =
src_handle_table.GetObjectForIpcWithoutPseudoHandle(handle);
ASSERT(object.IsNotNull());
// If we are moving, remove the old handle.
if (descriptor == IPC::DescriptorType::MoveHandle) {
src_handle_table.Remove(handle);
}
cmd_buf[i++] = AddOutgoingHandle(object.GetPointerUnsafe());
cmd_buf[i++] = AddOutgoingHandle(std::move(object));
}
break;
}
case IPC::DescriptorType::CallingPid: {
cmd_buf[i++] = src_process->process_id;
cmd_buf[i++] = src_process.process_id;
break;
}
case IPC::DescriptorType::StaticBuffer: {
@ -216,7 +206,7 @@ Result HLERequestContext::PopulateFromIncomingCommandBuffer(const u32_le* src_cm
// Copy the input buffer into our own vector and store it.
std::vector<u8> data(buffer_info.size);
kernel.memory.ReadBlock(*src_process, source_address, data.data(), data.size());
kernel.memory.ReadBlock(src_process, source_address, data.data(), data.size());
AddStaticBuffer(buffer_info.buffer_id, std::move(data));
cmd_buf[i++] = source_address;
@ -224,7 +214,7 @@ Result HLERequestContext::PopulateFromIncomingCommandBuffer(const u32_le* src_cm
}
case IPC::DescriptorType::MappedBuffer: {
u32 next_id = static_cast<u32>(request_mapped_buffers.size());
request_mapped_buffers.emplace_back(kernel.memory, src_process, descriptor,
request_mapped_buffers.emplace_back(kernel.memory, src_process_, descriptor,
src_cmdbuf[i], next_id);
cmd_buf[i++] = next_id;
break;
@ -269,13 +259,14 @@ Result HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf,
case IPC::DescriptorType::CopyHandle:
case IPC::DescriptorType::MoveHandle: {
// HLE services don't use handles, so we treat both CopyHandle and MoveHandle equally
const u32 num_handles = IPC::HandleNumberFromDesc(descriptor);
u32 num_handles = IPC::HandleNumberFromDesc(descriptor);
ASSERT(i + num_handles <= command_size);
for (u32 j = 0; j < num_handles; ++j) {
KAutoObject* object = GetIncomingHandle(cmd_buf[i]);
std::shared_ptr<Object> object = GetIncomingHandle(cmd_buf[i]);
Handle handle = 0;
if (object != nullptr) {
dst_process.handle_table.Add(std::addressof(handle), object);
// TODO(yuriks): Figure out the proper error handling for if this fails
R_ASSERT(dst_process.handle_table.Create(std::addressof(handle), object));
}
dst_cmdbuf[i++] = handle;
}
@ -336,7 +327,7 @@ void HLERequestContext::serialize(Archive& ar, const unsigned int) {
ar& cmd_buf;
ar& session;
ar& thread;
// ar& request_handles;
ar& request_handles;
ar& static_buffers;
ar& request_mapped_buffers;
}
@ -344,8 +335,8 @@ SERIALIZE_IMPL(HLERequestContext)
MappedBuffer::MappedBuffer() : memory(&Core::Global<Core::System>().Memory()) {}
MappedBuffer::MappedBuffer(Memory::MemorySystem& memory, Process* process, u32 descriptor,
VAddr address, u32 id)
MappedBuffer::MappedBuffer(Memory::MemorySystem& memory, std::shared_ptr<Process> process,
u32 descriptor, VAddr address, u32 id)
: memory(&memory), id(id), address(address), process(std::move(process)) {
IPC::MappedBufferDescInfo desc{descriptor};
size = desc.size;

View File

@ -17,8 +17,8 @@
#include "common/serialization/boost_small_vector.hpp"
#include "common/swap.h"
#include "core/hle/ipc.h"
#include "core/hle/kernel/k_server_session.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/server_session.h"
namespace Service {
class ServiceFrameworkBase;
@ -32,8 +32,8 @@ namespace Kernel {
class HandleTable;
class Process;
class KThread;
class KEvent;
class Thread;
class Event;
class HLERequestContext;
class KernelSystem;
@ -58,14 +58,14 @@ public:
* associated ServerSession alive for the duration of the connection.
* @param server_session Owning pointer to the ServerSession associated with the connection.
*/
virtual void ClientConnected(KServerSession* server_session);
virtual void ClientConnected(std::shared_ptr<ServerSession> server_session);
/**
* Signals that a client has just disconnected from this HLE handler and releases the
* associated ServerSession.
* @param server_session ServerSession associated with the connection.
*/
virtual void ClientDisconnected(KServerSession* server_session);
virtual void ClientDisconnected(std::shared_ptr<ServerSession> server_session);
/// Empty placeholder structure for services with no per-session data. The session data classes
/// in each service must inherit from this.
@ -79,9 +79,9 @@ public:
};
struct SessionInfo {
SessionInfo(KServerSession* session, std::unique_ptr<SessionDataBase> data);
SessionInfo(std::shared_ptr<ServerSession> session, std::unique_ptr<SessionDataBase> data);
KServerSession* session;
std::shared_ptr<ServerSession> session;
std::unique_ptr<SessionDataBase> data;
private:
@ -97,7 +97,7 @@ protected:
/// Returns the session data associated with the server session.
template <typename T>
T* GetSessionData(KServerSession* session) {
T* GetSessionData(std::shared_ptr<ServerSession> session) {
static_assert(std::is_base_of<SessionDataBase, T>(),
"T is not a subclass of SessionDataBase");
auto itr = std::find_if(connected_sessions.begin(), connected_sessions.end(),
@ -120,8 +120,8 @@ private:
class MappedBuffer {
public:
MappedBuffer(Memory::MemorySystem& memory, Process* process, u32 descriptor, VAddr address,
u32 id);
MappedBuffer(Memory::MemorySystem& memory, std::shared_ptr<Process> process, u32 descriptor,
VAddr address, u32 id);
// interface for service
void Read(void* dest_buffer, std::size_t offset, std::size_t size);
@ -144,7 +144,7 @@ private:
Memory::MemorySystem* memory;
u32 id;
VAddr address;
Process* process;
std::shared_ptr<Process> process;
u32 size;
IPC::MappedBufferPermissions perms;
@ -192,7 +192,8 @@ private:
*/
class HLERequestContext : public std::enable_shared_from_this<HLERequestContext> {
public:
explicit HLERequestContext(KernelSystem& kernel, KServerSession* session, KThread* thread);
HLERequestContext(KernelSystem& kernel, std::shared_ptr<ServerSession> session,
std::shared_ptr<Thread> thread);
~HLERequestContext();
/// Returns a pointer to the IPC command buffer for this request.
@ -209,21 +210,21 @@ public:
* Returns the session through which this request was made. This can be used as a map key to
* access per-client data on services.
*/
KServerSession* Session() const {
std::shared_ptr<ServerSession> Session() const {
return session;
}
/**
* Returns the client thread that made the service request.
*/
KThread* ClientThread() const {
std::shared_ptr<Thread> ClientThread() const {
return thread;
}
class WakeupCallback {
public:
virtual ~WakeupCallback() = default;
virtual void WakeUp(KThread* thread, HLERequestContext& context,
virtual void WakeUp(std::shared_ptr<Thread> thread, HLERequestContext& context,
ThreadWakeupReason reason) = 0;
private:
@ -243,8 +244,9 @@ public:
* was called.
* @returns Event that when signaled will resume the thread and call the callback function.
*/
KEvent* SleepClientThread(const std::string& reason, std::chrono::nanoseconds timeout,
std::shared_ptr<WakeupCallback> callback);
std::shared_ptr<Event> SleepClientThread(const std::string& reason,
std::chrono::nanoseconds timeout,
std::shared_ptr<WakeupCallback> callback);
private:
template <typename ResultFunctor>
@ -255,7 +257,7 @@ private:
future = std::move(fut);
}
void WakeUp(Kernel::KThread* thread, Kernel::HLERequestContext& ctx,
void WakeUp(std::shared_ptr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx,
Kernel::ThreadWakeupReason reason) {
functor(ctx);
}
@ -320,13 +322,13 @@ public:
* Resolves a object id from the request command buffer into a pointer to an object. See the
* "HLE handle protocol" section in the class documentation for more details.
*/
KAutoObject* GetIncomingHandle(u32 id_from_cmdbuf) const;
std::shared_ptr<Object> GetIncomingHandle(u32 id_from_cmdbuf) const;
/**
* Adds an outgoing object to the response, returning the id which should be used to reference
* it. See the "HLE handle protocol" section in the class documentation for more details.
*/
u32 AddOutgoingHandle(KAutoObject* object);
u32 AddOutgoingHandle(std::shared_ptr<Object> object);
/**
* Discards all Objects from the context, invalidating all ids. This may be called after reading
@ -354,8 +356,8 @@ public:
MappedBuffer& GetMappedBuffer(u32 id_from_cmdbuf);
/// Populates this context with data from the requesting process/thread.
Result PopulateFromIncomingCommandBuffer(const u32_le* src_cmdbuf, Process* src_process);
Result PopulateFromIncomingCommandBuffer(const u32_le* src_cmdbuf,
std::shared_ptr<Process> src_process);
/// Writes data from this context back to the requesting process/thread.
Result WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process) const;
@ -368,10 +370,10 @@ public:
private:
KernelSystem& kernel;
std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf;
KServerSession* session;
KThread* thread;
std::shared_ptr<ServerSession> session;
std::shared_ptr<Thread> thread;
// TODO(yuriks): Check common usage of this and optimize size accordingly
boost::container::small_vector<KAutoObject*, 8> request_handles;
boost::container::small_vector<std::shared_ptr<Object>, 8> request_handles;
// The static buffers will be created when the IPC request is translated.
std::array<std::vector<u8>, IPC::MAX_STATIC_BUFFERS> static_buffers;
// The mapped buffers will be created when the IPC request is translated

View File

@ -9,13 +9,13 @@
#include "common/memory_ref.h"
#include "core/core.h"
#include "core/hle/ipc.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/ipc.h"
#include "core/hle/kernel/ipc_debugger/recorder.h"
#include "core/hle/kernel/k_handle_table.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/memory.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/thread.h"
#include "core/memory.h"
SERIALIZE_EXPORT_IMPL(Kernel::MappedBufferContext)
@ -23,11 +23,12 @@ SERIALIZE_EXPORT_IMPL(Kernel::MappedBufferContext)
namespace Kernel {
Result TranslateCommandBuffer(Kernel::KernelSystem& kernel, Memory::MemorySystem& memory,
KThread* src_thread, KThread* dst_thread, VAddr src_address,
std::shared_ptr<Thread> src_thread,
std::shared_ptr<Thread> dst_thread, VAddr src_address,
VAddr dst_address,
std::vector<MappedBufferContext>& mapped_buffer_context, bool reply) {
auto src_process = src_thread->GetOwner();
auto dst_process = dst_thread->GetOwner();
auto src_process = src_thread->owner_process.lock();
auto dst_process = dst_thread->owner_process.lock();
ASSERT(src_process && dst_process);
IPC::Header header;
@ -68,34 +69,30 @@ Result TranslateCommandBuffer(Kernel::KernelSystem& kernel, Memory::MemorySystem
for (u32 j = 0; j < num_handles; ++j) {
Handle handle = cmd_buf[i];
std::shared_ptr<Object> object = nullptr;
// Perform pseudo-handle detection here because by the time this function is called,
// the current thread and process are no longer the ones which created this IPC
// request, but the ones that are handling it.
KScopedAutoObject object = [&]() -> KScopedAutoObject<KAutoObject> {
if (handle == CurrentThread) {
return src_thread;
} else if (handle == CurrentProcess) {
return src_process;
} else if (handle != 0) {
auto obj = src_process->handle_table.GetObject(handle);
if (descriptor == IPC::DescriptorType::MoveHandle) {
src_process->handle_table.Remove(handle);
}
return obj;
if (handle == CurrentThread) {
object = src_thread;
} else if (handle == CurrentProcess) {
object = src_process;
} else if (handle != 0) {
object = src_process->handle_table.GetGeneric(handle);
if (descriptor == IPC::DescriptorType::MoveHandle) {
src_process->handle_table.Close(handle);
}
return nullptr;
}();
}
if (object.IsNull()) {
if (object == nullptr) {
// Note: The real kernel sets invalid translated handles to 0 in the target
// command buffer.
cmd_buf[i++] = 0;
continue;
}
Handle dst_handle = 0;
dst_process->handle_table.Add(&dst_handle, object.GetPointerUnsafe());
cmd_buf[i++] = dst_handle;
R_ASSERT(dst_process->handle_table.Create(std::addressof(cmd_buf[i++]),
std::move(object)));
}
break;
}

View File

@ -4,11 +4,12 @@
#pragma once
#include <memory>
#include <vector>
#include <boost/serialization/export.hpp>
#include "common/common_types.h"
#include "common/memory_ref.h"
#include "core/hle/ipc.h"
#include "core/hle/kernel/thread.h"
namespace Memory {
class MemorySystem;
@ -17,7 +18,6 @@ class MemorySystem;
namespace Kernel {
class KernelSystem;
class KThread;
struct MappedBufferContext {
IPC::MappedBufferPermissions permissions;
@ -35,7 +35,8 @@ private:
/// Performs IPC command buffer translation from one process to another.
Result TranslateCommandBuffer(KernelSystem& system, Memory::MemorySystem& memory,
KThread* src_thread, KThread* dst_thread, VAddr src_address,
std::shared_ptr<Thread> src_thread,
std::shared_ptr<Thread> dst_thread, VAddr src_address,
VAddr dst_address,
std::vector<MappedBufferContext>& mapped_buffer_context, bool reply);
} // namespace Kernel

View File

@ -4,80 +4,73 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/scope_exit.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
#include "core/hle/kernel/ipc_debugger/recorder.h"
#include "core/hle/kernel/k_client_port.h"
#include "core/hle/kernel/k_client_session.h"
#include "core/hle/kernel/k_port.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_server_port.h"
#include "core/hle/kernel/k_server_session.h"
#include "core/hle/kernel/k_session.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/server_port.h"
#include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/session.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/service/service.h"
namespace IPCDebugger {
namespace {
ObjectInfo GetObjectInfo(const Kernel::KAutoObject* object) {
ObjectInfo GetObjectInfo(const Kernel::Object* object) {
if (object == nullptr) {
return {};
}
return {object->GetTypeName(), /*object->GetName()*/ "KAutoObject",
/*static_cast<int>(object->GetObjectId())*/ 1};
return {object->GetTypeName(), object->GetName(), static_cast<int>(object->GetObjectId())};
}
ObjectInfo GetObjectInfo(const Kernel::KThread* thread) {
ObjectInfo GetObjectInfo(const Kernel::Thread* thread) {
if (thread == nullptr) {
return {};
}
return {thread->GetTypeName(), /*thread->GetName()*/ "KThread",
/*static_cast<int>(object->GetObjectId())*/ 1};
return {thread->GetTypeName(), thread->GetName(), static_cast<int>(thread->GetThreadId())};
}
ObjectInfo GetObjectInfo(const Kernel::Process* process) {
if (process == nullptr) {
return {};
}
return {process->GetTypeName(), /*process->GetName()*/ "KProcess",
static_cast<int>(process->process_id)};
return {process->GetTypeName(), process->GetName(), static_cast<int>(process->process_id)};
}
} // Anonymous namespace
} // namespace
Recorder::Recorder() = default;
Recorder::~Recorder() = default;
bool Recorder::IsEnabled() const {
return enabled.load(std::memory_order_relaxed);
}
void Recorder::RegisterRequest(const Kernel::KClientSession* client_session,
const Kernel::KThread* client_thread) {
void Recorder::RegisterRequest(const std::shared_ptr<Kernel::ClientSession>& client_session,
const std::shared_ptr<Kernel::Thread>& client_thread) {
const u32 thread_id = client_thread->GetThreadId();
const RequestRecord record = {
.id = ++record_count,
.status = RequestStatus::Sent,
.client_process = GetObjectInfo(client_thread->GetOwner()),
.client_thread = GetObjectInfo(client_thread),
.client_session = GetObjectInfo(client_session),
.client_port = GetObjectInfo(client_session->GetParent()->GetParent()),
.server_process = {},
.server_thread = {},
.server_session = GetObjectInfo(&client_session->GetParent()->GetServerSession()),
};
record_map.insert_or_assign(thread_id, std::make_unique<RequestRecord>(record));
client_session_map.insert_or_assign(thread_id, client_session);
InvokeCallbacks(record);
if (auto owner_process = client_thread->owner_process.lock()) {
RequestRecord record = {/* id */ ++record_count,
/* status */ RequestStatus::Sent,
/* client_process */ GetObjectInfo(owner_process.get()),
/* client_thread */ GetObjectInfo(client_thread.get()),
/* client_session */ GetObjectInfo(client_session.get()),
/* client_port */ GetObjectInfo(client_session->parent->port.get()),
/* server_process */ {},
/* server_thread */ {},
/* server_session */ GetObjectInfo(client_session->parent->server)};
record_map.insert_or_assign(thread_id, std::make_unique<RequestRecord>(record));
client_session_map.insert_or_assign(thread_id, client_session);
InvokeCallbacks(record);
}
}
void Recorder::SetRequestInfo(const Kernel::KThread* client_thread,
void Recorder::SetRequestInfo(const std::shared_ptr<Kernel::Thread>& client_thread,
std::vector<u32> untranslated_cmdbuf,
std::vector<u32> translated_cmdbuf,
const Kernel::KThread* server_thread) {
const std::shared_ptr<Kernel::Thread>& server_thread) {
const u32 thread_id = client_thread->GetThreadId();
if (!record_map.count(thread_id)) {
// This is possible when the recorder is enabled after application started
@ -91,34 +84,30 @@ void Recorder::SetRequestInfo(const Kernel::KThread* client_thread,
record.translated_request_cmdbuf = std::move(translated_cmdbuf);
if (server_thread) {
record.server_process = GetObjectInfo(server_thread->GetOwner());
record.server_thread = GetObjectInfo(server_thread);
if (auto owner_process = server_thread->owner_process.lock()) {
record.server_process = GetObjectInfo(owner_process.get());
}
record.server_thread = GetObjectInfo(server_thread.get());
} else {
record.is_hle = true;
}
// Function name
ASSERT_MSG(client_session_map.count(thread_id), "Client session is missing");
const auto client_session = client_session_map[thread_id];
const auto& client_session = client_session_map[thread_id];
if (client_session->parent->port &&
client_session->parent->port->GetServerPort()->hle_handler) {
SCOPE_EXIT({
client_session_map.erase(thread_id);
InvokeCallbacks(record);
});
auto port = client_session->GetParent()->GetParent();
if (!port) {
return;
}
auto hle_handler = port->GetParent()->GetServerPort().GetHleHandler();
if (hle_handler) {
record.function_name = std::dynamic_pointer_cast<Service::ServiceFrameworkBase>(hle_handler)
record.function_name = std::dynamic_pointer_cast<Service::ServiceFrameworkBase>(
client_session->parent->port->GetServerPort()->hle_handler)
->GetFunctionName({record.untranslated_request_cmdbuf[0]});
}
client_session_map.erase(thread_id);
InvokeCallbacks(record);
}
void Recorder::SetReplyInfo(const Kernel::KThread* client_thread,
void Recorder::SetReplyInfo(const std::shared_ptr<Kernel::Thread>& client_thread,
std::vector<u32> untranslated_cmdbuf,
std::vector<u32> translated_cmdbuf) {
const u32 thread_id = client_thread->GetThreadId();
@ -140,7 +129,7 @@ void Recorder::SetReplyInfo(const Kernel::KThread* client_thread,
record_map.erase(thread_id);
}
void Recorder::SetHLEUnimplemented(const Kernel::KThread* client_thread) {
void Recorder::SetHLEUnimplemented(const std::shared_ptr<Kernel::Thread>& client_thread) {
const u32 thread_id = client_thread->GetThreadId();
if (!record_map.count(thread_id)) {
// This is possible when the recorder is enabled after application started

View File

@ -15,9 +15,8 @@
#include "common/common_types.h"
namespace Kernel {
class KClientSession;
class KThread;
enum class ClassTokenType : u32;
class ClientSession;
class Thread;
} // namespace Kernel
namespace IPCDebugger {
@ -28,7 +27,7 @@ namespace IPCDebugger {
struct ObjectInfo {
std::string type;
std::string name;
int id;
int id = -1;
};
/**
@ -81,28 +80,28 @@ public:
/**
* Registers a request into the recorder. The request is then assoicated with the client thread.
*/
void RegisterRequest(const Kernel::KClientSession* client_session,
const Kernel::KThread* client_thread);
void RegisterRequest(const std::shared_ptr<Kernel::ClientSession>& client_session,
const std::shared_ptr<Kernel::Thread>& client_thread);
/**
* Sets the request information of the request record associated with the client thread.
* When the server thread is empty, the request will be considered HLE.
*/
void SetRequestInfo(const Kernel::KThread* client_thread, std::vector<u32> untranslated_cmdbuf,
std::vector<u32> translated_cmdbuf,
const Kernel::KThread* server_thread = nullptr);
void SetRequestInfo(const std::shared_ptr<Kernel::Thread>& client_thread,
std::vector<u32> untranslated_cmdbuf, std::vector<u32> translated_cmdbuf,
const std::shared_ptr<Kernel::Thread>& server_thread = {});
/**
* Sets the reply information of the request record assoicated with the client thread.
* The request is then unlinked from the client thread.
*/
void SetReplyInfo(const Kernel::KThread* client_thread, std::vector<u32> untranslated_cmdbuf,
std::vector<u32> translated_cmdbuf);
void SetReplyInfo(const std::shared_ptr<Kernel::Thread>& client_thread,
std::vector<u32> untranslated_cmdbuf, std::vector<u32> translated_cmdbuf);
/**
* Set the status of a record to HLEUnimplemented.
*/
void SetHLEUnimplemented(const Kernel::KThread* client_thread);
void SetHLEUnimplemented(const std::shared_ptr<Kernel::Thread>& client_thread);
/**
* Set the status of the debugger (enabled/disabled).
@ -119,7 +118,7 @@ private:
int record_count{};
// Temporary client session map for function name handling
std::unordered_map<u32, const Kernel::KClientSession*> client_session_map;
std::unordered_map<u32, std::shared_ptr<Kernel::ClientSession>> client_session_map;
std::atomic_bool enabled{false};

View File

@ -1,226 +0,0 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/vector.hpp>
#include "common/archives.h"
#include "common/logging/log.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/k_address_arbiter.h"
#include "core/hle/kernel/k_auto_object_container.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_resource_limit.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
#include "core/memory.h"
namespace Kernel {
class KAddressArbiter::Callback : public WakeupCallback {
public:
explicit Callback(KAddressArbiter* _parent) : parent(_parent) {}
KAddressArbiter* parent;
void WakeUp(ThreadWakeupReason reason, KThread* thread,
KSynchronizationObject* object) override {
parent->WakeUp(reason, thread, object);
}
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::base_object<WakeupCallback>(*this);
}
friend class boost::serialization::access;
};
KAddressArbiter::KAddressArbiter(KernelSystem& kernel)
: KAutoObjectWithSlabHeapAndContainer{kernel},
m_timeout_callback(std::make_shared<Callback>(this)) {}
KAddressArbiter::~KAddressArbiter() = default;
void KAddressArbiter::Initialize(Process* owner) {
m_owner = owner;
m_owner->Open();
}
void KAddressArbiter::PostDestroy(uintptr_t arg) {
Process* owner = reinterpret_cast<Process*>(arg);
if (owner != nullptr) {
owner->ReleaseResource(ResourceLimitType::AddressArbiter, 1);
owner->Close();
}
}
void KAddressArbiter::WaitThread(KThread* thread, VAddr wait_address) {
thread->m_wait_address = wait_address;
thread->m_status = ThreadStatus::WaitArb;
m_waiting_threads.emplace_back(thread);
}
u64 KAddressArbiter::ResumeAllThreads(VAddr address) {
// Determine which threads are waiting on this address, those should be woken up.
auto itr = std::stable_partition(m_waiting_threads.begin(), m_waiting_threads.end(),
[address](KThread* thread) {
ASSERT_MSG(thread->GetStatus() == ThreadStatus::WaitArb,
"Inconsistent AddressArbiter state");
return thread->m_wait_address != address;
});
// Wake up all the found threads
const u64 num_threads = std::distance(itr, m_waiting_threads.end());
std::for_each(itr, m_waiting_threads.end(), [](KThread* thread) { thread->ResumeFromWait(); });
// Remove the woken up threads from the wait list.
m_waiting_threads.erase(itr, m_waiting_threads.end());
return num_threads;
}
bool KAddressArbiter::ResumeHighestPriorityThread(VAddr address) {
// Determine which threads are waiting on this address, those should be considered for wakeup.
auto matches_start = std::stable_partition(
m_waiting_threads.begin(), m_waiting_threads.end(), [address](KThread* thread) {
ASSERT_MSG(thread->GetStatus() == ThreadStatus::WaitArb,
"Inconsistent AddressArbiter state");
return thread->m_wait_address != address;
});
// Iterate through threads, find highest priority thread that is waiting to be arbitrated.
// Note: The real kernel will pick the first thread in the list if more than one have the
// same highest priority value. Lower priority values mean higher priority.
auto itr =
std::min_element(matches_start, m_waiting_threads.end(), [](KThread* lhs, KThread* rhs) {
return lhs->GetCurrentPriority() < rhs->GetCurrentPriority();
});
if (itr == m_waiting_threads.end()) {
return false;
}
auto thread = *itr;
thread->ResumeFromWait();
m_waiting_threads.erase(itr);
return true;
}
void KAddressArbiter::WakeUp(ThreadWakeupReason reason, KThread* thread,
KSynchronizationObject* object) {
ASSERT(reason == ThreadWakeupReason::Timeout);
// Remove the newly-awakened thread from the Arbiter's waiting list.
m_waiting_threads.erase(std::remove(m_waiting_threads.begin(), m_waiting_threads.end(), thread),
m_waiting_threads.end());
};
Result KAddressArbiter::ArbitrateAddress(KThread* thread, ArbitrationType type, VAddr address,
s32 value, u64 nanoseconds) {
switch (type) {
// Signal thread(s) waiting for arbitrate address...
case ArbitrationType::Signal: {
u64 num_threads{};
// Negative value means resume all threads
if (value < 0) {
num_threads = ResumeAllThreads(address);
} else {
// Resume first N threads
for (s32 i = 0; i < value; i++) {
num_threads += ResumeHighestPriorityThread(address);
}
}
// Prevents lag from low priority threads that spam svcArbitrateAddress and wake no threads
// The tick count is taken directly from official HOS kernel. The priority value is one less
// than official kernel as the affected FMV threads dont meet the priority threshold of 50.
// TODO: Revisit this when scheduler is rewritten and adjust if there isn't a problem there.
auto* core = m_kernel.current_cpu;
if (num_threads == 0 && core->GetID() == 0 && thread->GetCurrentPriority() >= 49) {
core->GetTimer().AddTicks(1614u);
}
break;
}
// Wait current thread (acquire the arbiter)...
case ArbitrationType::WaitIfLessThan:
if ((s32)m_kernel.memory.Read32(address) < value) {
WaitThread(thread, address);
}
break;
case ArbitrationType::WaitIfLessThanWithTimeout:
if ((s32)m_kernel.memory.Read32(address) < value) {
thread->SetWakeupCallback(m_timeout_callback);
thread->WakeAfterDelay(nanoseconds);
WaitThread(thread, address);
}
break;
case ArbitrationType::DecrementAndWaitIfLessThan: {
s32 memory_value = m_kernel.memory.Read32(address);
if (memory_value < value) {
// Only change the memory value if the thread should wait
m_kernel.memory.Write32(address, (s32)memory_value - 1);
WaitThread(thread, address);
}
break;
}
case ArbitrationType::DecrementAndWaitIfLessThanWithTimeout: {
s32 memory_value = m_kernel.memory.Read32(address);
if (memory_value < value) {
// Only change the memory value if the thread should wait
m_kernel.memory.Write32(address, (s32)memory_value - 1);
thread->SetWakeupCallback(m_timeout_callback);
thread->WakeAfterDelay(nanoseconds);
WaitThread(thread, address);
}
break;
}
default:
LOG_ERROR(Kernel, "unknown type={}", type);
return ResultInvalidEnumValueFnd;
}
// The calls that use a timeout seem to always return a Timeout error even if they did not put
// the thread to sleep
if (type == ArbitrationType::WaitIfLessThanWithTimeout ||
type == ArbitrationType::DecrementAndWaitIfLessThanWithTimeout) {
return ResultTimeout;
}
return ResultSuccess;
}
template <class Archive>
void KAddressArbiter::serialize(Archive& ar, const unsigned int file_version) {
ar& boost::serialization::base_object<KAutoObject>(*this);
ar& m_name;
ar& m_waiting_threads;
// ar& m_timeout_callback;
}
SERIALIZE_IMPL(KAddressArbiter)
} // namespace Kernel
namespace boost::serialization {
template <class Archive>
void save_construct_data(Archive& ar, const Kernel::KAddressArbiter::Callback* t,
const unsigned int) {
ar << t->parent;
}
template <class Archive>
void load_construct_data(Archive& ar, Kernel::KAddressArbiter::Callback* t, const unsigned int) {
Kernel::KAddressArbiter* parent;
ar >> parent;
::new (t) Kernel::KAddressArbiter::Callback(parent);
}
} // namespace boost::serialization
SERIALIZE_EXPORT_IMPL(Kernel::KAddressArbiter)
SERIALIZE_EXPORT_IMPL(Kernel::KAddressArbiter::Callback)

View File

@ -1,75 +0,0 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <vector>
#include <boost/serialization/export.hpp>
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/slab_helpers.h"
#include "core/hle/result.h"
namespace Kernel {
class KThread;
enum class ArbitrationType : u32 {
Signal,
WaitIfLessThan,
DecrementAndWaitIfLessThan,
WaitIfLessThanWithTimeout,
DecrementAndWaitIfLessThanWithTimeout,
};
class KAddressArbiter final : public KAutoObjectWithSlabHeapAndContainer<KAddressArbiter>,
public WakeupCallback {
KERNEL_AUTOOBJECT_TRAITS(KAddressArbiter, KAutoObject);
public:
explicit KAddressArbiter(KernelSystem& kernel);
~KAddressArbiter() override;
void Initialize(Process* owner);
uintptr_t GetPostDestroyArgument() const override {
return reinterpret_cast<uintptr_t>(m_owner);
}
static void PostDestroy(uintptr_t arg);
Process* GetOwner() const override {
return m_owner;
}
Result ArbitrateAddress(KThread* thread, ArbitrationType type, VAddr address, s32 value,
u64 nanoseconds);
private:
void WaitThread(KThread* thread, VAddr wait_address);
u64 ResumeAllThreads(VAddr address);
bool ResumeHighestPriorityThread(VAddr address);
void WakeUp(ThreadWakeupReason reason, KThread* thread,
KSynchronizationObject* object) override;
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const u32 file_version);
public:
Process* m_owner{};
std::string m_name{};
std::vector<KThread*> m_waiting_threads;
class Callback;
std::shared_ptr<Callback> m_timeout_callback;
};
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::KAddressArbiter)
BOOST_CLASS_EXPORT_KEY(Kernel::KAddressArbiter::Callback)
CONSTRUCT_KERNEL_OBJECT(Kernel::KAddressArbiter)

View File

@ -1,32 +0,0 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/archives.h"
#include "core/hle/kernel/k_auto_object.h"
#include "core/hle/kernel/kernel.h"
namespace Kernel {
KAutoObject* KAutoObject::Create(KAutoObject* obj) {
obj->m_ref_count = 1;
return obj;
}
void KAutoObject::RegisterWithKernel() {
m_kernel.RegisterKernelObject(this);
}
void KAutoObject::UnregisterWithKernel(KernelSystem& kernel, KAutoObject* self) {
kernel.UnregisterKernelObject(self);
}
template <class Archive>
void KAutoObject::serialize(Archive& ar, const unsigned int) {
ar& m_name;
// ar& m_ref_count;
}
SERIALIZE_IMPL(KAutoObject)
} // namespace Kernel

View File

@ -1,305 +0,0 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <atomic>
#include <boost/serialization/access.hpp>
#include "common/assert.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/global.h"
namespace Kernel {
class KernelSystem;
class Process;
using Handle = u32;
constexpr u32 DefaultStackSize = 0x4000;
enum class ClassTokenType : u32 {
KAutoObject = 0,
KSynchronizationObject = 1,
KSemaphore = 27,
KEvent = 31,
KTimer = 53,
KMutex = 57,
Debug = 77,
KServerPort = 85,
DmaObject = 89,
KClientPort = 101,
CodeSet = 104,
KSession = 112,
KThread = 141,
KServerSession = 149,
KAddressArbiter = 152,
KClientSession = 165,
KPort = 168,
KSharedMemory = 176,
Process = 197,
KResourceLimit = 200,
};
DECLARE_ENUM_FLAG_OPERATORS(ClassTokenType)
#define KERNEL_AUTOOBJECT_TRAITS_IMPL(CLASS, BASE_CLASS, ATTRIBUTE) \
private: \
static constexpr inline const char* const TypeName = #CLASS; \
static constexpr inline auto ClassToken = ClassTokenType::CLASS; \
\
public: \
CITRA_NON_COPYABLE(CLASS); \
CITRA_NON_MOVEABLE(CLASS); \
\
using BaseClass = BASE_CLASS; \
static constexpr TypeObj GetStaticTypeObj() { return TypeObj(TypeName, ClassToken); } \
static constexpr const char* GetStaticTypeName() { return TypeName; } \
virtual TypeObj GetTypeObj() ATTRIBUTE { return GetStaticTypeObj(); } \
virtual const char* GetTypeName() ATTRIBUTE { return GetStaticTypeName(); } \
\
private: \
constexpr bool operator!=(const TypeObj& rhs)
#define KERNEL_AUTOOBJECT_TRAITS(CLASS, BASE_CLASS) \
KERNEL_AUTOOBJECT_TRAITS_IMPL(CLASS, BASE_CLASS, const override)
class KAutoObject {
protected:
class TypeObj {
public:
constexpr explicit TypeObj(const char* n, ClassTokenType tok)
: m_name(n), m_class_token(tok) {}
constexpr const char* GetName() const {
return m_name;
}
constexpr ClassTokenType GetClassToken() const {
return m_class_token;
}
constexpr bool operator==(const TypeObj& rhs) const {
return this->GetClassToken() == rhs.GetClassToken();
}
constexpr bool operator!=(const TypeObj& rhs) const {
return this->GetClassToken() != rhs.GetClassToken();
}
constexpr bool IsDerivedFrom(const TypeObj& rhs) const {
return (this->GetClassToken() | rhs.GetClassToken()) == this->GetClassToken();
}
private:
const char* m_name;
ClassTokenType m_class_token;
};
private:
KERNEL_AUTOOBJECT_TRAITS_IMPL(KAutoObject, KAutoObject, const);
public:
explicit KAutoObject(KernelSystem& kernel) : m_kernel(kernel) {
RegisterWithKernel();
}
virtual ~KAutoObject() = default;
static KAutoObject* Create(KAutoObject* ptr);
// Destroy is responsible for destroying the auto object's resources when ref_count hits zero.
virtual void Destroy() {
UNIMPLEMENTED();
}
// Finalize is responsible for cleaning up resource, but does not destroy the object.
virtual void Finalize() {}
virtual Process* GetOwner() const {
return nullptr;
}
u32 GetReferenceCount() const {
return m_ref_count.load();
}
bool IsDerivedFrom(const TypeObj& rhs) const {
return this->GetTypeObj().IsDerivedFrom(rhs);
}
bool IsDerivedFrom(const KAutoObject& rhs) const {
return this->IsDerivedFrom(rhs.GetTypeObj());
}
template <typename Derived>
Derived DynamicCast() {
static_assert(std::is_pointer_v<Derived>);
using DerivedType = std::remove_pointer_t<Derived>;
if (this->IsDerivedFrom(DerivedType::GetStaticTypeObj())) {
return static_cast<Derived>(this);
} else {
return nullptr;
}
}
template <typename Derived>
const Derived DynamicCast() const {
static_assert(std::is_pointer_v<Derived>);
using DerivedType = std::remove_pointer_t<Derived>;
if (this->IsDerivedFrom(DerivedType::GetStaticTypeObj())) {
return static_cast<Derived>(this);
} else {
return nullptr;
}
}
bool Open() {
// Atomically increment the reference count, only if it's positive.
u32 cur_ref_count = m_ref_count.load(std::memory_order_acquire);
do {
if (cur_ref_count == 0) {
return false;
}
ASSERT(cur_ref_count < cur_ref_count + 1);
} while (!m_ref_count.compare_exchange_weak(cur_ref_count, cur_ref_count + 1,
std::memory_order_relaxed));
return true;
}
void Close() {
// Atomically decrement the reference count, not allowing it to become negative.
u32 cur_ref_count = m_ref_count.load(std::memory_order_acquire);
do {
ASSERT(cur_ref_count > 0);
} while (!m_ref_count.compare_exchange_weak(cur_ref_count, cur_ref_count - 1,
std::memory_order_acq_rel));
// If ref count hits zero, destroy the object.
if (cur_ref_count - 1 == 0) {
KernelSystem& kernel = m_kernel;
this->Destroy();
KAutoObject::UnregisterWithKernel(kernel, this);
}
}
private:
void RegisterWithKernel();
static void UnregisterWithKernel(KernelSystem& kernel, KAutoObject* self);
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
protected:
KernelSystem& m_kernel;
std::string m_name{};
private:
std::atomic<u32> m_ref_count{};
};
template <typename T>
class KScopedAutoObject {
public:
CITRA_NON_COPYABLE(KScopedAutoObject);
constexpr KScopedAutoObject() = default;
constexpr KScopedAutoObject(T* o) : m_obj(o) {
if (m_obj != nullptr) {
m_obj->Open();
}
}
~KScopedAutoObject() {
if (m_obj != nullptr) {
m_obj->Close();
}
m_obj = nullptr;
}
template <typename U>
requires(std::derived_from<T, U> || std::derived_from<U, T>)
constexpr KScopedAutoObject(KScopedAutoObject<U>&& rhs) {
if constexpr (std::derived_from<U, T>) {
// Upcast.
m_obj = rhs.m_obj;
rhs.m_obj = nullptr;
} else {
// Downcast.
T* derived = nullptr;
if (rhs.m_obj != nullptr) {
derived = rhs.m_obj->template DynamicCast<T*>();
if (derived == nullptr) {
rhs.m_obj->Close();
}
}
m_obj = derived;
rhs.m_obj = nullptr;
}
}
constexpr KScopedAutoObject<T>& operator=(KScopedAutoObject<T>&& rhs) {
rhs.Swap(*this);
return *this;
}
constexpr T* operator->() {
return m_obj;
}
constexpr T& operator*() {
return *m_obj;
}
constexpr void Reset(T* o) {
KScopedAutoObject(o).Swap(*this);
}
constexpr T* GetPointerUnsafe() {
return m_obj;
}
constexpr T* GetPointerUnsafe() const {
return m_obj;
}
constexpr T* ReleasePointerUnsafe() {
T* ret = m_obj;
m_obj = nullptr;
return ret;
}
constexpr bool IsNull() const {
return m_obj == nullptr;
}
constexpr bool IsNotNull() const {
return m_obj != nullptr;
}
private:
template <typename U>
friend class KScopedAutoObject;
private:
T* m_obj{};
private:
constexpr void Swap(KScopedAutoObject& rhs) noexcept {
std::swap(m_obj, rhs.m_obj);
}
};
} // namespace Kernel
#define CONSTRUCT_KERNEL_OBJECT(T) \
namespace boost::serialization { \
template <class Archive> \
void load_construct_data(Archive& ar, T* t, const unsigned int file_version) { \
::new (t) T(Core::Global<Kernel::KernelSystem>()); \
} \
}

View File

@ -1,31 +0,0 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include "core/hle/kernel/k_auto_object_container.h"
namespace Kernel {
void KAutoObjectWithListContainer::Register(KAutoObject* obj) {
// KScopedLightMutex lk{m_mutex};
m_object_list.push_back(*obj);
}
void KAutoObjectWithListContainer::Unregister(KAutoObject* obj) {
// KScopedLightMutex lk{m_mutex};
for (auto it = m_object_list.begin(); it != m_object_list.end(); it++) {
if (std::addressof(*it) == obj) {
m_object_list.erase(it);
return;
}
}
}
size_t KAutoObjectWithListContainer::GetOwnedCount(Process* owner) {
// KScopedLightMutex lk{m_mutex};
return std::count_if(m_object_list.begin(), m_object_list.end(),
[&](const auto& obj) { return obj.GetOwner() == owner; });
}
} // namespace Kernel

View File

@ -1,37 +0,0 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/common_funcs.h"
#include "core/hle/kernel/k_auto_object.h"
#include "core/hle/kernel/k_linked_list.h"
namespace Kernel {
class KernelSystem;
class Process;
class KAutoObjectWithListContainer {
public:
CITRA_NON_COPYABLE(KAutoObjectWithListContainer);
CITRA_NON_MOVEABLE(KAutoObjectWithListContainer);
using ListType = KLinkedList<KAutoObject>;
KAutoObjectWithListContainer(KernelSystem& kernel) : m_object_list(kernel) {}
void Initialize() {}
void Finalize() {}
void Register(KAutoObject* obj);
void Unregister(KAutoObject* obj);
size_t GetOwnedCount(Process* owner);
private:
// KLightMutex m_mutex;
ListType m_object_list;
};
} // namespace Kernel

View File

@ -1,79 +0,0 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <boost/serialization/export.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/string.hpp>
#include "common/archives.h"
#include "common/assert.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/k_client_port.h"
#include "core/hle/kernel/k_client_session.h"
#include "core/hle/kernel/k_port.h"
#include "core/hle/kernel/k_server_port.h"
#include "core/hle/kernel/k_session.h"
SERIALIZE_EXPORT_IMPL(Kernel::KClientPort)
namespace Kernel {
KClientPort::KClientPort(KernelSystem& kernel) : KAutoObject(kernel) {}
KClientPort::~KClientPort() = default;
void KClientPort::Initialize(KPort* parent, s32 max_sessions, std::string name) {
// Set member variables.
m_parent = parent;
m_max_sessions = max_sessions;
m_name = name + "_Client";
}
Result KClientPort::CreateSession(KClientSession** out) {
R_UNLESS(m_active_sessions < m_max_sessions, ResultMaxConnectionsReached);
m_active_sessions++;
// Allocate a new session.
KSession* session = KSession::Create(m_kernel);
// Initialize the session.
session->Initialize(this);
// Register the session.
KSession::Register(m_kernel, session);
// Let the created sessions inherit the parent port's HLE handler.
auto* server = &m_parent->GetServerPort();
auto hle_handler = server->GetHleHandler();
if (hle_handler) {
hle_handler->ClientConnected(&session->GetServerSession());
} else {
server->EnqueueSession(&session->GetServerSession());
}
// Wake the threads waiting on the ServerPort
m_parent->GetServerPort().WakeupAllWaitingThreads();
// We succeeded, so set the output.
*out = std::addressof(session->GetClientSession());
return ResultSuccess;
}
void KClientPort::ConnectionClosed() {
ASSERT(m_active_sessions > 0);
--m_active_sessions;
}
template <class Archive>
void KClientPort::serialize(Archive& ar, const u32 file_version) {
ar& boost::serialization::base_object<KAutoObject>(*this);
// ar& m_parent;
ar& m_max_sessions;
ar& m_active_sessions;
ar& m_name;
}
SERIALIZE_IMPL(KClientPort)
} // namespace Kernel

View File

@ -1,52 +0,0 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <string>
#include "core/hle/kernel/k_auto_object.h"
#include "core/hle/kernel/k_server_port.h"
#include "core/hle/result.h"
namespace Kernel {
class KClientSession;
class KClientPort final : public KAutoObject {
KERNEL_AUTOOBJECT_TRAITS(KClientPort, KAutoObject);
public:
explicit KClientPort(KernelSystem& kernel);
~KClientPort() override;
void Initialize(KPort* parent, s32 max_sessions, std::string name);
const KPort* GetParent() const {
return m_parent;
}
KPort* GetParent() {
return m_parent;
}
Result CreateSession(KClientSession** out);
void ConnectionClosed();
private:
KPort* m_parent{};
u32 m_max_sessions{};
u32 m_active_sessions{};
std::string m_name;
friend class KernelSystem;
private:
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const u32 file_version);
};
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::KClientPort)
CONSTRUCT_KERNEL_OBJECT(Kernel::KClientPort)

View File

@ -1,42 +0,0 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/string.hpp>
#include "common/archives.h"
#include "core/hle/kernel/k_auto_object_container.h"
#include "core/hle/kernel/k_client_session.h"
#include "core/hle/kernel/k_server_session.h"
#include "core/hle/kernel/k_session.h"
#include "core/hle/kernel/k_thread.h"
SERIALIZE_EXPORT_IMPL(Kernel::KClientSession)
namespace Kernel {
KClientSession::KClientSession(KernelSystem& kernel) : KAutoObject(kernel) {}
KClientSession::~KClientSession() = default;
void KClientSession::Destroy() {
m_parent->OnClientClosed();
m_parent->Close();
}
void KClientSession::OnServerClosed() {}
Result KClientSession::SendSyncRequest(KThread* thread) {
// Signal the server session that new data is available
return m_parent->GetServerSession().HandleSyncRequest(thread);
}
template <class Archive>
void KClientSession::serialize(Archive& ar, const u32 file_version) {
ar& boost::serialization::base_object<KAutoObject>(*this);
// ar& m_parent;
}
SERIALIZE_IMPL(KClientSession)
} // namespace Kernel

View File

@ -1,50 +0,0 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <boost/serialization/export.hpp>
#include "core/hle/kernel/k_auto_object.h"
#include "core/hle/result.h"
namespace Kernel {
class KSession;
class KThread;
class KClientSession final : public KAutoObject {
KERNEL_AUTOOBJECT_TRAITS(KClientSession, KAutoObject);
public:
explicit KClientSession(KernelSystem& kernel);
~KClientSession() override;
void Initialize(KSession* parent) {
// Set member variables.
m_parent = parent;
}
void Destroy() override;
KSession* GetParent() const {
return m_parent;
}
Result SendSyncRequest(KThread* thread);
void OnServerClosed();
private:
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const u32 file_version);
private:
KSession* m_parent{};
};
} // namespace Kernel
BOOST_CLASS_EXPORT_KEY(Kernel::KClientSession)
CONSTRUCT_KERNEL_OBJECT(Kernel::KClientSession)

View File

@ -1,76 +0,0 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <vector>
#include <boost/serialization/vector.hpp>
namespace Kernel {
class CodeSet {
public:
CodeSet() = default;
~CodeSet() = default;
struct Segment {
std::size_t offset = 0;
VAddr addr = 0;
u32 size = 0;
private:
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const u32 file_version) {
ar& offset;
ar& addr;
ar& size;
}
};
Segment& CodeSegment() {
return segments[0];
}
const Segment& CodeSegment() const {
return segments[0];
}
Segment& RODataSegment() {
return segments[1];
}
const Segment& RODataSegment() const {
return segments[1];
}
Segment& DataSegment() {
return segments[2];
}
const Segment& DataSegment() const {
return segments[2];
}
std::vector<u8> memory;
std::array<Segment, 3> segments;
VAddr entrypoint;
u64 program_id;
std::string name;
private:
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive& ar, const u32 file_version) {
ar& memory;
ar& segments;
ar& entrypoint;
ar& program_id;
ar& name;
}
};
} // namespace Kernel

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