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"
script: "./.travis/linux/build.sh"
after_success: "./.travis/linux/upload.sh"
- if: branch = master AND type = push
os: linux
env: NAME="transifex push"
sudo: required
dist: trusty
addons:
apt:
packages:
- libsdl2-dev
- qtbase5-dev
- libqt5opengl5-dev
- qttools5-dev
- qttools5-dev-tools
install: "./.travis/transifex/deps.sh"
script: "./.travis/transifex/build.sh"
after_success: "./.travis/transifex/upload.sh"
- os: osx
env: NAME="macos build"
sudo: false
osx_image: xcode7.3
osx_image: xcode9.2
install: "./.travis/macos/deps.sh"
script: "./.travis/macos/build.sh"
after_success: "./.travis/macos/upload.sh"

@ -3,7 +3,7 @@
cd /citra
apt-get update
apt-get install -y build-essential libsdl2-dev qtbase5-dev libqt5opengl5-dev libcurl4-openssl-dev libssl-dev wget git
apt-get install -y build-essential libsdl2-dev qtbase5-dev libqt5opengl5-dev qttools5-dev qttools5-dev-tools libcurl4-openssl-dev libssl-dev wget git
# Get a recent version of CMake
wget https://cmake.org/files/v3.9/cmake-3.9.0-Linux-x86_64.sh
@ -11,7 +11,7 @@ echo y | sh cmake-3.9.0-Linux-x86_64.sh --prefix=cmake
export PATH=/citra/cmake/cmake-3.9.0-Linux-x86_64/bin:$PATH
mkdir build && cd build
cmake .. -DUSE_SYSTEM_CURL=ON -DCMAKE_BUILD_TYPE=Release -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"}
cmake .. -DUSE_SYSTEM_CURL=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"}
make -j4
ctest -VV -C Release

@ -6,7 +6,7 @@ export MACOSX_DEPLOYMENT_TARGET=10.9
export Qt5_DIR=$(brew --prefix)/opt/qt5
mkdir build && cd build
cmake .. -DUSE_SYSTEM_CURL=ON -DCMAKE_OSX_ARCHITECTURES="x86_64;x86_64h" -DCMAKE_BUILD_TYPE=Release -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"}
cmake .. -DUSE_SYSTEM_CURL=ON -DCMAKE_OSX_ARCHITECTURES="x86_64;x86_64h" -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"}
make -j4
ctest -VV -C Release

@ -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(ENABLE_QT "Enable the Qt frontend" ON)
option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF)
option(CITRA_USE_BUNDLED_QT "Download bundled Qt binaries" OFF)
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
option(CITRA_USE_BUNDLED_CURL "FOR MINGW ONLY: Download curl configured against winssl instead of openssl" OFF)
if (ENABLE_WEB_SERVICE AND CITRA_USE_BUNDLED_CURL AND WINDOWS AND MSVC)
message("Turning off use bundled curl as msvc can compile curl on cpr")
SET(CITRA_USE_BUNDLED_CURL OFF CACHE BOOL "" FORCE)
endif()
if (ENABLE_WEB_SERVICE AND NOT CITRA_USE_BUNDLED_CURL AND MINGW)
message(AUTHOR_WARNING "Turning on CITRA_USE_BUNDLED_CURL. Override it only if you know what you are doing.")
SET(CITRA_USE_BUNDLED_CURL ON CACHE BOOL "" FORCE)
@ -65,14 +71,16 @@ function(detect_architecture symbol arch)
endif()
endfunction()
if (MSVC)
detect_architecture("_M_AMD64" x86_64)
detect_architecture("_M_IX86" x86)
detect_architecture("_M_ARM" ARM)
else()
detect_architecture("__x86_64__" x86_64)
detect_architecture("__i386__" x86)
detect_architecture("__arm__" ARM)
if (NOT ENABLE_GENERIC)
if (MSVC)
detect_architecture("_M_AMD64" x86_64)
detect_architecture("_M_IX86" x86)
detect_architecture("_M_ARM" ARM)
else()
detect_architecture("__x86_64__" x86_64)
detect_architecture("__i386__" x86)
detect_architecture("__arm__" ARM)
endif()
endif()
if (NOT DEFINED ARCHITECTURE)
set(ARCHITECTURE "GENERIC")
@ -230,6 +238,10 @@ if (ENABLE_QT)
endif()
find_package(Qt5 REQUIRED COMPONENTS Widgets OpenGL ${QT_PREFIX_HINT})
if (ENABLE_QT_TRANSLATION)
find_package(Qt5 REQUIRED COMPONENTS LinguistTools ${QT_PREFIX_HINT})
endif()
endif()
if (ENABLE_WEB_SERVICE)

