Merge branch 'master' into CompatibiltyReporting

master
BreadFish64 2018-01-18 10:36:32 +07:00 committed by GitHub
commit c3afd73592
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
75 changed files with 1527 additions and 745 deletions

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

@ -21,10 +21,26 @@ matrix:
install: "./.travis/linux/deps.sh" install: "./.travis/linux/deps.sh"
script: "./.travis/linux/build.sh" script: "./.travis/linux/build.sh"
after_success: "./.travis/linux/upload.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 - os: osx
env: NAME="macos build" env: NAME="macos build"
sudo: false sudo: false
osx_image: xcode7.3 osx_image: xcode9.2
install: "./.travis/macos/deps.sh" install: "./.travis/macos/deps.sh"
script: "./.travis/macos/build.sh" script: "./.travis/macos/build.sh"
after_success: "./.travis/macos/upload.sh" after_success: "./.travis/macos/upload.sh"

@ -3,7 +3,7 @@
cd /citra cd /citra
apt-get update 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 # Get a recent version of CMake
wget https://cmake.org/files/v3.9/cmake-3.9.0-Linux-x86_64.sh 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 export PATH=/citra/cmake/cmake-3.9.0-Linux-x86_64/bin:$PATH
mkdir build && cd build 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 make -j4
ctest -VV -C Release ctest -VV -C Release

@ -6,7 +6,7 @@ export MACOSX_DEPLOYMENT_TARGET=10.9
export Qt5_DIR=$(brew --prefix)/opt/qt5 export Qt5_DIR=$(brew --prefix)/opt/qt5
mkdir build && cd build 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 make -j4
ctest -VV -C Release ctest -VV -C Release

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

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

@ -0,0 +1,5 @@
#!/bin/bash -ex
cd dist/languages
tx push -s

@ -10,10 +10,16 @@ option(ENABLE_SDL2 "Enable the SDL2 frontend" ON)
option(CITRA_USE_BUNDLED_SDL2 "Download bundled SDL2 binaries" OFF) option(CITRA_USE_BUNDLED_SDL2 "Download bundled SDL2 binaries" OFF)
option(ENABLE_QT "Enable the Qt frontend" ON) 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(CITRA_USE_BUNDLED_QT "Download bundled Qt binaries" OFF)
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON) 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) 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) 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.") 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) SET(CITRA_USE_BUNDLED_CURL ON CACHE BOOL "" FORCE)
@ -65,14 +71,16 @@ function(detect_architecture symbol arch)
endif() endif()
endfunction() endfunction()
if (MSVC) if (NOT ENABLE_GENERIC)
detect_architecture("_M_AMD64" x86_64) if (MSVC)
detect_architecture("_M_IX86" x86) detect_architecture("_M_AMD64" x86_64)
detect_architecture("_M_ARM" ARM) detect_architecture("_M_IX86" x86)
else() detect_architecture("_M_ARM" ARM)
detect_architecture("__x86_64__" x86_64) else()
detect_architecture("__i386__" x86) detect_architecture("__x86_64__" x86_64)
detect_architecture("__arm__" ARM) detect_architecture("__i386__" x86)
detect_architecture("__arm__" ARM)
endif()
endif() endif()
if (NOT DEFINED ARCHITECTURE) if (NOT DEFINED ARCHITECTURE)
set(ARCHITECTURE "GENERIC") set(ARCHITECTURE "GENERIC")
@ -230,6 +238,10 @@ if (ENABLE_QT)
endif() endif()
find_package(Qt5 REQUIRED COMPONENTS Widgets OpenGL ${QT_PREFIX_HINT}) 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() endif()
if (ENABLE_WEB_SERVICE) if (ENABLE_WEB_SERVICE)

