diff --git a/.appveyor/UtilityFunctions.ps1 b/.appveyor/UtilityFunctions.ps1 new file mode 100644 index 000000000..fd7476314 --- /dev/null +++ b/.appveyor/UtilityFunctions.ps1 @@ -0,0 +1,39 @@ +# Set-up Visual Studio Command Prompt environment for PowerShell +pushd "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\" +cmd /c "VsDevCmd.bat -arch=x64 & set" | foreach { + if ($_ -match "=") { + $v = $_.split("="); Set-Item -Force -Path "ENV:\$($v[0])" -Value "$($v[1])" + } +} +popd + +function Which ($search_path, $name) { + ($search_path).Split(";") | Get-ChildItem -Filter $name | Select -First 1 -Exp FullName +} + +function GetDeps ($search_path, $binary) { + ((dumpbin /dependents $binary).Where({ $_ -match "dependencies:"}, "SkipUntil") | Select-String "[^ ]*\.dll").Matches | foreach { + Which $search_path $_.Value + } +} + +function RecursivelyGetDeps ($search_path, $binary) { + $final_deps = @() + $deps_to_process = GetDeps $search_path $binary + while ($deps_to_process.Count -gt 0) { + $current, $deps_to_process = $deps_to_process + if ($final_deps -contains $current) { continue } + + # Is this a system dll file? + # We use the same algorithm that cmake uses to determine this. + if ($current -match "$([regex]::Escape($env:SystemRoot))\\sys") { continue } + if ($current -match "$([regex]::Escape($env:WinDir))\\sys") { continue } + if ($current -match "\\msvc[^\\]+dll") { continue } + if ($current -match "\\api-ms-win-[^\\]+dll") { continue } + + $final_deps += $current + $new_deps = GetDeps $search_path $current + $deps_to_process += ($new_deps | ?{-not ($final_deps -contains $_)}) + } + return $final_deps +} diff --git a/.travis.yml b/.travis.yml index 9624d4483..1811bc130 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,10 +21,26 @@ matrix: install: "./.travis/linux/deps.sh" script: "./.travis/linux/build.sh" after_success: "./.travis/linux/upload.sh" + - if: branch = master AND type = push + os: linux + env: NAME="transifex push" + sudo: required + dist: trusty + addons: + apt: + packages: + - libsdl2-dev + - qtbase5-dev + - libqt5opengl5-dev + - qttools5-dev + - qttools5-dev-tools + install: "./.travis/transifex/deps.sh" + script: "./.travis/transifex/build.sh" + after_success: "./.travis/transifex/upload.sh" - os: osx env: NAME="macos build" sudo: false - osx_image: xcode7.3 + osx_image: xcode9.2 install: "./.travis/macos/deps.sh" script: "./.travis/macos/build.sh" after_success: "./.travis/macos/upload.sh" diff --git a/.travis/linux/docker.sh b/.travis/linux/docker.sh index b03424904..1a5197555 100755 --- a/.travis/linux/docker.sh +++ b/.travis/linux/docker.sh @@ -3,7 +3,7 @@ cd /citra apt-get update -apt-get install -y build-essential libsdl2-dev qtbase5-dev libqt5opengl5-dev libcurl4-openssl-dev libssl-dev wget git +apt-get install -y build-essential libsdl2-dev qtbase5-dev libqt5opengl5-dev qttools5-dev qttools5-dev-tools libcurl4-openssl-dev libssl-dev wget git # Get a recent version of CMake wget https://cmake.org/files/v3.9/cmake-3.9.0-Linux-x86_64.sh @@ -11,7 +11,7 @@ echo y | sh cmake-3.9.0-Linux-x86_64.sh --prefix=cmake export PATH=/citra/cmake/cmake-3.9.0-Linux-x86_64/bin:$PATH mkdir build && cd build -cmake .. -DUSE_SYSTEM_CURL=ON -DCMAKE_BUILD_TYPE=Release -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} +cmake .. -DUSE_SYSTEM_CURL=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} make -j4 ctest -VV -C Release diff --git a/.travis/macos/build.sh b/.travis/macos/build.sh index 2930292c2..25a4d4d5b 100755 --- a/.travis/macos/build.sh +++ b/.travis/macos/build.sh @@ -6,7 +6,7 @@ export MACOSX_DEPLOYMENT_TARGET=10.9 export Qt5_DIR=$(brew --prefix)/opt/qt5 mkdir build && cd build -cmake .. -DUSE_SYSTEM_CURL=ON -DCMAKE_OSX_ARCHITECTURES="x86_64;x86_64h" -DCMAKE_BUILD_TYPE=Release -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} +cmake .. -DUSE_SYSTEM_CURL=ON -DCMAKE_OSX_ARCHITECTURES="x86_64;x86_64h" -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} make -j4 ctest -VV -C Release diff --git a/.travis/transifex/build.sh b/.travis/transifex/build.sh new file mode 100755 index 000000000..2e9822360 --- /dev/null +++ b/.travis/transifex/build.sh @@ -0,0 +1,6 @@ +#!/bin/bash -ex + +mkdir build && cd build +cmake .. -DENABLE_QT_TRANSLATION=ON -DGENERATE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release +make translation + diff --git a/.travis/transifex/deps.sh b/.travis/transifex/deps.sh new file mode 100755 index 000000000..3fa6b1109 --- /dev/null +++ b/.travis/transifex/deps.sh @@ -0,0 +1,4 @@ +#!/bin/bash -ex + +sudo pip install transifex-client +echo $'[https://www.transifex.com]\nhostname = https://www.transifex.com\nusername = api\npassword = '"$TRANSIFEX_API_TOKEN"$'\n' > ~/.transifexrc diff --git a/.travis/transifex/upload.sh b/.travis/transifex/upload.sh new file mode 100755 index 000000000..f51847850 --- /dev/null +++ b/.travis/transifex/upload.sh @@ -0,0 +1,5 @@ +#!/bin/bash -ex + +cd dist/languages +tx push -s + diff --git a/CMakeLists.txt b/CMakeLists.txt index 14dc89e7e..a2835285f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,10 +10,16 @@ option(ENABLE_SDL2 "Enable the SDL2 frontend" ON) option(CITRA_USE_BUNDLED_SDL2 "Download bundled SDL2 binaries" OFF) option(ENABLE_QT "Enable the Qt frontend" ON) +option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF) option(CITRA_USE_BUNDLED_QT "Download bundled Qt binaries" OFF) option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON) option(CITRA_USE_BUNDLED_CURL "FOR MINGW ONLY: Download curl configured against winssl instead of openssl" OFF) + +if (ENABLE_WEB_SERVICE AND CITRA_USE_BUNDLED_CURL AND WINDOWS AND MSVC) + message("Turning off use bundled curl as msvc can compile curl on cpr") + SET(CITRA_USE_BUNDLED_CURL OFF CACHE BOOL "" FORCE) +endif() if (ENABLE_WEB_SERVICE AND NOT CITRA_USE_BUNDLED_CURL AND MINGW) message(AUTHOR_WARNING "Turning on CITRA_USE_BUNDLED_CURL. Override it only if you know what you are doing.") SET(CITRA_USE_BUNDLED_CURL ON CACHE BOOL "" FORCE) @@ -65,14 +71,16 @@ function(detect_architecture symbol arch) endif() endfunction() -if (MSVC) - detect_architecture("_M_AMD64" x86_64) - detect_architecture("_M_IX86" x86) - detect_architecture("_M_ARM" ARM) -else() - detect_architecture("__x86_64__" x86_64) - detect_architecture("__i386__" x86) - detect_architecture("__arm__" ARM) +if (NOT ENABLE_GENERIC) + if (MSVC) + detect_architecture("_M_AMD64" x86_64) + detect_architecture("_M_IX86" x86) + detect_architecture("_M_ARM" ARM) + else() + detect_architecture("__x86_64__" x86_64) + detect_architecture("__i386__" x86) + detect_architecture("__arm__" ARM) + endif() endif() if (NOT DEFINED ARCHITECTURE) set(ARCHITECTURE "GENERIC") @@ -230,6 +238,10 @@ if (ENABLE_QT) endif() find_package(Qt5 REQUIRED COMPONENTS Widgets OpenGL ${QT_PREFIX_HINT}) + + if (ENABLE_QT_TRANSLATION) + find_package(Qt5 REQUIRED COMPONENTS LinguistTools ${QT_PREFIX_HINT}) + endif() endif() if (ENABLE_WEB_SERVICE) diff --git a/README.md b/README.md index ad2f03a56..9b249f126 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ Most of the development happens on GitHub. It's also where [our central reposito If you want to contribute please take a look at the [Contributor's Guide](CONTRIBUTING.md) and [Developer Information](https://github.com/citra-emu/citra/wiki/Developer-Information). You should as well contact any of the developers in the forum in order to know about the current state of the emulator because the [TODO list](https://docs.google.com/document/d/1SWIop0uBI9IW8VGg97TAtoT_CHNoP42FzYmvG1F4QDA) isn't maintained anymore. +If you want to contribute to the user interface translation, please checkout [citra project on transifex](https://www.transifex.com/citra/citra). We centralize the translation work there, and periodically upstream translation. + ### Building * __Windows__: [Windows Build](https://github.com/citra-emu/citra/wiki/Building-For-Windows) diff --git a/appveyor.yml b/appveyor.yml index 9215bcd40..df98040d9 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -46,7 +46,7 @@ before_build: # redirect stderr and change the exit code to prevent powershell from cancelling the build if cmake prints a warning cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DCITRA_USE_BUNDLED_QT=1 -DCITRA_USE_BUNDLED_SDL2=1 -DCMAKE_USE_OPENSSL=0 -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} .. 2>&1 && exit 0' } else { - C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DUSE_SYSTEM_CURL=1 -DCITRA_USE_BUNDLED_CURL=1 -DCMAKE_BUILD_TYPE=Release -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} .. 2>&1" + C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DUSE_SYSTEM_CURL=1 -DCITRA_USE_BUNDLED_CURL=1 -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} .. 2>&1" } - cd .. @@ -118,23 +118,16 @@ after_build: Get-ChildItem "$CMAKE_BINARY_DIR" -Recurse -Filter "libcurl.dll" | Copy-Item -destination $RELEASE_DIST Copy-Item -path "$CMAKE_SOURCE_DIR/license.txt" -destination $RELEASE_DIST Copy-Item -path "$CMAKE_SOURCE_DIR/README.md" -destination $RELEASE_DIST + # copy all the dll dependencies to the release folder - # hardcoded list because we don't build static and determining the list of dlls from the binary is a pain. - $MingwDLLs = "Qt5Core.dll","Qt5Widgets.dll","Qt5Gui.dll","Qt5OpenGL.dll", - # QT dll dependencies - "libbz2-*.dll","libicudt*.dll","libicuin*.dll","libicuuc*.dll","libffi-*.dll", - "libfreetype-*.dll","libglib-*.dll","libgobject-*.dll","libgraphite2.dll","libiconv-*.dll", - "libharfbuzz-*.dll","libintl-*.dll","libpcre-*.dll","libpcre16-*.dll","libpcre2-16-*.dll","libpng16-*.dll", - # Runtime/Other dependencies - "libgcc_s_seh-*.dll","libstdc++-*.dll","libwinpthread-*.dll","SDL2.dll","zlib1.dll" + . "./.appveyor/UtilityFunctions.ps1" + $DLLSearchPath = "$CMAKE_BINARY_DIR\externals\curl-7_55_1\lib;C:\msys64\mingw64\bin;$env:PATH" + $MingwDLLs = RecursivelyGetDeps $DLLSearchPath "$RELEASE_DIST\citra.exe" + $MingwDLLs += RecursivelyGetDeps $DLLSearchPath "$RELEASE_DIST\citra-qt.exe" + Write-Host "Detected the following dependencies:" + Write-Host $MingwDLLs foreach ($file in $MingwDLLs) { - Copy-Item -path "C:/msys64/mingw64/bin/$file" -force -destination "$RELEASE_DIST" - } - # the above list copies a few extra debug dlls that aren't needed (thanks globbing patterns!) - # so we can remove them by hardcoding another list of extra dlls to remove - $DebugDLLs = "libicudtd*.dll","libicuind*.dll","libicuucd*.dll" - foreach ($file in $DebugDLLs) { - Remove-Item -path "$RELEASE_DIST/$file" + Copy-Item -path "$file" -force -destination "$RELEASE_DIST" } # copy the qt windows plugin dll to platforms diff --git a/dist/languages/.gitignore b/dist/languages/.gitignore new file mode 100644 index 000000000..8fc7e6a17 --- /dev/null +++ b/dist/languages/.gitignore @@ -0,0 +1,3 @@ +# Ignore the source language file +en.ts + diff --git a/dist/languages/.tx/config b/dist/languages/.tx/config new file mode 100644 index 000000000..84d8896b4 --- /dev/null +++ b/dist/languages/.tx/config @@ -0,0 +1,9 @@ +[main] +host = https://www.transifex.com + +[citra.emulator] +file_filter = .ts +source_file = en.ts +source_lang = en +type = QT + diff --git a/dist/languages/README.md b/dist/languages/README.md new file mode 100644 index 000000000..2156fd386 --- /dev/null +++ b/dist/languages/README.md @@ -0,0 +1 @@ +This directory stores translation patches (TS files) for citra Qt frontend. This directory is linked with [citra project on transifex](https://www.transifex.com/citra/citra), so you can update the translation by executing `tx pull -a`. If you want to contribute to the translation, please go the transifex link and submit your translation there. This directory on the main repo will be synchronized with transifex periodically. Do not directly open PRs on github to modify the translation. diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 4a4ba1101..88cc52588 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -11,11 +11,13 @@ target_include_directories(catch-single-include INTERFACE catch/single_include) add_subdirectory(cryptopp) # Dynarmic -# Dynarmic will skip defining xbyak if it's already defined, we then define it below -add_library(xbyak INTERFACE) -option(DYNARMIC_TESTS OFF) -set(DYNARMIC_NO_BUNDLED_FMT ON) -add_subdirectory(dynarmic) +if (ARCHITECTURE_x86_64) + # Dynarmic will skip defining xbyak if it's already defined, we then define it below + add_library(xbyak INTERFACE) + option(DYNARMIC_TESTS OFF) + set(DYNARMIC_NO_BUNDLED_FMT ON) + add_subdirectory(dynarmic) +endif() # libfmt add_subdirectory(fmt) diff --git a/externals/catch b/externals/catch index 19ab2117c..cd76f5730 160000 --- a/externals/catch +++ b/externals/catch @@ -1 +1 @@ -Subproject commit 19ab2117c5bac2f376f8da4a4b25e183137bcec0 +Subproject commit cd76f5730c9a3afa19f3b9c83608d9c7ab325a19 diff --git a/externals/enet b/externals/enet index a84c120ef..39a72ab19 160000 --- a/externals/enet +++ b/externals/enet @@ -1 +1 @@ -Subproject commit a84c120eff13d2fa3eadb41ef7afe0f7819f4d6c +Subproject commit 39a72ab1990014eb399cee9d538fd529df99c6a0 diff --git a/externals/nihstro b/externals/nihstro index 7e24743af..fd69de1a1 160000 --- a/externals/nihstro +++ b/externals/nihstro @@ -1 +1 @@ -Subproject commit 7e24743af21a7c2e3cef21ef174ae4269d0cfdac +Subproject commit fd69de1a1b960ec296cc67d32257b0f9e2d89ac6 diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 2a80ebbfe..ec89b7a4c 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -23,7 +23,7 @@ add_library(audio_core STATIC time_stretch.cpp time_stretch.h - $<$:sdl2_sink.cpp sdl2_sink.h> + $<$:sdl2_sink.cpp sdl2_sink.h> ) create_target_directory_groups(audio_core) diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp index 85d7ab099..f1c5a64db 100644 --- a/src/citra/citra.cpp +++ b/src/citra/citra.cpp @@ -11,10 +11,8 @@ // This needs to be included before getopt.h because the latter #defines symbols used by it #include "common/microprofile.h" -#ifdef _MSC_VER -#include -#else #include +#ifndef _MSC_VER #include #endif @@ -157,13 +155,14 @@ int main(int argc, char** argv) { errno = EINVAL; if (errno != 0) exit(1); + break; } case 'm': { use_multiplayer = true; - std::string str_arg(optarg); + const std::string str_arg(optarg); // regex to check if the format is nickname:password@ip:port // with optional :password - std::regex re("^([^:]+)(?::(.+))?@([^:]+)(?::([0-9]+))?$"); + const std::regex re("^([^:]+)(?::(.+))?@([^:]+)(?::([0-9]+))?$"); if (!std::regex_match(str_arg, re)) { std::cout << "Wrong format for option --multiplayer\n"; PrintHelp(argv[0]); @@ -188,10 +187,6 @@ int main(int argc, char** argv) { std::cout << "Address to room must not be empty.\n"; return 0; } - if (port > 65535) { - std::cout << "Port must be between 0 and 65535.\n"; - return 0; - } break; } case 'h': diff --git a/src/citra/config.cpp b/src/citra/config.cpp index 45c28ad09..0a84887d9 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -76,8 +76,9 @@ void Config::ReadValues() { Settings::values.analogs[i] = default_param; } - Settings::values.motion_device = sdl2_config->Get( - "Controls", "motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01"); + Settings::values.motion_device = + sdl2_config->Get("Controls", "motion_device", + "engine:motion_emu,update_period:100,sensitivity:0.01,tilt_clamp:90.0"); Settings::values.touch_device = sdl2_config->Get("Controls", "touch_device", "engine:emu_window"); diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp index 25643715a..1a6fd0fa3 100644 --- a/src/citra/emu_window/emu_window_sdl2.cpp +++ b/src/citra/emu_window/emu_window_sdl2.cpp @@ -89,19 +89,19 @@ EmuWindow_SDL2::EmuWindow_SDL2() { SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); if (render_window == nullptr) { - LOG_CRITICAL(Frontend, "Failed to create SDL2 window! Exiting..."); + LOG_CRITICAL(Frontend, "Failed to create SDL2 window: %s", SDL_GetError()); exit(1); } gl_context = SDL_GL_CreateContext(render_window); if (gl_context == nullptr) { - LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context! Exiting..."); + LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context: %s", SDL_GetError()); exit(1); } if (!gladLoadGLLoader(static_cast(SDL_GL_GetProcAddress))) { - LOG_CRITICAL(Frontend, "Failed to initialize GL functions! Exiting..."); + LOG_CRITICAL(Frontend, "Failed to initialize GL functions: %s", SDL_GetError()); exit(1); } diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 56a1eae99..b0fc1acca 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -90,12 +90,46 @@ file(GLOB_RECURSE THEMES ${CMAKE_SOURCE_DIR}/dist/qt_themes/*) qt5_wrap_ui(UI_HDRS ${UIS}) +if (ENABLE_QT_TRANSLATION) + set(CITRA_QT_LANGUAGES "${CMAKE_SOURCE_DIR}/dist/languages" CACHE PATH "Path to the translation bundle for the Qt frontend") + option(GENERATE_QT_TRANSLATION "Generate en.ts as the translation source file" OFF) + + # Update source TS file if enabled + if (GENERATE_QT_TRANSLATION) + get_target_property(SRCS citra-qt SOURCES) + qt5_create_translation(QM_FILES ${SRCS} ${UIS} ${CITRA_QT_LANGUAGES}/en.ts) + add_custom_target(translation ALL DEPENDS ${CITRA_QT_LANGUAGES}/en.ts) + endif() + + # Find all TS files except en.ts + file(GLOB_RECURSE LANGUAGES_TS ${CITRA_QT_LANGUAGES}/*.ts) + list(REMOVE_ITEM LANGUAGES_TS ${CITRA_QT_LANGUAGES}/en.ts) + + # Compile TS files to QM files + qt5_add_translation(LANGUAGES_QM ${LANGUAGES_TS}) + + # Build a QRC file from the QM file list + set(LANGUAGES_QRC ${CMAKE_CURRENT_BINARY_DIR}/languages.qrc) + file(WRITE ${LANGUAGES_QRC} "\n") + foreach (QM ${LANGUAGES_QM}) + get_filename_component(QM_FILE ${QM} NAME) + file(APPEND ${LANGUAGES_QRC} "${QM_FILE}\n") + endforeach (QM) + file(APPEND ${LANGUAGES_QRC} "") + + # Add the QRC file to package in all QM files + qt5_add_resources(LANGUAGES ${LANGUAGES_QRC}) +else() + set(LANGUAGES) +endif() + target_sources(citra-qt PRIVATE ${ICONS} ${THEMES} ${UI_HDRS} ${UIS} + ${LANGUAGES} ) if (APPLE) diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 64c0aa541..510d6587a 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -36,6 +36,7 @@ void EmuThread::run() { Core::System::ResultStatus result = Core::System::GetInstance().RunLoop(); if (result != Core::System::ResultStatus::Success) { + this->SetRunning(false); emit ErrorThrown(result, Core::System::GetInstance().GetStatusDetails()); } diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 97993e426..7e64fbaaf 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -58,7 +58,9 @@ void Config::ReadValues() { } Settings::values.motion_device = - qt_config->value("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01") + qt_config + ->value("motion_device", + "engine:motion_emu,update_period:100,sensitivity:0.01,tilt_clamp:90.0") .toString() .toStdString(); Settings::values.touch_device = @@ -182,6 +184,7 @@ void Config::ReadValues() { UISettings::values.gamedir = qt_config->value("gameListRootDir", ".").toString(); UISettings::values.gamedir_deepscan = qt_config->value("gameListDeepScan", false).toBool(); UISettings::values.recent_files = qt_config->value("recentFiles").toStringList(); + UISettings::values.language = qt_config->value("language", "").toString(); qt_config->endGroup(); qt_config->beginGroup("Shortcuts"); @@ -333,6 +336,7 @@ void Config::SaveValues() { qt_config->setValue("gameListRootDir", UISettings::values.gamedir); qt_config->setValue("gameListDeepScan", UISettings::values.gamedir_deepscan); qt_config->setValue("recentFiles", UISettings::values.recent_files); + qt_config->setValue("language", UISettings::values.language); qt_config->endGroup(); qt_config->beginGroup("Shortcuts"); diff --git a/src/citra_qt/configuration/configure.ui b/src/citra_qt/configuration/configure.ui index 6abd1917e..dce2b7739 100644 --- a/src/citra_qt/configuration/configure.ui +++ b/src/citra_qt/configuration/configure.ui @@ -6,7 +6,7 @@ 0 0 - 740 + 461 500 @@ -34,15 +34,15 @@ Input - - - Graphics - - + + + Graphics + + - - Audio - + + Audio + diff --git a/src/citra_qt/configuration/configure_audio.cpp b/src/citra_qt/configuration/configure_audio.cpp index 8f020c0e8..af58df308 100644 --- a/src/citra_qt/configuration/configure_audio.cpp +++ b/src/citra_qt/configuration/configure_audio.cpp @@ -76,3 +76,7 @@ void ConfigureAudio::updateAudioDevices(int sink_index) { ui->audio_device_combo_box->addItem(device.c_str()); } } + +void ConfigureAudio::retranslateUi() { + ui->retranslateUi(this); +} diff --git a/src/citra_qt/configuration/configure_audio.h b/src/citra_qt/configuration/configure_audio.h index 8190e694f..d4790db5c 100644 --- a/src/citra_qt/configuration/configure_audio.h +++ b/src/citra_qt/configuration/configure_audio.h @@ -19,6 +19,7 @@ public: ~ConfigureAudio(); void applyConfiguration(); + void retranslateUi(); public slots: void updateAudioDevices(int sink_index); diff --git a/src/citra_qt/configuration/configure_debug.cpp b/src/citra_qt/configuration/configure_debug.cpp index 263f73f38..48f57739e 100644 --- a/src/citra_qt/configuration/configure_debug.cpp +++ b/src/citra_qt/configuration/configure_debug.cpp @@ -24,3 +24,7 @@ void ConfigureDebug::applyConfiguration() { Settings::values.gdbstub_port = ui->gdbport_spinbox->value(); Settings::Apply(); } + +void ConfigureDebug::retranslateUi() { + ui->retranslateUi(this); +} diff --git a/src/citra_qt/configuration/configure_debug.h b/src/citra_qt/configuration/configure_debug.h index d167eb996..fc95c91ca 100644 --- a/src/citra_qt/configuration/configure_debug.h +++ b/src/citra_qt/configuration/configure_debug.h @@ -19,6 +19,7 @@ public: ~ConfigureDebug(); void applyConfiguration(); + void retranslateUi(); private: void setConfiguration(); diff --git a/src/citra_qt/configuration/configure_dialog.cpp b/src/citra_qt/configuration/configure_dialog.cpp index b87dc0e6c..ca468bf81 100644 --- a/src/citra_qt/configuration/configure_dialog.cpp +++ b/src/citra_qt/configuration/configure_dialog.cpp @@ -10,6 +10,8 @@ ConfigureDialog::ConfigureDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ConfigureDialog) { ui->setupUi(this); this->setConfiguration(); + connect(ui->generalTab, &ConfigureGeneral::languageChanged, this, + &ConfigureDialog::onLanguageChanged); } ConfigureDialog::~ConfigureDialog() {} @@ -26,3 +28,15 @@ void ConfigureDialog::applyConfiguration() { ui->webTab->applyConfiguration(); Settings::Apply(); } + +void ConfigureDialog::onLanguageChanged(const QString& locale) { + emit languageChanged(locale); + ui->retranslateUi(this); + ui->generalTab->retranslateUi(); + ui->systemTab->retranslateUi(); + ui->inputTab->retranslateUi(); + ui->graphicsTab->retranslateUi(); + ui->audioTab->retranslateUi(); + ui->debugTab->retranslateUi(); + ui->webTab->retranslateUi(); +} diff --git a/src/citra_qt/configuration/configure_dialog.h b/src/citra_qt/configuration/configure_dialog.h index 21fa1f501..481e30154 100644 --- a/src/citra_qt/configuration/configure_dialog.h +++ b/src/citra_qt/configuration/configure_dialog.h @@ -20,6 +20,12 @@ public: void applyConfiguration(); +private slots: + void onLanguageChanged(const QString& locale); + +signals: + void languageChanged(const QString& locale); + private: void setConfiguration(); diff --git a/src/citra_qt/configuration/configure_general.cpp b/src/citra_qt/configuration/configure_general.cpp index f8f6c305d..ad008a011 100644 --- a/src/citra_qt/configuration/configure_general.cpp +++ b/src/citra_qt/configuration/configure_general.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include "citra_qt/configuration/configure_general.h" #include "citra_qt/ui_settings.h" #include "core/core.h" @@ -12,6 +13,23 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureGeneral) { ui->setupUi(this); + ui->language_combobox->addItem(tr(""), QString("")); + ui->language_combobox->addItem(tr("English"), QString("en")); + QDirIterator it(":/languages", QDirIterator::NoIteratorFlags); + while (it.hasNext()) { + QString locale = it.next(); + locale.truncate(locale.lastIndexOf('.')); + locale.remove(0, locale.lastIndexOf('/') + 1); + QString lang = QLocale::languageToString(QLocale(locale).language()); + ui->language_combobox->addItem(lang, locale); + } + + // Unlike other configuration changes, interface language changes need to be reflected on the + // interface immediately. This is done by passing a signal to the main window, and then + // retranslating when passing back. + connect(ui->language_combobox, + static_cast(&QComboBox::currentIndexChanged), this, + &ConfigureGeneral::onLanguageChanged); for (auto theme : UISettings::themes) { ui->theme_combobox->addItem(theme.first, theme.second); @@ -37,6 +55,8 @@ void ConfigureGeneral::setConfiguration() { ui->region_combobox->setCurrentIndex(Settings::values.region_value + 1); ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); + ui->language_combobox->setCurrentIndex( + ui->language_combobox->findData(UISettings::values.language)); } void ConfigureGeneral::applyConfiguration() { @@ -52,3 +72,14 @@ void ConfigureGeneral::applyConfiguration() { Settings::values.use_cpu_jit = ui->toggle_cpu_jit->isChecked(); Settings::Apply(); } + +void ConfigureGeneral::onLanguageChanged(int index) { + if (index == -1) + return; + + emit languageChanged(ui->language_combobox->itemData(index).toString()); +} + +void ConfigureGeneral::retranslateUi() { + ui->retranslateUi(this); +} diff --git a/src/citra_qt/configuration/configure_general.h b/src/citra_qt/configuration/configure_general.h index 447552d8c..55fd93890 100644 --- a/src/citra_qt/configuration/configure_general.h +++ b/src/citra_qt/configuration/configure_general.h @@ -19,6 +19,13 @@ public: ~ConfigureGeneral(); void applyConfiguration(); + void retranslateUi(); + +private slots: + void onLanguageChanged(int index); + +signals: + void languageChanged(const QString& locale); private: void setConfiguration(); diff --git a/src/citra_qt/configuration/configure_general.ui b/src/citra_qt/configuration/configure_general.ui index 00e5b49f5..c2bf24b52 100644 --- a/src/citra_qt/configuration/configure_general.ui +++ b/src/citra_qt/configuration/configure_general.ui @@ -6,8 +6,8 @@ 0 0 - 300 - 377 + 345 + 493 @@ -38,6 +38,20 @@ + + + + + + Interface language + + + + + + + + diff --git a/src/citra_qt/configuration/configure_graphics.cpp b/src/citra_qt/configuration/configure_graphics.cpp index b5a5ab1e1..d1546f3aa 100644 --- a/src/citra_qt/configuration/configure_graphics.cpp +++ b/src/citra_qt/configuration/configure_graphics.cpp @@ -113,3 +113,7 @@ void ConfigureGraphics::applyConfiguration() { Settings::values.swap_screen = ui->swap_screen->isChecked(); Settings::Apply(); } + +void ConfigureGraphics::retranslateUi() { + ui->retranslateUi(this); +} diff --git a/src/citra_qt/configuration/configure_graphics.h b/src/citra_qt/configuration/configure_graphics.h index 5497a55f7..36bdef890 100644 --- a/src/citra_qt/configuration/configure_graphics.h +++ b/src/citra_qt/configuration/configure_graphics.h @@ -19,6 +19,7 @@ public: ~ConfigureGraphics(); void applyConfiguration(); + void retranslateUi(); private: void setConfiguration(); diff --git a/src/citra_qt/configuration/configure_input.cpp b/src/citra_qt/configuration/configure_input.cpp index 5a7d269cd..64a22cb03 100644 --- a/src/citra_qt/configuration/configure_input.cpp +++ b/src/citra_qt/configuration/configure_input.cpp @@ -277,3 +277,7 @@ void ConfigureInput::keyPressEvent(QKeyEvent* event) { } setPollingResult({}, true); } + +void ConfigureInput::retranslateUi() { + ui->retranslateUi(this); +} diff --git a/src/citra_qt/configuration/configure_input.h b/src/citra_qt/configuration/configure_input.h index a0bef86d5..2efebf20d 100644 --- a/src/citra_qt/configuration/configure_input.h +++ b/src/citra_qt/configuration/configure_input.h @@ -33,6 +33,7 @@ public: /// Save all button configurations to settings file void applyConfiguration(); + void retranslateUi(); private: std::unique_ptr ui; diff --git a/src/citra_qt/configuration/configure_system.cpp b/src/citra_qt/configuration/configure_system.cpp index 88a067c12..6793fb50d 100644 --- a/src/citra_qt/configuration/configure_system.cpp +++ b/src/citra_qt/configuration/configure_system.cpp @@ -167,3 +167,7 @@ void ConfigureSystem::refreshConsoleID() { Service::CFG::UpdateConfigNANDSavegame(); ui->label_console_id->setText("Console ID: 0x" + QString::number(console_id, 16).toUpper()); } + +void ConfigureSystem::retranslateUi() { + ui->retranslateUi(this); +} diff --git a/src/citra_qt/configuration/configure_system.h b/src/citra_qt/configuration/configure_system.h index f13de17d4..094887791 100644 --- a/src/citra_qt/configuration/configure_system.h +++ b/src/citra_qt/configuration/configure_system.h @@ -20,6 +20,7 @@ public: void applyConfiguration(); void setConfiguration(); + void retranslateUi(); public slots: void updateBirthdayComboBox(int birthmonth_index); diff --git a/src/citra_qt/configuration/configure_web.cpp b/src/citra_qt/configuration/configure_web.cpp index bf8c21ac7..8bfbb94c3 100644 --- a/src/citra_qt/configuration/configure_web.cpp +++ b/src/citra_qt/configuration/configure_web.cpp @@ -100,3 +100,7 @@ void ConfigureWeb::OnLoginVerified() { "correctly, and that your internet connection is working.")); } } + +void ConfigureWeb::retranslateUi() { + ui->retranslateUi(this); +} diff --git a/src/citra_qt/configuration/configure_web.h b/src/citra_qt/configuration/configure_web.h index ad2d58f6e..b8e71ffdd 100644 --- a/src/citra_qt/configuration/configure_web.h +++ b/src/citra_qt/configuration/configure_web.h @@ -20,6 +20,7 @@ public: ~ConfigureWeb(); void applyConfiguration(); + void retranslateUi(); public slots: void RefreshTelemetryID(); diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index ab8de008a..7e7f57625 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -98,6 +98,8 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { // register size_t to use in slots and signals qRegisterMetaType("size_t"); + LoadTranslation(); + Pica::g_debug_context = Pica::DebugContext::Construct(); setAcceptDrops(true); ui.setupUi(this); @@ -115,8 +117,8 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { ConnectMenuEvents(); ConnectWidgetEvents(); - setWindowTitle(QString("Citra %1| %2-%3") - .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); + SetupUIStrings(); + show(); game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); @@ -892,6 +894,8 @@ void GMainWindow::ToggleWindowMode() { void GMainWindow::OnConfigure() { ConfigureDialog configureDialog(this); + connect(&configureDialog, &ConfigureDialog::languageChanged, this, + &GMainWindow::OnLanguageChanged); auto result = configureDialog.exec(); if (result == QDialog::Accepted) { configureDialog.applyConfiguration(); @@ -995,6 +999,7 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det } else { // Only show the message if the game is still running. if (emu_thread) { + emu_thread->SetRunning(true); message_label->setText(status_message); message_label->setVisible(true); } @@ -1105,6 +1110,45 @@ void GMainWindow::UpdateUITheme() { } } +void GMainWindow::LoadTranslation() { + // If the selected language is English, no need to install any translation + if (UISettings::values.language == "en") { + return; + } + + bool loaded; + + if (UISettings::values.language.isEmpty()) { + // If the selected language is empty, use system locale + loaded = translator.load(QLocale(), "", "", ":/languages/"); + } else { + // Otherwise load from the specified file + loaded = translator.load(UISettings::values.language, ":/languages/"); + } + + if (loaded) { + qApp->installTranslator(&translator); + } else { + UISettings::values.language = "en"; + } +} + +void GMainWindow::OnLanguageChanged(const QString& locale) { + if (UISettings::values.language != "en") { + qApp->removeTranslator(&translator); + } + + UISettings::values.language = locale; + LoadTranslation(); + ui.retranslateUi(this); + SetupUIStrings(); +} + +void GMainWindow::SetupUIStrings() { + setWindowTitle( + tr("Citra %1| %2-%3").arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); +} + #ifdef main #undef main #endif diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index a96a9b061..fa36546d4 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -7,6 +7,7 @@ #include #include #include +#include #include "core/core.h" #include "core/hle/service/am/am.h" #include "ui_main.h" @@ -152,9 +153,12 @@ private slots: void OnUpdateFound(bool found, bool error); void OnCheckForUpdates(); void OnOpenUpdater(); + void OnLanguageChanged(const QString& locale); private: void UpdateStatusBar(); + void LoadTranslation(); + void SetupUIStrings(); Ui::MainWindow ui; @@ -193,6 +197,8 @@ private: QAction* actions_recent_files[max_recent_files_item]; + QTranslator translator; + protected: void dropEvent(QDropEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; diff --git a/src/citra_qt/ui_settings.h b/src/citra_qt/ui_settings.h index dba4c5d3c..caf6aea6a 100644 --- a/src/citra_qt/ui_settings.h +++ b/src/citra_qt/ui_settings.h @@ -48,6 +48,7 @@ struct Values { QString gamedir; bool gamedir_deepscan; QStringList recent_files; + QString language; QString theme; diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index bdec3c43f..2fe864f92 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -32,6 +32,8 @@ add_library(common STATIC break_points.cpp break_points.h chunk_file.h + cityhash.cpp + cityhash.h code_block.h color.h common_funcs.h @@ -39,7 +41,6 @@ add_library(common STATIC common_types.h file_util.cpp file_util.h - hash.cpp hash.h linear_disk_cache.h logging/backend.cpp diff --git a/src/common/cityhash.cpp b/src/common/cityhash.cpp new file mode 100644 index 000000000..de31ffbd8 --- /dev/null +++ b/src/common/cityhash.cpp @@ -0,0 +1,340 @@ +// Copyright (c) 2011 Google, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// CityHash, by Geoff Pike and Jyrki Alakuijala +// +// This file provides CityHash64() and related functions. +// +// It's probably possible to create even faster hash functions by +// writing a program that systematically explores some of the space of +// possible hash functions, by using SIMD instructions, or by +// compromising on hash quality. + +#include +#include // for memcpy and memset +#include "cityhash.h" +#include "common/swap.h" + +// #include "config.h" +#ifdef __GNUC__ +#define HAVE_BUILTIN_EXPECT 1 +#endif +#ifdef COMMON_BIG_ENDIAN +#define WORDS_BIGENDIAN 1 +#endif + +using namespace std; + +typedef uint8_t uint8; +typedef uint32_t uint32; +typedef uint64_t uint64; + +namespace Common { + +static uint64 UNALIGNED_LOAD64(const char* p) { + uint64 result; + memcpy(&result, p, sizeof(result)); + return result; +} + +static uint32 UNALIGNED_LOAD32(const char* p) { + uint32 result; + memcpy(&result, p, sizeof(result)); + return result; +} + +#ifdef WORDS_BIGENDIAN +#define uint32_in_expected_order(x) (swap32(x)) +#define uint64_in_expected_order(x) (swap64(x)) +#else +#define uint32_in_expected_order(x) (x) +#define uint64_in_expected_order(x) (x) +#endif + +#if !defined(LIKELY) +#if HAVE_BUILTIN_EXPECT +#define LIKELY(x) (__builtin_expect(!!(x), 1)) +#else +#define LIKELY(x) (x) +#endif +#endif + +static uint64 Fetch64(const char* p) { + return uint64_in_expected_order(UNALIGNED_LOAD64(p)); +} + +static uint32 Fetch32(const char* p) { + return uint32_in_expected_order(UNALIGNED_LOAD32(p)); +} + +// Some primes between 2^63 and 2^64 for various uses. +static const uint64 k0 = 0xc3a5c85c97cb3127ULL; +static const uint64 k1 = 0xb492b66fbe98f273ULL; +static const uint64 k2 = 0x9ae16a3b2f90404fULL; + +// Bitwise right rotate. Normally this will compile to a single +// instruction, especially if the shift is a manifest constant. +static uint64 Rotate(uint64 val, int shift) { + // Avoid shifting by 64: doing so yields an undefined result. + return shift == 0 ? val : ((val >> shift) | (val << (64 - shift))); +} + +static uint64 ShiftMix(uint64 val) { + return val ^ (val >> 47); +} + +static uint64 HashLen16(uint64 u, uint64 v) { + return Hash128to64(uint128(u, v)); +} + +static uint64 HashLen16(uint64 u, uint64 v, uint64 mul) { + // Murmur-inspired hashing. + uint64 a = (u ^ v) * mul; + a ^= (a >> 47); + uint64 b = (v ^ a) * mul; + b ^= (b >> 47); + b *= mul; + return b; +} + +static uint64 HashLen0to16(const char* s, size_t len) { + if (len >= 8) { + uint64 mul = k2 + len * 2; + uint64 a = Fetch64(s) + k2; + uint64 b = Fetch64(s + len - 8); + uint64 c = Rotate(b, 37) * mul + a; + uint64 d = (Rotate(a, 25) + b) * mul; + return HashLen16(c, d, mul); + } + if (len >= 4) { + uint64 mul = k2 + len * 2; + uint64 a = Fetch32(s); + return HashLen16(len + (a << 3), Fetch32(s + len - 4), mul); + } + if (len > 0) { + uint8 a = s[0]; + uint8 b = s[len >> 1]; + uint8 c = s[len - 1]; + uint32 y = static_cast(a) + (static_cast(b) << 8); + uint32 z = static_cast(len) + (static_cast(c) << 2); + return ShiftMix(y * k2 ^ z * k0) * k2; + } + return k2; +} + +// This probably works well for 16-byte strings as well, but it may be overkill +// in that case. +static uint64 HashLen17to32(const char* s, size_t len) { + uint64 mul = k2 + len * 2; + uint64 a = Fetch64(s) * k1; + uint64 b = Fetch64(s + 8); + uint64 c = Fetch64(s + len - 8) * mul; + uint64 d = Fetch64(s + len - 16) * k2; + return HashLen16(Rotate(a + b, 43) + Rotate(c, 30) + d, a + Rotate(b + k2, 18) + c, mul); +} + +// Return a 16-byte hash for 48 bytes. Quick and dirty. +// Callers do best to use "random-looking" values for a and b. +static pair WeakHashLen32WithSeeds(uint64 w, uint64 x, uint64 y, uint64 z, uint64 a, + uint64 b) { + a += w; + b = Rotate(b + a + z, 21); + uint64 c = a; + a += x; + a += y; + b += Rotate(a, 44); + return make_pair(a + z, b + c); +} + +// Return a 16-byte hash for s[0] ... s[31], a, and b. Quick and dirty. +static pair WeakHashLen32WithSeeds(const char* s, uint64 a, uint64 b) { + return WeakHashLen32WithSeeds(Fetch64(s), Fetch64(s + 8), Fetch64(s + 16), Fetch64(s + 24), a, + b); +} + +// Return an 8-byte hash for 33 to 64 bytes. +static uint64 HashLen33to64(const char* s, size_t len) { + uint64 mul = k2 + len * 2; + uint64 a = Fetch64(s) * k2; + uint64 b = Fetch64(s + 8); + uint64 c = Fetch64(s + len - 24); + uint64 d = Fetch64(s + len - 32); + uint64 e = Fetch64(s + 16) * k2; + uint64 f = Fetch64(s + 24) * 9; + uint64 g = Fetch64(s + len - 8); + uint64 h = Fetch64(s + len - 16) * mul; + uint64 u = Rotate(a + g, 43) + (Rotate(b, 30) + c) * 9; + uint64 v = ((a + g) ^ d) + f + 1; + uint64 w = swap64((u + v) * mul) + h; + uint64 x = Rotate(e + f, 42) + c; + uint64 y = (swap64((v + w) * mul) + g) * mul; + uint64 z = e + f + c; + a = swap64((x + z) * mul + y) + b; + b = ShiftMix((z + a) * mul + d + h) * mul; + return b + x; +} + +uint64 CityHash64(const char* s, size_t len) { + if (len <= 32) { + if (len <= 16) { + return HashLen0to16(s, len); + } else { + return HashLen17to32(s, len); + } + } else if (len <= 64) { + return HashLen33to64(s, len); + } + + // For strings over 64 bytes we hash the end first, and then as we + // loop we keep 56 bytes of state: v, w, x, y, and z. + uint64 x = Fetch64(s + len - 40); + uint64 y = Fetch64(s + len - 16) + Fetch64(s + len - 56); + uint64 z = HashLen16(Fetch64(s + len - 48) + len, Fetch64(s + len - 24)); + pair v = WeakHashLen32WithSeeds(s + len - 64, len, z); + pair w = WeakHashLen32WithSeeds(s + len - 32, y + k1, x); + x = x * k1 + Fetch64(s); + + // Decrease len to the nearest multiple of 64, and operate on 64-byte chunks. + len = (len - 1) & ~static_cast(63); + do { + x = Rotate(x + y + v.first + Fetch64(s + 8), 37) * k1; + y = Rotate(y + v.second + Fetch64(s + 48), 42) * k1; + x ^= w.second; + y += v.first + Fetch64(s + 40); + z = Rotate(z + w.first, 33) * k1; + v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first); + w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16)); + std::swap(z, x); + s += 64; + len -= 64; + } while (len != 0); + return HashLen16(HashLen16(v.first, w.first) + ShiftMix(y) * k1 + z, + HashLen16(v.second, w.second) + x); +} + +uint64 CityHash64WithSeed(const char* s, size_t len, uint64 seed) { + return CityHash64WithSeeds(s, len, k2, seed); +} + +uint64 CityHash64WithSeeds(const char* s, size_t len, uint64 seed0, uint64 seed1) { + return HashLen16(CityHash64(s, len) - seed0, seed1); +} + +// A subroutine for CityHash128(). Returns a decent 128-bit hash for strings +// of any length representable in signed long. Based on City and Murmur. +static uint128 CityMurmur(const char* s, size_t len, uint128 seed) { + uint64 a = Uint128Low64(seed); + uint64 b = Uint128High64(seed); + uint64 c = 0; + uint64 d = 0; + signed long l = static_cast(len) - 16; + if (l <= 0) { // len <= 16 + a = ShiftMix(a * k1) * k1; + c = b * k1 + HashLen0to16(s, len); + d = ShiftMix(a + (len >= 8 ? Fetch64(s) : c)); + } else { // len > 16 + c = HashLen16(Fetch64(s + len - 8) + k1, a); + d = HashLen16(b + len, c + Fetch64(s + len - 16)); + a += d; + do { + a ^= ShiftMix(Fetch64(s) * k1) * k1; + a *= k1; + b ^= a; + c ^= ShiftMix(Fetch64(s + 8) * k1) * k1; + c *= k1; + d ^= c; + s += 16; + l -= 16; + } while (l > 0); + } + a = HashLen16(a, c); + b = HashLen16(d, b); + return uint128(a ^ b, HashLen16(b, a)); +} + +uint128 CityHash128WithSeed(const char* s, size_t len, uint128 seed) { + if (len < 128) { + return CityMurmur(s, len, seed); + } + + // We expect len >= 128 to be the common case. Keep 56 bytes of state: + // v, w, x, y, and z. + pair v, w; + uint64 x = Uint128Low64(seed); + uint64 y = Uint128High64(seed); + uint64 z = len * k1; + v.first = Rotate(y ^ k1, 49) * k1 + Fetch64(s); + v.second = Rotate(v.first, 42) * k1 + Fetch64(s + 8); + w.first = Rotate(y + z, 35) * k1 + x; + w.second = Rotate(x + Fetch64(s + 88), 53) * k1; + + // This is the same inner loop as CityHash64(), manually unrolled. + do { + x = Rotate(x + y + v.first + Fetch64(s + 8), 37) * k1; + y = Rotate(y + v.second + Fetch64(s + 48), 42) * k1; + x ^= w.second; + y += v.first + Fetch64(s + 40); + z = Rotate(z + w.first, 33) * k1; + v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first); + w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16)); + std::swap(z, x); + s += 64; + x = Rotate(x + y + v.first + Fetch64(s + 8), 37) * k1; + y = Rotate(y + v.second + Fetch64(s + 48), 42) * k1; + x ^= w.second; + y += v.first + Fetch64(s + 40); + z = Rotate(z + w.first, 33) * k1; + v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first); + w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16)); + std::swap(z, x); + s += 64; + len -= 128; + } while (LIKELY(len >= 128)); + x += Rotate(v.first + z, 49) * k0; + y = y * k0 + Rotate(w.second, 37); + z = z * k0 + Rotate(w.first, 27); + w.first *= 9; + v.first *= k0; + // If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s. + for (size_t tail_done = 0; tail_done < len;) { + tail_done += 32; + y = Rotate(x + y, 42) * k0 + v.second; + w.first += Fetch64(s + len - tail_done + 16); + x = x * k0 + w.first; + z += w.second + Fetch64(s + len - tail_done); + w.second += v.first; + v = WeakHashLen32WithSeeds(s + len - tail_done, v.first + z, v.second); + v.first *= k0; + } + // At this point our 56 bytes of state should contain more than + // enough information for a strong 128-bit hash. We use two + // different 56-byte-to-8-byte hashes to get a 16-byte final result. + x = HashLen16(x, v.first); + y = HashLen16(y + z, w.first); + return uint128(HashLen16(x + v.second, w.second) + y, HashLen16(x + w.second, y + v.second)); +} + +uint128 CityHash128(const char* s, size_t len) { + return len >= 16 + ? CityHash128WithSeed(s + 16, len - 16, uint128(Fetch64(s), Fetch64(s + 8) + k0)) + : CityHash128WithSeed(s, len, uint128(k0, k1)); +} + +} // namespace Common diff --git a/src/common/cityhash.h b/src/common/cityhash.h new file mode 100644 index 000000000..bcebdb150 --- /dev/null +++ b/src/common/cityhash.h @@ -0,0 +1,110 @@ +// Copyright (c) 2011 Google, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// CityHash, by Geoff Pike and Jyrki Alakuijala +// +// http://code.google.com/p/cityhash/ +// +// This file provides a few functions for hashing strings. All of them are +// high-quality functions in the sense that they pass standard tests such +// as Austin Appleby's SMHasher. They are also fast. +// +// For 64-bit x86 code, on short strings, we don't know of anything faster than +// CityHash64 that is of comparable quality. We believe our nearest competitor +// is Murmur3. For 64-bit x86 code, CityHash64 is an excellent choice for hash +// tables and most other hashing (excluding cryptography). +// +// For 64-bit x86 code, on long strings, the picture is more complicated. +// On many recent Intel CPUs, such as Nehalem, Westmere, Sandy Bridge, etc., +// CityHashCrc128 appears to be faster than all competitors of comparable +// quality. CityHash128 is also good but not quite as fast. We believe our +// nearest competitor is Bob Jenkins' Spooky. We don't have great data for +// other 64-bit CPUs, but for long strings we know that Spooky is slightly +// faster than CityHash on some relatively recent AMD x86-64 CPUs, for example. +// Note that CityHashCrc128 is declared in citycrc.h. +// +// For 32-bit x86 code, we don't know of anything faster than CityHash32 that +// is of comparable quality. We believe our nearest competitor is Murmur3A. +// (On 64-bit CPUs, it is typically faster to use the other CityHash variants.) +// +// Functions in the CityHash family are not suitable for cryptography. +// +// Please see CityHash's README file for more details on our performance +// measurements and so on. +// +// WARNING: This code has been only lightly tested on big-endian platforms! +// It is known to work well on little-endian platforms that have a small penalty +// for unaligned reads, such as current Intel and AMD moderate-to-high-end CPUs. +// It should work on all 32-bit and 64-bit platforms that allow unaligned reads; +// bug reports are welcome. +// +// By the way, for some hash functions, given strings a and b, the hash +// of a+b is easily derived from the hashes of a and b. This property +// doesn't hold for any hash functions in this file. + +#pragma once + +#include +#include +#include // for size_t. + +namespace Common { + +typedef std::pair uint128; + +inline uint64_t Uint128Low64(const uint128& x) { + return x.first; +} +inline uint64_t Uint128High64(const uint128& x) { + return x.second; +} + +// Hash function for a byte array. +uint64_t CityHash64(const char* buf, size_t len); + +// Hash function for a byte array. For convenience, a 64-bit seed is also +// hashed into the result. +uint64_t CityHash64WithSeed(const char* buf, size_t len, uint64_t seed); + +// Hash function for a byte array. For convenience, two seeds are also +// hashed into the result. +uint64_t CityHash64WithSeeds(const char* buf, size_t len, uint64_t seed0, uint64_t seed1); + +// Hash function for a byte array. +uint128 CityHash128(const char* s, size_t len); + +// Hash function for a byte array. For convenience, a 128-bit seed is also +// hashed into the result. +uint128 CityHash128WithSeed(const char* s, size_t len, uint128 seed); + +// Hash 128 input bits down to 64 bits of output. +// This is intended to be a reasonably good hash function. +inline uint64_t Hash128to64(const uint128& x) { + // Murmur-inspired hashing. + const uint64_t kMul = 0x9ddfea08eb382d69ULL; + uint64_t a = (Uint128Low64(x) ^ Uint128High64(x)) * kMul; + a ^= (a >> 47); + uint64_t b = (Uint128High64(x) ^ a) * kMul; + b ^= (b >> 47); + b *= kMul; + return b; +} + +} // namespace Common diff --git a/src/common/hash.cpp b/src/common/hash.cpp deleted file mode 100644 index a02e9e5b9..000000000 --- a/src/common/hash.cpp +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2015 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#if defined(_MSC_VER) -#include -#endif -#include "common/common_funcs.h" -#include "common/common_types.h" -#include "common/hash.h" - -namespace Common { - -// MurmurHash3 was written by Austin Appleby, and is placed in the public -// domain. The author hereby disclaims copyright to this source code. - -// Block read - if your platform needs to do endian-swapping or can only handle aligned reads, do -// the conversion here -static FORCE_INLINE u64 getblock64(const u64* p, size_t i) { - return p[i]; -} - -// Finalization mix - force all bits of a hash block to avalanche -static FORCE_INLINE u64 fmix64(u64 k) { - k ^= k >> 33; - k *= 0xff51afd7ed558ccdllu; - k ^= k >> 33; - k *= 0xc4ceb9fe1a85ec53llu; - k ^= k >> 33; - - return k; -} - -// This is the 128-bit variant of the MurmurHash3 hash function that is targeted for 64-bit -// platforms (MurmurHash3_x64_128). It was taken from: -// https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp -void MurmurHash3_128(const void* key, size_t len, u32 seed, void* out) { - const u8* data = (const u8*)key; - const size_t nblocks = len / 16; - - u64 h1 = seed; - u64 h2 = seed; - - const u64 c1 = 0x87c37b91114253d5llu; - const u64 c2 = 0x4cf5ad432745937fllu; - - // Body - - const u64* blocks = (const u64*)(data); - - for (size_t i = 0; i < nblocks; i++) { - u64 k1 = getblock64(blocks, i * 2 + 0); - u64 k2 = getblock64(blocks, i * 2 + 1); - - k1 *= c1; - k1 = _rotl64(k1, 31); - k1 *= c2; - h1 ^= k1; - - h1 = _rotl64(h1, 27); - h1 += h2; - h1 = h1 * 5 + 0x52dce729; - - k2 *= c2; - k2 = _rotl64(k2, 33); - k2 *= c1; - h2 ^= k2; - - h2 = _rotl64(h2, 31); - h2 += h1; - h2 = h2 * 5 + 0x38495ab5; - } - - // Tail - - const u8* tail = (const u8*)(data + nblocks * 16); - - u64 k1 = 0; - u64 k2 = 0; - - switch (len & 15) { - case 15: - k2 ^= ((u64)tail[14]) << 48; - case 14: - k2 ^= ((u64)tail[13]) << 40; - case 13: - k2 ^= ((u64)tail[12]) << 32; - case 12: - k2 ^= ((u64)tail[11]) << 24; - case 11: - k2 ^= ((u64)tail[10]) << 16; - case 10: - k2 ^= ((u64)tail[9]) << 8; - case 9: - k2 ^= ((u64)tail[8]) << 0; - k2 *= c2; - k2 = _rotl64(k2, 33); - k2 *= c1; - h2 ^= k2; - - case 8: - k1 ^= ((u64)tail[7]) << 56; - case 7: - k1 ^= ((u64)tail[6]) << 48; - case 6: - k1 ^= ((u64)tail[5]) << 40; - case 5: - k1 ^= ((u64)tail[4]) << 32; - case 4: - k1 ^= ((u64)tail[3]) << 24; - case 3: - k1 ^= ((u64)tail[2]) << 16; - case 2: - k1 ^= ((u64)tail[1]) << 8; - case 1: - k1 ^= ((u64)tail[0]) << 0; - k1 *= c1; - k1 = _rotl64(k1, 31); - k1 *= c2; - h1 ^= k1; - }; - - // Finalization - - h1 ^= len; - h2 ^= len; - - h1 += h2; - h2 += h1; - - h1 = fmix64(h1); - h2 = fmix64(h2); - - h1 += h2; - h2 += h1; - - ((u64*)out)[0] = h1; - ((u64*)out)[1] = h2; -} - -} // namespace Common diff --git a/src/common/hash.h b/src/common/hash.h index ee2560dad..cc3cece68 100644 --- a/src/common/hash.h +++ b/src/common/hash.h @@ -5,12 +5,11 @@ #pragma once #include +#include "common/cityhash.h" #include "common/common_types.h" namespace Common { -void MurmurHash3_128(const void* key, size_t len, u32 seed, void* out); - /** * Computes a 64-bit hash over the specified block of data * @param data Block of data to compute hash over @@ -18,9 +17,20 @@ void MurmurHash3_128(const void* key, size_t len, u32 seed, void* out); * @returns 64-bit hash value that was computed over the data block */ static inline u64 ComputeHash64(const void* data, size_t len) { - u64 res[2]; - MurmurHash3_128(data, len, 0, res); - return res[0]; + return CityHash64(static_cast(data), len); +} + +/** + * Computes a 64-bit hash of a struct. In addition to being POD (trivially copyable and having + * standard layout), it is also critical that either the struct includes no padding, or that any + * padding is initialized to a known value by memsetting the struct to 0 before filling it in. + */ +template +static inline u64 ComputeStructHash64(const T& data) { + static_assert( + std::is_trivially_copyable::value && std::is_standard_layout::value, + "Type passed to ComputeStructHash64 must be trivially copyable and standard layout"); + return ComputeHash64(&data, sizeof(data)); } } // namespace Common diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 6057b183f..54df27193 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,10 +1,6 @@ add_library(core STATIC 3ds.h arm/arm_interface.h - arm/dynarmic/arm_dynarmic.cpp - arm/dynarmic/arm_dynarmic.h - arm/dynarmic/arm_dynarmic_cp15.cpp - arm/dynarmic/arm_dynarmic_cp15.h arm/dyncom/arm_dyncom.cpp arm/dyncom/arm_dyncom.h arm/dyncom/arm_dyncom_dec.cpp @@ -404,7 +400,17 @@ add_library(core STATIC create_target_directory_groups(core) target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core) -target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp dynarmic fmt) +target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp fmt) if (ENABLE_WEB_SERVICE) target_link_libraries(core PUBLIC json-headers web_service) endif() + +if (ARCHITECTURE_x86_64) + target_sources(core PRIVATE + arm/dynarmic/arm_dynarmic.cpp + arm/dynarmic/arm_dynarmic.h + arm/dynarmic/arm_dynarmic_cp15.cpp + arm/dynarmic/arm_dynarmic_cp15.h + ) + target_link_libraries(core PRIVATE dynarmic) +endif() diff --git a/src/core/core.cpp b/src/core/core.cpp index 0c658d1ff..ad7474ff4 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -7,7 +7,9 @@ #include "audio_core/audio_core.h" #include "common/logging/log.h" #include "core/arm/arm_interface.h" +#ifdef ARCHITECTURE_x86_64 #include "core/arm/dynarmic/arm_dynarmic.h" +#endif #include "core/arm/dyncom/arm_dyncom.h" #include "core/core.h" #include "core/core_timing.h" @@ -147,7 +149,12 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { LOG_DEBUG(HW_Memory, "initialized OK"); if (Settings::values.use_cpu_jit) { +#ifdef ARCHITECTURE_x86_64 cpu_core = std::make_unique(USER32MODE); +#else + cpu_core = std::make_unique(USER32MODE); + LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available"); +#endif } else { cpu_core = std::make_unique(USER32MODE); } diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index ccff81a21..95d9624db 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -59,7 +59,9 @@ public: /// Empty placeholder structure for services with no per-session data. The session data classes /// in each service must inherit from this. - struct SessionDataBase {}; + struct SessionDataBase { + virtual ~SessionDataBase() = default; + }; protected: /// Creates the storage for the session data of the service. diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index 478680550..a66a4bc16 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -40,6 +40,7 @@ SharedPtr Process::Create(SharedPtr code_set) { process->codeset = std::move(code_set); process->flags.raw = 0; process->flags.memory_region.Assign(MemoryRegion::APPLICATION); + process->status = ProcessStatus::Created; process_list.push_back(process); return process; @@ -151,6 +152,8 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) { HandleSpecialMapping(vm_manager, mapping); } + status = ProcessStatus::Running; + vm_manager.LogLayout(Log::Level::Debug); Kernel::SetupMainThread(codeset->entrypoint, main_thread_priority, this); } diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index 461391fd3..a21b8e704 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -49,6 +49,8 @@ union ProcessFlags { BitField<12, 1, u16> loaded_high; ///< Application loaded high (not at 0x00100000). }; +enum class ProcessStatus { Created, Running, Exited }; + class ResourceLimit; struct MemoryRegionInfo; @@ -122,6 +124,8 @@ public: u16 kernel_version = 0; /// The default CPU for this process, threads are scheduled on this cpu by default. u8 ideal_processor = 0; + /// Current status of the process + ProcessStatus status; /// The id of this process u32 process_id = next_process_id++; diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 06c905284..7f219ffde 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -143,6 +143,36 @@ static ResultCode ControlMemory(u32* out_addr, u32 operation, u32 addr0, u32 add return RESULT_SUCCESS; } +static void ExitProcess() { + LOG_INFO(Kernel_SVC, "Process %u exiting", g_current_process->process_id); + + ASSERT_MSG(g_current_process->status == ProcessStatus::Running, "Process has already exited"); + + g_current_process->status = ProcessStatus::Exited; + + // Stop all the process threads that are currently waiting for objects. + auto& thread_list = GetThreadList(); + for (auto& thread : thread_list) { + if (thread->owner_process != g_current_process) + continue; + + if (thread == GetCurrentThread()) + continue; + + // TODO(Subv): When are the other running/ready threads terminated? + ASSERT_MSG(thread->status == THREADSTATUS_WAIT_SYNCH_ANY || + thread->status == THREADSTATUS_WAIT_SYNCH_ALL, + "Exiting processes with non-waiting threads is currently unimplemented"); + + thread->Stop(); + } + + // Kill the current thread + GetCurrentThread()->Stop(); + + Core::System::GetInstance().PrepareReschedule(); +} + /// Maps a memory block to specified address static ResultCode MapMemoryBlock(Handle handle, u32 addr, u32 permissions, u32 other_permissions) { LOG_TRACE(Kernel_SVC, @@ -685,7 +715,7 @@ static ResultCode GetResourceLimitLimitValues(VAddr values, Handle resource_limi for (unsigned int i = 0; i < name_count; ++i) { u32 name = Memory::Read32(names + i * sizeof(u32)); - s64 value = resource_limit->GetMaxResourceValue(names); + s64 value = resource_limit->GetMaxResourceValue(name); Memory::Write64(values + i * sizeof(u64), value); } @@ -1232,7 +1262,7 @@ static const FunctionDef SVC_Table[] = { {0x00, nullptr, "Unknown"}, {0x01, HLE::Wrap, "ControlMemory"}, {0x02, HLE::Wrap, "QueryMemory"}, - {0x03, nullptr, "ExitProcess"}, + {0x03, ExitProcess, "ExitProcess"}, {0x04, nullptr, "GetProcessAffinityMask"}, {0x05, nullptr, "SetProcessAffinityMask"}, {0x06, nullptr, "GetProcessIdealProcessor"}, @@ -1373,6 +1403,9 @@ void CallSVC(u32 immediate) { // Lock the global kernel mutex when we enter the kernel HLE. std::lock_guard lock(HLE::g_hle_lock); + ASSERT_MSG(g_current_process->status == ProcessStatus::Running, + "Running threads from exiting processes is unimplemented"); + const FunctionDef* info = GetSVCInfo(immediate); if (info) { if (info->func) { diff --git a/src/core/hle/service/gsp/gsp_gpu.cpp b/src/core/hle/service/gsp/gsp_gpu.cpp index bb3cedbe3..3a919e87e 100644 --- a/src/core/hle/service/gsp/gsp_gpu.cpp +++ b/src/core/hle/service/gsp/gsp_gpu.cpp @@ -9,7 +9,6 @@ #include "core/core.h" #include "core/hle/ipc.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/event.h" #include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/shared_memory.h" #include "core/hle/result.h" @@ -51,6 +50,20 @@ constexpr ResultCode ERR_REGS_INVALID_SIZE(ErrorDescription::InvalidSize, ErrorM ErrorSummary::InvalidArgument, ErrorLevel::Usage); // 0xE0E02BEC +/// Maximum number of threads that can be registered at the same time in the GSP module. +constexpr u32 MaxGSPThreads = 4; + +/// Thread ids currently in use by the sessions connected to the GSPGPU service. +static std::array used_thread_ids = {false, false, false, false}; + +static u32 GetUnusedThreadId() { + for (u32 id = 0; id < MaxGSPThreads; ++id) { + if (!used_thread_ids[id]) + return id; + } + ASSERT_MSG(false, "All GSP threads are in use"); +} + /// Gets a pointer to a thread command buffer in GSP shared memory static inline u8* GetCommandBuffer(Kernel::SharedPtr shared_memory, u32 thread_id) { @@ -319,12 +332,16 @@ void GSP_GPU::RegisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x13, 1, 2); u32 flags = rp.Pop(); - interrupt_event = rp.PopObject(); + auto interrupt_event = rp.PopObject(); // TODO(mailwl): return right error code instead assert ASSERT_MSG((interrupt_event != nullptr), "handle is not valid!"); interrupt_event->name = "GSP_GSP_GPU::interrupt_event"; + SessionData* session_data = GetSessionData(ctx.Session()); + session_data->interrupt_event = std::move(interrupt_event); + session_data->registered = true; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); if (first_initialization) { @@ -335,25 +352,60 @@ void GSP_GPU::RegisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } - rb.Push(thread_id); + rb.Push(session_data->thread_id); rb.PushCopyObjects(shared_memory); - thread_id++; - interrupt_event->Signal(); // TODO(bunnei): Is this correct? - - LOG_WARNING(Service_GSP, "called, flags=0x%08X", flags); + LOG_DEBUG(Service_GSP, "called, flags=0x%08X", flags); } void GSP_GPU::UnregisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x14, 0, 0); - thread_id = 0; - interrupt_event = nullptr; + SessionData* session_data = GetSessionData(ctx.Session()); + session_data->interrupt_event = nullptr; + session_data->registered = false; IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_GSP, "(STUBBED) called"); + LOG_DEBUG(Service_GSP, "called"); +} + +void GSP_GPU::SignalInterruptForThread(InterruptId interrupt_id, u32 thread_id) { + SessionData* session_data = FindRegisteredThreadData(thread_id); + if (session_data == nullptr) + return; + + auto interrupt_event = session_data->interrupt_event; + if (interrupt_event == nullptr) { + LOG_WARNING(Service_GSP, "cannot synchronize until GSP event has been created!"); + return; + } + InterruptRelayQueue* interrupt_relay_queue = GetInterruptRelayQueue(shared_memory, thread_id); + u8 next = interrupt_relay_queue->index; + next += interrupt_relay_queue->number_interrupts; + next = next % 0x34; // 0x34 is the number of interrupt slots + + interrupt_relay_queue->number_interrupts += 1; + + interrupt_relay_queue->slot[next] = interrupt_id; + interrupt_relay_queue->error_code = 0x0; // No error + + // Update framebuffer information if requested + // TODO(yuriks): Confirm where this code should be called. It is definitely updated without + // executing any GSP commands, only waiting on the event. + // TODO(Subv): The real GSP module triggers PDC0 after updating both the top and bottom + // screen, it is currently unknown what PDC1 does. + int screen_id = + (interrupt_id == InterruptId::PDC0) ? 0 : (interrupt_id == InterruptId::PDC1) ? 1 : -1; + if (screen_id != -1) { + FrameBufferUpdate* info = GetFrameBufferInfo(thread_id, screen_id); + if (info->is_dirty) { + GSP::SetBufferSwap(screen_id, info->framebuffer_info[info->index]); + info->is_dirty.Assign(false); + } + } + interrupt_event->Signal(); } /** @@ -363,43 +415,26 @@ void GSP_GPU::UnregisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) { * @todo This probably does not belong in the GSP module, instead move to video_core */ void GSP_GPU::SignalInterrupt(InterruptId interrupt_id) { - if (!gpu_right_acquired) { - return; - } - if (nullptr == interrupt_event) { - LOG_WARNING(Service_GSP, "cannot synchronize until GSP event has been created!"); - return; - } if (nullptr == shared_memory) { LOG_WARNING(Service_GSP, "cannot synchronize until GSP shared memory has been created!"); return; } - for (int thread_id = 0; thread_id < 0x4; ++thread_id) { - InterruptRelayQueue* interrupt_relay_queue = - GetInterruptRelayQueue(shared_memory, thread_id); - u8 next = interrupt_relay_queue->index; - next += interrupt_relay_queue->number_interrupts; - next = next % 0x34; // 0x34 is the number of interrupt slots - interrupt_relay_queue->number_interrupts += 1; - - interrupt_relay_queue->slot[next] = interrupt_id; - interrupt_relay_queue->error_code = 0x0; // No error - - // Update framebuffer information if requested - // TODO(yuriks): Confirm where this code should be called. It is definitely updated without - // executing any GSP commands, only waiting on the event. - int screen_id = - (interrupt_id == InterruptId::PDC0) ? 0 : (interrupt_id == InterruptId::PDC1) ? 1 : -1; - if (screen_id != -1) { - FrameBufferUpdate* info = GetFrameBufferInfo(thread_id, screen_id); - if (info->is_dirty) { - GSP::SetBufferSwap(screen_id, info->framebuffer_info[info->index]); - info->is_dirty.Assign(false); - } + // The PDC0 and PDC1 interrupts are fired even if the GPU right hasn't been acquired. + // Normal interrupts are only signaled for the active thread (ie, the thread that has the GPU + // right), but the PDC0/1 interrupts are signaled for every registered thread. + if (interrupt_id == InterruptId::PDC0 || interrupt_id == InterruptId::PDC1) { + for (u32 thread_id = 0; thread_id < MaxGSPThreads; ++thread_id) { + SignalInterruptForThread(interrupt_id, thread_id); } + return; } - interrupt_event->Signal(); + + // For normal interrupts, don't do anything if no process has acquired the GPU right. + if (active_thread_id == -1) + return; + + SignalInterruptForThread(interrupt_id, active_thread_id); } MICROPROFILE_DEFINE(GPU_GSP_DMA, "GPU", "GSP DMA", MP_RGB(100, 0, 255)); @@ -622,18 +657,34 @@ void GSP_GPU::AcquireRight(Kernel::HLERequestContext& ctx) { u32 flag = rp.Pop(); auto process = rp.PopObject(); - gpu_right_acquired = true; + SessionData* session_data = GetSessionData(ctx.Session()); + + LOG_WARNING(Service_GSP, "called flag=%08X process=%u thread_id=%u", flag, process->process_id, + session_data->thread_id); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_GSP, "called flag=%08X process=%u", flag, process->process_id); + if (active_thread_id == session_data->thread_id) { + rb.Push(ResultCode(ErrorDescription::AlreadyDone, ErrorModule::GX, ErrorSummary::Success, + ErrorLevel::Success)); + return; + } + + // TODO(Subv): This case should put the caller thread to sleep until the right is released. + ASSERT_MSG(active_thread_id == -1, "GPU right has already been acquired"); + + active_thread_id = session_data->thread_id; + + rb.Push(RESULT_SUCCESS); } void GSP_GPU::ReleaseRight(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x17, 0, 0); - gpu_right_acquired = false; + SessionData* session_data = GetSessionData(ctx.Session()); + ASSERT_MSG(active_thread_id == session_data->thread_id, + "Wrong thread tried to release GPU right"); + active_thread_id = -1; IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); @@ -655,6 +706,17 @@ void GSP_GPU::StoreDataCache(Kernel::HLERequestContext& ctx) { size, process->process_id); } +SessionData* GSP_GPU::FindRegisteredThreadData(u32 thread_id) { + for (auto& session_info : connected_sessions) { + SessionData* data = static_cast(session_info.data.get()); + if (!data->registered) + continue; + if (data->thread_id == thread_id) + return data; + } + return nullptr; +} + GSP_GPU::GSP_GPU() : ServiceFramework("gsp::Gpu", 2) { static const FunctionInfo functions[] = { {0x00010082, &GSP_GPU::WriteHWRegs, "WriteHWRegs"}, @@ -691,17 +753,26 @@ GSP_GPU::GSP_GPU() : ServiceFramework("gsp::Gpu", 2) { }; RegisterHandlers(functions); - interrupt_event = nullptr; - using Kernel::MemoryPermission; shared_memory = Kernel::SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite, 0, Kernel::MemoryRegion::BASE, "GSP:SharedMemory"); - thread_id = 0; - gpu_right_acquired = false; first_initialization = true; }; +SessionData::SessionData() { + // Assign a new thread id to this session when it connects. Note: In the real GSP service this + // is done through a real thread (svcCreateThread) but we have to simulate it since our HLE + // services don't have threads. + thread_id = GetUnusedThreadId(); + used_thread_ids[thread_id] = true; +} + +SessionData::~SessionData() { + // Free the thread id slot so that other sessions can use it. + used_thread_ids[thread_id] = false; +} + } // namespace GSP } // namespace Service diff --git a/src/core/hle/service/gsp/gsp_gpu.h b/src/core/hle/service/gsp/gsp_gpu.h index 98756a8ff..214d96c0e 100644 --- a/src/core/hle/service/gsp/gsp_gpu.h +++ b/src/core/hle/service/gsp/gsp_gpu.h @@ -8,12 +8,12 @@ #include #include "common/bit_field.h" #include "common/common_types.h" +#include "core/hle/kernel/event.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/result.h" #include "core/hle/service/service.h" namespace Kernel { -class Event; class SharedMemory; } // namespace Kernel @@ -179,7 +179,19 @@ struct CommandBuffer { }; static_assert(sizeof(CommandBuffer) == 0x200, "CommandBuffer struct has incorrect size"); -class GSP_GPU final : public ServiceFramework { +struct SessionData : public Kernel::SessionRequestHandler::SessionDataBase { + SessionData(); + ~SessionData(); + + /// Event triggered when GSP interrupt has been signalled + Kernel::SharedPtr interrupt_event; + /// Thread index into interrupt relay queue + u32 thread_id; + /// Whether RegisterInterruptRelayQueue was called for this session + bool registered = false; +}; + +class GSP_GPU final : public ServiceFramework { public: GSP_GPU(); ~GSP_GPU() = default; @@ -201,6 +213,14 @@ public: FrameBufferUpdate* GetFrameBufferInfo(u32 thread_id, u32 screen_index); private: + /** + * Signals that the specified interrupt type has occurred to userland code for the specified GSP + * thread id. + * @param interrupt_id ID of interrupt that is being signalled. + * @param thread_id GSP thread that will receive the interrupt. + */ + void SignalInterruptForThread(InterruptId interrupt_id, u32 thread_id); + /** * GSP_GPU::WriteHWRegs service function * @@ -351,14 +371,15 @@ private: */ void StoreDataCache(Kernel::HLERequestContext& ctx); - /// Event triggered when GSP interrupt has been signalled - Kernel::SharedPtr interrupt_event; - /// GSP shared memoryings - Kernel::SharedPtr shared_memory; - /// Thread index into interrupt relay queue - u32 thread_id = 0; + /// Returns the session data for the specified registered thread id, or nullptr if not found. + SessionData* FindRegisteredThreadData(u32 thread_id); + + /// GSP shared memory + Kernel::SharedPtr shared_memory; + + /// Thread id that currently has GPU rights or -1 if none. + int active_thread_id = -1; - bool gpu_right_acquired = false; bool first_initialization = true; }; diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 9074cd2b0..1d553864b 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -3,15 +3,12 @@ // Refer to the license.txt file included. #include -#include #include -#include #include "common/logging/log.h" #include "core/3ds.h" #include "core/core.h" #include "core/core_timing.h" -#include "core/frontend/input.h" -#include "core/hle/ipc.h" +#include "core/hle/ipc_helpers.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/shared_memory.h" @@ -23,27 +20,7 @@ namespace Service { namespace HID { -// Handle to shared memory region designated to HID_User service -static Kernel::SharedPtr shared_mem; - -// Event handles -static Kernel::SharedPtr event_pad_or_touch_1; -static Kernel::SharedPtr event_pad_or_touch_2; -static Kernel::SharedPtr event_accelerometer; -static Kernel::SharedPtr event_gyroscope; -static Kernel::SharedPtr event_debug_pad; - -static u32 next_pad_index; -static u32 next_touch_index; -static u32 next_accelerometer_index; -static u32 next_gyroscope_index; - -static int enable_accelerometer_count; // positive means enabled -static int enable_gyroscope_count; // positive means enabled - -static CoreTiming::EventType* pad_update_event; -static CoreTiming::EventType* accelerometer_update_event; -static CoreTiming::EventType* gyroscope_update_event; +static std::weak_ptr current_module; // Updating period for each HID device. These empirical values are measured from a 11.2 3DS. constexpr u64 pad_update_ticks = BASE_CLOCK_RATE_ARM11 / 234; @@ -53,13 +30,6 @@ constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE_ARM11 / 101; constexpr float accelerometer_coef = 512.0f; // measured from hw test result constexpr float gyroscope_coef = 14.375f; // got from hwtest GetGyroscopeLowRawToDpsCoefficient call -static std::atomic is_device_reload_pending; -static std::array, Settings::NativeButton::NUM_BUTTONS_HID> - buttons; -static std::unique_ptr circle_pad; -static std::unique_ptr motion_device; -static std::unique_ptr touch_device; - DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) { // 30 degree and 60 degree are angular thresholds for directions constexpr float TAN30 = 0.577350269f; @@ -89,7 +59,7 @@ DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) { return state; } -static void LoadInputDevices() { +void Module::LoadInputDevices() { std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN, Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END, buttons.begin(), Input::CreateDevice); @@ -99,16 +69,7 @@ static void LoadInputDevices() { touch_device = Input::CreateDevice(Settings::values.touch_device); } -static void UnloadInputDevices() { - for (auto& button : buttons) { - button.reset(); - } - circle_pad.reset(); - motion_device.reset(); - touch_device.reset(); -} - -static void UpdatePadCallback(u64 userdata, int cycles_late) { +void Module::UpdatePadCallback(u64 userdata, int cycles_late) { SharedMem* mem = reinterpret_cast(shared_mem->GetPointer()); if (is_device_reload_pending.exchange(false)) @@ -198,7 +159,7 @@ static void UpdatePadCallback(u64 userdata, int cycles_late) { CoreTiming::ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event); } -static void UpdateAccelerometerCallback(u64 userdata, int cycles_late) { +void Module::UpdateAccelerometerCallback(u64 userdata, int cycles_late) { SharedMem* mem = reinterpret_cast(shared_mem->GetPointer()); mem->accelerometer.index = next_accelerometer_index; @@ -240,7 +201,7 @@ static void UpdateAccelerometerCallback(u64 userdata, int cycles_late) { CoreTiming::ScheduleEvent(accelerometer_update_ticks - cycles_late, accelerometer_update_event); } -static void UpdateGyroscopeCallback(u64 userdata, int cycles_late) { +void Module::UpdateGyroscopeCallback(u64 userdata, int cycles_late) { SharedMem* mem = reinterpret_cast(shared_mem->GetPointer()); mem->gyroscope.index = next_gyroscope_index; @@ -273,134 +234,122 @@ static void UpdateGyroscopeCallback(u64 userdata, int cycles_late) { CoreTiming::ScheduleEvent(gyroscope_update_ticks - cycles_late, gyroscope_update_event); } -void GetIPCHandles(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - - cmd_buff[1] = 0; // No error - cmd_buff[2] = 0x14000000; // IPC Command Structure translate-header - // TODO(yuriks): Return error from SendSyncRequest is this fails (part of IPC marshalling) - cmd_buff[3] = Kernel::g_handle_table.Create(Service::HID::shared_mem).Unwrap(); - cmd_buff[4] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_1).Unwrap(); - cmd_buff[5] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_2).Unwrap(); - cmd_buff[6] = Kernel::g_handle_table.Create(Service::HID::event_accelerometer).Unwrap(); - cmd_buff[7] = Kernel::g_handle_table.Create(Service::HID::event_gyroscope).Unwrap(); - cmd_buff[8] = Kernel::g_handle_table.Create(Service::HID::event_debug_pad).Unwrap(); +void Module::Interface::GetIPCHandles(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx, 0xA, 0, 0}; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 7); + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(hid->shared_mem, hid->event_pad_or_touch_1, hid->event_pad_or_touch_2, + hid->event_accelerometer, hid->event_gyroscope, hid->event_debug_pad); } -void EnableAccelerometer(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); +void Module::Interface::EnableAccelerometer(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx, 0x11, 0, 0}; - ++enable_accelerometer_count; + ++hid->enable_accelerometer_count; // Schedules the accelerometer update event if the accelerometer was just enabled - if (enable_accelerometer_count == 1) { - CoreTiming::ScheduleEvent(accelerometer_update_ticks, accelerometer_update_event); + if (hid->enable_accelerometer_count == 1) { + CoreTiming::ScheduleEvent(accelerometer_update_ticks, hid->accelerometer_update_event); } - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_HID, "called"); } -void DisableAccelerometer(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); +void Module::Interface::DisableAccelerometer(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx, 0x12, 0, 0}; - --enable_accelerometer_count; + --hid->enable_accelerometer_count; // Unschedules the accelerometer update event if the accelerometer was just disabled - if (enable_accelerometer_count == 0) { - CoreTiming::UnscheduleEvent(accelerometer_update_event, 0); + if (hid->enable_accelerometer_count == 0) { + CoreTiming::UnscheduleEvent(hid->accelerometer_update_event, 0); } - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_HID, "called"); } -void EnableGyroscopeLow(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); +void Module::Interface::EnableGyroscopeLow(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx, 0x13, 0, 0}; - ++enable_gyroscope_count; + ++hid->enable_gyroscope_count; // Schedules the gyroscope update event if the gyroscope was just enabled - if (enable_gyroscope_count == 1) { - CoreTiming::ScheduleEvent(gyroscope_update_ticks, gyroscope_update_event); + if (hid->enable_gyroscope_count == 1) { + CoreTiming::ScheduleEvent(gyroscope_update_ticks, hid->gyroscope_update_event); } - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_HID, "called"); } -void DisableGyroscopeLow(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); +void Module::Interface::DisableGyroscopeLow(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx, 0x14, 0, 0}; - --enable_gyroscope_count; + --hid->enable_gyroscope_count; // Unschedules the gyroscope update event if the gyroscope was just disabled - if (enable_gyroscope_count == 0) { - CoreTiming::UnscheduleEvent(gyroscope_update_event, 0); + if (hid->enable_gyroscope_count == 0) { + CoreTiming::UnscheduleEvent(hid->gyroscope_update_event, 0); } - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_HID, "called"); } -void GetGyroscopeLowRawToDpsCoefficient(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); +void Module::Interface::GetGyroscopeLowRawToDpsCoefficient(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx, 0x15, 0, 0}; - cmd_buff[1] = RESULT_SUCCESS.raw; - - f32 coef = gyroscope_coef; - memcpy(&cmd_buff[2], &coef, 4); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.PushRaw(gyroscope_coef); } -void GetGyroscopeLowCalibrateParam(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); +void Module::Interface::GetGyroscopeLowCalibrateParam(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx, 0x16, 0, 0}; - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(6, 0); + rb.Push(RESULT_SUCCESS); const s16 param_unit = 6700; // an approximate value taken from hw GyroscopeCalibrateParam param = { {0, param_unit, -param_unit}, {0, param_unit, -param_unit}, {0, param_unit, -param_unit}, }; - memcpy(&cmd_buff[2], ¶m, sizeof(param)); + rb.PushRaw(param); LOG_WARNING(Service_HID, "(STUBBED) called"); } -void GetSoundVolume(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); +void Module::Interface::GetSoundVolume(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx, 0x17, 0, 0}; const u8 volume = 0x3F; // TODO(purpasmart): Find out if this is the max value for the volume - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = volume; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(volume); LOG_WARNING(Service_HID, "(STUBBED) called"); } -void Init() { +Module::Interface::Interface(std::shared_ptr hid, const char* name, u32 max_session) + : ServiceFramework(name, max_session), hid(std::move(hid)) {} + +Module::Module() { using namespace Kernel; - AddService(new HID_U_Interface); - AddService(new HID_SPVR_Interface); - - is_device_reload_pending.store(true); - - using Kernel::MemoryPermission; shared_mem = SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, MemoryPermission::Read, - 0, Kernel::MemoryRegion::BASE, "HID:SharedMemory"); - - next_pad_index = 0; - next_touch_index = 0; - next_accelerometer_index = 0; - next_gyroscope_index = 0; - - enable_accelerometer_count = 0; - enable_gyroscope_count = 0; + 0, MemoryRegion::BASE, "HID:SharedMemory"); // Create event handles event_pad_or_touch_1 = Event::Create(ResetType::OneShot, "HID:EventPadOrTouch1"); @@ -410,27 +359,35 @@ void Init() { event_debug_pad = Event::Create(ResetType::OneShot, "HID:EventDebugPad"); // Register update callbacks - pad_update_event = CoreTiming::RegisterEvent("HID::UpdatePadCallback", UpdatePadCallback); - accelerometer_update_event = - CoreTiming::RegisterEvent("HID::UpdateAccelerometerCallback", UpdateAccelerometerCallback); - gyroscope_update_event = - CoreTiming::RegisterEvent("HID::UpdateGyroscopeCallback", UpdateGyroscopeCallback); + pad_update_event = + CoreTiming::RegisterEvent("HID::UpdatePadCallback", [this](u64 userdata, int cycles_late) { + UpdatePadCallback(userdata, cycles_late); + }); + accelerometer_update_event = CoreTiming::RegisterEvent( + "HID::UpdateAccelerometerCallback", [this](u64 userdata, int cycles_late) { + UpdateAccelerometerCallback(userdata, cycles_late); + }); + gyroscope_update_event = CoreTiming::RegisterEvent( + "HID::UpdateGyroscopeCallback", + [this](u64 userdata, int cycles_late) { UpdateGyroscopeCallback(userdata, cycles_late); }); CoreTiming::ScheduleEvent(pad_update_ticks, pad_update_event); } -void Shutdown() { - shared_mem = nullptr; - event_pad_or_touch_1 = nullptr; - event_pad_or_touch_2 = nullptr; - event_accelerometer = nullptr; - event_gyroscope = nullptr; - event_debug_pad = nullptr; - UnloadInputDevices(); +void Module::ReloadInputDevices() { + is_device_reload_pending.store(true); } void ReloadInputDevices() { - is_device_reload_pending.store(true); + if (auto hid = current_module.lock()) + hid->ReloadInputDevices(); +} + +void InstallInterfaces(SM::ServiceManager& service_manager) { + auto hid = std::make_shared(); + std::make_shared(hid)->InstallAsService(service_manager); + std::make_shared(hid)->InstallAsService(service_manager); + current_module = hid; } } // namespace HID diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index ef25926b5..1518111d3 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -5,17 +5,29 @@ #pragma once #include +#include #ifndef _MSC_VER #include #endif +#include #include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" +#include "core/frontend/input.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/service/service.h" #include "core/settings.h" -namespace Service { +namespace Kernel { +class Event; +class SharedMemory; +} -class Interface; +namespace CoreTiming { +class EventType; +}; + +namespace Service { namespace HID { @@ -186,93 +198,140 @@ struct DirectionState { /// Translates analog stick axes to directions. This is exposed for ir_rst module to use. DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y); -/** - * HID::GetIPCHandles service function - * Inputs: - * None - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2 : IPC Command Structure translate-header - * 3 : Handle to HID shared memory - * 4 : Event signaled by HID - * 5 : Event signaled by HID - * 6 : Event signaled by HID - * 7 : Gyroscope event - * 8 : Event signaled by HID - */ -void GetIPCHandles(Interface* self); +class Module final { +public: + Module(); -/** - * HID::EnableAccelerometer service function - * Inputs: - * None - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -void EnableAccelerometer(Interface* self); + class Interface : public ServiceFramework { + public: + Interface(std::shared_ptr hid, const char* name, u32 max_session); -/** - * HID::DisableAccelerometer service function - * Inputs: - * None - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -void DisableAccelerometer(Interface* self); + protected: + /** + * HID::GetIPCHandles service function + * Inputs: + * None + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : IPC Command Structure translate-header + * 3 : Handle to HID shared memory + * 4 : Event signaled by HID + * 5 : Event signaled by HID + * 6 : Event signaled by HID + * 7 : Gyroscope event + * 8 : Event signaled by HID + */ + void GetIPCHandles(Kernel::HLERequestContext& ctx); -/** - * HID::EnableGyroscopeLow service function - * Inputs: - * None - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -void EnableGyroscopeLow(Interface* self); + /** + * HID::EnableAccelerometer service function + * Inputs: + * None + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ + void EnableAccelerometer(Kernel::HLERequestContext& ctx); -/** - * HID::DisableGyroscopeLow service function - * Inputs: - * None - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - */ -void DisableGyroscopeLow(Interface* self); + /** + * HID::DisableAccelerometer service function + * Inputs: + * None + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ + void DisableAccelerometer(Kernel::HLERequestContext& ctx); -/** - * HID::GetSoundVolume service function - * Inputs: - * None - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2 : u8 output value - */ -void GetSoundVolume(Interface* self); + /** + * HID::EnableGyroscopeLow service function + * Inputs: + * None + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ + void EnableGyroscopeLow(Kernel::HLERequestContext& ctx); -/** - * HID::GetGyroscopeLowRawToDpsCoefficient service function - * Inputs: - * None - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2 : float output value - */ -void GetGyroscopeLowRawToDpsCoefficient(Service::Interface* self); + /** + * HID::DisableGyroscopeLow service function + * Inputs: + * None + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ + void DisableGyroscopeLow(Kernel::HLERequestContext& ctx); -/** - * HID::GetGyroscopeLowCalibrateParam service function - * Inputs: - * None - * Outputs: - * 1 : Result of function, 0 on success, otherwise error code - * 2~6 (18 bytes) : struct GyroscopeCalibrateParam - */ -void GetGyroscopeLowCalibrateParam(Service::Interface* self); + /** + * HID::GetSoundVolume service function + * Inputs: + * None + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : u8 output value + */ + void GetSoundVolume(Kernel::HLERequestContext& ctx); -/// Initialize HID service -void Init(); + /** + * HID::GetGyroscopeLowRawToDpsCoefficient service function + * Inputs: + * None + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : float output value + */ + void GetGyroscopeLowRawToDpsCoefficient(Kernel::HLERequestContext& ctx); -/// Shutdown HID service -void Shutdown(); + /** + * HID::GetGyroscopeLowCalibrateParam service function + * Inputs: + * None + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2~6 (18 bytes) : struct GyroscopeCalibrateParam + */ + void GetGyroscopeLowCalibrateParam(Kernel::HLERequestContext& ctx); + + private: + std::shared_ptr hid; + }; + + void ReloadInputDevices(); + +private: + void LoadInputDevices(); + void UpdatePadCallback(u64 userdata, int cycles_late); + void UpdateAccelerometerCallback(u64 userdata, int cycles_late); + void UpdateGyroscopeCallback(u64 userdata, int cycles_late); + + // Handle to shared memory region designated to HID_User service + Kernel::SharedPtr shared_mem; + + // Event handles + Kernel::SharedPtr event_pad_or_touch_1; + Kernel::SharedPtr event_pad_or_touch_2; + Kernel::SharedPtr event_accelerometer; + Kernel::SharedPtr event_gyroscope; + Kernel::SharedPtr event_debug_pad; + + u32 next_pad_index = 0; + u32 next_touch_index = 0; + u32 next_accelerometer_index = 0; + u32 next_gyroscope_index = 0; + + int enable_accelerometer_count = 0; // positive means enabled + int enable_gyroscope_count = 0; // positive means enabled + + CoreTiming::EventType* pad_update_event; + CoreTiming::EventType* accelerometer_update_event; + CoreTiming::EventType* gyroscope_update_event; + + std::atomic is_device_reload_pending{true}; + std::array, Settings::NativeButton::NUM_BUTTONS_HID> + buttons; + std::unique_ptr circle_pad; + std::unique_ptr motion_device; + std::unique_ptr touch_device; +}; + +void InstallInterfaces(SM::ServiceManager& service_manager); /// Reload input devices. Used when input configuration changed void ReloadInputDevices(); diff --git a/src/core/hle/service/hid/hid_spvr.cpp b/src/core/hle/service/hid/hid_spvr.cpp index 09007e304..3d5900fd9 100644 --- a/src/core/hle/service/hid/hid_spvr.cpp +++ b/src/core/hle/service/hid/hid_spvr.cpp @@ -2,27 +2,26 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "core/hle/service/hid/hid.h" #include "core/hle/service/hid/hid_spvr.h" namespace Service { namespace HID { -const Interface::FunctionInfo FunctionTable[] = { - {0x000A0000, GetIPCHandles, "GetIPCHandles"}, - {0x000B0000, nullptr, "StartAnalogStickCalibration"}, - {0x000E0000, nullptr, "GetAnalogStickCalibrateParam"}, - {0x00110000, EnableAccelerometer, "EnableAccelerometer"}, - {0x00120000, DisableAccelerometer, "DisableAccelerometer"}, - {0x00130000, EnableGyroscopeLow, "EnableGyroscopeLow"}, - {0x00140000, DisableGyroscopeLow, "DisableGyroscopeLow"}, - {0x00150000, GetGyroscopeLowRawToDpsCoefficient, "GetGyroscopeLowRawToDpsCoefficient"}, - {0x00160000, GetGyroscopeLowCalibrateParam, "GetGyroscopeLowCalibrateParam"}, - {0x00170000, GetSoundVolume, "GetSoundVolume"}, -}; - -HID_SPVR_Interface::HID_SPVR_Interface() { - Register(FunctionTable); +Spvr::Spvr(std::shared_ptr hid) : Module::Interface(std::move(hid), "hid:SPVR", 6) { + static const FunctionInfo functions[] = { + {0x000A0000, &Spvr::GetIPCHandles, "GetIPCHandles"}, + {0x000B0000, nullptr, "StartAnalogStickCalibration"}, + {0x000E0000, nullptr, "GetAnalogStickCalibrateParam"}, + {0x00110000, &Spvr::EnableAccelerometer, "EnableAccelerometer"}, + {0x00120000, &Spvr::DisableAccelerometer, "DisableAccelerometer"}, + {0x00130000, &Spvr::EnableGyroscopeLow, "EnableGyroscopeLow"}, + {0x00140000, &Spvr::DisableGyroscopeLow, "DisableGyroscopeLow"}, + {0x00150000, &Spvr::GetGyroscopeLowRawToDpsCoefficient, + "GetGyroscopeLowRawToDpsCoefficient"}, + {0x00160000, &Spvr::GetGyroscopeLowCalibrateParam, "GetGyroscopeLowCalibrateParam"}, + {0x00170000, &Spvr::GetSoundVolume, "GetSoundVolume"}, + }; + RegisterHandlers(functions); } } // namespace HID diff --git a/src/core/hle/service/hid/hid_spvr.h b/src/core/hle/service/hid/hid_spvr.h index ba61583d2..c0d831146 100644 --- a/src/core/hle/service/hid/hid_spvr.h +++ b/src/core/hle/service/hid/hid_spvr.h @@ -4,19 +4,15 @@ #pragma once -#include "core/hle/service/service.h" +#include "core/hle/service/hid/hid.h" namespace Service { namespace HID { -class HID_SPVR_Interface : public Service::Interface { +class Spvr final : public Module::Interface { public: - HID_SPVR_Interface(); - - std::string GetPortName() const override { - return "hid:SPVR"; - } + explicit Spvr(std::shared_ptr hid); }; } // namespace HID -} // namespace Service \ No newline at end of file +} // namespace Service diff --git a/src/core/hle/service/hid/hid_user.cpp b/src/core/hle/service/hid/hid_user.cpp index 42591543c..88213dab2 100644 --- a/src/core/hle/service/hid/hid_user.cpp +++ b/src/core/hle/service/hid/hid_user.cpp @@ -2,27 +2,26 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "core/hle/service/hid/hid.h" #include "core/hle/service/hid/hid_user.h" namespace Service { namespace HID { -const Interface::FunctionInfo FunctionTable[] = { - {0x000A0000, GetIPCHandles, "GetIPCHandles"}, - {0x000B0000, nullptr, "StartAnalogStickCalibration"}, - {0x000E0000, nullptr, "GetAnalogStickCalibrateParam"}, - {0x00110000, EnableAccelerometer, "EnableAccelerometer"}, - {0x00120000, DisableAccelerometer, "DisableAccelerometer"}, - {0x00130000, EnableGyroscopeLow, "EnableGyroscopeLow"}, - {0x00140000, DisableGyroscopeLow, "DisableGyroscopeLow"}, - {0x00150000, GetGyroscopeLowRawToDpsCoefficient, "GetGyroscopeLowRawToDpsCoefficient"}, - {0x00160000, GetGyroscopeLowCalibrateParam, "GetGyroscopeLowCalibrateParam"}, - {0x00170000, GetSoundVolume, "GetSoundVolume"}, -}; - -HID_U_Interface::HID_U_Interface() { - Register(FunctionTable); +User::User(std::shared_ptr hid) : Module::Interface(std::move(hid), "hid:USER", 6) { + static const FunctionInfo functions[] = { + {0x000A0000, &User::GetIPCHandles, "GetIPCHandles"}, + {0x000B0000, nullptr, "StartAnalogStickCalibration"}, + {0x000E0000, nullptr, "GetAnalogStickCalibrateParam"}, + {0x00110000, &User::EnableAccelerometer, "EnableAccelerometer"}, + {0x00120000, &User::DisableAccelerometer, "DisableAccelerometer"}, + {0x00130000, &User::EnableGyroscopeLow, "EnableGyroscopeLow"}, + {0x00140000, &User::DisableGyroscopeLow, "DisableGyroscopeLow"}, + {0x00150000, &User::GetGyroscopeLowRawToDpsCoefficient, + "GetGyroscopeLowRawToDpsCoefficient"}, + {0x00160000, &User::GetGyroscopeLowCalibrateParam, "GetGyroscopeLowCalibrateParam"}, + {0x00170000, &User::GetSoundVolume, "GetSoundVolume"}, + }; + RegisterHandlers(functions); } } // namespace HID diff --git a/src/core/hle/service/hid/hid_user.h b/src/core/hle/service/hid/hid_user.h index baf7fed79..8e4ca52bb 100644 --- a/src/core/hle/service/hid/hid_user.h +++ b/src/core/hle/service/hid/hid_user.h @@ -4,7 +4,7 @@ #pragma once -#include "core/hle/service/service.h" +#include "core/hle/service/hid/hid.h" // This service is used for interfacing to physical user controls. // Uses include game pad controls, touchscreen, accelerometers, gyroscopes, and debug pad. @@ -12,17 +12,10 @@ namespace Service { namespace HID { -/** - * HID service interface. - */ -class HID_U_Interface : public Service::Interface { +class User final : public Module::Interface { public: - HID_U_Interface(); - - std::string GetPortName() const override { - return "hid:USER"; - } + explicit User(std::shared_ptr hid); }; } // namespace HID -} // namespace Service \ No newline at end of file +} // namespace Service diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 600628234..8794c84db 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -277,7 +277,7 @@ void Init() { DLP::Init(); FRD::Init(); GSP::InstallInterfaces(*SM::g_service_manager); - HID::Init(); + HID::InstallInterfaces(*SM::g_service_manager); IR::InstallInterfaces(*SM::g_service_manager); MVD::Init(); NDM::Init(); @@ -307,7 +307,6 @@ void Shutdown() { NIM::Shutdown(); NEWS::Shutdown(); NDM::Shutdown(); - HID::Shutdown(); FRD::Shutdown(); DLP::Shutdown(); CFG::Shutdown(); diff --git a/src/core/hle/service/y2r_u.cpp b/src/core/hle/service/y2r_u.cpp index 3f1a0c452..786ca1186 100644 --- a/src/core/hle/service/y2r_u.cpp +++ b/src/core/hle/service/y2r_u.cpp @@ -84,86 +84,86 @@ ResultCode ConversionConfiguration::SetStandardCoefficient( } static void SetInputFormat(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1, 1, 0); - conversion.input_format = static_cast(cmd_buff[1]); + conversion.input_format = rp.PopEnum(); - cmd_buff[0] = IPC::MakeHeader(0x1, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_Y2R, "called input_format=%hhu", static_cast(conversion.input_format)); } static void GetInputFormat(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x2, 0, 0); - cmd_buff[0] = IPC::MakeHeader(0x2, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = static_cast(conversion.input_format); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.PushEnum(conversion.input_format); LOG_DEBUG(Service_Y2R, "called input_format=%hhu", static_cast(conversion.input_format)); } static void SetOutputFormat(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x3, 1, 0); - conversion.output_format = static_cast(cmd_buff[1]); + conversion.output_format = rp.PopEnum(); - cmd_buff[0] = IPC::MakeHeader(0x3, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_Y2R, "called output_format=%hhu", static_cast(conversion.output_format)); } static void GetOutputFormat(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x4, 0, 0); - cmd_buff[0] = IPC::MakeHeader(0x4, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = static_cast(conversion.output_format); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.PushEnum(conversion.output_format); LOG_DEBUG(Service_Y2R, "called output_format=%hhu", static_cast(conversion.output_format)); } static void SetRotation(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x5, 1, 0); - conversion.rotation = static_cast(cmd_buff[1]); + conversion.rotation = rp.PopEnum(); - cmd_buff[0] = IPC::MakeHeader(0x5, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_Y2R, "called rotation=%hhu", static_cast(conversion.rotation)); } static void GetRotation(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x6, 0, 0); - cmd_buff[0] = IPC::MakeHeader(0x6, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = static_cast(conversion.rotation); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.PushEnum(conversion.rotation); LOG_DEBUG(Service_Y2R, "called rotation=%hhu", static_cast(conversion.rotation)); } static void SetBlockAlignment(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x7, 1, 0); - conversion.block_alignment = static_cast(cmd_buff[1]); + conversion.block_alignment = rp.PopEnum(); - cmd_buff[0] = IPC::MakeHeader(0x7, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_Y2R, "called block_alignment=%hhu", static_cast(conversion.block_alignment)); } static void GetBlockAlignment(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x8, 0, 0); - cmd_buff[0] = IPC::MakeHeader(0x8, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = static_cast(conversion.block_alignment); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.PushEnum(conversion.block_alignment); LOG_DEBUG(Service_Y2R, "called block_alignment=%hhu", static_cast(conversion.block_alignment)); @@ -177,11 +177,12 @@ static void GetBlockAlignment(Interface* self) { * 1 : Result of function, 0 on success, otherwise error code */ static void SetSpacialDithering(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - spacial_dithering_enabled = cmd_buff[1] & 0xF; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x9, 1, 0); - cmd_buff[0] = IPC::MakeHeader(0x9, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + spacial_dithering_enabled = rp.Pop() & 0xF; + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_WARNING(Service_Y2R, "(STUBBED) called"); } @@ -193,7 +194,9 @@ static void SetSpacialDithering(Interface* self) { * 2 : u8, 0 = Disabled, 1 = Enabled */ static void GetSpacialDithering(Interface* self) { - IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0xA, 2, 0); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xA, 0, 0); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(RESULT_SUCCESS); rb.Push(spacial_dithering_enabled != 0); @@ -208,11 +211,11 @@ static void GetSpacialDithering(Interface* self) { * 1 : Result of function, 0 on success, otherwise error code */ static void SetTemporalDithering(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - temporal_dithering_enabled = cmd_buff[1] & 0xF; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xB, 1, 0); + temporal_dithering_enabled = rp.Pop() & 0xF; - cmd_buff[0] = IPC::MakeHeader(0xB, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_WARNING(Service_Y2R, "(STUBBED) called"); } @@ -224,11 +227,11 @@ static void SetTemporalDithering(Interface* self) { * 2 : u8, 0 = Disabled, 1 = Enabled */ static void GetTemporalDithering(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xC, 0, 0); - cmd_buff[0] = IPC::MakeHeader(0xC, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = temporal_dithering_enabled; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(temporal_dithering_enabled); LOG_WARNING(Service_Y2R, "(STUBBED) called"); } @@ -241,11 +244,11 @@ static void GetTemporalDithering(Interface* self) { * 1 : Result of function, 0 on success, otherwise error code */ static void SetTransferEndInterrupt(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - transfer_end_interrupt_enabled = cmd_buff[1] & 0xf; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xD, 1, 0); + transfer_end_interrupt_enabled = rp.Pop() & 0xF; - cmd_buff[0] = IPC::MakeHeader(0xD, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_WARNING(Service_Y2R, "(STUBBED) called"); } @@ -257,11 +260,11 @@ static void SetTransferEndInterrupt(Interface* self) { * 2 : u8, 0 = Disabled, 1 = Enabled */ static void GetTransferEndInterrupt(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xE, 0, 0); - cmd_buff[0] = IPC::MakeHeader(0xE, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = transfer_end_interrupt_enabled; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(transfer_end_interrupt_enabled); LOG_WARNING(Service_Y2R, "(STUBBED) called"); } @@ -273,18 +276,18 @@ static void GetTransferEndInterrupt(Interface* self) { * 3 : The handle of the completion event */ static void GetTransferEndEvent(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xF, 0, 0); - cmd_buff[0] = IPC::MakeHeader(0xF, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[3] = Kernel::g_handle_table.Create(completion_event).Unwrap(); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(RESULT_SUCCESS); + rb.PushCopyHandles(Kernel::g_handle_table.Create(completion_event).Unwrap()); LOG_DEBUG(Service_Y2R, "called"); } static void SetSendingY(Interface* self) { // The helper should be passed by argument to the function - IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x00100102); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x10, 4, 2); conversion.src_Y.address = rp.Pop(); conversion.src_Y.image_size = rp.Pop(); conversion.src_Y.transfer_unit = rp.Pop(); @@ -302,7 +305,7 @@ static void SetSendingY(Interface* self) { static void SetSendingU(Interface* self) { // The helper should be passed by argument to the function - IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x00110102); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x11, 4, 2); conversion.src_U.address = rp.Pop(); conversion.src_U.image_size = rp.Pop(); conversion.src_U.transfer_unit = rp.Pop(); @@ -319,37 +322,41 @@ static void SetSendingU(Interface* self) { } static void SetSendingV(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + // The helper should be passed by argument to the function + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x12, 4, 2); - conversion.src_V.address = cmd_buff[1]; - conversion.src_V.image_size = cmd_buff[2]; - conversion.src_V.transfer_unit = cmd_buff[3]; - conversion.src_V.gap = cmd_buff[4]; + conversion.src_V.address = rp.Pop(); + conversion.src_V.image_size = rp.Pop(); + conversion.src_V.transfer_unit = rp.Pop(); + conversion.src_V.gap = rp.Pop(); + Kernel::Handle src_process_handle = rp.PopHandle(); - cmd_buff[0] = IPC::MakeHeader(0x12, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, " "src_process_handle=0x%08X", conversion.src_V.image_size, conversion.src_V.transfer_unit, conversion.src_V.gap, - cmd_buff[6]); + static_cast(src_process_handle)); } static void SetSendingYUYV(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + // The helper should be passed by argument to the function + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x13, 4, 2); - conversion.src_YUYV.address = cmd_buff[1]; - conversion.src_YUYV.image_size = cmd_buff[2]; - conversion.src_YUYV.transfer_unit = cmd_buff[3]; - conversion.src_YUYV.gap = cmd_buff[4]; + conversion.src_YUYV.address = rp.Pop(); + conversion.src_YUYV.image_size = rp.Pop(); + conversion.src_YUYV.transfer_unit = rp.Pop(); + conversion.src_YUYV.gap = rp.Pop(); + Kernel::Handle src_process_handle = rp.PopHandle(); - cmd_buff[0] = IPC::MakeHeader(0x13, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, " "src_process_handle=0x%08X", conversion.src_YUYV.image_size, conversion.src_YUYV.transfer_unit, - conversion.src_YUYV.gap, cmd_buff[6]); + conversion.src_YUYV.gap, static_cast(src_process_handle)); } /** @@ -359,11 +366,11 @@ static void SetSendingYUYV(Interface* self) { * 2 : u8, 0 = Not Finished, 1 = Finished */ static void IsFinishedSendingYuv(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x14, 0, 0); - cmd_buff[0] = IPC::MakeHeader(0x14, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 1; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(1); LOG_WARNING(Service_Y2R, "(STUBBED) called"); } @@ -375,11 +382,11 @@ static void IsFinishedSendingYuv(Interface* self) { * 2 : u8, 0 = Not Finished, 1 = Finished */ static void IsFinishedSendingY(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x15, 0, 0); - cmd_buff[0] = IPC::MakeHeader(0x15, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 1; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(1); LOG_WARNING(Service_Y2R, "(STUBBED) called"); } @@ -391,11 +398,11 @@ static void IsFinishedSendingY(Interface* self) { * 2 : u8, 0 = Not Finished, 1 = Finished */ static void IsFinishedSendingU(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x16, 0, 0); - cmd_buff[0] = IPC::MakeHeader(0x16, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 1; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(1); LOG_WARNING(Service_Y2R, "(STUBBED) called"); } @@ -407,30 +414,31 @@ static void IsFinishedSendingU(Interface* self) { * 2 : u8, 0 = Not Finished, 1 = Finished */ static void IsFinishedSendingV(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x17, 0, 0); - cmd_buff[0] = IPC::MakeHeader(0x17, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 1; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(1); LOG_WARNING(Service_Y2R, "(STUBBED) called"); } static void SetReceiving(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x18, 4, 2); - conversion.dst.address = cmd_buff[1]; - conversion.dst.image_size = cmd_buff[2]; - conversion.dst.transfer_unit = cmd_buff[3]; - conversion.dst.gap = cmd_buff[4]; + conversion.dst.address = rp.Pop(); + conversion.dst.image_size = rp.Pop(); + conversion.dst.transfer_unit = rp.Pop(); + conversion.dst.gap = rp.Pop(); + Kernel::Handle dst_process_handle = rp.PopHandle(); - cmd_buff[0] = IPC::MakeHeader(0x18, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, " "dst_process_handle=0x%08X", conversion.dst.image_size, conversion.dst.transfer_unit, conversion.dst.gap, - cmd_buff[6]); + static_cast(dst_process_handle)); } /** @@ -440,65 +448,67 @@ static void SetReceiving(Interface* self) { * 2 : u8, 0 = Not Finished, 1 = Finished */ static void IsFinishedReceiving(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x19, 0, 0); - cmd_buff[0] = IPC::MakeHeader(0x19, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 1; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(1); LOG_WARNING(Service_Y2R, "(STUBBED) called"); } static void SetInputLineWidth(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1A, 1, 0); + u32 input_line_width = rp.Pop(); - cmd_buff[0] = IPC::MakeHeader(0x1A, 1, 0); - cmd_buff[1] = conversion.SetInputLineWidth(cmd_buff[1]).raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(conversion.SetInputLineWidth(input_line_width)); - LOG_DEBUG(Service_Y2R, "called input_line_width=%u", cmd_buff[1]); + LOG_DEBUG(Service_Y2R, "called input_line_width=%u", input_line_width); } static void GetInputLineWidth(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1B, 0, 0); - cmd_buff[0] = IPC::MakeHeader(0x1B, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = conversion.input_line_width; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(conversion.input_line_width); LOG_DEBUG(Service_Y2R, "called input_line_width=%u", conversion.input_line_width); } static void SetInputLines(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1C, 1, 0); + u32 input_lines = rp.Pop(); - cmd_buff[0] = IPC::MakeHeader(0x1C, 1, 0); - cmd_buff[1] = conversion.SetInputLines(cmd_buff[1]).raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(conversion.SetInputLines(input_lines)); - LOG_DEBUG(Service_Y2R, "called input_lines=%u", cmd_buff[1]); + LOG_DEBUG(Service_Y2R, "called input_lines=%u", input_lines); } static void GetInputLines(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1D, 0, 0); - cmd_buff[0] = IPC::MakeHeader(0x1D, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = static_cast(conversion.input_lines); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(static_cast(conversion.input_lines)); LOG_DEBUG(Service_Y2R, "called input_lines=%u", conversion.input_lines); } static void SetCoefficient(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1E, 4, 0); - const u16* coefficients = reinterpret_cast(&cmd_buff[1]); - std::memcpy(conversion.coefficients.data(), coefficients, sizeof(CoefficientSet)); + rp.PopRaw(conversion.coefficients); - cmd_buff[0] = IPC::MakeHeader(0x1E, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_Y2R, "called coefficients=[%hX, %hX, %hX, %hX, %hX, %hX, %hX, %hX]", - coefficients[0], coefficients[1], coefficients[2], coefficients[3], coefficients[4], - coefficients[5], coefficients[6], coefficients[7]); + conversion.coefficients[0], conversion.coefficients[1], conversion.coefficients[2], + conversion.coefficients[3], conversion.coefficients[4], conversion.coefficients[5], + conversion.coefficients[6], conversion.coefficients[7]); } static void GetCoefficient(Interface* self) { @@ -512,12 +522,11 @@ static void GetCoefficient(Interface* self) { } static void SetStandardCoefficient(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x20, 1, 0); + u32 index = rp.Pop(); - u32 index = cmd_buff[1]; - - cmd_buff[0] = IPC::MakeHeader(0x20, 1, 0); - cmd_buff[1] = conversion.SetStandardCoefficient((StandardCoefficient)index).raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(conversion.SetStandardCoefficient(static_cast(index))); LOG_DEBUG(Service_Y2R, "called standard_coefficient=%u", index); } @@ -544,22 +553,21 @@ static void GetStandardCoefficient(Interface* self) { } static void SetAlpha(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x22, 1, 0); + conversion.alpha = rp.Pop(); - conversion.alpha = cmd_buff[1]; - - cmd_buff[0] = IPC::MakeHeader(0x22, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_Y2R, "called alpha=%hu", conversion.alpha); } static void GetAlpha(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x23, 0, 0); - cmd_buff[0] = IPC::MakeHeader(0x23, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = conversion.alpha; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(conversion.alpha); LOG_DEBUG(Service_Y2R, "called alpha=%hu", conversion.alpha); } @@ -584,7 +592,7 @@ static void GetDitheringWeightParams(Interface* self) { } static void StartConversion(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x26, 0, 0); // dst_image_size would seem to be perfect for this, but it doesn't include the gap :( u32 total_output_size = @@ -596,17 +604,17 @@ static void StartConversion(Interface* self) { completion_event->Signal(); - cmd_buff[0] = IPC::MakeHeader(0x26, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_Y2R, "called"); } static void StopConversion(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x27, 0, 0); - cmd_buff[0] = IPC::MakeHeader(0x27, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_Y2R, "called"); } @@ -618,11 +626,11 @@ static void StopConversion(Interface* self) { * 2 : 1 if there's a conversion running, otherwise 0. */ static void IsBusyConversion(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x28, 0, 0); - cmd_buff[0] = IPC::MakeHeader(0x28, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0; // StartConversion always finishes immediately + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(0); // StartConversion always finishes immediately LOG_DEBUG(Service_Y2R, "called"); } @@ -631,59 +639,60 @@ static void IsBusyConversion(Interface* self) { * Y2R_U::SetPackageParameter service function */ static void SetPackageParameter(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x29, 7, 0); + auto params = rp.PopRaw(); - auto params = reinterpret_cast(&cmd_buff[1]); + conversion.input_format = params.input_format; + conversion.output_format = params.output_format; + conversion.rotation = params.rotation; + conversion.block_alignment = params.block_alignment; - conversion.input_format = params->input_format; - conversion.output_format = params->output_format; - conversion.rotation = params->rotation; - conversion.block_alignment = params->block_alignment; - - ResultCode result = conversion.SetInputLineWidth(params->input_line_width); + ResultCode result = conversion.SetInputLineWidth(params.input_line_width); if (result.IsError()) goto cleanup; - result = conversion.SetInputLines(params->input_lines); + result = conversion.SetInputLines(params.input_lines); if (result.IsError()) goto cleanup; - result = conversion.SetStandardCoefficient(params->standard_coefficient); + result = conversion.SetStandardCoefficient(params.standard_coefficient); if (result.IsError()) goto cleanup; - conversion.padding = params->padding; - conversion.alpha = params->alpha; + conversion.padding = params.padding; + conversion.alpha = params.alpha; cleanup: - cmd_buff[0] = IPC::MakeHeader(0x29, 1, 0); - cmd_buff[1] = result.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(result); LOG_DEBUG( Service_Y2R, "called input_format=%hhu output_format=%hhu rotation=%hhu block_alignment=%hhu " "input_line_width=%hu input_lines=%hu standard_coefficient=%hhu reserved=%hhu alpha=%hX", - static_cast(params->input_format), static_cast(params->output_format), - static_cast(params->rotation), static_cast(params->block_alignment), - params->input_line_width, params->input_lines, - static_cast(params->standard_coefficient), params->padding, params->alpha); + static_cast(params.input_format), static_cast(params.output_format), + static_cast(params.rotation), static_cast(params.block_alignment), + params.input_line_width, params.input_lines, static_cast(params.standard_coefficient), + params.padding, params.alpha); } static void PingProcess(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x2A, 0, 0); - cmd_buff[0] = IPC::MakeHeader(0x2A, 2, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(0); LOG_WARNING(Service_Y2R, "(STUBBED) called"); } static void DriverInitialize(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x2B, 0, 0); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); conversion.input_format = InputFormat::YUV422_Indiv8; conversion.output_format = OutputFormat::RGBA8; @@ -702,17 +711,16 @@ static void DriverInitialize(Interface* self) { completion_event->Clear(); - cmd_buff[0] = IPC::MakeHeader(0x2B, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_Y2R, "called"); } static void DriverFinalize(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x2C, 0, 0); - cmd_buff[0] = IPC::MakeHeader(0x2C, 1, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_Y2R, "called"); } @@ -787,4 +795,4 @@ Y2R_U::~Y2R_U() { } } // namespace Y2R -} // namespace Service \ No newline at end of file +} // namespace Service diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index ca517ff44..92abbeb5e 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -8,7 +8,9 @@ #include "common/assert.h" #include "common/file_util.h" #include "common/scm_rev.h" +#ifdef ARCHITECTURE_x86_64 #include "common/x64/cpu_detect.h" +#endif #include "core/core.h" #include "core/settings.h" #include "core/telemetry_session.h" @@ -20,6 +22,7 @@ namespace Core { +#ifdef ARCHITECTURE_x86_64 static const char* CpuVendorToStr(Common::CPUVendor vendor) { switch (vendor) { case Common::CPUVendor::INTEL: @@ -31,6 +34,7 @@ static const char* CpuVendorToStr(Common::CPUVendor vendor) { } UNREACHABLE(); } +#endif static u64 GenerateTelemetryId() { u64 telemetry_id{}; @@ -121,7 +125,8 @@ TelemetrySession::TelemetrySession() { AddField(Telemetry::FieldType::App, "BuildDate", Common::g_build_date); AddField(Telemetry::FieldType::App, "BuildName", Common::g_build_name); - // Log user system information +// Log user system information +#ifdef ARCHITECTURE_x86_64 AddField(Telemetry::FieldType::UserSystem, "CPU_Model", Common::GetCPUCaps().cpu_string); AddField(Telemetry::FieldType::UserSystem, "CPU_BrandString", Common::GetCPUCaps().brand_string); @@ -143,6 +148,9 @@ TelemetrySession::TelemetrySession() { Common::GetCPUCaps().sse4_1); AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE42", Common::GetCPUCaps().sse4_2); +#else + AddField(Telemetry::FieldType::UserSystem, "CPU_Model", "Other"); +#endif #ifdef __APPLE__ AddField(Telemetry::FieldType::UserSystem, "OsPlatform", "Apple"); #elif defined(_WIN32) diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index e38d83158..1c7db28c0 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -8,7 +8,7 @@ add_library(input_common STATIC motion_emu.cpp motion_emu.h - $<$:sdl/sdl.cpp sdl/sdl.h> + $<$:sdl/sdl.cpp sdl/sdl.h> ) create_target_directory_groups(input_common) diff --git a/src/input_common/motion_emu.cpp b/src/input_common/motion_emu.cpp index 59a035e70..27c81e73b 100644 --- a/src/input_common/motion_emu.cpp +++ b/src/input_common/motion_emu.cpp @@ -17,11 +17,12 @@ namespace InputCommon { // Implementation class of the motion emulation device class MotionEmuDevice { public: - MotionEmuDevice(int update_millisecond, float sensitivity) + MotionEmuDevice(int update_millisecond, float sensitivity, float tilt_clamp) : update_millisecond(update_millisecond), update_duration(std::chrono::duration_cast( std::chrono::milliseconds(update_millisecond))), - sensitivity(sensitivity), motion_emu_thread(&MotionEmuDevice::MotionEmuThread, this) {} + sensitivity(sensitivity), tilt_clamp(tilt_clamp), + motion_emu_thread(&MotionEmuDevice::MotionEmuThread, this) {} ~MotionEmuDevice() { if (motion_emu_thread.joinable()) { @@ -44,7 +45,7 @@ public: } else { tilt_direction = mouse_move.Cast(); tilt_angle = MathUtil::Clamp(tilt_direction.Normalize() * sensitivity, 0.0f, - MathUtil::PI * 0.5f); + MathUtil::PI * this->tilt_clamp / 180.0f); } } } @@ -70,6 +71,7 @@ private: std::mutex tilt_mutex; Math::Vec2 tilt_direction; float tilt_angle = 0; + float tilt_clamp = 90; bool is_tilting = false; @@ -126,8 +128,8 @@ private: // can forward all the inputs to the implementation only when it is valid. class MotionEmuDeviceWrapper : public Input::MotionDevice { public: - MotionEmuDeviceWrapper(int update_millisecond, float sensitivity) { - device = std::make_shared(update_millisecond, sensitivity); + MotionEmuDeviceWrapper(int update_millisecond, float sensitivity, float tilt_clamp) { + device = std::make_shared(update_millisecond, sensitivity, tilt_clamp); } std::tuple, Math::Vec3> GetStatus() const { @@ -140,7 +142,9 @@ public: std::unique_ptr MotionEmu::Create(const Common::ParamPackage& params) { int update_period = params.Get("update_period", 100); float sensitivity = params.Get("sensitivity", 0.01f); - auto device_wrapper = std::make_unique(update_period, sensitivity); + float tilt_clamp = params.Get("tilt_clamp", 90.0f); + auto device_wrapper = + std::make_unique(update_period, sensitivity, tilt_clamp); // Previously created device is disconnected here. Having two motion devices for 3DS is not // expected. current_device = device_wrapper->device; diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index c34a277db..3ccd1297e 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -221,6 +221,8 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { MICROPROFILE_SCOPE(GPU_Drawing); immediate_attribute_id = 0; + Shader::OutputVertex::ValidateSemantics(regs.rasterizer); + auto* shader_engine = Shader::GetEngine(); shader_engine->SetupBatch(g_state.vs, regs.vs.main_offset); @@ -289,6 +291,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { // Later, these can be compiled and cached. const u32 base_address = regs.pipeline.vertex_attributes.GetPhysicalBaseAddress(); VertexLoader loader(regs.pipeline); + Shader::OutputVertex::ValidateSemantics(regs.rasterizer); // Load vertices bool is_indexed = (id == PICA_REG_INDEX(pipeline.trigger_draw_indexed)); diff --git a/src/video_core/regs_rasterizer.h b/src/video_core/regs_rasterizer.h index 4fef00d76..9f68494ce 100644 --- a/src/video_core/regs_rasterizer.h +++ b/src/video_core/regs_rasterizer.h @@ -87,6 +87,8 @@ struct RasterizerRegs { BitField<8, 5, Semantic> map_y; BitField<16, 5, Semantic> map_z; BitField<24, 5, Semantic> map_w; + + u32 raw; } vs_output_attributes[7]; INSERT_PADDING_WORDS(0xe); diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index 9a61c0cfc..3053b0038 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -65,6 +65,7 @@ PicaShaderConfig PicaShaderConfig::BuildFromRegs(const Pica::Regs& regs) { PicaShaderConfig res; auto& state = res.state; + // Memset structure to zero padding bits, so that they will be deterministic when hashing std::memset(&state, 0, sizeof(PicaShaderConfig::State)); state.scissor_test_mode = regs.rasterizer.scissor_test.mode; diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h index 2302ae453..c67ceb54d 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.h +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -131,10 +131,6 @@ union PicaShaderConfig { } state; }; -#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER) -static_assert(std::is_trivially_copyable::value, - "PicaShaderConfig::State must be trivially copyable"); -#endif /** * Generates the GLSL vertex shader program source code for the current Pica state @@ -156,7 +152,7 @@ namespace std { template <> struct hash { size_t operator()(const GLShader::PicaShaderConfig& k) const { - return Common::ComputeHash64(&k.state, sizeof(GLShader::PicaShaderConfig::State)); + return Common::ComputeStructHash64(k.state); } }; } // namespace std diff --git a/src/video_core/shader/shader.cpp b/src/video_core/shader/shader.cpp index 2d0ffe821..194c9df11 100644 --- a/src/video_core/shader/shader.cpp +++ b/src/video_core/shader/shader.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include #include #include "common/bit_set.h" @@ -21,32 +22,41 @@ namespace Pica { namespace Shader { +void OutputVertex::ValidateSemantics(const RasterizerRegs& regs) { + unsigned int num_attributes = regs.vs_output_total; + ASSERT(num_attributes <= 7); + for (size_t attrib = 0; attrib < num_attributes; ++attrib) { + u32 output_register_map = regs.vs_output_attributes[attrib].raw; + for (size_t comp = 0; comp < 4; ++comp) { + u32 semantic = (output_register_map >> (8 * comp)) & 0x1F; + ASSERT_MSG(semantic < 24 || semantic == RasterizerRegs::VSOutputAttributes::INVALID, + "Invalid/unknown semantic id: %" PRIu32, semantic); + } + } +} + OutputVertex OutputVertex::FromAttributeBuffer(const RasterizerRegs& regs, const AttributeBuffer& input) { // Setup output data union { OutputVertex ret{}; - std::array vertex_slots; + // Allow us to overflow OutputVertex to avoid branches, since + // RasterizerRegs::VSOutputAttributes::INVALID would write to slot 31, which + // would be out of bounds otherwise. + std::array vertex_slots_overflow; }; - static_assert(sizeof(vertex_slots) == sizeof(ret), "Struct and array have different sizes."); - unsigned int num_attributes = regs.vs_output_total; - ASSERT(num_attributes <= 7); - for (unsigned int i = 0; i < num_attributes; ++i) { - const auto& output_register_map = regs.vs_output_attributes[i]; + // Assert that OutputVertex has enough space for 24 semantic registers + static_assert(sizeof(std::array) == sizeof(ret), + "Struct and array have different sizes."); - RasterizerRegs::VSOutputAttributes::Semantic semantics[4] = { - output_register_map.map_x, output_register_map.map_y, output_register_map.map_z, - output_register_map.map_w}; - - for (unsigned comp = 0; comp < 4; ++comp) { - RasterizerRegs::VSOutputAttributes::Semantic semantic = semantics[comp]; - if (semantic < vertex_slots.size()) { - vertex_slots[semantic] = input.attr[i][comp]; - } else if (semantic != RasterizerRegs::VSOutputAttributes::INVALID) { - LOG_ERROR(HW_GPU, "Invalid/unknown semantic id: %u", (unsigned int)semantic); - } - } + unsigned int num_attributes = regs.vs_output_total & 7; + for (size_t attrib = 0; attrib < num_attributes; ++attrib) { + const auto output_register_map = regs.vs_output_attributes[attrib]; + vertex_slots_overflow[output_register_map.map_x] = input.attr[attrib][0]; + vertex_slots_overflow[output_register_map.map_y] = input.attr[attrib][1]; + vertex_slots_overflow[output_register_map.map_z] = input.attr[attrib][2]; + vertex_slots_overflow[output_register_map.map_w] = input.attr[attrib][3]; } // The hardware takes the absolute and saturates vertex colors like this, *before* doing diff --git a/src/video_core/shader/shader.h b/src/video_core/shader/shader.h index 8740a1618..21ac01064 100644 --- a/src/video_core/shader/shader.h +++ b/src/video_core/shader/shader.h @@ -50,6 +50,7 @@ struct OutputVertex { INSERT_PADDING_WORDS(1); Math::Vec2 tc2; + static void ValidateSemantics(const RasterizerRegs& regs); static OutputVertex FromAttributeBuffer(const RasterizerRegs& regs, const AttributeBuffer& output); };