@ -21,6 +21,8 @@ Most of the development happens on GitHub. It's also where [our central reposito
If you want to contribute please take a look at the [Contributor's Guide](CONTRIBUTING.md) and [Developer Information](https://github.com/citra-emu/citra/wiki/Developer-Information). You should as well contact any of the developers in the forum in order to know about the current state of the emulator because the [TODO list](https://docs.google.com/document/d/1SWIop0uBI9IW8VGg97TAtoT_CHNoP42FzYmvG1F4QDA) isn't maintained anymore.
If you want to contribute to the user interface translation, please checkout [citra project on transifex](https://www.transifex.com/citra/citra). We centralize the translation work there, and periodically upstream translation.
### Building
* __Windows__: [Windows Build](https://github.com/citra-emu/citra/wiki/Building-For-Windows)

@ -46,7 +46,7 @@ before_build:
# redirect stderr and change the exit code to prevent powershell from cancelling the build if cmake prints a warning
cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DCITRA_USE_BUNDLED_QT=1 -DCITRA_USE_BUNDLED_SDL2=1 -DCMAKE_USE_OPENSSL=0 -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} .. 2>&1 && exit 0'
} else {
C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DUSE_SYSTEM_CURL=1 -DCITRA_USE_BUNDLED_CURL=1 -DCMAKE_BUILD_TYPE=Release -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} .. 2>&1"
C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DUSE_SYSTEM_CURL=1 -DCITRA_USE_BUNDLED_CURL=1 -DCMAKE_BUILD_TYPE=Release -DENABLE_QT_TRANSLATION=ON -DCITRA_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} .. 2>&1"
}
- cd ..
@ -118,23 +118,16 @@ after_build:
Get-ChildItem "$CMAKE_BINARY_DIR" -Recurse -Filter "libcurl.dll" | Copy-Item -destination $RELEASE_DIST
Copy-Item -path "$CMAKE_SOURCE_DIR/license.txt" -destination $RELEASE_DIST
Copy-Item -path "$CMAKE_SOURCE_DIR/README.md" -destination $RELEASE_DIST
# copy all the dll dependencies to the release folder
# hardcoded list because we don't build static and determining the list of dlls from the binary is a pain.
$MingwDLLs = "Qt5Core.dll","Qt5Widgets.dll","Qt5Gui.dll","Qt5OpenGL.dll",
# QT dll dependencies
"libbz2-*.dll","libicudt*.dll","libicuin*.dll","libicuuc*.dll","libffi-*.dll",
"libfreetype-*.dll","libglib-*.dll","libgobject-*.dll","libgraphite2.dll","libiconv-*.dll",
"libharfbuzz-*.dll","libintl-*.dll","libpcre-*.dll","libpcre16-*.dll","libpcre2-16-*.dll","libpng16-*.dll",
# Runtime/Other dependencies
"libgcc_s_seh-*.dll","libstdc++-*.dll","libwinpthread-*.dll","SDL2.dll","zlib1.dll"
. "./.appveyor/UtilityFunctions.ps1"
$DLLSearchPath = "$CMAKE_BINARY_DIR\externals\curl-7_55_1\lib;C:\msys64\mingw64\bin;$env:PATH"
$MingwDLLs = RecursivelyGetDeps $DLLSearchPath "$RELEASE_DIST\citra.exe"
$MingwDLLs += RecursivelyGetDeps $DLLSearchPath "$RELEASE_DIST\citra-qt.exe"
Write-Host "Detected the following dependencies:"
Write-Host $MingwDLLs
foreach ($file in $MingwDLLs) {
Copy-Item -path "C:/msys64/mingw64/bin/$file" -force -destination "$RELEASE_DIST"
}
# the above list copies a few extra debug dlls that aren't needed (thanks globbing patterns!)
# so we can remove them by hardcoding another list of extra dlls to remove
$DebugDLLs = "libicudtd*.dll","libicuind*.dll","libicuucd*.dll"
foreach ($file in $DebugDLLs) {
Remove-Item -path "$RELEASE_DIST/$file"
Copy-Item -path "$file" -force -destination "$RELEASE_DIST"
}
# copy the qt windows plugin dll to platforms

@ -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)
# Dynarmic
# Dynarmic will skip defining xbyak if it's already defined, we then define it below
add_library(xbyak INTERFACE)
option(DYNARMIC_TESTS OFF)
set(DYNARMIC_NO_BUNDLED_FMT ON)
add_subdirectory(dynarmic)
if (ARCHITECTURE_x86_64)
# Dynarmic will skip defining xbyak if it's already defined, we then define it below
add_library(xbyak INTERFACE)
option(DYNARMIC_TESTS OFF)
set(DYNARMIC_NO_BUNDLED_FMT ON)
add_subdirectory(dynarmic)
endif()
# libfmt
add_subdirectory(fmt)

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

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

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