@ -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 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 ### Building
* __Windows__: [Windows Build](https://github.com/citra-emu/citra/wiki/Building-For-Windows) * __Windows__: [Windows Build](https://github.com/citra-emu/citra/wiki/Building-For-Windows)

@ -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 # 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' 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 { } 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 .. - cd ..
@ -118,23 +118,16 @@ after_build:
Get-ChildItem "$CMAKE_BINARY_DIR" -Recurse -Filter "libcurl.dll" | Copy-Item -destination $RELEASE_DIST 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/license.txt" -destination $RELEASE_DIST
Copy-Item -path "$CMAKE_SOURCE_DIR/README.md" -destination $RELEASE_DIST Copy-Item -path "$CMAKE_SOURCE_DIR/README.md" -destination $RELEASE_DIST
# copy all the dll dependencies to the release folder # 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. . "./.appveyor/UtilityFunctions.ps1"
$MingwDLLs = "Qt5Core.dll","Qt5Widgets.dll","Qt5Gui.dll","Qt5OpenGL.dll", $DLLSearchPath = "$CMAKE_BINARY_DIR\externals\curl-7_55_1\lib;C:\msys64\mingw64\bin;$env:PATH"
# QT dll dependencies $MingwDLLs = RecursivelyGetDeps $DLLSearchPath "$RELEASE_DIST\citra.exe"
"libbz2-*.dll","libicudt*.dll","libicuin*.dll","libicuuc*.dll","libffi-*.dll", $MingwDLLs += RecursivelyGetDeps $DLLSearchPath "$RELEASE_DIST\citra-qt.exe"
"libfreetype-*.dll","libglib-*.dll","libgobject-*.dll","libgraphite2.dll","libiconv-*.dll", Write-Host "Detected the following dependencies:"
"libharfbuzz-*.dll","libintl-*.dll","libpcre-*.dll","libpcre16-*.dll","libpcre2-16-*.dll","libpng16-*.dll", Write-Host $MingwDLLs
# Runtime/Other dependencies
"libgcc_s_seh-*.dll","libstdc++-*.dll","libwinpthread-*.dll","SDL2.dll","zlib1.dll"
foreach ($file in $MingwDLLs) { foreach ($file in $MingwDLLs) {
Copy-Item -path "C:/msys64/mingw64/bin/$file" -force -destination "$RELEASE_DIST" Copy-Item -path "$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 the qt windows plugin dll to platforms # copy the qt windows plugin dll to platforms

@ -0,0 +1,3 @@
# Ignore the source language file
en.ts

@ -0,0 +1,9 @@
[main]
host = https://www.transifex.com
[citra.emulator]
file_filter = <lang>.ts
source_file = en.ts
source_lang = en
type = QT

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

@ -11,11 +11,13 @@ target_include_directories(catch-single-include INTERFACE catch/single_include)
add_subdirectory(cryptopp) add_subdirectory(cryptopp)
# Dynarmic # Dynarmic
# Dynarmic will skip defining xbyak if it's already defined, we then define it below if (ARCHITECTURE_x86_64)
add_library(xbyak INTERFACE) # Dynarmic will skip defining xbyak if it's already defined, we then define it below
option(DYNARMIC_TESTS OFF) add_library(xbyak INTERFACE)
set(DYNARMIC_NO_BUNDLED_FMT ON) option(DYNARMIC_TESTS OFF)
add_subdirectory(dynarmic) set(DYNARMIC_NO_BUNDLED_FMT ON)
add_subdirectory(dynarmic)
endif()
# libfmt # libfmt
add_subdirectory(fmt) add_subdirectory(fmt)

2
externals/catch vendored

@ -1 +1 @@
Subproject commit 19ab2117c5bac2f376f8da4a4b25e183137bcec0 Subproject commit cd76f5730c9a3afa19f3b9c83608d9c7ab325a19

2
externals/enet vendored

@ -1 +1 @@
Subproject commit a84c120eff13d2fa3eadb41ef7afe0f7819f4d6c Subproject commit 39a72ab1990014eb399cee9d538fd529df99c6a0

2
externals/nihstro vendored

@ -1 +1 @@
Subproject commit 7e24743af21a7c2e3cef21ef174ae4269d0cfdac Subproject commit fd69de1a1b960ec296cc67d32257b0f9e2d89ac6

@ -23,7 +23,7 @@ add_library(audio_core STATIC
time_stretch.cpp time_stretch.cpp
time_stretch.h time_stretch.h
$<$<BOOL:SDL2_FOUND>:sdl2_sink.cpp sdl2_sink.h> $<$<BOOL:${SDL2_FOUND}>:sdl2_sink.cpp sdl2_sink.h>
) )
create_target_directory_groups(audio_core) create_target_directory_groups(audio_core)

@ -11,10 +11,8 @@
// This needs to be included before getopt.h because the latter #defines symbols used by it // This needs to be included before getopt.h because the latter #defines symbols used by it
#include "common/microprofile.h" #include "common/microprofile.h"
#ifdef _MSC_VER
#include <getopt.h>
#else
#include <getopt.h> #include <getopt.h>
#ifndef _MSC_VER
#include <unistd.h> #include <unistd.h>
#endif #endif
@ -157,13 +155,14 @@ int main(int argc, char** argv) {
errno = EINVAL; errno = EINVAL;
if (errno != 0) if (errno != 0)
exit(1); exit(1);
break;
} }
case 'm': { case 'm': {
use_multiplayer = true; 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 // regex to check if the format is nickname:password@ip:port
// with optional :password // with optional :password
std::regex re("^([^:]+)(?::(.+))?@([^:]+)(?::([0-9]+))?$"); const std::regex re("^([^:]+)(?::(.+))?@([^:]+)(?::([0-9]+))?$");
if (!std::regex_match(str_arg, re)) { if (!std::regex_match(str_arg, re)) {
std::cout << "Wrong format for option --multiplayer\n"; std::cout << "Wrong format for option --multiplayer\n";
PrintHelp(argv[0]); PrintHelp(argv[0]);
@ -188,10 +187,6 @@ int main(int argc, char** argv) {
std::cout << "Address to room must not be empty.\n"; std::cout << "Address to room must not be empty.\n";
return 0; return 0;
} }
if (port > 65535) {
std::cout << "Port must be between 0 and 65535.\n";
return 0;
}
break; break;
} }
case 'h': case 'h':

@ -76,8 +76,9 @@ void Config::ReadValues() {
Settings::values.analogs[i] = default_param; Settings::values.analogs[i] = default_param;
} }
Settings::values.motion_device = sdl2_config->Get( Settings::values.motion_device =
"Controls", "motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01"); sdl2_config->Get("Controls", "motion_device",
"engine:motion_emu,update_period:100,sensitivity:0.01,tilt_clamp:90.0");
Settings::values.touch_device = Settings::values.touch_device =
sdl2_config->Get("Controls", "touch_device", "engine:emu_window"); sdl2_config->Get("Controls", "touch_device", "engine:emu_window");

@ -89,19 +89,19 @@ EmuWindow_SDL2::EmuWindow_SDL2() {
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
if (render_window == nullptr) { 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); exit(1);
} }
gl_context = SDL_GL_CreateContext(render_window); gl_context = SDL_GL_CreateContext(render_window);
if (gl_context == nullptr) { 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); exit(1);
} }
if (!gladLoadGLLoader(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) { if (!gladLoadGLLoader(static_cast<GLADloadproc>(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); exit(1);
} }

@ -90,12 +90,46 @@ file(GLOB_RECURSE THEMES ${CMAKE_SOURCE_DIR}/dist/qt_themes/*)
qt5_wrap_ui(UI_HDRS ${UIS}) 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} "<RCC><qresource prefix=\"languages\">\n")
foreach (QM ${LANGUAGES_QM})
get_filename_component(QM_FILE ${QM} NAME)
file(APPEND ${LANGUAGES_QRC} "<file>${QM_FILE}</file>\n")
endforeach (QM)
file(APPEND ${LANGUAGES_QRC} "</qresource></RCC>")
# Add the QRC file to package in all QM files
qt5_add_resources(LANGUAGES ${LANGUAGES_QRC})
else()
set(LANGUAGES)
endif()
target_sources(citra-qt target_sources(citra-qt
PRIVATE PRIVATE
${ICONS} ${ICONS}
${THEMES} ${THEMES}
${UI_HDRS} ${UI_HDRS}
${UIS} ${UIS}
${LANGUAGES}
) )
if (APPLE) if (APPLE)

@ -36,6 +36,7 @@ void EmuThread::run() {
Core::System::ResultStatus result = Core::System::GetInstance().RunLoop(); Core::System::ResultStatus result = Core::System::GetInstance().RunLoop();
if (result != Core::System::ResultStatus::Success) { if (result != Core::System::ResultStatus::Success) {
this->SetRunning(false);
emit ErrorThrown(result, Core::System::GetInstance().GetStatusDetails()); emit ErrorThrown(result, Core::System::GetInstance().GetStatusDetails());
} }

@ -58,7 +58,9 @@ void Config::ReadValues() {
} }
Settings::values.motion_device = 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() .toString()
.toStdString(); .toStdString();
Settings::values.touch_device = Settings::values.touch_device =
@ -182,6 +184,7 @@ void Config::ReadValues() {
UISettings::values.gamedir = qt_config->value("gameListRootDir", ".").toString(); UISettings::values.gamedir = qt_config->value("gameListRootDir", ".").toString();
UISettings::values.gamedir_deepscan = qt_config->value("gameListDeepScan", false).toBool(); UISettings::values.gamedir_deepscan = qt_config->value("gameListDeepScan", false).toBool();
UISettings::values.recent_files = qt_config->value("recentFiles").toStringList(); UISettings::values.recent_files = qt_config->value("recentFiles").toStringList();
UISettings::values.language = qt_config->value("language", "").toString();
qt_config->endGroup(); qt_config->endGroup();
qt_config->beginGroup("Shortcuts"); qt_config->beginGroup("Shortcuts");
@ -333,6 +336,7 @@ void Config::SaveValues() {
qt_config->setValue("gameListRootDir", UISettings::values.gamedir); qt_config->setValue("gameListRootDir", UISettings::values.gamedir);
qt_config->setValue("gameListDeepScan", UISettings::values.gamedir_deepscan); qt_config->setValue("gameListDeepScan", UISettings::values.gamedir_deepscan);
qt_config->setValue("recentFiles", UISettings::values.recent_files); qt_config->setValue("recentFiles", UISettings::values.recent_files);
qt_config->setValue("language", UISettings::values.language);
qt_config->endGroup(); qt_config->endGroup();
qt_config->beginGroup("Shortcuts"); qt_config->beginGroup("Shortcuts");

@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>740</width> <width>461</width>
<height>500</height> <height>500</height>
</rect> </rect>
</property> </property>
@ -34,15 +34,15 @@
<string>Input</string> <string>Input</string>
</attribute> </attribute>
</widget> </widget>
<widget class="ConfigureGraphics" name="graphicsTab"> <widget class="ConfigureGraphics" name="graphicsTab">
<attribute name="title"> <attribute name="title">
<string>Graphics</string> <string>Graphics</string>
</attribute> </attribute>
</widget> </widget>
<widget class="ConfigureAudio" name="audioTab"> <widget class="ConfigureAudio" name="audioTab">
<attribute name="title"> <attribute name="title">
<string>Audio</string> <string>Audio</string>
</attribute> </attribute>
</widget> </widget>
<widget class="ConfigureDebug" name="debugTab"> <widget class="ConfigureDebug" name="debugTab">
<attribute name="title"> <attribute name="title">

@ -76,3 +76,7 @@ void ConfigureAudio::updateAudioDevices(int sink_index) {
ui->audio_device_combo_box->addItem(device.c_str()); ui->audio_device_combo_box->addItem(device.c_str());
} }
} }
void ConfigureAudio::retranslateUi() {
ui->retranslateUi(this);
}

@ -19,6 +19,7 @@ public:
~ConfigureAudio(); ~ConfigureAudio();
void applyConfiguration(); void applyConfiguration();
void retranslateUi();
public slots: public slots:
void updateAudioDevices(int sink_index); void updateAudioDevices(int sink_index);

@ -24,3 +24,7 @@ void ConfigureDebug::applyConfiguration() {
Settings::values.gdbstub_port = ui->gdbport_spinbox->value(); Settings::values.gdbstub_port = ui->gdbport_spinbox->value();
Settings::Apply(); Settings::Apply();
} }
void ConfigureDebug::retranslateUi() {
ui->retranslateUi(this);
}

@ -19,6 +19,7 @@ public:
~ConfigureDebug(); ~ConfigureDebug();
void applyConfiguration(); void applyConfiguration();
void retranslateUi();
private: private:
void setConfiguration(); void setConfiguration();

@ -10,6 +10,8 @@
ConfigureDialog::ConfigureDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ConfigureDialog) { ConfigureDialog::ConfigureDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ConfigureDialog) {
ui->setupUi(this); ui->setupUi(this);
this->setConfiguration(); this->setConfiguration();
connect(ui->generalTab, &ConfigureGeneral::languageChanged, this,
&ConfigureDialog::onLanguageChanged);
} }
ConfigureDialog::~ConfigureDialog() {} ConfigureDialog::~ConfigureDialog() {}
@ -26,3 +28,15 @@ void ConfigureDialog::applyConfiguration() {
ui->webTab->applyConfiguration(); ui->webTab->applyConfiguration();
Settings::Apply(); 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();
}

@ -20,6 +20,12 @@ public:
void applyConfiguration(); void applyConfiguration();
private slots:
void onLanguageChanged(const QString& locale);
signals:
void languageChanged(const QString& locale);
private: private:
void setConfiguration(); void setConfiguration();

@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <QDirIterator>
#include "citra_qt/configuration/configure_general.h" #include "citra_qt/configuration/configure_general.h"
#include "citra_qt/ui_settings.h" #include "citra_qt/ui_settings.h"
#include "core/core.h" #include "core/core.h"
@ -12,6 +13,23 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
: QWidget(parent), ui(new Ui::ConfigureGeneral) { : QWidget(parent), ui(new Ui::ConfigureGeneral) {
ui->setupUi(this); ui->setupUi(this);
ui->language_combobox->addItem(tr("<System>"), 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<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
&ConfigureGeneral::onLanguageChanged);
for (auto theme : UISettings::themes) { for (auto theme : UISettings::themes) {
ui->theme_combobox->addItem(theme.first, theme.second); 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->region_combobox->setCurrentIndex(Settings::values.region_value + 1);
ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); 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() { void ConfigureGeneral::applyConfiguration() {
@ -52,3 +72,14 @@ void ConfigureGeneral::applyConfiguration() {
Settings::values.use_cpu_jit = ui->toggle_cpu_jit->isChecked(); Settings::values.use_cpu_jit = ui->toggle_cpu_jit->isChecked();
Settings::Apply(); 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);
}

@ -19,6 +19,13 @@ public:
~ConfigureGeneral(); ~ConfigureGeneral();
void applyConfiguration(); void applyConfiguration();
void retranslateUi();
private slots:
void onLanguageChanged(int index);
signals:
void languageChanged(const QString& locale);
private: private:
void setConfiguration(); void setConfiguration();

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>300</width> <width>345</width>
<height>377</height> <height>493</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -38,6 +38,20 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="language_label">
<property name="text">
<string>Interface language</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="language_combobox"/>
</item>
</layout>
</item>
</layout> </layout>
</item> </item>
</layout> </layout>

@ -113,3 +113,7 @@ void ConfigureGraphics::applyConfiguration() {
Settings::values.swap_screen = ui->swap_screen->isChecked(); Settings::values.swap_screen = ui->swap_screen->isChecked();
Settings::Apply(); Settings::Apply();
} }
void ConfigureGraphics::retranslateUi() {
ui->retranslateUi(this);
}

@ -19,6 +19,7 @@ public:
~ConfigureGraphics(); ~ConfigureGraphics();
void applyConfiguration(); void applyConfiguration();
void retranslateUi();
private: private:
void setConfiguration(); void setConfiguration();

@ -277,3 +277,7 @@ void ConfigureInput::keyPressEvent(QKeyEvent* event) {
} }
setPollingResult({}, true); setPollingResult({}, true);
} }
void ConfigureInput::retranslateUi() {
ui->retranslateUi(this);
}

@ -33,6 +33,7 @@ public:
/// Save all button configurations to settings file /// Save all button configurations to settings file
void applyConfiguration(); void applyConfiguration();
void retranslateUi();
private: private:
std::unique_ptr<Ui::ConfigureInput> ui; std::unique_ptr<Ui::ConfigureInput> ui;

@ -167,3 +167,7 @@ void ConfigureSystem::refreshConsoleID() {
Service::CFG::UpdateConfigNANDSavegame(); Service::CFG::UpdateConfigNANDSavegame();
ui->label_console_id->setText("Console ID: 0x" + QString::number(console_id, 16).toUpper()); ui->label_console_id->setText("Console ID: 0x" + QString::number(console_id, 16).toUpper());
} }
void ConfigureSystem::retranslateUi() {
ui->retranslateUi(this);
}

@ -20,6 +20,7 @@ public:
void applyConfiguration(); void applyConfiguration();
void setConfiguration(); void setConfiguration();
void retranslateUi();
public slots: public slots:
void updateBirthdayComboBox(int birthmonth_index); void updateBirthdayComboBox(int birthmonth_index);

@ -100,3 +100,7 @@ void ConfigureWeb::OnLoginVerified() {
"correctly, and that your internet connection is working.")); "correctly, and that your internet connection is working."));
} }
} }
void ConfigureWeb::retranslateUi() {
ui->retranslateUi(this);
}

@ -20,6 +20,7 @@ public:
~ConfigureWeb(); ~ConfigureWeb();
void applyConfiguration(); void applyConfiguration();
void retranslateUi();
public slots: public slots:
void RefreshTelemetryID(); void RefreshTelemetryID();

@ -98,6 +98,8 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
// register size_t to use in slots and signals // register size_t to use in slots and signals
qRegisterMetaType<size_t>("size_t"); qRegisterMetaType<size_t>("size_t");
LoadTranslation();
Pica::g_debug_context = Pica::DebugContext::Construct(); Pica::g_debug_context = Pica::DebugContext::Construct();
setAcceptDrops(true); setAcceptDrops(true);
ui.setupUi(this); ui.setupUi(this);
@ -115,8 +117,8 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
ConnectMenuEvents(); ConnectMenuEvents();
ConnectWidgetEvents(); ConnectWidgetEvents();
setWindowTitle(QString("Citra %1| %2-%3") SetupUIStrings();
.arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
show(); show();
game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
@ -892,6 +894,8 @@ void GMainWindow::ToggleWindowMode() {
void GMainWindow::OnConfigure() { void GMainWindow::OnConfigure() {
ConfigureDialog configureDialog(this); ConfigureDialog configureDialog(this);
connect(&configureDialog, &ConfigureDialog::languageChanged, this,
&GMainWindow::OnLanguageChanged);
auto result = configureDialog.exec(); auto result = configureDialog.exec();
if (result == QDialog::Accepted) { if (result == QDialog::Accepted) {
configureDialog.applyConfiguration(); configureDialog.applyConfiguration();
@ -995,6 +999,7 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
} else { } else {
// Only show the message if the game is still running. // Only show the message if the game is still running.
if (emu_thread) { if (emu_thread) {
emu_thread->SetRunning(true);
message_label->setText(status_message); message_label->setText(status_message);
message_label->setVisible(true); 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 #ifdef main
#undef main #undef main
#endif #endif

@ -7,6 +7,7 @@
#include <memory> #include <memory>
#include <QMainWindow> #include <QMainWindow>
#include <QTimer> #include <QTimer>
#include <QTranslator>
#include "core/core.h" #include "core/core.h"
#include "core/hle/service/am/am.h" #include "core/hle/service/am/am.h"
#include "ui_main.h" #include "ui_main.h"
@ -152,9 +153,12 @@ private slots:
void OnUpdateFound(bool found, bool error); void OnUpdateFound(bool found, bool error);
void OnCheckForUpdates(); void OnCheckForUpdates();
void OnOpenUpdater(); void OnOpenUpdater();
void OnLanguageChanged(const QString& locale);
private: private:
void UpdateStatusBar(); void UpdateStatusBar();
void LoadTranslation();
void SetupUIStrings();
Ui::MainWindow ui; Ui::MainWindow ui;
@ -193,6 +197,8 @@ private:
QAction* actions_recent_files[max_recent_files_item]; QAction* actions_recent_files[max_recent_files_item];
QTranslator translator;
protected: protected:
void dropEvent(QDropEvent* event) override; void dropEvent(QDropEvent* event) override;
void dragEnterEvent(QDragEnterEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override;

@ -48,6 +48,7 @@ struct Values {
QString gamedir; QString gamedir;
bool gamedir_deepscan; bool gamedir_deepscan;
QStringList recent_files; QStringList recent_files;
QString language;
QString theme; QString theme;

@ -32,6 +32,8 @@ add_library(common STATIC
break_points.cpp break_points.cpp
break_points.h break_points.h
chunk_file.h chunk_file.h
cityhash.cpp
cityhash.h
code_block.h code_block.h
color.h color.h
common_funcs.h common_funcs.h
@ -39,7 +41,6 @@ add_library(common STATIC
common_types.h common_types.h
file_util.cpp file_util.cpp
file_util.h file_util.h
hash.cpp
hash.h hash.h
linear_disk_cache.h linear_disk_cache.h
logging/backend.cpp logging/backend.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 <algorithm>
#include <string.h> // 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<uint32>(a) + (static_cast<uint32>(b) << 8);
uint32 z = static_cast<uint32>(len) + (static_cast<uint32>(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<uint64, uint64> 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<uint64, uint64> 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<uint64, uint64> v = WeakHashLen32WithSeeds(s + len - 64, len, z);
pair<uint64, uint64> 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<size_t>(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<long>(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<uint64, uint64> 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

@ -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 <utility>
#include <stdint.h>
#include <stdlib.h> // for size_t.
namespace Common {
typedef std::pair<uint64_t, uint64_t> 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

@ -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 <stdlib.h>
#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

@ -5,12 +5,11 @@
#pragma once #pragma once
#include <cstddef> #include <cstddef>
#include "common/cityhash.h"
#include "common/common_types.h" #include "common/common_types.h"
namespace Common { 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 * Computes a 64-bit hash over the specified block of data
* @param data Block of data to compute hash over * @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 * @returns 64-bit hash value that was computed over the data block
*/ */
static inline u64 ComputeHash64(const void* data, size_t len) { static inline u64 ComputeHash64(const void* data, size_t len) {
u64 res[2]; return CityHash64(static_cast<const char*>(data), len);
MurmurHash3_128(data, len, 0, res); }
return res[0];
/**
* 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 <typename T>
static inline u64 ComputeStructHash64(const T& data) {
static_assert(
std::is_trivially_copyable<T>::value && std::is_standard_layout<T>::value,
"Type passed to ComputeStructHash64 must be trivially copyable and standard layout");
return ComputeHash64(&data, sizeof(data));
} }
} // namespace Common } // namespace Common

@ -1,10 +1,6 @@
add_library(core STATIC add_library(core STATIC
3ds.h 3ds.h
arm/arm_interface.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.cpp
arm/dyncom/arm_dyncom.h arm/dyncom/arm_dyncom.h
arm/dyncom/arm_dyncom_dec.cpp arm/dyncom/arm_dyncom_dec.cpp
@ -404,7 +400,17 @@ add_library(core STATIC
create_target_directory_groups(core) create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE audio_core network video_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) if (ENABLE_WEB_SERVICE)
target_link_libraries(core PUBLIC json-headers web_service) target_link_libraries(core PUBLIC json-headers web_service)
endif() 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()

@ -7,7 +7,9 @@
#include "audio_core/audio_core.h" #include "audio_core/audio_core.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/arm/arm_interface.h" #include "core/arm/arm_interface.h"
#ifdef ARCHITECTURE_x86_64
#include "core/arm/dynarmic/arm_dynarmic.h" #include "core/arm/dynarmic/arm_dynarmic.h"
#endif
#include "core/arm/dyncom/arm_dyncom.h" #include "core/arm/dyncom/arm_dyncom.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_timing.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"); LOG_DEBUG(HW_Memory, "initialized OK");
if (Settings::values.use_cpu_jit) { if (Settings::values.use_cpu_jit) {
#ifdef ARCHITECTURE_x86_64
cpu_core = std::make_unique<ARM_Dynarmic>(USER32MODE); cpu_core = std::make_unique<ARM_Dynarmic>(USER32MODE);
#else
cpu_core = std::make_unique<ARM_DynCom>(USER32MODE);
LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
#endif
} else { } else {
cpu_core = std::make_unique<ARM_DynCom>(USER32MODE); cpu_core = std::make_unique<ARM_DynCom>(USER32MODE);
} }

@ -59,7 +59,9 @@ public:
/// Empty placeholder structure for services with no per-session data. The session data classes /// Empty placeholder structure for services with no per-session data. The session data classes
/// in each service must inherit from this. /// in each service must inherit from this.
struct SessionDataBase {}; struct SessionDataBase {
virtual ~SessionDataBase() = default;
};
protected: protected:
/// Creates the storage for the session data of the service. /// Creates the storage for the session data of the service.

@ -40,6 +40,7 @@ SharedPtr<Process> Process::Create(SharedPtr<CodeSet> code_set) {
process->codeset = std::move(code_set); process->codeset = std::move(code_set);
process->flags.raw = 0; process->flags.raw = 0;
process->flags.memory_region.Assign(MemoryRegion::APPLICATION); process->flags.memory_region.Assign(MemoryRegion::APPLICATION);
process->status = ProcessStatus::Created;
process_list.push_back(process); process_list.push_back(process);
return process; return process;
@ -151,6 +152,8 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) {
HandleSpecialMapping(vm_manager, mapping); HandleSpecialMapping(vm_manager, mapping);
} }
status = ProcessStatus::Running;
vm_manager.LogLayout(Log::Level::Debug); vm_manager.LogLayout(Log::Level::Debug);
Kernel::SetupMainThread(codeset->entrypoint, main_thread_priority, this); Kernel::SetupMainThread(codeset->entrypoint, main_thread_priority, this);
} }

@ -49,6 +49,8 @@ union ProcessFlags {
BitField<12, 1, u16> loaded_high; ///< Application loaded high (not at 0x00100000). BitField<12, 1, u16> loaded_high; ///< Application loaded high (not at 0x00100000).
}; };
enum class ProcessStatus { Created, Running, Exited };
class ResourceLimit; class ResourceLimit;
struct MemoryRegionInfo; struct MemoryRegionInfo;
@ -122,6 +124,8 @@ public:
u16 kernel_version = 0; u16 kernel_version = 0;
/// The default CPU for this process, threads are scheduled on this cpu by default. /// The default CPU for this process, threads are scheduled on this cpu by default.
u8 ideal_processor = 0; u8 ideal_processor = 0;
/// Current status of the process
ProcessStatus status;
/// The id of this process /// The id of this process
u32 process_id = next_process_id++; u32 process_id = next_process_id++;

@ -143,6 +143,36 @@ static ResultCode ControlMemory(u32* out_addr, u32 operation, u32 addr0, u32 add
return RESULT_SUCCESS; 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 /// Maps a memory block to specified address
static ResultCode MapMemoryBlock(Handle handle, u32 addr, u32 permissions, u32 other_permissions) { static ResultCode MapMemoryBlock(Handle handle, u32 addr, u32 permissions, u32 other_permissions) {
LOG_TRACE(Kernel_SVC, 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) { for (unsigned int i = 0; i < name_count; ++i) {
u32 name = Memory::Read32(names + i * sizeof(u32)); 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); Memory::Write64(values + i * sizeof(u64), value);
} }
@ -1232,7 +1262,7 @@ static const FunctionDef SVC_Table[] = {
{0x00, nullptr, "Unknown"}, {0x00, nullptr, "Unknown"},
{0x01, HLE::Wrap<ControlMemory>, "ControlMemory"}, {0x01, HLE::Wrap<ControlMemory>, "ControlMemory"},
{0x02, HLE::Wrap<QueryMemory>, "QueryMemory"}, {0x02, HLE::Wrap<QueryMemory>, "QueryMemory"},
{0x03, nullptr, "ExitProcess"}, {0x03, ExitProcess, "ExitProcess"},
{0x04, nullptr, "GetProcessAffinityMask"}, {0x04, nullptr, "GetProcessAffinityMask"},
{0x05, nullptr, "SetProcessAffinityMask"}, {0x05, nullptr, "SetProcessAffinityMask"},
{0x06, nullptr, "GetProcessIdealProcessor"}, {0x06, nullptr, "GetProcessIdealProcessor"},
@ -1373,6 +1403,9 @@ void CallSVC(u32 immediate) {
// Lock the global kernel mutex when we enter the kernel HLE. // Lock the global kernel mutex when we enter the kernel HLE.
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); std::lock_guard<std::recursive_mutex> 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); const FunctionDef* info = GetSVCInfo(immediate);
if (info) { if (info) {
if (info->func) { if (info->func) {

@ -9,7 +9,6 @@
#include "core/core.h" #include "core/core.h"
#include "core/hle/ipc.h" #include "core/hle/ipc.h"
#include "core/hle/ipc_helpers.h" #include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/shared_memory.h" #include "core/hle/kernel/shared_memory.h"
#include "core/hle/result.h" #include "core/hle/result.h"
@ -51,6 +50,20 @@ constexpr ResultCode ERR_REGS_INVALID_SIZE(ErrorDescription::InvalidSize, ErrorM
ErrorSummary::InvalidArgument, ErrorSummary::InvalidArgument,
ErrorLevel::Usage); // 0xE0E02BEC 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<bool, MaxGSPThreads> 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 /// Gets a pointer to a thread command buffer in GSP shared memory
static inline u8* GetCommandBuffer(Kernel::SharedPtr<Kernel::SharedMemory> shared_memory, static inline u8* GetCommandBuffer(Kernel::SharedPtr<Kernel::SharedMemory> shared_memory,
u32 thread_id) { u32 thread_id) {
@ -319,12 +332,16 @@ void GSP_GPU::RegisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x13, 1, 2); IPC::RequestParser rp(ctx, 0x13, 1, 2);
u32 flags = rp.Pop<u32>(); u32 flags = rp.Pop<u32>();
interrupt_event = rp.PopObject<Kernel::Event>(); auto interrupt_event = rp.PopObject<Kernel::Event>();
// TODO(mailwl): return right error code instead assert // TODO(mailwl): return right error code instead assert
ASSERT_MSG((interrupt_event != nullptr), "handle is not valid!"); ASSERT_MSG((interrupt_event != nullptr), "handle is not valid!");
interrupt_event->name = "GSP_GSP_GPU::interrupt_event"; 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); IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
if (first_initialization) { if (first_initialization) {
@ -335,25 +352,60 @@ void GSP_GPU::RegisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
} }
rb.Push(thread_id); rb.Push(session_data->thread_id);
rb.PushCopyObjects(shared_memory); rb.PushCopyObjects(shared_memory);
thread_id++; LOG_DEBUG(Service_GSP, "called, flags=0x%08X", flags);
interrupt_event->Signal(); // TODO(bunnei): Is this correct?
LOG_WARNING(Service_GSP, "called, flags=0x%08X", flags);
} }
void GSP_GPU::UnregisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) { void GSP_GPU::UnregisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x14, 0, 0); IPC::RequestParser rp(ctx, 0x14, 0, 0);
thread_id = 0; SessionData* session_data = GetSessionData(ctx.Session());
interrupt_event = nullptr; session_data->interrupt_event = nullptr;
session_data->registered = false;
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(RESULT_SUCCESS); 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 * @todo This probably does not belong in the GSP module, instead move to video_core
*/ */
void GSP_GPU::SignalInterrupt(InterruptId interrupt_id) { 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) { if (nullptr == shared_memory) {
LOG_WARNING(Service_GSP, "cannot synchronize until GSP shared memory has been created!"); LOG_WARNING(Service_GSP, "cannot synchronize until GSP shared memory has been created!");
return; 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; // 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
interrupt_relay_queue->slot[next] = interrupt_id; // right), but the PDC0/1 interrupts are signaled for every registered thread.
interrupt_relay_queue->error_code = 0x0; // No error if (interrupt_id == InterruptId::PDC0 || interrupt_id == InterruptId::PDC1) {
for (u32 thread_id = 0; thread_id < MaxGSPThreads; ++thread_id) {
// Update framebuffer information if requested SignalInterruptForThread(interrupt_id, thread_id);
// 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);
}
} }
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)); 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<u32>(); u32 flag = rp.Pop<u32>();
auto process = rp.PopObject<Kernel::Process>(); auto process = rp.PopObject<Kernel::Process>();
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); 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) { void GSP_GPU::ReleaseRight(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x17, 0, 0); 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); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(RESULT_SUCCESS); rb.Push(RESULT_SUCCESS);
@ -655,6 +706,17 @@ void GSP_GPU::StoreDataCache(Kernel::HLERequestContext& ctx) {
size, process->process_id); size, process->process_id);
} }
SessionData* GSP_GPU::FindRegisteredThreadData(u32 thread_id) {
for (auto& session_info : connected_sessions) {
SessionData* data = static_cast<SessionData*>(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) { GSP_GPU::GSP_GPU() : ServiceFramework("gsp::Gpu", 2) {
static const FunctionInfo functions[] = { static const FunctionInfo functions[] = {
{0x00010082, &GSP_GPU::WriteHWRegs, "WriteHWRegs"}, {0x00010082, &GSP_GPU::WriteHWRegs, "WriteHWRegs"},
@ -691,17 +753,26 @@ GSP_GPU::GSP_GPU() : ServiceFramework("gsp::Gpu", 2) {
}; };
RegisterHandlers(functions); RegisterHandlers(functions);
interrupt_event = nullptr;
using Kernel::MemoryPermission; using Kernel::MemoryPermission;
shared_memory = Kernel::SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, shared_memory = Kernel::SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite,
MemoryPermission::ReadWrite, 0, MemoryPermission::ReadWrite, 0,
Kernel::MemoryRegion::BASE, "GSP:SharedMemory"); Kernel::MemoryRegion::BASE, "GSP:SharedMemory");
thread_id = 0;
gpu_right_acquired = false;
first_initialization = true; 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 GSP
} // namespace Service } // namespace Service

@ -8,12 +8,12 @@
#include <string> #include <string>
#include "common/bit_field.h" #include "common/bit_field.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/hle_ipc.h"
#include "core/hle/result.h" #include "core/hle/result.h"
#include "core/hle/service/service.h" #include "core/hle/service/service.h"
namespace Kernel { namespace Kernel {
class Event;
class SharedMemory; class SharedMemory;
} // namespace Kernel } // namespace Kernel
@ -179,7 +179,19 @@ struct CommandBuffer {
}; };
static_assert(sizeof(CommandBuffer) == 0x200, "CommandBuffer struct has incorrect size"); static_assert(sizeof(CommandBuffer) == 0x200, "CommandBuffer struct has incorrect size");
class GSP_GPU final : public ServiceFramework<GSP_GPU> { struct SessionData : public Kernel::SessionRequestHandler::SessionDataBase {
SessionData();
~SessionData();
/// Event triggered when GSP interrupt has been signalled
Kernel::SharedPtr<Kernel::Event> 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<GSP_GPU, SessionData> {
public: public:
GSP_GPU(); GSP_GPU();
~GSP_GPU() = default; ~GSP_GPU() = default;
@ -201,6 +213,14 @@ public:
FrameBufferUpdate* GetFrameBufferInfo(u32 thread_id, u32 screen_index); FrameBufferUpdate* GetFrameBufferInfo(u32 thread_id, u32 screen_index);
private: 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 * GSP_GPU::WriteHWRegs service function
* *
@ -351,14 +371,15 @@ private:
*/ */
void StoreDataCache(Kernel::HLERequestContext& ctx); void StoreDataCache(Kernel::HLERequestContext& ctx);
/// Event triggered when GSP interrupt has been signalled /// Returns the session data for the specified registered thread id, or nullptr if not found.
Kernel::SharedPtr<Kernel::Event> interrupt_event; SessionData* FindRegisteredThreadData(u32 thread_id);
/// GSP shared memoryings
Kernel::SharedPtr<Kernel::SharedMemory> shared_memory; /// GSP shared memory
/// Thread index into interrupt relay queue Kernel::SharedPtr<Kernel::SharedMemory> shared_memory;
u32 thread_id = 0;
/// 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; bool first_initialization = true;
}; };

@ -3,15 +3,12 @@
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <algorithm> #include <algorithm>
#include <atomic>
#include <cmath> #include <cmath>
#include <memory>
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/3ds.h" #include "core/3ds.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/frontend/input.h" #include "core/hle/ipc_helpers.h"
#include "core/hle/ipc.h"
#include "core/hle/kernel/event.h" #include "core/hle/kernel/event.h"
#include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/shared_memory.h" #include "core/hle/kernel/shared_memory.h"
@ -23,27 +20,7 @@
namespace Service { namespace Service {
namespace HID { namespace HID {
// Handle to shared memory region designated to HID_User service static std::weak_ptr<Module> current_module;
static Kernel::SharedPtr<Kernel::SharedMemory> shared_mem;
// Event handles
static Kernel::SharedPtr<Kernel::Event> event_pad_or_touch_1;
static Kernel::SharedPtr<Kernel::Event> event_pad_or_touch_2;
static Kernel::SharedPtr<Kernel::Event> event_accelerometer;
static Kernel::SharedPtr<Kernel::Event> event_gyroscope;
static Kernel::SharedPtr<Kernel::Event> 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;
// Updating period for each HID device. These empirical values are measured from a 11.2 3DS. // 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; 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 accelerometer_coef = 512.0f; // measured from hw test result
constexpr float gyroscope_coef = 14.375f; // got from hwtest GetGyroscopeLowRawToDpsCoefficient call constexpr float gyroscope_coef = 14.375f; // got from hwtest GetGyroscopeLowRawToDpsCoefficient call
static std::atomic<bool> is_device_reload_pending;
static std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
buttons;
static std::unique_ptr<Input::AnalogDevice> circle_pad;
static std::unique_ptr<Input::MotionDevice> motion_device;
static std::unique_ptr<Input::TouchDevice> touch_device;
DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) { DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
// 30 degree and 60 degree are angular thresholds for directions // 30 degree and 60 degree are angular thresholds for directions
constexpr float TAN30 = 0.577350269f; constexpr float TAN30 = 0.577350269f;
@ -89,7 +59,7 @@ DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
return state; return state;
} }
static void LoadInputDevices() { void Module::LoadInputDevices() {
std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN, std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END, Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END,
buttons.begin(), Input::CreateDevice<Input::ButtonDevice>); buttons.begin(), Input::CreateDevice<Input::ButtonDevice>);
@ -99,16 +69,7 @@ static void LoadInputDevices() {
touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touch_device); touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touch_device);
} }
static void UnloadInputDevices() { void Module::UpdatePadCallback(u64 userdata, int cycles_late) {
for (auto& button : buttons) {
button.reset();
}
circle_pad.reset();
motion_device.reset();
touch_device.reset();
}
static void UpdatePadCallback(u64 userdata, int cycles_late) {
SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer()); SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer());
if (is_device_reload_pending.exchange(false)) 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); 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<SharedMem*>(shared_mem->GetPointer()); SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer());
mem->accelerometer.index = next_accelerometer_index; 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); 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<SharedMem*>(shared_mem->GetPointer()); SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer());
mem->gyroscope.index = next_gyroscope_index; 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); CoreTiming::ScheduleEvent(gyroscope_update_ticks - cycles_late, gyroscope_update_event);
} }
void GetIPCHandles(Service::Interface* self) { void Module::Interface::GetIPCHandles(Kernel::HLERequestContext& ctx) {
u32* cmd_buff = Kernel::GetCommandBuffer(); IPC::RequestParser rp{ctx, 0xA, 0, 0};
IPC::RequestBuilder rb = rp.MakeBuilder(1, 7);
cmd_buff[1] = 0; // No error rb.Push(RESULT_SUCCESS);
cmd_buff[2] = 0x14000000; // IPC Command Structure translate-header rb.PushCopyObjects(hid->shared_mem, hid->event_pad_or_touch_1, hid->event_pad_or_touch_2,
// TODO(yuriks): Return error from SendSyncRequest is this fails (part of IPC marshalling) hid->event_accelerometer, hid->event_gyroscope, hid->event_debug_pad);
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 EnableAccelerometer(Service::Interface* self) { void Module::Interface::EnableAccelerometer(Kernel::HLERequestContext& ctx) {
u32* cmd_buff = Kernel::GetCommandBuffer(); 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 // Schedules the accelerometer update event if the accelerometer was just enabled
if (enable_accelerometer_count == 1) { if (hid->enable_accelerometer_count == 1) {
CoreTiming::ScheduleEvent(accelerometer_update_ticks, accelerometer_update_event); 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"); LOG_DEBUG(Service_HID, "called");
} }
void DisableAccelerometer(Service::Interface* self) { void Module::Interface::DisableAccelerometer(Kernel::HLERequestContext& ctx) {
u32* cmd_buff = Kernel::GetCommandBuffer(); 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 // Unschedules the accelerometer update event if the accelerometer was just disabled
if (enable_accelerometer_count == 0) { if (hid->enable_accelerometer_count == 0) {
CoreTiming::UnscheduleEvent(accelerometer_update_event, 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"); LOG_DEBUG(Service_HID, "called");
} }
void EnableGyroscopeLow(Service::Interface* self) { void Module::Interface::EnableGyroscopeLow(Kernel::HLERequestContext& ctx) {
u32* cmd_buff = Kernel::GetCommandBuffer(); 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 // Schedules the gyroscope update event if the gyroscope was just enabled
if (enable_gyroscope_count == 1) { if (hid->enable_gyroscope_count == 1) {
CoreTiming::ScheduleEvent(gyroscope_update_ticks, gyroscope_update_event); 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"); LOG_DEBUG(Service_HID, "called");
} }
void DisableGyroscopeLow(Service::Interface* self) { void Module::Interface::DisableGyroscopeLow(Kernel::HLERequestContext& ctx) {
u32* cmd_buff = Kernel::GetCommandBuffer(); 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 // Unschedules the gyroscope update event if the gyroscope was just disabled
if (enable_gyroscope_count == 0) { if (hid->enable_gyroscope_count == 0) {
CoreTiming::UnscheduleEvent(gyroscope_update_event, 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"); LOG_DEBUG(Service_HID, "called");
} }
void GetGyroscopeLowRawToDpsCoefficient(Service::Interface* self) { void Module::Interface::GetGyroscopeLowRawToDpsCoefficient(Kernel::HLERequestContext& ctx) {
u32* cmd_buff = Kernel::GetCommandBuffer(); IPC::RequestParser rp{ctx, 0x15, 0, 0};
cmd_buff[1] = RESULT_SUCCESS.raw; IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(RESULT_SUCCESS);
f32 coef = gyroscope_coef; rb.PushRaw<f32>(gyroscope_coef);
memcpy(&cmd_buff[2], &coef, 4);
} }
void GetGyroscopeLowCalibrateParam(Service::Interface* self) { void Module::Interface::GetGyroscopeLowCalibrateParam(Kernel::HLERequestContext& ctx) {
u32* cmd_buff = Kernel::GetCommandBuffer(); 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 const s16 param_unit = 6700; // an approximate value taken from hw
GyroscopeCalibrateParam param = { GyroscopeCalibrateParam param = {
{0, param_unit, -param_unit}, {0, param_unit, -param_unit}, {0, param_unit, -param_unit}, {0, param_unit, -param_unit}, {0, param_unit, -param_unit}, {0, param_unit, -param_unit},
}; };
memcpy(&cmd_buff[2], &param, sizeof(param)); rb.PushRaw(param);
LOG_WARNING(Service_HID, "(STUBBED) called"); LOG_WARNING(Service_HID, "(STUBBED) called");
} }
void GetSoundVolume(Service::Interface* self) { void Module::Interface::GetSoundVolume(Kernel::HLERequestContext& ctx) {
u32* cmd_buff = Kernel::GetCommandBuffer(); IPC::RequestParser rp{ctx, 0x17, 0, 0};
const u8 volume = 0x3F; // TODO(purpasmart): Find out if this is the max value for the volume const u8 volume = 0x3F; // TODO(purpasmart): Find out if this is the max value for the volume
cmd_buff[1] = RESULT_SUCCESS.raw; IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
cmd_buff[2] = volume; rb.Push(RESULT_SUCCESS);
rb.Push(volume);
LOG_WARNING(Service_HID, "(STUBBED) called"); LOG_WARNING(Service_HID, "(STUBBED) called");
} }
void Init() { Module::Interface::Interface(std::shared_ptr<Module> hid, const char* name, u32 max_session)
: ServiceFramework(name, max_session), hid(std::move(hid)) {}
Module::Module() {
using namespace Kernel; using namespace Kernel;
AddService(new HID_U_Interface);
AddService(new HID_SPVR_Interface);
is_device_reload_pending.store(true);
using Kernel::MemoryPermission;
shared_mem = shared_mem =
SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, MemoryPermission::Read, SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, MemoryPermission::Read,
0, Kernel::MemoryRegion::BASE, "HID:SharedMemory"); 0, 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;
// Create event handles // Create event handles
event_pad_or_touch_1 = Event::Create(ResetType::OneShot, "HID:EventPadOrTouch1"); 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"); event_debug_pad = Event::Create(ResetType::OneShot, "HID:EventDebugPad");
// Register update callbacks // Register update callbacks
pad_update_event = CoreTiming::RegisterEvent("HID::UpdatePadCallback", UpdatePadCallback); pad_update_event =
accelerometer_update_event = CoreTiming::RegisterEvent("HID::UpdatePadCallback", [this](u64 userdata, int cycles_late) {
CoreTiming::RegisterEvent("HID::UpdateAccelerometerCallback", UpdateAccelerometerCallback); UpdatePadCallback(userdata, cycles_late);
gyroscope_update_event = });
CoreTiming::RegisterEvent("HID::UpdateGyroscopeCallback", UpdateGyroscopeCallback); 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); CoreTiming::ScheduleEvent(pad_update_ticks, pad_update_event);
} }
void Shutdown() { void Module::ReloadInputDevices() {
shared_mem = nullptr; is_device_reload_pending.store(true);
event_pad_or_touch_1 = nullptr;
event_pad_or_touch_2 = nullptr;
event_accelerometer = nullptr;
event_gyroscope = nullptr;
event_debug_pad = nullptr;
UnloadInputDevices();
} }
void ReloadInputDevices() { 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<Module>();
std::make_shared<User>(hid)->InstallAsService(service_manager);
std::make_shared<Spvr>(hid)->InstallAsService(service_manager);
current_module = hid;
} }
} // namespace HID } // namespace HID

@ -5,17 +5,29 @@
#pragma once #pragma once
#include <array> #include <array>
#include <atomic>
#ifndef _MSC_VER #ifndef _MSC_VER
#include <cstddef> #include <cstddef>
#endif #endif
#include <memory>
#include "common/bit_field.h" #include "common/bit_field.h"
#include "common/common_funcs.h" #include "common/common_funcs.h"
#include "common/common_types.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" #include "core/settings.h"
namespace Service { namespace Kernel {
class Event;
class SharedMemory;
}
class Interface; namespace CoreTiming {
class EventType;
};
namespace Service {
namespace HID { namespace HID {
@ -186,93 +198,140 @@ struct DirectionState {
/// Translates analog stick axes to directions. This is exposed for ir_rst module to use. /// 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); DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y);
/** class Module final {
* HID::GetIPCHandles service function public:
* Inputs: Module();
* 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 Interface : public ServiceFramework<Interface> {
* HID::EnableAccelerometer service function public:
* Inputs: Interface(std::shared_ptr<Module> hid, const char* name, u32 max_session);
* None
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void EnableAccelerometer(Interface* self);
/** protected:
* HID::DisableAccelerometer service function /**
* Inputs: * HID::GetIPCHandles service function
* None * Inputs:
* Outputs: * None
* 1 : Result of function, 0 on success, otherwise error code * Outputs:
*/ * 1 : Result of function, 0 on success, otherwise error code
void DisableAccelerometer(Interface* self); * 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 * HID::EnableAccelerometer service function
* Inputs: * Inputs:
* None * None
* Outputs: * Outputs:
* 1 : Result of function, 0 on success, otherwise error code * 1 : Result of function, 0 on success, otherwise error code
*/ */
void EnableGyroscopeLow(Interface* self); void EnableAccelerometer(Kernel::HLERequestContext& ctx);
/** /**
* HID::DisableGyroscopeLow service function * HID::DisableAccelerometer service function
* Inputs: * Inputs:
* None * None
* Outputs: * Outputs:
* 1 : Result of function, 0 on success, otherwise error code * 1 : Result of function, 0 on success, otherwise error code
*/ */
void DisableGyroscopeLow(Interface* self); void DisableAccelerometer(Kernel::HLERequestContext& ctx);
/** /**
* HID::GetSoundVolume service function * HID::EnableGyroscopeLow service function
* Inputs: * Inputs:
* None * None
* Outputs: * Outputs:
* 1 : Result of function, 0 on success, otherwise error code * 1 : Result of function, 0 on success, otherwise error code
* 2 : u8 output value */
*/ void EnableGyroscopeLow(Kernel::HLERequestContext& ctx);
void GetSoundVolume(Interface* self);
/** /**
* HID::GetGyroscopeLowRawToDpsCoefficient service function * HID::DisableGyroscopeLow service function
* Inputs: * Inputs:
* None * None
* Outputs: * Outputs:
* 1 : Result of function, 0 on success, otherwise error code * 1 : Result of function, 0 on success, otherwise error code
* 2 : float output value */
*/ void DisableGyroscopeLow(Kernel::HLERequestContext& ctx);
void GetGyroscopeLowRawToDpsCoefficient(Service::Interface* self);
/** /**
* HID::GetGyroscopeLowCalibrateParam service function * HID::GetSoundVolume service function
* Inputs: * Inputs:
* None * None
* Outputs: * Outputs:
* 1 : Result of function, 0 on success, otherwise error code * 1 : Result of function, 0 on success, otherwise error code
* 2~6 (18 bytes) : struct GyroscopeCalibrateParam * 2 : u8 output value
*/ */
void GetGyroscopeLowCalibrateParam(Service::Interface* self); 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<Module> 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<Kernel::SharedMemory> shared_mem;
// Event handles
Kernel::SharedPtr<Kernel::Event> event_pad_or_touch_1;
Kernel::SharedPtr<Kernel::Event> event_pad_or_touch_2;
Kernel::SharedPtr<Kernel::Event> event_accelerometer;
Kernel::SharedPtr<Kernel::Event> event_gyroscope;
Kernel::SharedPtr<Kernel::Event> 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<bool> is_device_reload_pending{true};
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
buttons;
std::unique_ptr<Input::AnalogDevice> circle_pad;
std::unique_ptr<Input::MotionDevice> motion_device;
std::unique_ptr<Input::TouchDevice> touch_device;
};
void InstallInterfaces(SM::ServiceManager& service_manager);
/// Reload input devices. Used when input configuration changed /// Reload input devices. Used when input configuration changed
void ReloadInputDevices(); void ReloadInputDevices();

@ -2,27 +2,26 @@
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/hid/hid_spvr.h" #include "core/hle/service/hid/hid_spvr.h"
namespace Service { namespace Service {
namespace HID { namespace HID {
const Interface::FunctionInfo FunctionTable[] = { Spvr::Spvr(std::shared_ptr<Module> hid) : Module::Interface(std::move(hid), "hid:SPVR", 6) {
{0x000A0000, GetIPCHandles, "GetIPCHandles"}, static const FunctionInfo functions[] = {
{0x000B0000, nullptr, "StartAnalogStickCalibration"}, {0x000A0000, &Spvr::GetIPCHandles, "GetIPCHandles"},
{0x000E0000, nullptr, "GetAnalogStickCalibrateParam"}, {0x000B0000, nullptr, "StartAnalogStickCalibration"},
{0x00110000, EnableAccelerometer, "EnableAccelerometer"}, {0x000E0000, nullptr, "GetAnalogStickCalibrateParam"},
{0x00120000, DisableAccelerometer, "DisableAccelerometer"}, {0x00110000, &Spvr::EnableAccelerometer, "EnableAccelerometer"},
{0x00130000, EnableGyroscopeLow, "EnableGyroscopeLow"}, {0x00120000, &Spvr::DisableAccelerometer, "DisableAccelerometer"},
{0x00140000, DisableGyroscopeLow, "DisableGyroscopeLow"}, {0x00130000, &Spvr::EnableGyroscopeLow, "EnableGyroscopeLow"},
{0x00150000, GetGyroscopeLowRawToDpsCoefficient, "GetGyroscopeLowRawToDpsCoefficient"}, {0x00140000, &Spvr::DisableGyroscopeLow, "DisableGyroscopeLow"},
{0x00160000, GetGyroscopeLowCalibrateParam, "GetGyroscopeLowCalibrateParam"}, {0x00150000, &Spvr::GetGyroscopeLowRawToDpsCoefficient,
{0x00170000, GetSoundVolume, "GetSoundVolume"}, "GetGyroscopeLowRawToDpsCoefficient"},
}; {0x00160000, &Spvr::GetGyroscopeLowCalibrateParam, "GetGyroscopeLowCalibrateParam"},
{0x00170000, &Spvr::GetSoundVolume, "GetSoundVolume"},
HID_SPVR_Interface::HID_SPVR_Interface() { };
Register(FunctionTable); RegisterHandlers(functions);
} }
} // namespace HID } // namespace HID

@ -4,18 +4,14 @@
#pragma once #pragma once
#include "core/hle/service/service.h" #include "core/hle/service/hid/hid.h"
namespace Service { namespace Service {
namespace HID { namespace HID {
class HID_SPVR_Interface : public Service::Interface { class Spvr final : public Module::Interface {
public: public:
HID_SPVR_Interface(); explicit Spvr(std::shared_ptr<Module> hid);
std::string GetPortName() const override {
return "hid:SPVR";
}
}; };
} // namespace HID } // namespace HID

@ -2,27 +2,26 @@
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/hid/hid_user.h" #include "core/hle/service/hid/hid_user.h"
namespace Service { namespace Service {
namespace HID { namespace HID {
const Interface::FunctionInfo FunctionTable[] = { User::User(std::shared_ptr<Module> hid) : Module::Interface(std::move(hid), "hid:USER", 6) {
{0x000A0000, GetIPCHandles, "GetIPCHandles"}, static const FunctionInfo functions[] = {
{0x000B0000, nullptr, "StartAnalogStickCalibration"}, {0x000A0000, &User::GetIPCHandles, "GetIPCHandles"},
{0x000E0000, nullptr, "GetAnalogStickCalibrateParam"}, {0x000B0000, nullptr, "StartAnalogStickCalibration"},
{0x00110000, EnableAccelerometer, "EnableAccelerometer"}, {0x000E0000, nullptr, "GetAnalogStickCalibrateParam"},
{0x00120000, DisableAccelerometer, "DisableAccelerometer"}, {0x00110000, &User::EnableAccelerometer, "EnableAccelerometer"},
{0x00130000, EnableGyroscopeLow, "EnableGyroscopeLow"}, {0x00120000, &User::DisableAccelerometer, "DisableAccelerometer"},
{0x00140000, DisableGyroscopeLow, "DisableGyroscopeLow"}, {0x00130000, &User::EnableGyroscopeLow, "EnableGyroscopeLow"},
{0x00150000, GetGyroscopeLowRawToDpsCoefficient, "GetGyroscopeLowRawToDpsCoefficient"}, {0x00140000, &User::DisableGyroscopeLow, "DisableGyroscopeLow"},
{0x00160000, GetGyroscopeLowCalibrateParam, "GetGyroscopeLowCalibrateParam"}, {0x00150000, &User::GetGyroscopeLowRawToDpsCoefficient,
{0x00170000, GetSoundVolume, "GetSoundVolume"}, "GetGyroscopeLowRawToDpsCoefficient"},
}; {0x00160000, &User::GetGyroscopeLowCalibrateParam, "GetGyroscopeLowCalibrateParam"},
{0x00170000, &User::GetSoundVolume, "GetSoundVolume"},
HID_U_Interface::HID_U_Interface() { };
Register(FunctionTable); RegisterHandlers(functions);
} }
} // namespace HID } // namespace HID

@ -4,7 +4,7 @@
#pragma once #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. // This service is used for interfacing to physical user controls.
// Uses include game pad controls, touchscreen, accelerometers, gyroscopes, and debug pad. // Uses include game pad controls, touchscreen, accelerometers, gyroscopes, and debug pad.
@ -12,16 +12,9 @@
namespace Service { namespace Service {
namespace HID { namespace HID {
/** class User final : public Module::Interface {
* HID service interface.
*/
class HID_U_Interface : public Service::Interface {
public: public:
HID_U_Interface(); explicit User(std::shared_ptr<Module> hid);
std::string GetPortName() const override {
return "hid:USER";
}
}; };
} // namespace HID } // namespace HID

@ -277,7 +277,7 @@ void Init() {
DLP::Init(); DLP::Init();
FRD::Init(); FRD::Init();
GSP::InstallInterfaces(*SM::g_service_manager); GSP::InstallInterfaces(*SM::g_service_manager);
HID::Init(); HID::InstallInterfaces(*SM::g_service_manager);
IR::InstallInterfaces(*SM::g_service_manager); IR::InstallInterfaces(*SM::g_service_manager);
MVD::Init(); MVD::Init();
NDM::Init(); NDM::Init();
@ -307,7 +307,6 @@ void Shutdown() {
NIM::Shutdown(); NIM::Shutdown();
NEWS::Shutdown(); NEWS::Shutdown();
NDM::Shutdown(); NDM::Shutdown();
HID::Shutdown();
FRD::Shutdown(); FRD::Shutdown();
DLP::Shutdown(); DLP::Shutdown();
CFG::Shutdown(); CFG::Shutdown();

@ -84,86 +84,86 @@ ResultCode ConversionConfiguration::SetStandardCoefficient(
} }
static void SetInputFormat(Interface* self) { static void SetInputFormat(Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1, 1, 0);
conversion.input_format = static_cast<InputFormat>(cmd_buff[1]); conversion.input_format = rp.PopEnum<InputFormat>();
cmd_buff[0] = IPC::MakeHeader(0x1, 1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_Y2R, "called input_format=%hhu", static_cast<u8>(conversion.input_format)); LOG_DEBUG(Service_Y2R, "called input_format=%hhu", static_cast<u8>(conversion.input_format));
} }
static void GetInputFormat(Interface* self) { 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); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
cmd_buff[2] = static_cast<u32>(conversion.input_format); rb.PushEnum(conversion.input_format);
LOG_DEBUG(Service_Y2R, "called input_format=%hhu", static_cast<u8>(conversion.input_format)); LOG_DEBUG(Service_Y2R, "called input_format=%hhu", static_cast<u8>(conversion.input_format));
} }
static void SetOutputFormat(Interface* self) { static void SetOutputFormat(Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x3, 1, 0);
conversion.output_format = static_cast<OutputFormat>(cmd_buff[1]); conversion.output_format = rp.PopEnum<OutputFormat>();
cmd_buff[0] = IPC::MakeHeader(0x3, 1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_Y2R, "called output_format=%hhu", static_cast<u8>(conversion.output_format)); LOG_DEBUG(Service_Y2R, "called output_format=%hhu", static_cast<u8>(conversion.output_format));
} }
static void GetOutputFormat(Interface* self) { 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); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
cmd_buff[2] = static_cast<u32>(conversion.output_format); rb.PushEnum(conversion.output_format);
LOG_DEBUG(Service_Y2R, "called output_format=%hhu", static_cast<u8>(conversion.output_format)); LOG_DEBUG(Service_Y2R, "called output_format=%hhu", static_cast<u8>(conversion.output_format));
} }
static void SetRotation(Interface* self) { static void SetRotation(Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x5, 1, 0);
conversion.rotation = static_cast<Rotation>(cmd_buff[1]); conversion.rotation = rp.PopEnum<Rotation>();
cmd_buff[0] = IPC::MakeHeader(0x5, 1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_Y2R, "called rotation=%hhu", static_cast<u8>(conversion.rotation)); LOG_DEBUG(Service_Y2R, "called rotation=%hhu", static_cast<u8>(conversion.rotation));
} }
static void GetRotation(Interface* self) { 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); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
cmd_buff[2] = static_cast<u32>(conversion.rotation); rb.PushEnum(conversion.rotation);
LOG_DEBUG(Service_Y2R, "called rotation=%hhu", static_cast<u8>(conversion.rotation)); LOG_DEBUG(Service_Y2R, "called rotation=%hhu", static_cast<u8>(conversion.rotation));
} }
static void SetBlockAlignment(Interface* self) { static void SetBlockAlignment(Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x7, 1, 0);
conversion.block_alignment = static_cast<BlockAlignment>(cmd_buff[1]); conversion.block_alignment = rp.PopEnum<BlockAlignment>();
cmd_buff[0] = IPC::MakeHeader(0x7, 1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_Y2R, "called block_alignment=%hhu", LOG_DEBUG(Service_Y2R, "called block_alignment=%hhu",
static_cast<u8>(conversion.block_alignment)); static_cast<u8>(conversion.block_alignment));
} }
static void GetBlockAlignment(Interface* self) { 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); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
cmd_buff[2] = static_cast<u32>(conversion.block_alignment); rb.PushEnum(conversion.block_alignment);
LOG_DEBUG(Service_Y2R, "called block_alignment=%hhu", LOG_DEBUG(Service_Y2R, "called block_alignment=%hhu",
static_cast<u8>(conversion.block_alignment)); static_cast<u8>(conversion.block_alignment));
@ -177,11 +177,12 @@ static void GetBlockAlignment(Interface* self) {
* 1 : Result of function, 0 on success, otherwise error code * 1 : Result of function, 0 on success, otherwise error code
*/ */
static void SetSpacialDithering(Interface* self) { static void SetSpacialDithering(Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x9, 1, 0);
spacial_dithering_enabled = cmd_buff[1] & 0xF;
cmd_buff[0] = IPC::MakeHeader(0x9, 1, 0); spacial_dithering_enabled = rp.Pop<u8>() & 0xF;
cmd_buff[1] = RESULT_SUCCESS.raw;
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_Y2R, "(STUBBED) called"); LOG_WARNING(Service_Y2R, "(STUBBED) called");
} }
@ -193,7 +194,9 @@ static void SetSpacialDithering(Interface* self) {
* 2 : u8, 0 = Disabled, 1 = Enabled * 2 : u8, 0 = Disabled, 1 = Enabled
*/ */
static void GetSpacialDithering(Interface* self) { 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(RESULT_SUCCESS);
rb.Push(spacial_dithering_enabled != 0); 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 * 1 : Result of function, 0 on success, otherwise error code
*/ */
static void SetTemporalDithering(Interface* self) { static void SetTemporalDithering(Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xB, 1, 0);
temporal_dithering_enabled = cmd_buff[1] & 0xF; temporal_dithering_enabled = rp.Pop<u8>() & 0xF;
cmd_buff[0] = IPC::MakeHeader(0xB, 1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_Y2R, "(STUBBED) called"); LOG_WARNING(Service_Y2R, "(STUBBED) called");
} }
@ -224,11 +227,11 @@ static void SetTemporalDithering(Interface* self) {
* 2 : u8, 0 = Disabled, 1 = Enabled * 2 : u8, 0 = Disabled, 1 = Enabled
*/ */
static void GetTemporalDithering(Interface* self) { 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); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
cmd_buff[2] = temporal_dithering_enabled; rb.Push(temporal_dithering_enabled);
LOG_WARNING(Service_Y2R, "(STUBBED) called"); 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 * 1 : Result of function, 0 on success, otherwise error code
*/ */
static void SetTransferEndInterrupt(Interface* self) { static void SetTransferEndInterrupt(Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xD, 1, 0);
transfer_end_interrupt_enabled = cmd_buff[1] & 0xf; transfer_end_interrupt_enabled = rp.Pop<u8>() & 0xF;
cmd_buff[0] = IPC::MakeHeader(0xD, 1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_Y2R, "(STUBBED) called"); LOG_WARNING(Service_Y2R, "(STUBBED) called");
} }
@ -257,11 +260,11 @@ static void SetTransferEndInterrupt(Interface* self) {
* 2 : u8, 0 = Disabled, 1 = Enabled * 2 : u8, 0 = Disabled, 1 = Enabled
*/ */
static void GetTransferEndInterrupt(Interface* self) { 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); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
cmd_buff[2] = transfer_end_interrupt_enabled; rb.Push(transfer_end_interrupt_enabled);
LOG_WARNING(Service_Y2R, "(STUBBED) called"); LOG_WARNING(Service_Y2R, "(STUBBED) called");
} }
@ -273,18 +276,18 @@ static void GetTransferEndInterrupt(Interface* self) {
* 3 : The handle of the completion event * 3 : The handle of the completion event
*/ */
static void GetTransferEndEvent(Interface* self) { 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); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
cmd_buff[3] = Kernel::g_handle_table.Create(completion_event).Unwrap(); rb.PushCopyHandles(Kernel::g_handle_table.Create(completion_event).Unwrap());
LOG_DEBUG(Service_Y2R, "called"); LOG_DEBUG(Service_Y2R, "called");
} }
static void SetSendingY(Interface* self) { static void SetSendingY(Interface* self) {
// The helper should be passed by argument to the function // 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<u32>(); conversion.src_Y.address = rp.Pop<u32>();
conversion.src_Y.image_size = rp.Pop<u32>(); conversion.src_Y.image_size = rp.Pop<u32>();
conversion.src_Y.transfer_unit = rp.Pop<u32>(); conversion.src_Y.transfer_unit = rp.Pop<u32>();
@ -302,7 +305,7 @@ static void SetSendingY(Interface* self) {
static void SetSendingU(Interface* self) { static void SetSendingU(Interface* self) {
// The helper should be passed by argument to the function // 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<u32>(); conversion.src_U.address = rp.Pop<u32>();
conversion.src_U.image_size = rp.Pop<u32>(); conversion.src_U.image_size = rp.Pop<u32>();
conversion.src_U.transfer_unit = rp.Pop<u32>(); conversion.src_U.transfer_unit = rp.Pop<u32>();
@ -319,37 +322,41 @@ static void SetSendingU(Interface* self) {
} }
static void SetSendingV(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.address = rp.Pop<u32>();
conversion.src_V.image_size = cmd_buff[2]; conversion.src_V.image_size = rp.Pop<u32>();
conversion.src_V.transfer_unit = cmd_buff[3]; conversion.src_V.transfer_unit = rp.Pop<u32>();
conversion.src_V.gap = cmd_buff[4]; conversion.src_V.gap = rp.Pop<u32>();
Kernel::Handle src_process_handle = rp.PopHandle();
cmd_buff[0] = IPC::MakeHeader(0x12, 1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, " LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, "
"src_process_handle=0x%08X", "src_process_handle=0x%08X",
conversion.src_V.image_size, conversion.src_V.transfer_unit, conversion.src_V.gap, conversion.src_V.image_size, conversion.src_V.transfer_unit, conversion.src_V.gap,
cmd_buff[6]); static_cast<u32>(src_process_handle));
} }
static void SetSendingYUYV(Interface* self) { 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.address = rp.Pop<u32>();
conversion.src_YUYV.image_size = cmd_buff[2]; conversion.src_YUYV.image_size = rp.Pop<u32>();
conversion.src_YUYV.transfer_unit = cmd_buff[3]; conversion.src_YUYV.transfer_unit = rp.Pop<u32>();
conversion.src_YUYV.gap = cmd_buff[4]; conversion.src_YUYV.gap = rp.Pop<u32>();
Kernel::Handle src_process_handle = rp.PopHandle();
cmd_buff[0] = IPC::MakeHeader(0x13, 1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, " LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, "
"src_process_handle=0x%08X", "src_process_handle=0x%08X",
conversion.src_YUYV.image_size, conversion.src_YUYV.transfer_unit, conversion.src_YUYV.image_size, conversion.src_YUYV.transfer_unit,
conversion.src_YUYV.gap, cmd_buff[6]); conversion.src_YUYV.gap, static_cast<u32>(src_process_handle));
} }
/** /**
@ -359,11 +366,11 @@ static void SetSendingYUYV(Interface* self) {
* 2 : u8, 0 = Not Finished, 1 = Finished * 2 : u8, 0 = Not Finished, 1 = Finished
*/ */
static void IsFinishedSendingYuv(Interface* self) { 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); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
cmd_buff[2] = 1; rb.Push<u8>(1);
LOG_WARNING(Service_Y2R, "(STUBBED) called"); LOG_WARNING(Service_Y2R, "(STUBBED) called");
} }
@ -375,11 +382,11 @@ static void IsFinishedSendingYuv(Interface* self) {
* 2 : u8, 0 = Not Finished, 1 = Finished * 2 : u8, 0 = Not Finished, 1 = Finished
*/ */
static void IsFinishedSendingY(Interface* self) { 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); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
cmd_buff[2] = 1; rb.Push<u8>(1);
LOG_WARNING(Service_Y2R, "(STUBBED) called"); LOG_WARNING(Service_Y2R, "(STUBBED) called");
} }
@ -391,11 +398,11 @@ static void IsFinishedSendingY(Interface* self) {
* 2 : u8, 0 = Not Finished, 1 = Finished * 2 : u8, 0 = Not Finished, 1 = Finished
*/ */
static void IsFinishedSendingU(Interface* self) { 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); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
cmd_buff[2] = 1; rb.Push<u8>(1);
LOG_WARNING(Service_Y2R, "(STUBBED) called"); LOG_WARNING(Service_Y2R, "(STUBBED) called");
} }
@ -407,30 +414,31 @@ static void IsFinishedSendingU(Interface* self) {
* 2 : u8, 0 = Not Finished, 1 = Finished * 2 : u8, 0 = Not Finished, 1 = Finished
*/ */
static void IsFinishedSendingV(Interface* self) { 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); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
cmd_buff[2] = 1; rb.Push<u8>(1);
LOG_WARNING(Service_Y2R, "(STUBBED) called"); LOG_WARNING(Service_Y2R, "(STUBBED) called");
} }
static void SetReceiving(Interface* self) { 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.address = rp.Pop<u32>();
conversion.dst.image_size = cmd_buff[2]; conversion.dst.image_size = rp.Pop<u32>();
conversion.dst.transfer_unit = cmd_buff[3]; conversion.dst.transfer_unit = rp.Pop<u32>();
conversion.dst.gap = cmd_buff[4]; conversion.dst.gap = rp.Pop<u32>();
Kernel::Handle dst_process_handle = rp.PopHandle();
cmd_buff[0] = IPC::MakeHeader(0x18, 1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, " LOG_DEBUG(Service_Y2R, "called image_size=0x%08X, transfer_unit=%hu, transfer_stride=%hu, "
"dst_process_handle=0x%08X", "dst_process_handle=0x%08X",
conversion.dst.image_size, conversion.dst.transfer_unit, conversion.dst.gap, conversion.dst.image_size, conversion.dst.transfer_unit, conversion.dst.gap,
cmd_buff[6]); static_cast<u32>(dst_process_handle));
} }
/** /**
@ -440,65 +448,67 @@ static void SetReceiving(Interface* self) {
* 2 : u8, 0 = Not Finished, 1 = Finished * 2 : u8, 0 = Not Finished, 1 = Finished
*/ */
static void IsFinishedReceiving(Interface* self) { 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); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
cmd_buff[2] = 1; rb.Push<u8>(1);
LOG_WARNING(Service_Y2R, "(STUBBED) called"); LOG_WARNING(Service_Y2R, "(STUBBED) called");
} }
static void SetInputLineWidth(Interface* self) { static void SetInputLineWidth(Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1A, 1, 0);
u32 input_line_width = rp.Pop<u32>();
cmd_buff[0] = IPC::MakeHeader(0x1A, 1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
cmd_buff[1] = conversion.SetInputLineWidth(cmd_buff[1]).raw; 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) { 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); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
cmd_buff[2] = conversion.input_line_width; rb.Push(conversion.input_line_width);
LOG_DEBUG(Service_Y2R, "called input_line_width=%u", conversion.input_line_width); LOG_DEBUG(Service_Y2R, "called input_line_width=%u", conversion.input_line_width);
} }
static void SetInputLines(Interface* self) { static void SetInputLines(Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1C, 1, 0);
u32 input_lines = rp.Pop<u32>();
cmd_buff[0] = IPC::MakeHeader(0x1C, 1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
cmd_buff[1] = conversion.SetInputLines(cmd_buff[1]).raw; 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) { 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); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
cmd_buff[2] = static_cast<u32>(conversion.input_lines); rb.Push(static_cast<u32>(conversion.input_lines));
LOG_DEBUG(Service_Y2R, "called input_lines=%u", conversion.input_lines); LOG_DEBUG(Service_Y2R, "called input_lines=%u", conversion.input_lines);
} }
static void SetCoefficient(Interface* self) { static void SetCoefficient(Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1E, 4, 0);
const u16* coefficients = reinterpret_cast<const u16*>(&cmd_buff[1]); rp.PopRaw<CoefficientSet>(conversion.coefficients);
std::memcpy(conversion.coefficients.data(), coefficients, sizeof(CoefficientSet));
cmd_buff[0] = IPC::MakeHeader(0x1E, 1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_Y2R, "called coefficients=[%hX, %hX, %hX, %hX, %hX, %hX, %hX, %hX]", LOG_DEBUG(Service_Y2R, "called coefficients=[%hX, %hX, %hX, %hX, %hX, %hX, %hX, %hX]",
coefficients[0], coefficients[1], coefficients[2], coefficients[3], coefficients[4], conversion.coefficients[0], conversion.coefficients[1], conversion.coefficients[2],
coefficients[5], coefficients[6], coefficients[7]); conversion.coefficients[3], conversion.coefficients[4], conversion.coefficients[5],
conversion.coefficients[6], conversion.coefficients[7]);
} }
static void GetCoefficient(Interface* self) { static void GetCoefficient(Interface* self) {
@ -512,12 +522,11 @@ static void GetCoefficient(Interface* self) {
} }
static void SetStandardCoefficient(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>();
u32 index = cmd_buff[1]; IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(conversion.SetStandardCoefficient(static_cast<StandardCoefficient>(index)));
cmd_buff[0] = IPC::MakeHeader(0x20, 1, 0);
cmd_buff[1] = conversion.SetStandardCoefficient((StandardCoefficient)index).raw;
LOG_DEBUG(Service_Y2R, "called standard_coefficient=%u", index); LOG_DEBUG(Service_Y2R, "called standard_coefficient=%u", index);
} }
@ -544,22 +553,21 @@ static void GetStandardCoefficient(Interface* self) {
} }
static void SetAlpha(Interface* self) { static void SetAlpha(Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x22, 1, 0);
conversion.alpha = rp.Pop<u32>();
conversion.alpha = cmd_buff[1]; IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(RESULT_SUCCESS);
cmd_buff[0] = IPC::MakeHeader(0x22, 1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw;
LOG_DEBUG(Service_Y2R, "called alpha=%hu", conversion.alpha); LOG_DEBUG(Service_Y2R, "called alpha=%hu", conversion.alpha);
} }
static void GetAlpha(Interface* self) { 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); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
cmd_buff[2] = conversion.alpha; rb.Push(conversion.alpha);
LOG_DEBUG(Service_Y2R, "called alpha=%hu", 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) { 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 :( // dst_image_size would seem to be perfect for this, but it doesn't include the gap :(
u32 total_output_size = u32 total_output_size =
@ -596,17 +604,17 @@ static void StartConversion(Interface* self) {
completion_event->Signal(); completion_event->Signal();
cmd_buff[0] = IPC::MakeHeader(0x26, 1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_Y2R, "called"); LOG_DEBUG(Service_Y2R, "called");
} }
static void StopConversion(Interface* self) { 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); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_Y2R, "called"); LOG_DEBUG(Service_Y2R, "called");
} }
@ -618,11 +626,11 @@ static void StopConversion(Interface* self) {
* 2 : 1 if there's a conversion running, otherwise 0. * 2 : 1 if there's a conversion running, otherwise 0.
*/ */
static void IsBusyConversion(Interface* self) { 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); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
cmd_buff[2] = 0; // StartConversion always finishes immediately rb.Push<u8>(0); // StartConversion always finishes immediately
LOG_DEBUG(Service_Y2R, "called"); LOG_DEBUG(Service_Y2R, "called");
} }
@ -631,59 +639,60 @@ static void IsBusyConversion(Interface* self) {
* Y2R_U::SetPackageParameter service function * Y2R_U::SetPackageParameter service function
*/ */
static void SetPackageParameter(Interface* self) { static void SetPackageParameter(Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x29, 7, 0);
auto params = rp.PopRaw<ConversionParameters>();
auto params = reinterpret_cast<const ConversionParameters*>(&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; ResultCode result = conversion.SetInputLineWidth(params.input_line_width);
conversion.output_format = params->output_format;
conversion.rotation = params->rotation;
conversion.block_alignment = params->block_alignment;
ResultCode result = conversion.SetInputLineWidth(params->input_line_width);
if (result.IsError()) if (result.IsError())
goto cleanup; goto cleanup;
result = conversion.SetInputLines(params->input_lines); result = conversion.SetInputLines(params.input_lines);
if (result.IsError()) if (result.IsError())
goto cleanup; goto cleanup;
result = conversion.SetStandardCoefficient(params->standard_coefficient); result = conversion.SetStandardCoefficient(params.standard_coefficient);
if (result.IsError()) if (result.IsError())
goto cleanup; goto cleanup;
conversion.padding = params->padding; conversion.padding = params.padding;
conversion.alpha = params->alpha; conversion.alpha = params.alpha;
cleanup: cleanup:
cmd_buff[0] = IPC::MakeHeader(0x29, 1, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
cmd_buff[1] = result.raw; rb.Push(result);
LOG_DEBUG( LOG_DEBUG(
Service_Y2R, Service_Y2R,
"called input_format=%hhu output_format=%hhu rotation=%hhu block_alignment=%hhu " "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", "input_line_width=%hu input_lines=%hu standard_coefficient=%hhu reserved=%hhu alpha=%hX",
static_cast<u8>(params->input_format), static_cast<u8>(params->output_format), static_cast<u8>(params.input_format), static_cast<u8>(params.output_format),
static_cast<u8>(params->rotation), static_cast<u8>(params->block_alignment), static_cast<u8>(params.rotation), static_cast<u8>(params.block_alignment),
params->input_line_width, params->input_lines, params.input_line_width, params.input_lines, static_cast<u8>(params.standard_coefficient),
static_cast<u8>(params->standard_coefficient), params->padding, params->alpha); params.padding, params.alpha);
} }
static void PingProcess(Interface* self) { 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); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
cmd_buff[2] = 0; rb.Push<u8>(0);
LOG_WARNING(Service_Y2R, "(STUBBED) called"); LOG_WARNING(Service_Y2R, "(STUBBED) called");
} }
static void DriverInitialize(Interface* self) { 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.input_format = InputFormat::YUV422_Indiv8;
conversion.output_format = OutputFormat::RGBA8; conversion.output_format = OutputFormat::RGBA8;
@ -702,17 +711,16 @@ static void DriverInitialize(Interface* self) {
completion_event->Clear(); completion_event->Clear();
cmd_buff[0] = IPC::MakeHeader(0x2B, 1, 0); rb.Push(RESULT_SUCCESS);
cmd_buff[1] = RESULT_SUCCESS.raw;
LOG_DEBUG(Service_Y2R, "called"); LOG_DEBUG(Service_Y2R, "called");
} }
static void DriverFinalize(Interface* self) { 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); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_Y2R, "called"); LOG_DEBUG(Service_Y2R, "called");
} }