@ -89,19 +89,19 @@ EmuWindow_SDL2::EmuWindow_SDL2() {
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
if (render_window == nullptr) {
LOG_CRITICAL(Frontend, "Failed to create SDL2 window! Exiting...");
LOG_CRITICAL(Frontend, "Failed to create SDL2 window: %s", SDL_GetError());
exit(1);
}
gl_context = SDL_GL_CreateContext(render_window);
if (gl_context == nullptr) {
LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context! Exiting...");
LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context: %s", SDL_GetError());
exit(1);
}
if (!gladLoadGLLoader(static_cast<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);
}

@ -90,12 +90,46 @@ file(GLOB_RECURSE THEMES ${CMAKE_SOURCE_DIR}/dist/qt_themes/*)
qt5_wrap_ui(UI_HDRS ${UIS})
if (ENABLE_QT_TRANSLATION)
set(CITRA_QT_LANGUAGES "${CMAKE_SOURCE_DIR}/dist/languages" CACHE PATH "Path to the translation bundle for the Qt frontend")
option(GENERATE_QT_TRANSLATION "Generate en.ts as the translation source file" OFF)
# Update source TS file if enabled
if (GENERATE_QT_TRANSLATION)
get_target_property(SRCS citra-qt SOURCES)
qt5_create_translation(QM_FILES ${SRCS} ${UIS} ${CITRA_QT_LANGUAGES}/en.ts)
add_custom_target(translation ALL DEPENDS ${CITRA_QT_LANGUAGES}/en.ts)
endif()
# Find all TS files except en.ts
file(GLOB_RECURSE LANGUAGES_TS ${CITRA_QT_LANGUAGES}/*.ts)
list(REMOVE_ITEM LANGUAGES_TS ${CITRA_QT_LANGUAGES}/en.ts)
# Compile TS files to QM files
qt5_add_translation(LANGUAGES_QM ${LANGUAGES_TS})
# Build a QRC file from the QM file list
set(LANGUAGES_QRC ${CMAKE_CURRENT_BINARY_DIR}/languages.qrc)
file(WRITE ${LANGUAGES_QRC} "<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
PRIVATE
${ICONS}
${THEMES}
${UI_HDRS}
${UIS}
${LANGUAGES}
)
if (APPLE)

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

@ -58,7 +58,9 @@ void Config::ReadValues() {
}
Settings::values.motion_device =
qt_config->value("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01")
qt_config
->value("motion_device",
"engine:motion_emu,update_period:100,sensitivity:0.01,tilt_clamp:90.0")
.toString()
.toStdString();
Settings::values.touch_device =
@ -182,6 +184,7 @@ void Config::ReadValues() {
UISettings::values.gamedir = qt_config->value("gameListRootDir", ".").toString();
UISettings::values.gamedir_deepscan = qt_config->value("gameListDeepScan", false).toBool();
UISettings::values.recent_files = qt_config->value("recentFiles").toStringList();
UISettings::values.language = qt_config->value("language", "").toString();
qt_config->endGroup();
qt_config->beginGroup("Shortcuts");
@ -333,6 +336,7 @@ void Config::SaveValues() {
qt_config->setValue("gameListRootDir", UISettings::values.gamedir);
qt_config->setValue("gameListDeepScan", UISettings::values.gamedir_deepscan);
qt_config->setValue("recentFiles", UISettings::values.recent_files);
qt_config->setValue("language", UISettings::values.language);
qt_config->endGroup();
qt_config->beginGroup("Shortcuts");

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

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

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

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

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

@ -10,6 +10,8 @@
ConfigureDialog::ConfigureDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ConfigureDialog) {
ui->setupUi(this);
this->setConfiguration();
connect(ui->generalTab, &ConfigureGeneral::languageChanged, this,
&ConfigureDialog::onLanguageChanged);
}
ConfigureDialog::~ConfigureDialog() {}
@ -26,3 +28,15 @@ void ConfigureDialog::applyConfiguration() {
ui->webTab->applyConfiguration();
Settings::Apply();
}
void ConfigureDialog::onLanguageChanged(const QString& locale) {
emit languageChanged(locale);
ui->retranslateUi(this);
ui->generalTab->retranslateUi();
ui->systemTab->retranslateUi();
ui->inputTab->retranslateUi();
ui->graphicsTab->retranslateUi();
ui->audioTab->retranslateUi();
ui->debugTab->retranslateUi();
ui->webTab->retranslateUi();
}

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

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

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

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>300</width>
<height>377</height>
<width>345</width>
<height>493</height>
</rect>
</property>
<property name="windowTitle">
@ -38,6 +38,20 @@
</property>
</widget>
</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>
</item>
</layout>

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

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

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

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

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

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

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

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

@ -98,6 +98,8 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
// register size_t to use in slots and signals
qRegisterMetaType<size_t>("size_t");
LoadTranslation();
Pica::g_debug_context = Pica::DebugContext::Construct();
setAcceptDrops(true);
ui.setupUi(this);
@ -115,8 +117,8 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
ConnectMenuEvents();
ConnectWidgetEvents();
setWindowTitle(QString("Citra %1| %2-%3")
.arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
SetupUIStrings();
show();
game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
@ -892,6 +894,8 @@ void GMainWindow::ToggleWindowMode() {
void GMainWindow::OnConfigure() {
ConfigureDialog configureDialog(this);
connect(&configureDialog, &ConfigureDialog::languageChanged, this,
&GMainWindow::OnLanguageChanged);
auto result = configureDialog.exec();
if (result == QDialog::Accepted) {
configureDialog.applyConfiguration();
@ -995,6 +999,7 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
} else {
// Only show the message if the game is still running.
if (emu_thread) {
emu_thread->SetRunning(true);
message_label->setText(status_message);
message_label->setVisible(true);
}
@ -1105,6 +1110,45 @@ void GMainWindow::UpdateUITheme() {
}
}
void GMainWindow::LoadTranslation() {
// If the selected language is English, no need to install any translation
if (UISettings::values.language == "en") {
return;
}
bool loaded;
if (UISettings::values.language.isEmpty()) {
// If the selected language is empty, use system locale
loaded = translator.load(QLocale(), "", "", ":/languages/");
} else {
// Otherwise load from the specified file
loaded = translator.load(UISettings::values.language, ":/languages/");
}
if (loaded) {
qApp->installTranslator(&translator);
} else {
UISettings::values.language = "en";
}
}
void GMainWindow::OnLanguageChanged(const QString& locale) {
if (UISettings::values.language != "en") {
qApp->removeTranslator(&translator);
}
UISettings::values.language = locale;
LoadTranslation();
ui.retranslateUi(this);
SetupUIStrings();
}
void GMainWindow::SetupUIStrings() {
setWindowTitle(
tr("Citra %1| %2-%3").arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
}
#ifdef main
#undef main
#endif

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

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

@ -32,6 +32,8 @@ add_library(common STATIC
break_points.cpp
break_points.h
chunk_file.h
cityhash.cpp
cityhash.h
code_block.h
color.h
common_funcs.h
@ -39,7 +41,6 @@ add_library(common STATIC
common_types.h
file_util.cpp
file_util.h
hash.cpp
hash.h
linear_disk_cache.h
logging/backend.cpp

@ -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
#include <cstddef>
#include "common/cityhash.h"
#include "common/common_types.h"
namespace Common {
void MurmurHash3_128(const void* key, size_t len, u32 seed, void* out);
/**
* Computes a 64-bit hash over the specified block of data
* @param data Block of data to compute hash over
@ -18,9 +17,20 @@ void MurmurHash3_128(const void* key, size_t len, u32 seed, void* out);
* @returns 64-bit hash value that was computed over the data block
*/
static inline u64 ComputeHash64(const void* data, size_t len) {
u64 res[2];
MurmurHash3_128(data, len, 0, res);
return res[0];
return CityHash64(static_cast<const char*>(data), len);
}
/**
* Computes a 64-bit hash of a struct. In addition to being POD (trivially copyable and having
* standard layout), it is also critical that either the struct includes no padding, or that any
* padding is initialized to a known value by memsetting the struct to 0 before filling it in.
*/
template <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

@ -1,10 +1,6 @@
add_library(core STATIC
3ds.h
arm/arm_interface.h
arm/dynarmic/arm_dynarmic.cpp
arm/dynarmic/arm_dynarmic.h
arm/dynarmic/arm_dynarmic_cp15.cpp
arm/dynarmic/arm_dynarmic_cp15.h
arm/dyncom/arm_dyncom.cpp
arm/dyncom/arm_dyncom.h
arm/dyncom/arm_dyncom_dec.cpp
@ -404,7 +400,17 @@ add_library(core STATIC
create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core)
target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp dynarmic fmt)
target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp fmt)
if (ENABLE_WEB_SERVICE)
target_link_libraries(core PUBLIC json-headers web_service)
endif()
if (ARCHITECTURE_x86_64)
target_sources(core PRIVATE
arm/dynarmic/arm_dynarmic.cpp
arm/dynarmic/arm_dynarmic.h
arm/dynarmic/arm_dynarmic_cp15.cpp
arm/dynarmic/arm_dynarmic_cp15.h
)
target_link_libraries(core PRIVATE dynarmic)
endif()

@ -7,7 +7,9 @@
#include "audio_core/audio_core.h"
#include "common/logging/log.h"
#include "core/arm/arm_interface.h"
#ifdef ARCHITECTURE_x86_64
#include "core/arm/dynarmic/arm_dynarmic.h"
#endif
#include "core/arm/dyncom/arm_dyncom.h"
#include "core/core.h"
#include "core/core_timing.h"
@ -147,7 +149,12 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
LOG_DEBUG(HW_Memory, "initialized OK");
if (Settings::values.use_cpu_jit) {
#ifdef ARCHITECTURE_x86_64
cpu_core = std::make_unique<ARM_Dynarmic>(USER32MODE);
#else
cpu_core = std::make_unique<ARM_DynCom>(USER32MODE);
LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
#endif
} else {
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
/// in each service must inherit from this.
struct SessionDataBase {};
struct SessionDataBase {
virtual ~SessionDataBase() = default;
};
protected:
/// Creates the storage for the session data of the service.

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

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

@ -143,6 +143,36 @@ static ResultCode ControlMemory(u32* out_addr, u32 operation, u32 addr0, u32 add
return RESULT_SUCCESS;
}
static void ExitProcess() {
LOG_INFO(Kernel_SVC, "Process %u exiting", g_current_process->process_id);
ASSERT_MSG(g_current_process->status == ProcessStatus::Running, "Process has already exited");
g_current_process->status = ProcessStatus::Exited;
// Stop all the process threads that are currently waiting for objects.
auto& thread_list = GetThreadList();
for (auto& thread : thread_list) {
if (thread->owner_process != g_current_process)
continue;
if (thread == GetCurrentThread())
continue;
// TODO(Subv): When are the other running/ready threads terminated?
ASSERT_MSG(thread->status == THREADSTATUS_WAIT_SYNCH_ANY ||
thread->status == THREADSTATUS_WAIT_SYNCH_ALL,
"Exiting processes with non-waiting threads is currently unimplemented");
thread->Stop();
}
// Kill the current thread
GetCurrentThread()->Stop();
Core::System::GetInstance().PrepareReschedule();
}
/// Maps a memory block to specified address
static ResultCode MapMemoryBlock(Handle handle, u32 addr, u32 permissions, u32 other_permissions) {
LOG_TRACE(Kernel_SVC,
@ -685,7 +715,7 @@ static ResultCode GetResourceLimitLimitValues(VAddr values, Handle resource_limi
for (unsigned int i = 0; i < name_count; ++i) {
u32 name = Memory::Read32(names + i * sizeof(u32));
s64 value = resource_limit->GetMaxResourceValue(names);
s64 value = resource_limit->GetMaxResourceValue(name);
Memory::Write64(values + i * sizeof(u64), value);
}
@ -1232,7 +1262,7 @@ static const FunctionDef SVC_Table[] = {
{0x00, nullptr, "Unknown"},
{0x01, HLE::Wrap<ControlMemory>, "ControlMemory"},
{0x02, HLE::Wrap<QueryMemory>, "QueryMemory"},
{0x03, nullptr, "ExitProcess"},
{0x03, ExitProcess, "ExitProcess"},
{0x04, nullptr, "GetProcessAffinityMask"},
{0x05, nullptr, "SetProcessAffinityMask"},
{0x06, nullptr, "GetProcessIdealProcessor"},
@ -1373,6 +1403,9 @@ void CallSVC(u32 immediate) {
// Lock the global kernel mutex when we enter the kernel HLE.
std::lock_guard<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);
if (info) {
if (info->func) {

@ -9,7 +9,6 @@
#include "core/core.h"
#include "core/hle/ipc.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/result.h"
@ -51,6 +50,20 @@ constexpr ResultCode ERR_REGS_INVALID_SIZE(ErrorDescription::InvalidSize, ErrorM
ErrorSummary::InvalidArgument,
ErrorLevel::Usage); // 0xE0E02BEC
/// Maximum number of threads that can be registered at the same time in the GSP module.
constexpr u32 MaxGSPThreads = 4;
/// Thread ids currently in use by the sessions connected to the GSPGPU service.
static std::array<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
static inline u8* GetCommandBuffer(Kernel::SharedPtr<Kernel::SharedMemory> shared_memory,
u32 thread_id) {
@ -319,12 +332,16 @@ void GSP_GPU::RegisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x13, 1, 2);
u32 flags = rp.Pop<u32>();
interrupt_event = rp.PopObject<Kernel::Event>();
auto interrupt_event = rp.PopObject<Kernel::Event>();
// TODO(mailwl): return right error code instead assert
ASSERT_MSG((interrupt_event != nullptr), "handle is not valid!");
interrupt_event->name = "GSP_GSP_GPU::interrupt_event";
SessionData* session_data = GetSessionData(ctx.Session());
session_data->interrupt_event = std::move(interrupt_event);
session_data->registered = true;
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
if (first_initialization) {
@ -335,25 +352,60 @@ void GSP_GPU::RegisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
}
rb.Push(thread_id);
rb.Push(session_data->thread_id);
rb.PushCopyObjects(shared_memory);
thread_id++;
interrupt_event->Signal(); // TODO(bunnei): Is this correct?
LOG_WARNING(Service_GSP, "called, flags=0x%08X", flags);
LOG_DEBUG(Service_GSP, "called, flags=0x%08X", flags);
}
void GSP_GPU::UnregisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x14, 0, 0);
thread_id = 0;
interrupt_event = nullptr;
SessionData* session_data = GetSessionData(ctx.Session());
session_data->interrupt_event = nullptr;
session_data->registered = false;
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_GSP, "(STUBBED) called");
LOG_DEBUG(Service_GSP, "called");
}
void GSP_GPU::SignalInterruptForThread(InterruptId interrupt_id, u32 thread_id) {
SessionData* session_data = FindRegisteredThreadData(thread_id);
if (session_data == nullptr)
return;
auto interrupt_event = session_data->interrupt_event;
if (interrupt_event == nullptr) {
LOG_WARNING(Service_GSP, "cannot synchronize until GSP event has been created!");
return;
}
InterruptRelayQueue* interrupt_relay_queue = GetInterruptRelayQueue(shared_memory, thread_id);
u8 next = interrupt_relay_queue->index;
next += interrupt_relay_queue->number_interrupts;
next = next % 0x34; // 0x34 is the number of interrupt slots
interrupt_relay_queue->number_interrupts += 1;
interrupt_relay_queue->slot[next] = interrupt_id;
interrupt_relay_queue->error_code = 0x0; // No error
// Update framebuffer information if requested
// TODO(yuriks): Confirm where this code should be called. It is definitely updated without
// executing any GSP commands, only waiting on the event.
// TODO(Subv): The real GSP module triggers PDC0 after updating both the top and bottom
// screen, it is currently unknown what PDC1 does.
int screen_id =
(interrupt_id == InterruptId::PDC0) ? 0 : (interrupt_id == InterruptId::PDC1) ? 1 : -1;
if (screen_id != -1) {
FrameBufferUpdate* info = GetFrameBufferInfo(thread_id, screen_id);
if (info->is_dirty) {
GSP::SetBufferSwap(screen_id, info->framebuffer_info[info->index]);
info->is_dirty.Assign(false);
}
}
interrupt_event->Signal();
}
/**
@ -363,43 +415,26 @@ void GSP_GPU::UnregisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) {
* @todo This probably does not belong in the GSP module, instead move to video_core
*/
void GSP_GPU::SignalInterrupt(InterruptId interrupt_id) {
if (!gpu_right_acquired) {
return;
}
if (nullptr == interrupt_event) {
LOG_WARNING(Service_GSP, "cannot synchronize until GSP event has been created!");
return;
}
if (nullptr == shared_memory) {
LOG_WARNING(Service_GSP, "cannot synchronize until GSP shared memory has been created!");
return;
}
for (int thread_id = 0; thread_id < 0x4; ++thread_id) {
InterruptRelayQueue* interrupt_relay_queue =
GetInterruptRelayQueue(shared_memory, thread_id);
u8 next = interrupt_relay_queue->index;
next += interrupt_relay_queue->number_interrupts;
next = next % 0x34; // 0x34 is the number of interrupt slots
interrupt_relay_queue->number_interrupts += 1;
interrupt_relay_queue->slot[next] = interrupt_id;
interrupt_relay_queue->error_code = 0x0; // No error
// Update framebuffer information if requested
// TODO(yuriks): Confirm where this code should be called. It is definitely updated without
// executing any GSP commands, only waiting on the event.
int screen_id =
(interrupt_id == InterruptId::PDC0) ? 0 : (interrupt_id == InterruptId::PDC1) ? 1 : -1;
if (screen_id != -1) {
FrameBufferUpdate* info = GetFrameBufferInfo(thread_id, screen_id);
if (info->is_dirty) {
GSP::SetBufferSwap(screen_id, info->framebuffer_info[info->index]);
info->is_dirty.Assign(false);
}
// The PDC0 and PDC1 interrupts are fired even if the GPU right hasn't been acquired.
// Normal interrupts are only signaled for the active thread (ie, the thread that has the GPU
// right), but the PDC0/1 interrupts are signaled for every registered thread.
if (interrupt_id == InterruptId::PDC0 || interrupt_id == InterruptId::PDC1) {
for (u32 thread_id = 0; thread_id < MaxGSPThreads; ++thread_id) {
SignalInterruptForThread(interrupt_id, thread_id);
}
return;
}
interrupt_event->Signal();
// For normal interrupts, don't do anything if no process has acquired the GPU right.
if (active_thread_id == -1)
return;
SignalInterruptForThread(interrupt_id, active_thread_id);
}
MICROPROFILE_DEFINE(GPU_GSP_DMA, "GPU", "GSP DMA", MP_RGB(100, 0, 255));
@ -622,18 +657,34 @@ void GSP_GPU::AcquireRight(Kernel::HLERequestContext& ctx) {
u32 flag = rp.Pop<u32>();
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);
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_GSP, "called flag=%08X process=%u", flag, process->process_id);
if (active_thread_id == session_data->thread_id) {
rb.Push(ResultCode(ErrorDescription::AlreadyDone, ErrorModule::GX, ErrorSummary::Success,
ErrorLevel::Success));
return;
}
// TODO(Subv): This case should put the caller thread to sleep until the right is released.
ASSERT_MSG(active_thread_id == -1, "GPU right has already been acquired");
active_thread_id = session_data->thread_id;
rb.Push(RESULT_SUCCESS);
}
void GSP_GPU::ReleaseRight(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x17, 0, 0);
gpu_right_acquired = false;
SessionData* session_data = GetSessionData(ctx.Session());
ASSERT_MSG(active_thread_id == session_data->thread_id,
"Wrong thread tried to release GPU right");
active_thread_id = -1;
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(RESULT_SUCCESS);
@ -655,6 +706,17 @@ void GSP_GPU::StoreDataCache(Kernel::HLERequestContext& ctx) {
size, process->process_id);
}
SessionData* GSP_GPU::FindRegisteredThreadData(u32 thread_id) {
for (auto& session_info : connected_sessions) {
SessionData* data = static_cast<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) {
static const FunctionInfo functions[] = {
{0x00010082, &GSP_GPU::WriteHWRegs, "WriteHWRegs"},
@ -691,17 +753,26 @@ GSP_GPU::GSP_GPU() : ServiceFramework("gsp::Gpu", 2) {
};
RegisterHandlers(functions);
interrupt_event = nullptr;
using Kernel::MemoryPermission;
shared_memory = Kernel::SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite,
MemoryPermission::ReadWrite, 0,
Kernel::MemoryRegion::BASE, "GSP:SharedMemory");
thread_id = 0;
gpu_right_acquired = false;
first_initialization = true;
};
SessionData::SessionData() {
// Assign a new thread id to this session when it connects. Note: In the real GSP service this
// is done through a real thread (svcCreateThread) but we have to simulate it since our HLE
// services don't have threads.
thread_id = GetUnusedThreadId();
used_thread_ids[thread_id] = true;
}
SessionData::~SessionData() {
// Free the thread id slot so that other sessions can use it.
used_thread_ids[thread_id] = false;
}
} // namespace GSP
} // namespace Service

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

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

@ -5,17 +5,29 @@
#pragma once
#include <array>
#include <atomic>
#ifndef _MSC_VER
#include <cstddef>
#endif
#include <memory>
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "core/frontend/input.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/service/service.h"
#include "core/settings.h"
namespace Service {
namespace Kernel {
class Event;
class SharedMemory;
}
class Interface;
namespace CoreTiming {
class EventType;
};
namespace Service {
namespace HID {
@ -186,93 +198,140 @@ struct DirectionState {
/// Translates analog stick axes to directions. This is exposed for ir_rst module to use.
DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y);
/**
* HID::GetIPCHandles service function
* Inputs:
* None
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 2 : IPC Command Structure translate-header
* 3 : Handle to HID shared memory
* 4 : Event signaled by HID
* 5 : Event signaled by HID
* 6 : Event signaled by HID
* 7 : Gyroscope event
* 8 : Event signaled by HID
*/
void GetIPCHandles(Interface* self);
class Module final {
public:
Module();
/**
* HID::EnableAccelerometer service function
* Inputs:
* None
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void EnableAccelerometer(Interface* self);
class Interface : public ServiceFramework<Interface> {
public:
Interface(std::shared_ptr<Module> hid, const char* name, u32 max_session);
/**
* HID::DisableAccelerometer service function
* Inputs:
* None
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void DisableAccelerometer(Interface* self);
protected:
/**
* HID::GetIPCHandles service function
* Inputs:
* None
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 2 : IPC Command Structure translate-header
* 3 : Handle to HID shared memory
* 4 : Event signaled by HID
* 5 : Event signaled by HID
* 6 : Event signaled by HID
* 7 : Gyroscope event
* 8 : Event signaled by HID
*/
void GetIPCHandles(Kernel::HLERequestContext& ctx);
/**
* HID::EnableGyroscopeLow service function
* Inputs:
* None
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void EnableGyroscopeLow(Interface* self);
/**
* HID::EnableAccelerometer service function
* Inputs:
* None
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void EnableAccelerometer(Kernel::HLERequestContext& ctx);
/**
* HID::DisableGyroscopeLow service function
* Inputs:
* None
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void DisableGyroscopeLow(Interface* self);
/**
* HID::DisableAccelerometer service function
* Inputs:
* None
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void DisableAccelerometer(Kernel::HLERequestContext& ctx);
/**
* HID::GetSoundVolume service function
* Inputs:
* None
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 2 : u8 output value
*/
void GetSoundVolume(Interface* self);
/**
* HID::EnableGyroscopeLow service function
* Inputs:
* None
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void EnableGyroscopeLow(Kernel::HLERequestContext& ctx);
/**
* HID::GetGyroscopeLowRawToDpsCoefficient service function
* Inputs:
* None
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 2 : float output value
*/
void GetGyroscopeLowRawToDpsCoefficient(Service::Interface* self);
/**
* HID::DisableGyroscopeLow service function
* Inputs:
* None
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void DisableGyroscopeLow(Kernel::HLERequestContext& ctx);
/**
* HID::GetGyroscopeLowCalibrateParam service function
* Inputs:
* None
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 2~6 (18 bytes) : struct GyroscopeCalibrateParam
*/
void GetGyroscopeLowCalibrateParam(Service::Interface* self);
/**
* HID::GetSoundVolume service function
* Inputs:
* None
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 2 : u8 output value
*/
void GetSoundVolume(Kernel::HLERequestContext& ctx);
/// Initialize HID service
void Init();
/**
* HID::GetGyroscopeLowRawToDpsCoefficient service function
* Inputs:
* None
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 2 : float output value
*/
void GetGyroscopeLowRawToDpsCoefficient(Kernel::HLERequestContext& ctx);
/// Shutdown HID service
void Shutdown();
/**
* HID::GetGyroscopeLowCalibrateParam service function
* Inputs:
* None
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 2~6 (18 bytes) : struct GyroscopeCalibrateParam
*/
void GetGyroscopeLowCalibrateParam(Kernel::HLERequestContext& ctx);
private:
std::shared_ptr<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
void ReloadInputDevices();

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

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

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

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

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

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

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

@ -8,7 +8,7 @@ add_library(input_common STATIC
motion_emu.cpp
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)

@ -17,11 +17,12 @@ namespace InputCommon {
// Implementation class of the motion emulation device
class MotionEmuDevice {
public:
MotionEmuDevice(int update_millisecond, float sensitivity)
MotionEmuDevice(int update_millisecond, float sensitivity, float tilt_clamp)
: update_millisecond(update_millisecond),
update_duration(std::chrono::duration_cast<std::chrono::steady_clock::duration>(
std::chrono::milliseconds(update_millisecond))),
sensitivity(sensitivity), motion_emu_thread(&MotionEmuDevice::MotionEmuThread, this) {}
sensitivity(sensitivity), tilt_clamp(tilt_clamp),
motion_emu_thread(&MotionEmuDevice::MotionEmuThread, this) {}
~MotionEmuDevice() {
if (motion_emu_thread.joinable()) {
@ -44,7 +45,7 @@ public:
} else {
tilt_direction = mouse_move.Cast<float>();
tilt_angle = MathUtil::Clamp(tilt_direction.Normalize() * sensitivity, 0.0f,
MathUtil::PI * 0.5f);
MathUtil::PI * this->tilt_clamp / 180.0f);
}
}
}
@ -70,6 +71,7 @@ private:
std::mutex tilt_mutex;
Math::Vec2<float> tilt_direction;
float tilt_angle = 0;
float tilt_clamp = 90;
bool is_tilting = false;
@ -126,8 +128,8 @@ private:
// can forward all the inputs to the implementation only when it is valid.
class MotionEmuDeviceWrapper : public Input::MotionDevice {
public:
MotionEmuDeviceWrapper(int update_millisecond, float sensitivity) {
device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity);
MotionEmuDeviceWrapper(int update_millisecond, float sensitivity, float tilt_clamp) {
device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity, tilt_clamp);
}
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) {
int update_period = params.Get("update_period", 100);
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
// expected.
current_device = device_wrapper->device;

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

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

@ -65,6 +65,7 @@ PicaShaderConfig PicaShaderConfig::BuildFromRegs(const Pica::Regs& regs) {
PicaShaderConfig res;
auto& state = res.state;
// Memset structure to zero padding bits, so that they will be deterministic when hashing
std::memset(&state, 0, sizeof(PicaShaderConfig::State));
state.scissor_test_mode = regs.rasterizer.scissor_test.mode;

@ -131,10 +131,6 @@ union PicaShaderConfig {
} 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
@ -156,7 +152,7 @@ namespace std {
template <>
struct hash<GLShader::PicaShaderConfig> {
size_t operator()(const GLShader::PicaShaderConfig& k) const {
return Common::ComputeHash64(&k.state, sizeof(GLShader::PicaShaderConfig::State));
return Common::ComputeStructHash64(k.state);
}
};
} // namespace std

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

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