@ -8,7 +8,9 @@
#include "common/assert.h" #include "common/assert.h"
#include "common/file_util.h" #include "common/file_util.h"
#include "common/scm_rev.h" #include "common/scm_rev.h"
#ifdef ARCHITECTURE_x86_64
#include "common/x64/cpu_detect.h" #include "common/x64/cpu_detect.h"
#endif
#include "core/core.h" #include "core/core.h"
#include "core/settings.h" #include "core/settings.h"
#include "core/telemetry_session.h" #include "core/telemetry_session.h"
@ -20,6 +22,7 @@
namespace Core { namespace Core {
#ifdef ARCHITECTURE_x86_64
static const char* CpuVendorToStr(Common::CPUVendor vendor) { static const char* CpuVendorToStr(Common::CPUVendor vendor) {
switch (vendor) { switch (vendor) {
case Common::CPUVendor::INTEL: case Common::CPUVendor::INTEL:
@ -31,6 +34,7 @@ static const char* CpuVendorToStr(Common::CPUVendor vendor) {
} }
UNREACHABLE(); UNREACHABLE();
} }
#endif
static u64 GenerateTelemetryId() { static u64 GenerateTelemetryId() {
u64 telemetry_id{}; u64 telemetry_id{};
@ -121,7 +125,8 @@ TelemetrySession::TelemetrySession() {
AddField(Telemetry::FieldType::App, "BuildDate", Common::g_build_date); AddField(Telemetry::FieldType::App, "BuildDate", Common::g_build_date);
AddField(Telemetry::FieldType::App, "BuildName", Common::g_build_name); 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_Model", Common::GetCPUCaps().cpu_string);
AddField(Telemetry::FieldType::UserSystem, "CPU_BrandString", AddField(Telemetry::FieldType::UserSystem, "CPU_BrandString",
Common::GetCPUCaps().brand_string); Common::GetCPUCaps().brand_string);
@ -143,6 +148,9 @@ TelemetrySession::TelemetrySession() {
Common::GetCPUCaps().sse4_1); Common::GetCPUCaps().sse4_1);
AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE42", AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE42",
Common::GetCPUCaps().sse4_2); Common::GetCPUCaps().sse4_2);
#else
AddField(Telemetry::FieldType::UserSystem, "CPU_Model", "Other");
#endif
#ifdef __APPLE__ #ifdef __APPLE__
AddField(Telemetry::FieldType::UserSystem, "OsPlatform", "Apple"); AddField(Telemetry::FieldType::UserSystem, "OsPlatform", "Apple");
#elif defined(_WIN32) #elif defined(_WIN32)

@ -8,7 +8,7 @@ add_library(input_common STATIC
motion_emu.cpp motion_emu.cpp
motion_emu.h motion_emu.h
$<$<BOOL:SDL2_FOUND>:sdl/sdl.cpp sdl/sdl.h> $<$<BOOL:${SDL2_FOUND}>:sdl/sdl.cpp sdl/sdl.h>
) )
create_target_directory_groups(input_common) create_target_directory_groups(input_common)

@ -17,11 +17,12 @@ namespace InputCommon {
// Implementation class of the motion emulation device // Implementation class of the motion emulation device
class MotionEmuDevice { class MotionEmuDevice {
public: public:
MotionEmuDevice(int update_millisecond, float sensitivity) MotionEmuDevice(int update_millisecond, float sensitivity, float tilt_clamp)
: update_millisecond(update_millisecond), : update_millisecond(update_millisecond),
update_duration(std::chrono::duration_cast<std::chrono::steady_clock::duration>( update_duration(std::chrono::duration_cast<std::chrono::steady_clock::duration>(
std::chrono::milliseconds(update_millisecond))), 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() { ~MotionEmuDevice() {
if (motion_emu_thread.joinable()) { if (motion_emu_thread.joinable()) {
@ -44,7 +45,7 @@ public:
} else { } else {
tilt_direction = mouse_move.Cast<float>(); tilt_direction = mouse_move.Cast<float>();
tilt_angle = MathUtil::Clamp(tilt_direction.Normalize() * sensitivity, 0.0f, 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; std::mutex tilt_mutex;
Math::Vec2<float> tilt_direction; Math::Vec2<float> tilt_direction;
float tilt_angle = 0; float tilt_angle = 0;
float tilt_clamp = 90;
bool is_tilting = false; bool is_tilting = false;
@ -126,8 +128,8 @@ private:
// can forward all the inputs to the implementation only when it is valid. // can forward all the inputs to the implementation only when it is valid.
class MotionEmuDeviceWrapper : public Input::MotionDevice { class MotionEmuDeviceWrapper : public Input::MotionDevice {
public: public:
MotionEmuDeviceWrapper(int update_millisecond, float sensitivity) { MotionEmuDeviceWrapper(int update_millisecond, float sensitivity, float tilt_clamp) {
device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity); device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity, tilt_clamp);
} }
std::tuple<Math::Vec3<float>, Math::Vec3<float>> GetStatus() const { std::tuple<Math::Vec3<float>, Math::Vec3<float>> GetStatus() const {
@ -140,7 +142,9 @@ public:
std::unique_ptr<Input::MotionDevice> MotionEmu::Create(const Common::ParamPackage& params) { std::unique_ptr<Input::MotionDevice> MotionEmu::Create(const Common::ParamPackage& params) {
int update_period = params.Get("update_period", 100); int update_period = params.Get("update_period", 100);
float sensitivity = params.Get("sensitivity", 0.01f); float sensitivity = params.Get("sensitivity", 0.01f);
auto device_wrapper = std::make_unique<MotionEmuDeviceWrapper>(update_period, sensitivity); float tilt_clamp = params.Get("tilt_clamp", 90.0f);
auto device_wrapper =
std::make_unique<MotionEmuDeviceWrapper>(update_period, sensitivity, tilt_clamp);
// Previously created device is disconnected here. Having two motion devices for 3DS is not // Previously created device is disconnected here. Having two motion devices for 3DS is not
// expected. // expected.
current_device = device_wrapper->device; current_device = device_wrapper->device;

@ -221,6 +221,8 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
MICROPROFILE_SCOPE(GPU_Drawing); MICROPROFILE_SCOPE(GPU_Drawing);
immediate_attribute_id = 0; immediate_attribute_id = 0;
Shader::OutputVertex::ValidateSemantics(regs.rasterizer);
auto* shader_engine = Shader::GetEngine(); auto* shader_engine = Shader::GetEngine();
shader_engine->SetupBatch(g_state.vs, regs.vs.main_offset); 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. // Later, these can be compiled and cached.
const u32 base_address = regs.pipeline.vertex_attributes.GetPhysicalBaseAddress(); const u32 base_address = regs.pipeline.vertex_attributes.GetPhysicalBaseAddress();
VertexLoader loader(regs.pipeline); VertexLoader loader(regs.pipeline);
Shader::OutputVertex::ValidateSemantics(regs.rasterizer);
// Load vertices // Load vertices
bool is_indexed = (id == PICA_REG_INDEX(pipeline.trigger_draw_indexed)); bool is_indexed = (id == PICA_REG_INDEX(pipeline.trigger_draw_indexed));

@ -87,6 +87,8 @@ struct RasterizerRegs {
BitField<8, 5, Semantic> map_y; BitField<8, 5, Semantic> map_y;
BitField<16, 5, Semantic> map_z; BitField<16, 5, Semantic> map_z;
BitField<24, 5, Semantic> map_w; BitField<24, 5, Semantic> map_w;
u32 raw;
} vs_output_attributes[7]; } vs_output_attributes[7];
INSERT_PADDING_WORDS(0xe); INSERT_PADDING_WORDS(0xe);

@ -65,6 +65,7 @@ PicaShaderConfig PicaShaderConfig::BuildFromRegs(const Pica::Regs& regs) {
PicaShaderConfig res; PicaShaderConfig res;
auto& state = res.state; 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)); std::memset(&state, 0, sizeof(PicaShaderConfig::State));
state.scissor_test_mode = regs.rasterizer.scissor_test.mode; state.scissor_test_mode = regs.rasterizer.scissor_test.mode;

@ -131,10 +131,6 @@ union PicaShaderConfig {
} state; } state;
}; };
#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER)
static_assert(std::is_trivially_copyable<PicaShaderConfig::State>::value,
"PicaShaderConfig::State must be trivially copyable");
#endif
/** /**
* Generates the GLSL vertex shader program source code for the current Pica state * Generates the GLSL vertex shader program source code for the current Pica state
@ -156,7 +152,7 @@ namespace std {
template <> template <>
struct hash<GLShader::PicaShaderConfig> { struct hash<GLShader::PicaShaderConfig> {
size_t operator()(const GLShader::PicaShaderConfig& k) const { size_t operator()(const GLShader::PicaShaderConfig& k) const {
return Common::ComputeHash64(&k.state, sizeof(GLShader::PicaShaderConfig::State)); return Common::ComputeStructHash64(k.state);
} }
}; };
} // namespace std } // namespace std

@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <cinttypes>
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
#include "common/bit_set.h" #include "common/bit_set.h"
@ -21,32 +22,41 @@ namespace Pica {
namespace Shader { 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, OutputVertex OutputVertex::FromAttributeBuffer(const RasterizerRegs& regs,
const AttributeBuffer& input) { const AttributeBuffer& input) {
// Setup output data // Setup output data
union { union {
OutputVertex ret{}; OutputVertex ret{};
std::array<float24, 24> 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<float24, 32> 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 that OutputVertex has enough space for 24 semantic registers
ASSERT(num_attributes <= 7); static_assert(sizeof(std::array<float24, 24>) == sizeof(ret),
for (unsigned int i = 0; i < num_attributes; ++i) { "Struct and array have different sizes.");
const auto& output_register_map = regs.vs_output_attributes[i];
RasterizerRegs::VSOutputAttributes::Semantic semantics[4] = { unsigned int num_attributes = regs.vs_output_total & 7;
output_register_map.map_x, output_register_map.map_y, output_register_map.map_z, for (size_t attrib = 0; attrib < num_attributes; ++attrib) {
output_register_map.map_w}; const auto output_register_map = regs.vs_output_attributes[attrib];
vertex_slots_overflow[output_register_map.map_x] = input.attr[attrib][0];
for (unsigned comp = 0; comp < 4; ++comp) { vertex_slots_overflow[output_register_map.map_y] = input.attr[attrib][1];
RasterizerRegs::VSOutputAttributes::Semantic semantic = semantics[comp]; vertex_slots_overflow[output_register_map.map_z] = input.attr[attrib][2];
if (semantic < vertex_slots.size()) { vertex_slots_overflow[output_register_map.map_w] = input.attr[attrib][3];
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);
}
}
} }
// The hardware takes the absolute and saturates vertex colors like this, *before* doing // The hardware takes the absolute and saturates vertex colors like this, *before* doing

@ -50,6 +50,7 @@ struct OutputVertex {
INSERT_PADDING_WORDS(1); INSERT_PADDING_WORDS(1);
Math::Vec2<float24> tc2; Math::Vec2<float24> tc2;
static void ValidateSemantics(const RasterizerRegs& regs);
static OutputVertex FromAttributeBuffer(const RasterizerRegs& regs, static OutputVertex FromAttributeBuffer(const RasterizerRegs& regs,
const AttributeBuffer& output); const AttributeBuffer& output);
}; };