summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis-build-docker.sh20
-rwxr-xr-x.travis-build.sh10
-rwxr-xr-x.travis-deps.sh30
-rwxr-xr-x.travis-upload.sh235
-rw-r--r--.travis.yml18
-rw-r--r--CMakeLists.txt4
-rw-r--r--README.md2
-rw-r--r--appveyor.yml185
-rw-r--r--dist/citra.icnsbin1027012 -> 211056 bytes
-rw-r--r--dist/citra.icobin509287 -> 370070 bytes
-rw-r--r--dist/citra.manifest24
-rw-r--r--dist/citra.svg80
-rw-r--r--dist/doc-icon.pngbin8791 -> 7768 bytes
m---------externals/soundtouch0
-rw-r--r--src/audio_core/hle/source.cpp49
-rw-r--r--src/audio_core/interpolate.cpp86
-rw-r--r--src/audio_core/interpolate.h27
-rw-r--r--src/citra/citra.cpp2
-rw-r--r--src/citra/citra.rc8
-rw-r--r--src/citra/config.cpp9
-rw-r--r--src/citra/default_ini.h27
-rw-r--r--src/citra/emu_window/emu_window_sdl2.cpp10
-rw-r--r--src/citra/emu_window/emu_window_sdl2.h4
-rw-r--r--src/citra_qt/CMakeLists.txt3
-rw-r--r--src/citra_qt/bootmanager.cpp10
-rw-r--r--src/citra_qt/bootmanager.h4
-rw-r--r--src/citra_qt/citra-qt.rc12
-rw-r--r--src/citra_qt/configuration/config.cpp17
-rw-r--r--src/citra_qt/configuration/configure.ui15
-rw-r--r--src/citra_qt/configuration/configure_dialog.cpp1
-rw-r--r--src/citra_qt/configuration/configure_graphics.ui11
-rw-r--r--src/citra_qt/configuration/configure_web.cpp52
-rw-r--r--src/citra_qt/configuration/configure_web.h30
-rw-r--r--src/citra_qt/configuration/configure_web.ui153
-rw-r--r--src/citra_qt/main.cpp46
-rw-r--r--src/citra_qt/main.h2
-rw-r--r--src/citra_qt/ui_settings.h2
-rw-r--r--src/core/CMakeLists.txt8
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.cpp2
-rw-r--r--src/core/arm/dyncom/arm_dyncom_interpreter.cpp8
-rw-r--r--src/core/arm/skyeye_common/armstate.h2
-rw-r--r--src/core/core.cpp5
-rw-r--r--src/core/file_sys/archive_backend.cpp2
-rw-r--r--src/core/frontend/emu_window.cpp97
-rw-r--r--src/core/frontend/emu_window.h114
-rw-r--r--src/core/frontend/framebuffer_layout.cpp36
-rw-r--r--src/core/frontend/framebuffer_layout.h11
-rw-r--r--src/core/frontend/input.h25
-rw-r--r--src/core/frontend/motion_emu.cpp89
-rw-r--r--src/core/frontend/motion_emu.h52
-rw-r--r--src/core/hle/applets/erreula.cpp4
-rw-r--r--src/core/hle/applets/mii_selector.cpp10
-rw-r--r--src/core/hle/applets/mii_selector.h57
-rw-r--r--src/core/hle/applets/mint.cpp4
-rw-r--r--src/core/hle/applets/swkbd.cpp4
-rw-r--r--src/core/hle/kernel/kernel.h5
-rw-r--r--src/core/hle/kernel/thread.cpp6
-rw-r--r--src/core/hle/lock.cpp11
-rw-r--r--src/core/hle/lock.h18
-rw-r--r--src/core/hle/service/apt/apt.cpp229
-rw-r--r--src/core/hle/service/apt/apt.h6
-rw-r--r--src/core/hle/service/dsp_dsp.cpp7
-rw-r--r--src/core/hle/service/hid/hid.cpp44
-rw-r--r--src/core/hle/service/hid/hid.h2
-rw-r--r--src/core/hle/service/ir/ir_rst.cpp2
-rw-r--r--src/core/hle/service/nwm/nwm_uds.cpp165
-rw-r--r--src/core/hle/service/nwm/nwm_uds.h12
-rw-r--r--src/core/hle/service/nwm/uds_beacon.cpp3
-rw-r--r--src/core/hle/service/nwm/uds_beacon.h30
-rw-r--r--src/core/hle/service/nwm/uds_connection.cpp79
-rw-r--r--src/core/hle/service/nwm/uds_connection.h51
-rw-r--r--src/core/hle/svc.cpp8
-rw-r--r--src/core/hw/gpu.cpp2
-rw-r--r--src/core/hw/gpu.h4
-rw-r--r--src/core/loader/ncch.cpp8
-rw-r--r--src/core/memory.cpp9
-rw-r--r--src/core/settings.cpp2
-rw-r--r--src/core/settings.h10
-rw-r--r--src/core/telemetry_session.cpp57
-rw-r--r--src/core/telemetry_session.h12
-rw-r--r--src/input_common/CMakeLists.txt2
-rw-r--r--src/input_common/main.cpp15
-rw-r--r--src/input_common/main.h7
-rw-r--r--src/input_common/motion_emu.cpp168
-rw-r--r--src/input_common/motion_emu.h46
-rw-r--r--src/input_common/sdl/sdl.cpp2
-rw-r--r--src/network/packet.cpp38
-rw-r--r--src/network/packet.h4
-rw-r--r--src/network/room.cpp84
-rw-r--r--src/network/room.h19
-rw-r--r--src/network/room_member.cpp128
-rw-r--r--src/network/room_member.h59
-rw-r--r--src/video_core/regs_framebuffer.h10
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp3
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.cpp37
-rw-r--r--src/video_core/renderer_opengl/gl_state.cpp13
-rw-r--r--src/video_core/renderer_opengl/gl_state.h3
-rw-r--r--src/video_core/swrasterizer/clipper.cpp4
-rw-r--r--src/video_core/swrasterizer/framebuffer.cpp2
-rw-r--r--src/video_core/swrasterizer/lighting.cpp90
-rw-r--r--src/video_core/swrasterizer/lighting.h3
-rw-r--r--src/video_core/swrasterizer/rasterizer.cpp4
-rw-r--r--src/video_core/swrasterizer/rasterizer.h6
-rw-r--r--src/video_core/swrasterizer/texturing.cpp4
-rw-r--r--src/web_service/telemetry_json.cpp3
-rw-r--r--src/web_service/telemetry_json.h7
-rw-r--r--src/web_service/web_backend.cpp67
-rw-r--r--src/web_service/web_backend.h18
108 files changed, 2346 insertions, 1000 deletions
diff --git a/.travis-build-docker.sh b/.travis-build-docker.sh
new file mode 100644
index 000000000..ca6fae42b
--- /dev/null
+++ b/.travis-build-docker.sh
@@ -0,0 +1,20 @@
1#!/bin/sh
2
3set -e
4set -x
5
6cd /citra
7
8apt-get update
9apt-get install -y build-essential libsdl2-dev qtbase5-dev libqt5opengl5-dev libcurl4-openssl-dev libssl-dev wget git
10
11# Get a recent version of CMake
12wget https://cmake.org/files/v3.9/cmake-3.9.0-Linux-x86_64.sh
13echo y | sh cmake-3.9.0-Linux-x86_64.sh --prefix=cmake
14export PATH=/citra/cmake/cmake-3.9.0-Linux-x86_64/bin:$PATH
15
16mkdir build && cd build
17cmake .. -DCMAKE_BUILD_TYPE=Release
18make -j4
19
20ctest -VV -C Release
diff --git a/.travis-build.sh b/.travis-build.sh
index df6e236b6..64f5aed94 100755
--- a/.travis-build.sh
+++ b/.travis-build.sh
@@ -44,15 +44,7 @@ fi
44 44
45#if OS is linux or is not set 45#if OS is linux or is not set
46if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then 46if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then
47 export CC=gcc-6 47 docker run -v $(pwd):/citra ubuntu:16.04 /bin/bash /citra/.travis-build-docker.sh
48 export CXX=g++-6
49 export PKG_CONFIG_PATH=$HOME/.local/lib/pkgconfig:$PKG_CONFIG_PATH
50
51 mkdir build && cd build
52 cmake ..
53 make -j4
54
55 ctest -VV -C Release
56elif [ "$TRAVIS_OS_NAME" = "osx" ]; then 48elif [ "$TRAVIS_OS_NAME" = "osx" ]; then
57 set -o pipefail 49 set -o pipefail
58 50
diff --git a/.travis-deps.sh b/.travis-deps.sh
index 25a287c7f..0cee68041 100755
--- a/.travis-deps.sh
+++ b/.travis-deps.sh
@@ -5,35 +5,7 @@ set -x
5 5
6#if OS is linux or is not set 6#if OS is linux or is not set
7if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then 7if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then
8 export CC=gcc-6 8 docker pull ubuntu:16.04
9 export CXX=g++-6
10 mkdir -p $HOME/.local
11
12 if [ ! -e $HOME/.local/bin/cmake ]; then
13 echo "CMake not found in the cache, get and extract it..."
14 curl -L http://www.cmake.org/files/v3.6/cmake-3.6.3-Linux-x86_64.tar.gz \
15 | tar -xz -C $HOME/.local --strip-components=1
16 else
17 echo "Using cached CMake"
18 fi
19
20 if [ ! -e $HOME/.local/lib/libSDL2.la ]; then
21 echo "SDL2 not found in cache, get and build it..."
22 wget http://libsdl.org/release/SDL2-2.0.5.tar.gz -O - | tar xz
23 cd SDL2-2.0.5
24 ./configure --prefix=$HOME/.local
25 make -j4 && make install
26 else
27 echo "Using cached SDL2"
28 fi
29
30 export DEBIAN_FRONTEND=noninteractive
31 # Amazing placebo security
32 curl http://apt.llvm.org/llvm-snapshot.gpg.key | sudo -E apt-key add -
33 sudo -E add-apt-repository "deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-3.9 main"
34 sudo -E apt-get -yq update
35 sudo -E apt-get -yq install clang-format-3.9
36
37elif [ "$TRAVIS_OS_NAME" = "osx" ]; then 9elif [ "$TRAVIS_OS_NAME" = "osx" ]; then
38 brew update 10 brew update
39 brew install qt5 sdl2 dylibbundler p7zip 11 brew install qt5 sdl2 dylibbundler p7zip
diff --git a/.travis-upload.sh b/.travis-upload.sh
index 8cfab31cb..8c1fa21c5 100755
--- a/.travis-upload.sh
+++ b/.travis-upload.sh
@@ -1,134 +1,139 @@
1if [ "$TRAVIS_EVENT_TYPE" = "push" ]&&[ "$TRAVIS_BRANCH" = "master" ]; then 1GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
2 GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`" 2GITREV="`git show -s --format='%h'`"
3 GITREV="`git show -s --format='%h'`" 3mkdir -p artifacts
4 mkdir -p artifacts 4
5 5if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then
6 if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then 6 REV_NAME="citra-linux-${GITDATE}-${GITREV}"
7 REV_NAME="citra-linux-${GITDATE}-${GITREV}" 7 ARCHIVE_NAME="${REV_NAME}.tar.xz"
8 ARCHIVE_NAME="${REV_NAME}.tar.xz" 8 COMPRESSION_FLAGS="-cJvf"
9 COMPRESSION_FLAGS="-cJvf" 9 mkdir "$REV_NAME"
10 mkdir "$REV_NAME" 10
11 11 cp build/src/citra/citra "$REV_NAME"
12 cp build/src/citra/citra "$REV_NAME" 12 cp build/src/citra_qt/citra-qt "$REV_NAME"
13 cp build/src/citra_qt/citra-qt "$REV_NAME" 13elif [ "$TRAVIS_OS_NAME" = "osx" ]; then
14 elif [ "$TRAVIS_OS_NAME" = "osx" ]; then 14 REV_NAME="citra-osx-${GITDATE}-${GITREV}"
15 REV_NAME="citra-osx-${GITDATE}-${GITREV}" 15 ARCHIVE_NAME="${REV_NAME}.tar.gz"
16 ARCHIVE_NAME="${REV_NAME}.tar.gz" 16 COMPRESSION_FLAGS="-czvf"
17 COMPRESSION_FLAGS="-czvf" 17 mkdir "$REV_NAME"
18 mkdir "$REV_NAME" 18
19 19 cp build/src/citra/Release/citra "$REV_NAME"
20 cp build/src/citra/Release/citra "$REV_NAME" 20 cp -r build/src/citra_qt/Release/citra-qt.app "$REV_NAME"
21 cp -r build/src/citra_qt/Release/citra-qt.app "$REV_NAME" 21
22 22 # move qt libs into app bundle for deployment
23 # move qt libs into app bundle for deployment 23 $(brew --prefix)/opt/qt5/bin/macdeployqt "${REV_NAME}/citra-qt.app"
24 $(brew --prefix)/opt/qt5/bin/macdeployqt "${REV_NAME}/citra-qt.app" 24
25 25 # move SDL2 libs into folder for deployment
26 # move SDL2 libs into folder for deployment 26 dylibbundler -b -x "${REV_NAME}/citra" -cd -d "${REV_NAME}/libs" -p "@executable_path/libs/"
27 dylibbundler -b -x "${REV_NAME}/citra" -cd -d "${REV_NAME}/libs" -p "@executable_path/libs/" 27
28 28 # Make the changes to make the citra-qt app standalone (i.e. not dependent on the current brew installation).
29 # Make the changes to make the citra-qt app standalone (i.e. not dependent on the current brew installation). 29 # To do this, the absolute references to each and every QT framework must be re-written to point to the local frameworks
30 # To do this, the absolute references to each and every QT framework must be re-written to point to the local frameworks 30 # (in the Contents/Frameworks folder).
31 # (in the Contents/Frameworks folder). 31 # The "install_name_tool" is used to do so.
32 # The "install_name_tool" is used to do so. 32
33 33 # Coreutils is a hack to coerce Homebrew to point to the absolute Cellar path (symlink dereferenced). i.e:
34 # Coreutils is a hack to coerce Homebrew to point to the absolute Cellar path (symlink dereferenced). i.e: 34 # ls -l /usr/local/opt/qt5:: /usr/local/opt/qt5 -> ../Cellar/qt5/5.6.1-1
35 # ls -l /usr/local/opt/qt5:: /usr/local/opt/qt5 -> ../Cellar/qt5/5.6.1-1 35 # grealpath ../Cellar/qt5/5.6.1-1:: /usr/local/Cellar/qt5/5.6.1-1
36 # grealpath ../Cellar/qt5/5.6.1-1:: /usr/local/Cellar/qt5/5.6.1-1 36 brew install coreutils
37 brew install coreutils 37
38 38 REV_NAME_ALT=$REV_NAME/
39 REV_NAME_ALT=$REV_NAME/ 39 # grealpath is located in coreutils, there is no "realpath" for OS X :(
40 # grealpath is located in coreutils, there is no "realpath" for OS X :( 40 QT_BREWS_PATH=$(grealpath "$(brew --prefix qt5)")
41 QT_BREWS_PATH=$(grealpath "$(brew --prefix qt5)") 41 BREW_PATH=$(brew --prefix)
42 BREW_PATH=$(brew --prefix) 42 QT_VERSION_NUM=5
43 QT_VERSION_NUM=5 43
44 44 $BREW_PATH/opt/qt5/bin/macdeployqt "${REV_NAME_ALT}citra-qt.app" \
45 $BREW_PATH/opt/qt5/bin/macdeployqt "${REV_NAME_ALT}citra-qt.app" \ 45 -executable="${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt"
46 -executable="${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt" 46
47 47 # These are the files that macdeployqt packed into Contents/Frameworks/ - we don't want those, so we replace them.
48 # These are the files that macdeployqt packed into Contents/Frameworks/ - we don't want those, so we replace them. 48 declare -a macos_libs=("QtCore" "QtWidgets" "QtGui" "QtOpenGL" "QtPrintSupport")
49 declare -a macos_libs=("QtCore" "QtWidgets" "QtGui" "QtOpenGL" "QtPrintSupport") 49
50 50 for macos_lib in "${macos_libs[@]}"
51 for macos_lib in "${macos_libs[@]}" 51 do
52 SC_FRAMEWORK_PART=$macos_lib.framework/Versions/$QT_VERSION_NUM/$macos_lib
53 # Replace macdeployqt versions of the Frameworks with our own (from /usr/local/opt/qt5/lib/)
54 cp "$BREW_PATH/opt/qt5/lib/$SC_FRAMEWORK_PART" "${REV_NAME_ALT}citra-qt.app/Contents/Frameworks/$SC_FRAMEWORK_PART"
55
56 # Replace references within the embedded Framework files with "internal" versions.
57 for macos_lib2 in "${macos_libs[@]}"
52 do 58 do
53 SC_FRAMEWORK_PART=$macos_lib.framework/Versions/$QT_VERSION_NUM/$macos_lib 59 # Since brew references both the non-symlinked and symlink paths of QT5, it needs to be duplicated.
54 # Replace macdeployqt versions of the Frameworks with our own (from /usr/local/opt/qt5/lib/) 60 # /usr/local/Cellar/qt5/5.6.1-1/lib and /usr/local/opt/qt5/lib both resolve to the same files.
55 cp "$BREW_PATH/opt/qt5/lib/$SC_FRAMEWORK_PART" "${REV_NAME_ALT}citra-qt.app/Contents/Frameworks/$SC_FRAMEWORK_PART" 61 # So the two lines below are effectively duplicates when resolved as a path, but as strings, they aren't.
56 62 RM_FRAMEWORK_PART=$macos_lib2.framework/Versions/$QT_VERSION_NUM/$macos_lib2
57 # Replace references within the embedded Framework files with "internal" versions. 63 install_name_tool -change \
58 for macos_lib2 in "${macos_libs[@]}" 64 $QT_BREWS_PATH/lib/$RM_FRAMEWORK_PART \
59 do 65 @executable_path/../Frameworks/$RM_FRAMEWORK_PART \
60 # Since brew references both the non-symlinked and symlink paths of QT5, it needs to be duplicated. 66 "${REV_NAME_ALT}citra-qt.app/Contents/Frameworks/$SC_FRAMEWORK_PART"
61 # /usr/local/Cellar/qt5/5.6.1-1/lib and /usr/local/opt/qt5/lib both resolve to the same files. 67 install_name_tool -change \
62 # So the two lines below are effectively duplicates when resolved as a path, but as strings, they aren't. 68 "$BREW_PATH/opt/qt5/lib/$RM_FRAMEWORK_PART" \
63 RM_FRAMEWORK_PART=$macos_lib2.framework/Versions/$QT_VERSION_NUM/$macos_lib2 69 @executable_path/../Frameworks/$RM_FRAMEWORK_PART \
64 install_name_tool -change \ 70 "${REV_NAME_ALT}citra-qt.app/Contents/Frameworks/$SC_FRAMEWORK_PART"
65 $QT_BREWS_PATH/lib/$RM_FRAMEWORK_PART \
66 @executable_path/../Frameworks/$RM_FRAMEWORK_PART \
67 "${REV_NAME_ALT}citra-qt.app/Contents/Frameworks/$SC_FRAMEWORK_PART"
68 install_name_tool -change \
69 "$BREW_PATH/opt/qt5/lib/$RM_FRAMEWORK_PART" \
70 @executable_path/../Frameworks/$RM_FRAMEWORK_PART \
71 "${REV_NAME_ALT}citra-qt.app/Contents/Frameworks/$SC_FRAMEWORK_PART"
72 done
73 done 71 done
74 72 done
75 # Handles `This application failed to start because it could not find or load the Qt platform plugin "cocoa"` 73
76 # Which manifests itself as: 74 # Handles `This application failed to start because it could not find or load the Qt platform plugin "cocoa"`
77 # "Exception Type: EXC_CRASH (SIGABRT) | Exception Codes: 0x0000000000000000, 0x0000000000000000 | Exception Note: EXC_CORPSE_NOTIFY" 75 # Which manifests itself as:
78 # There may be more dylibs needed to be fixed... 76 # "Exception Type: EXC_CRASH (SIGABRT) | Exception Codes: 0x0000000000000000, 0x0000000000000000 | Exception Note: EXC_CORPSE_NOTIFY"
79 declare -a macos_plugins=("Plugins/platforms/libqcocoa.dylib") 77 # There may be more dylibs needed to be fixed...
80 78 declare -a macos_plugins=("Plugins/platforms/libqcocoa.dylib")
81 for macos_lib in "${macos_plugins[@]}" 79
80 for macos_lib in "${macos_plugins[@]}"
81 do
82 install_name_tool -id @executable_path/../$macos_lib "${REV_NAME_ALT}citra-qt.app/Contents/$macos_lib"
83 for macos_lib2 in "${macos_libs[@]}"
82 do 84 do
83 install_name_tool -id @executable_path/../$macos_lib "${REV_NAME_ALT}citra-qt.app/Contents/$macos_lib" 85 RM_FRAMEWORK_PART=$macos_lib2.framework/Versions/$QT_VERSION_NUM/$macos_lib2
84 for macos_lib2 in "${macos_libs[@]}" 86 install_name_tool -change \
85 do 87 $QT_BREWS_PATH/lib/$RM_FRAMEWORK_PART \
86 RM_FRAMEWORK_PART=$macos_lib2.framework/Versions/$QT_VERSION_NUM/$macos_lib2 88 @executable_path/../Frameworks/$RM_FRAMEWORK_PART \
87 install_name_tool -change \ 89 "${REV_NAME_ALT}citra-qt.app/Contents/$macos_lib"
88 $QT_BREWS_PATH/lib/$RM_FRAMEWORK_PART \ 90 install_name_tool -change \
89 @executable_path/../Frameworks/$RM_FRAMEWORK_PART \ 91 "$BREW_PATH/opt/qt5/lib/$RM_FRAMEWORK_PART" \
90 "${REV_NAME_ALT}citra-qt.app/Contents/$macos_lib" 92 @executable_path/../Frameworks/$RM_FRAMEWORK_PART \
91 install_name_tool -change \ 93 "${REV_NAME_ALT}citra-qt.app/Contents/$macos_lib"
92 "$BREW_PATH/opt/qt5/lib/$RM_FRAMEWORK_PART" \
93 @executable_path/../Frameworks/$RM_FRAMEWORK_PART \
94 "${REV_NAME_ALT}citra-qt.app/Contents/$macos_lib"
95 done
96 done 94 done
95 done
97 96
98 for macos_lib in "${macos_libs[@]}" 97 for macos_lib in "${macos_libs[@]}"
99 do 98 do
100 # Debugging info for Travis-CI 99 # Debugging info for Travis-CI
101 otool -L "${REV_NAME_ALT}citra-qt.app/Contents/Frameworks/$macos_lib.framework/Versions/$QT_VERSION_NUM/$macos_lib" 100 otool -L "${REV_NAME_ALT}citra-qt.app/Contents/Frameworks/$macos_lib.framework/Versions/$QT_VERSION_NUM/$macos_lib"
102 done 101 done
103 102
104 # Make the citra-qt.app application launch a debugging terminal. 103 # Make the citra-qt.app application launch a debugging terminal.
105 # Store away the actual binary 104 # Store away the actual binary
106 mv ${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt ${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt-bin 105 mv ${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt ${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt-bin
107 106
108 cat > ${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt <<EOL 107 cat > ${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt <<EOL
109#!/usr/bin/env bash 108#!/usr/bin/env bash
110cd "\`dirname "\$0"\`" 109cd "\`dirname "\$0"\`"
111chmod +x citra-qt-bin 110chmod +x citra-qt-bin
112open citra-qt-bin --args "\$@" 111open citra-qt-bin --args "\$@"
113EOL 112EOL
114 # Content that will serve as the launching script for citra (within the .app folder) 113 # Content that will serve as the launching script for citra (within the .app folder)
115 114
116 # Make the launching script executable 115 # Make the launching script executable
117 chmod +x ${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt 116 chmod +x ${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt
118 117
119 fi 118fi
119
120# Copy documentation
121cp license.txt "$REV_NAME"
122cp README.md "$REV_NAME"
120 123
121 # Copy documentation 124tar $COMPRESSION_FLAGS "$ARCHIVE_NAME" "$REV_NAME"
122 cp license.txt "$REV_NAME"
123 cp README.md "$REV_NAME"
124 125
125 tar $COMPRESSION_FLAGS "$ARCHIVE_NAME" "$REV_NAME" 126# Find out what release we are building
127if [ -z $TRAVIS_TAG ]; then
128 RELEASE_NAME=head
129else
130 RELEASE_NAME=$(echo $TRAVIS_TAG | cut -d- -f1)
131fi
126 132
127 mv "$REV_NAME" nightly 133mv "$REV_NAME" $RELEASE_NAME
128 134
129 7z a "$REV_NAME.7z" nightly 1357z a "$REV_NAME.7z" $RELEASE_NAME
130 136
131 # move the compiled archive into the artifacts directory to be uploaded by travis releases 137# move the compiled archive into the artifacts directory to be uploaded by travis releases
132 mv "$ARCHIVE_NAME" artifacts/ 138mv "$ARCHIVE_NAME" artifacts/
133 mv "$REV_NAME.7z" artifacts/ 139mv "$REV_NAME.7z" artifacts/
134fi
diff --git a/.travis.yml b/.travis.yml
index 846758881..b92d7f236 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,23 +8,15 @@ matrix:
8 sudo: false 8 sudo: false
9 osx_image: xcode7.3 9 osx_image: xcode7.3
10 10
11services:
12 - docker
13
11addons: 14addons:
12 apt: 15 apt:
13 sources:
14 - ubuntu-toolchain-r-test
15 packages: 16 packages:
16 - gcc-6 17 - clang-format-3.9
17 - g++-6
18 - qt5-default
19 - libqt5opengl5-dev
20 - xorg-dev
21 - lib32stdc++6 # For CMake
22 - p7zip-full 18 - p7zip-full
23 19
24cache:
25 directories:
26 - "$HOME/.local"
27
28install: "./.travis-deps.sh" 20install: "./.travis-deps.sh"
29script: "./.travis-build.sh" 21script: "./.travis-build.sh"
30after_success: "./.travis-upload.sh" 22after_success: "./.travis-upload.sh"
@@ -37,4 +29,4 @@ deploy:
37 file: "artifacts/*" 29 file: "artifacts/*"
38 skip_cleanup: true 30 skip_cleanup: true
39 on: 31 on:
40 repo: citra-emu/citra-nightly 32 tags: true
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ddba04ef9..f8060270e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -129,8 +129,8 @@ else()
129 set(CMAKE_C_FLAGS_RELEASE "/O2 /GS- /MD" CACHE STRING "" FORCE) 129 set(CMAKE_C_FLAGS_RELEASE "/O2 /GS- /MD" CACHE STRING "" FORCE)
130 set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}" CACHE STRING "" FORCE) 130 set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}" CACHE STRING "" FORCE)
131 131
132 set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG" CACHE STRING "" FORCE) 132 set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG /MANIFEST:NO" CACHE STRING "" FORCE)
133 set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE) 133 set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE)
134endif() 134endif()
135 135
136# Set file offset size to 64 bits. 136# Set file offset size to 64 bits.
diff --git a/README.md b/README.md
index e766918f7..31f5afe27 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ For development discussion, please join us @ #citra on freenode.
17 17
18Most of the development happens on GitHub. It's also where [our central repository](https://github.com/citra-emu/citra) is hosted. 18Most of the development happens on GitHub. It's also where [our central repository](https://github.com/citra-emu/citra) is hosted.
19 19
20If you want to contribute please take a look at the [Contributor's Guide](CONTRIBUTING.md), [TODO list](https://docs.google.com/document/d/1SWIop0uBI9IW8VGg97TAtoT_CHNoP42FzYmvG1F4QDA) 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. 20If 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.
21 21
22### Building 22### Building
23 23
diff --git a/appveyor.yml b/appveyor.yml
index d062a1f3e..ec9ca3747 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,15 +1,21 @@
1# shallow clone 1# shallow clone
2clone_depth: 10 2clone_depth: 10
3 3
4# don't build on tag
5skip_tags: true
6
7cache: 4cache:
8 - C:\ProgramData\chocolatey\bin -> appveyor.yml 5 - C:\ProgramData\chocolatey\bin -> appveyor.yml
9 - C:\ProgramData\chocolatey\lib -> appveyor.yml 6 - C:\ProgramData\chocolatey\lib -> appveyor.yml
10 7
11os: Visual Studio 2017 8os: Visual Studio 2017
12 9
10environment:
11 # Tell msys2 to add mingw64 to the path
12 MSYSTEM: MINGW64
13 # Tell msys2 to inherit the current directory when starting the shell
14 CHERE_INVOKING: 1
15 matrix:
16 - BUILD_TYPE: mingw
17 - BUILD_TYPE: msvc
18
13platform: 19platform:
14 - x64 20 - x64
15 21
@@ -18,70 +24,153 @@ configuration:
18 24
19install: 25install:
20 - git submodule update --init --recursive 26 - git submodule update --init --recursive
27 - ps: |
28 if ($env:BUILD_TYPE -eq 'mingw') {
29 $dependencies = "mingw64/mingw-w64-x86_64-cmake",
30 "mingw64/mingw-w64-x86_64-qt5",
31 "mingw64/mingw-w64-x86_64-curl",
32 "mingw64/mingw-w64-x86_64-SDL2"
33 # redirect err to null to prevent warnings from becoming errors
34 # workaround to prevent pacman from failing due to cyclical dependencies
35 C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S mingw64/mingw-w64-x86_64-freetype mingw64/mingw-w64-x86_64-fontconfig" 2> $null
36 C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S $dependencies" 2> $null
37 }
21 38
22before_build: 39before_build:
23 - mkdir build 40 - mkdir %BUILD_TYPE%_build
24 - cd build 41 - cd %BUILD_TYPE%_build
25 - cmake -G "Visual Studio 15 2017 Win64" -DCITRA_USE_BUNDLED_QT=1 -DCITRA_USE_BUNDLED_SDL2=1 -DCMAKE_USE_OPENSSL=0 .. 42 - ps: |
43 if ($env:BUILD_TYPE -eq 'msvc') {
44 # redirect stderr and change the exit code to prevent powershell from cancelling the build if cmake prints a warning
45 cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DCITRA_USE_BUNDLED_QT=1 -DCITRA_USE_BUNDLED_SDL2=1 -DCMAKE_USE_OPENSSL=0 .. 2>&1 && exit 0'
46 } else {
47 C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DUSE_SYSTEM_CURL=1 -DCMAKE_BUILD_TYPE=Release .. 2>&1"
48 }
26 - cd .. 49 - cd ..
27 50
28build: 51build_script:
29 project: build/citra.sln 52 - ps: |
30 parallel: true 53 if ($env:BUILD_TYPE -eq 'msvc') {
54 # https://www.appveyor.com/docs/build-phase
55 msbuild msvc_build/citra.sln /maxcpucount /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
56 } else {
57 C:\msys64\usr\bin\bash.exe -lc 'mingw32-make -C mingw_build/ 2>&1'
58 }
31 59
32after_build: 60after_build:
33 - ps: | 61 - ps: |
34 $GITDATE = $(git show -s --date=short --format='%ad') -replace "-","" 62 $GITDATE = $(git show -s --date=short --format='%ad') -replace "-",""
35 $GITREV = $(git show -s --format='%h') 63 $GITREV = $(git show -s --format='%h')
36 $GIT_LONG_HASH = $(git rev-parse HEAD) 64
37 # Where are these spaces coming from? Regardless, let's remove them 65 # Find out which kind of release we are producing by tag name
38 $MSVC_BUILD_NAME = "citra-windows-msvc-$GITDATE-$GITREV.zip" -replace " ", "" 66 if ($env:APPVEYOR_REPO_TAG_NAME) {
39 $MSVC_BUILD_PDB = "citra-windows-msvc-$GITDATE-$GITREV-debugsymbols.zip" -replace " ", "" 67 $RELEASE_DIST, $RELEASE_VERSION = $env:APPVEYOR_REPO_TAG_NAME.split('-')
40 $MSVC_SEVENZIP = "citra-windows-msvc-$GITDATE-$GITREV.7z" -replace " ", "" 68 } else {
41 $BINTRAY_VERSION = "nightly-$GIT_LONG_HASH" -replace " ", "" 69 # There is no repo tag - make assumptions
42 70 $RELEASE_DIST = "head"
43 # set the build names as env vars so the artifacts can upload them 71 }
44 $env:MSVC_BUILD_NAME = $MSVC_BUILD_NAME 72
45 $env:MSVC_BUILD_PDB = $MSVC_BUILD_PDB 73 if ($env:BUILD_TYPE -eq 'msvc') {
46 $env:MSVC_SEVENZIP = $MSVC_SEVENZIP 74 # Where are these spaces coming from? Regardless, let's remove them
47 $env:GITREV = $GITREV 75 $MSVC_BUILD_ZIP = "citra-windows-msvc-$GITDATE-$GITREV.zip" -replace " ", ""
48 76 $MSVC_BUILD_PDB = "citra-windows-msvc-$GITDATE-$GITREV-debugsymbols.zip" -replace " ", ""
49 7z a -tzip $MSVC_BUILD_PDB .\build\bin\release\*.pdb 77 $MSVC_SEVENZIP = "citra-windows-msvc-$GITDATE-$GITREV.7z" -replace " ", ""
50 rm .\build\bin\release\*.pdb 78
51 79 # set the build names as env vars so the artifacts can upload them
52 mkdir nightly 80 $env:BUILD_ZIP = $MSVC_BUILD_ZIP
53 Copy-Item .\build\bin\release\* -Destination nightly -Recurse 81 $env:BUILD_SYMBOLS = $MSVC_BUILD_PDB
54 Copy-Item .\license.txt -Destination nightly 82 $env:BUILD_UPDATE = $MSVC_SEVENZIP
55 Copy-Item .\README.md -Destination nightly 83
56 84 7z a -tzip $MSVC_BUILD_PDB .\msvc_build\bin\release\*.pdb
57 7z a -tzip $MSVC_BUILD_NAME nightly\* 85 rm .\msvc_build\bin\release\*.pdb
58 7z a $MSVC_SEVENZIP nightly 86
87 mkdir $RELEASE_DIST
88 Copy-Item .\msvc_build\bin\release\* -Destination $RELEASE_DIST -Recurse
89 Copy-Item .\license.txt -Destination $RELEASE_DIST
90 Copy-Item .\README.md -Destination $RELEASE_DIST
91 7z a -tzip $MSVC_BUILD_ZIP $RELEASE_DIST\*
92 7z a $MSVC_SEVENZIP $RELEASE_DIST
93 } else {
94 $MINGW_BUILD_ZIP = "citra-windows-mingw-$GITDATE-$GITREV.zip" -replace " ", ""
95 $MINGW_SEVENZIP = "citra-windows-mingw-$GITDATE-$GITREV.7z" -replace " ", ""
96 # not going to bother adding separate debug symbols for mingw, so just upload a README for it
97 # if someone wants to add them, change mingw to compile with -g and use objdump and strip to separate the symbols from the binary
98 $MINGW_NO_DEBUG_SYMBOLS = "README_No_Debug_Symbols.txt"
99 Set-Content -Path $MINGW_NO_DEBUG_SYMBOLS -Value "This is a workaround for Appveyor since msvc has debug symbols but mingw doesnt" -Force
100
101 # store the build information in env vars so we can use them as artifacts
102 $env:BUILD_ZIP = $MINGW_BUILD_ZIP
103 $env:BUILD_SYMBOLS = $MINGW_NO_DEBUG_SYMBOLS
104 $env:BUILD_UPDATE = $MINGW_SEVENZIP
105
106 $CMAKE_SOURCE_DIR = "$env:APPVEYOR_BUILD_FOLDER"
107 $CMAKE_BINARY_DIR = "$CMAKE_SOURCE_DIR/mingw_build"
108 $RELEASE_DIST = $RELEASE_DIST + "-mingw"
109
110 mkdir $RELEASE_DIST
111 mkdir $RELEASE_DIST/platforms
112
113 # copy the compiled binaries and other release files to the release folder
114 Get-ChildItem "$CMAKE_BINARY_DIR" -Recurse -Filter "citra*.exe" | Copy-Item -destination $RELEASE_DIST
115 Copy-Item -path "$CMAKE_SOURCE_DIR/license.txt" -destination $RELEASE_DIST
116 Copy-Item -path "$CMAKE_SOURCE_DIR/README.md" -destination $RELEASE_DIST
117
118 # copy all the dll dependencies to the release folder
119 # hardcoded list because we don't build static and determining the list of dlls from the binary is a pain.
120 $MingwDLLs = "Qt5Core.dll","Qt5Widgets.dll","Qt5Gui.dll","Qt5OpenGL.dll",
121 # QT dll dependencies
122 "libbz2-*.dll","libicudt*.dll","libicuin*.dll","libicuuc*.dll","libffi-*.dll",
123 "libfreetype-*.dll","libglib-*.dll","libgobject-*.dll","libgraphite2.dll","libiconv-*.dll",
124 "libharfbuzz-*.dll","libintl-*.dll","libpcre-*.dll","libpcre16-*.dll","libpng16-*.dll",
125 # Runtime/Other dependencies
126 "libgcc_s_seh-*.dll","libstdc++-*.dll","libwinpthread-*.dll","SDL2.dll","zlib1.dll",
127 # curl dependencies
128 "libcurl-*.dll","libnghttp2-*.dll","libeay32.dll","libgmp-*.dll","librtmp-*.dll",
129 "libgnutls-*.dll","libhogweed-*.dll","libnettle-*.dll","libssh2-*.dll",
130 "ssleay32.dll","libidn-*.dll","libp11-kit-*.dll","libtasn1-*.dll","libunistring-*.dll"
131 foreach ($file in $MingwDLLs) {
132 Copy-Item -path "C:/msys64/mingw64/bin/$file" -force -destination "$RELEASE_DIST"
133 }
134 # the above list copies a few extra debug dlls that aren't needed (thanks globbing patterns!)
135 # so we can remove them by hardcoding another list of extra dlls to remove
136 $DebugDLLs = "libicudtd*.dll","libicuind*.dll","libicuucd*.dll"
137 foreach ($file in $DebugDLLs) {
138 Remove-Item -path "$RELEASE_DIST/$file"
139 }
140
141 # copy the qt windows plugin dll to platforms
142 Copy-Item -path "C:/msys64/mingw64/share/qt5/plugins/platforms/qwindows.dll" -force -destination "$RELEASE_DIST/platforms"
143
144 7z a -tzip $MINGW_BUILD_ZIP $RELEASE_DIST\*
145 7z a $MINGW_SEVENZIP $RELEASE_DIST
146 }
59 147
60test_script: 148test_script:
61 - cd build && ctest -VV -C Release && cd .. 149 - cd %BUILD_TYPE%_build
150 - ps: |
151 if ($env:BUILD_TYPE -eq 'msvc') {
152 ctest -VV -C Release
153 } else {
154 C:\msys64\usr\bin\bash.exe -lc "ctest -VV -C Release"
155 }
156 - cd ..
62 157
63artifacts: 158artifacts:
64 - path: $(MSVC_BUILD_NAME) 159 - path: $(BUILD_ZIP)
65 name: msvcbuild 160 name: build
66 type: zip 161 type: zip
67 - path: $(MSVC_BUILD_PDB) 162 - path: $(BUILD_SYMBOLS)
68 name: msvcdebug 163 name: debugsymbols
69 type: zip 164 - path: $(BUILD_UPDATE)
70 - path: $(MSVC_SEVENZIP) 165 name: update
71 name: msvcupdate
72 166
73deploy: 167deploy:
74 provider: GitHub 168 provider: GitHub
75 release: nightly-$(appveyor_build_number) 169 release: $(appveyor_repo_tag_name)
76 description: |
77 Citra nightly releases. Please choose the correct download for your operating system from the list below.
78
79 Short Commit Hash $(GITREV)
80 auth_token: 170 auth_token:
81 secure: "dbpsMC/MgPKWFNJCXpQl4cR8FYhepkPLjgNp/pRMktZ8oLKTqPYErfreaIxb/4P1" 171 secure: "dbpsMC/MgPKWFNJCXpQl4cR8FYhepkPLjgNp/pRMktZ8oLKTqPYErfreaIxb/4P1"
82 artifact: msvcupdate,msvcbuild 172 artifact: update,build
83 draft: false 173 draft: false
84 prerelease: false 174 prerelease: false
85 on: 175 on:
86 branch: master 176 appveyor_repo_tag: true
87 appveyor_repo_name: citra-emu/citra-nightly
diff --git a/dist/citra.icns b/dist/citra.icns
index 9d3dcca83..ef7bf4e6e 100644
--- a/dist/citra.icns
+++ b/dist/citra.icns
Binary files differ
diff --git a/dist/citra.ico b/dist/citra.ico
index 4fef651e2..2c408b935 100644
--- a/dist/citra.ico
+++ b/dist/citra.ico
Binary files differ
diff --git a/dist/citra.manifest b/dist/citra.manifest
new file mode 100644
index 000000000..fd30b656f
--- /dev/null
+++ b/dist/citra.manifest
@@ -0,0 +1,24 @@
1<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
3 <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
4 <security>
5 <requestedPrivileges>
6 <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
7 </requestedPrivileges>
8 </security>
9 </trustInfo>
10 <application xmlns="urn:schemas-microsoft-com:asm.v3">
11 <windowsSettings>
12 <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
13 <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
14 </windowsSettings>
15 </application>
16 <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
17 <application>
18 <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
19 <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
20 <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
21 <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
22 </application>
23 </compatibility>
24</assembly> \ No newline at end of file
diff --git a/dist/citra.svg b/dist/citra.svg
index 7b299cd89..b6abc1ccf 100644
--- a/dist/citra.svg
+++ b/dist/citra.svg
@@ -1,80 +1,2 @@
1<?xml version="1.0" encoding="UTF-8"?> 1<?xml version="1.0" encoding="UTF-8"?>
2<!-- 2<svg version="1.1" viewBox="0 0 433.93 397.43" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:xlink="http://www.w3.org/1999/xlink"><metadata><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/><dc:title/></cc:Work></rdf:RDF></metadata><defs><linearGradient id="linearGradient7230"><stop stop-color="#f15a24" offset="0"/><stop stop-color="#f15a24" stop-opacity="0" offset="1"/></linearGradient><radialGradient id="radialGradient4297" cx="0" cy="0" r="1" fx=".33908" fy="-.55275" gradientTransform="matrix(135.72 53.102 32.823 -83.888 479.47 287.23)" gradientUnits="userSpaceOnUse"><stop stop-color="#ffff87" offset="0"/><stop stop-color="#ffff87" offset=".12648"/><stop stop-color="#ffc503" offset=".14449"/><stop stop-color="#ffc503" offset=".21612"/><stop stop-color="#ffc503" offset=".39528"/><stop stop-color="#ffc503" offset=".4406"/><stop stop-color="#ffc003" offset=".49983"/><stop stop-color="#ffc003" offset=".62818"/><stop stop-color="#ffc003" offset=".72864"/><stop stop-color="#ff8904" stop-opacity=".86935" offset=".84938"/><stop stop-color="#ff8904" offset=".98742"/><stop stop-color="#ff8904" offset="1"/></radialGradient><radialGradient id="radialGradient4331" cx="-.042447" cy=".042662" r="1" gradientTransform="matrix(97.003 -81.357 -81.357 -97.003 516.33 432.83)" gradientUnits="userSpaceOnUse"><stop stop-color="#ff0" offset="0"/><stop stop-color="#ffba05" offset=".66567"/><stop stop-color="#ffba05" offset="1"/></radialGradient><radialGradient id="radialGradient4351" cx="0" cy="0" r="1" gradientTransform="matrix(-12.353 -91.85 -91.85 12.353 493.83 420.5)" gradientUnits="userSpaceOnUse"><stop stop-color="#ff0" offset="0"/><stop stop-color="#ffba05" offset="1"/></radialGradient><radialGradient id="radialGradient4371" cx="0" cy="0" r="1" fx="-.89202" fy="-.452" gradientTransform="matrix(58.797 44.174 44.174 -58.797 591.5 452.83)" gradientUnits="userSpaceOnUse"><stop stop-color="#ff0" offset="0"/><stop stop-color="#ffba05" offset="1"/></radialGradient><radialGradient id="radialGradient4391" cx="0" cy="0" r="1" gradientTransform="matrix(-63.936 -51.968 -51.968 63.936 480.5 419.5)" gradientUnits="userSpaceOnUse"><stop stop-color="#ff0" offset="0"/><stop stop-color="#ffba05" offset="1"/></radialGradient><radialGradient id="radialGradient4413" cx="-.27681" cy="-.01256" r="1" gradientTransform="matrix(166.21 117.68 75.941 -107.26 545.45 466.23)" gradientUnits="userSpaceOnUse"><stop stop-color="#ff0" offset="0"/><stop stop-color="#ffee05" offset=".49699"/><stop stop-color="#ffba05" offset="1"/></radialGradient><radialGradient id="radialGradient4433" cx=".0693" cy=".0013088" r="1" gradientTransform="matrix(92.166 1.56 1.3981 -82.601 484.17 431.17)" gradientUnits="userSpaceOnUse"><stop stop-color="#ff0" offset="0"/><stop stop-color="#ffba05" offset="1"/></radialGradient><radialGradient id="radialGradient4453" cx="0" cy="0" r="1" gradientTransform="matrix(112.47 78.731 51.056 -72.937 527.5 460.5)" gradientUnits="userSpaceOnUse"><stop stop-color="#ff0" offset="0"/><stop stop-color="#ffba05" offset="1"/></radialGradient><radialGradient id="radialGradient4475" cx=".13237" cy="-.0012055" r="1" fx="-.080444" fy=".038791" gradientTransform="matrix(64.042 -58.572 -66.76 -72.994 505.91 439.83)" gradientUnits="userSpaceOnUse"><stop stop-color="#ff0" offset="0"/><stop stop-color="#ffcf05" offset=".62977"/><stop stop-color="#ffcf05" offset="1"/></radialGradient><linearGradient id="linearGradient7248-9" x1="382.8" x2="662.93" y1="393.32" y2="393.32" gradientUnits="userSpaceOnUse" xlink:href="#linearGradient7230"/><clipPath id="clipPath4307-2"><path d="m640.25 512.56c0.366-0.686 1.015-1.901 1.507-3.229 0.237-0.588 0.284-0.774 0.375-1.057 0.174-0.52 0.277-0.819 0.368-1.099 0.09-0.28 0.019-0.115 0.107-0.392 0.087-0.277 0.181-0.577 0.264-0.852 0.082-0.276 0.15-0.659 0.224-0.934 0.076-0.276 0.167-0.604 0.233-0.881 0.066-0.276 0.257-1.337 0.312-1.615 0.316-1.599 0.241-2.12 0.401-3.701 0.16-1.58 0.244-3.151 0.26-4.712 0.016-1.563-0.038-3.115-0.154-4.66-0.117-1.545-0.296-3.081-0.533-4.61-0.237-1.528-0.531-3.049-0.877-4.562-0.345-1.513-0.742-3.02-1.184-4.518-0.442-1.5-0.93-2.992-1.457-4.478-0.527-1.485-1.094-2.964-1.694-4.438-1.181-2.896-2.451-5.732-3.805-8.51-1.353-2.778-2.789-5.5-4.302-8.169-1.512-2.671-3.099-5.288-4.755-7.858-1.656-2.571-3.38-5.093-5.165-7.574-1.785-2.48-3.631-4.918-5.53-7.318-1.9-2.4-3.853-4.762-5.852-7.09s-4.044-4.624-6.129-6.89c-2.084-2.267-4.207-4.506-6.362-6.72-2.218-2.28-4.472-4.518-6.76-6.714-2.287-2.197-4.608-4.353-6.961-6.47s-4.737-4.196-7.152-6.237-4.86-4.046-7.334-6.016c-2.473-1.969-4.975-3.904-7.504-5.806-2.528-1.902-5.084-3.771-7.664-5.609-2.581-1.838-5.187-3.645-7.816-5.423-2.63-1.778-5.282-3.527-7.957-5.247-3.325-2.142-6.677-4.217-10.058-6.225-3.381-2.007-6.792-3.946-10.232-5.813-3.441-1.867-6.912-3.661-10.416-5.38s-7.041-3.362-10.611-4.924c-3.571-1.563-7.177-3.046-10.818-4.446-1.846-0.709-3.702-1.398-5.566-2.064-1.814-0.648-3.636-1.274-5.468-1.88-3.716-1.228-7.47-2.37-11.263-3.42-3.794-1.049-7.627-2.009-11.502-2.872-1.624-0.362-3.249-0.695-4.876-1-1.627-0.304-3.255-0.578-4.886-0.823-1.631-0.244-3.262-0.457-4.896-0.638s-3.269-0.33-4.907-0.445c-1.636-0.116-3.275-0.197-4.915-0.245-1.639-0.046-3.28-0.058-4.923-0.034-1.641 0.024-3.285 0.085-4.929 0.183-1.645 0.097-3.29 0.233-4.937 0.408-1.337 0.142-2.664 0.317-3.979 0.531s-2.617 0.466-3.906 0.759c-1.288 0.294-2.562 0.627-3.819 1.007-1.256 0.38-2.496 0.805-3.717 1.278-1.22 0.475-2.422 0.997-3.602 1.571-1.179 0.575-2.337 1.201-3.471 1.884-1.133 0.683-2.244 1.421-3.326 2.22-1.084 0.798-2.656 1.973-3.685 2.893l-3.143-7.259c-3.143-7.26 1.5-6 3.143-8.5 1.644-2.5 0.357-9 0.217-15s6.64-11.5 9.64-14.5c3-3.001 10-10.5 14.5-11.82 4.5-1.319 23.5-16.18 30-18.181 6.5-1.999 13.5-6.999 22-8.499s19-3 27.5-3 26.5-0.5 38.5 0 27 3 33 4 20 2.5 27.5 4.5 23.5 8 27 11.5 28 13.999 33.5 20c5.5 6 24 39.5 25 45.5s14 47.499 17 53c3 5.5 0 39.499 0 45.5 0 6-10 45-10 45l-13.5 28.5-14 14.5z"/></clipPath><radialGradient id="radialGradient7556" cx="698.21" cy="478.94" r="186.38" gradientTransform="matrix(1.2034 -.41433 .38181 1.1089 -338.27 245.75)" gradientUnits="userSpaceOnUse"><stop stop-color="#f15a24" stop-opacity="0" offset="0"/><stop stop-color="#f15a24" stop-opacity="0" offset=".68534"/><stop stop-color="#f14a24" offset="1"/></radialGradient></defs><g transform="translate(-265.81 -357.85)"><g transform="translate(-206,70)"><path d="m651.83 684.53c-17.986-2.258-43.941-8.1382-59.303-13.436-45.416-15.66-91.627-51.387-112.29-86.813-7.3922-12.675-9.8553-29.065-7.6405-50.846 1.5707-15.448 4.2385-24.955 10.938-38.978 20.81-43.561 55.611-85.05 100.64-119.99 33.898-26.298 69.589-47.881 101.64-61.46 27.534-11.666 61.915-21.532 84.068-24.123 27.343-3.1985 56.096 1.1638 74.647 11.325 18.752 10.271 30.61 25.375 43.922 55.939 12.492 28.684 17.312 55.294 17.291 95.461-0.0151 34.075-4.1728 58.552-14.29 84.174-8.402 21.279-21.718 44.014-36.542 62.391-6.6008 8.1824-26.727 28.053-35.45 35-19.832 15.793-45.654 30.387-66.248 37.441-17.156 5.8765-39.99 11.132-59.144 13.612-9.676 1.253-33.307 1.4198-42.243 0.29802z" fill="#fff" stroke-width=".75166"/><g transform="matrix(1.3333 0 0 -1.3333 0 1024)"><path d="m508.37 418.38c-1.948-9.873-2.175-19.875-2.148-30.734 0.105-4.803-8e-3 -10.436 0.607-16.082 1.248-11.467 5.143-14.109 15.617-9.51 17.862 7.84 33.696 18.829 47.854 32.241 3.988 3.779 8.185 7.422 11.089 12.202 2.673 4.397 2.097 6.88-2.266 9.608-1.396 0.873-2.949 1.54-4.502 2.103-5.181 1.881-10.542 3.121-15.918 4.281-12.621 2.721-25.222 5.644-38.216 5.823-0.08 1e-3 -0.159 1e-3 -0.239 1e-3 -6.452 0-10.63-3.605-11.878-9.933" fill="url(#radialGradient4331)"/></g><g transform="matrix(1.3333 0 0 -1.3333 0 1024)"><path d="m492.04 416.52c-3.243-1.617-5.823-4.156-8.361-6.684-16.977-16.905-32.998-34.704-48.672-52.819-4.26-4.924-8.651-9.823-11.376-15.886-1.919-4.27-0.934-6.454 3.654-7.668 4.725-1.249 9.544-1.787 14.436-1.358 11.696 1.026 22.799 4.32 33.655 8.677 10.044 4.032 16.339 11.18 18.544 21.838 2.891 13.979 3.907 28.175 4.618 42.392 0 2.182 0.021 4.364-0.013 6.545-0.01 0.636-0.15 1.274-0.276 1.902-0.545 2.722-1.447 4.01-3.104 4.01-0.83 0-1.848-0.323-3.105-0.949" fill="url(#radialGradient4351)"/></g><g transform="matrix(1.3333 0 0 -1.3333 0 1024)"><path d="m623.43 485.96c-2.879-0.515-5.736-1.318-8.503-2.282-15.719-5.482-30.649-12.847-45.782-19.703-12.92-5.855-25.833-11.785-38.083-19.013-2.413-1.425-5.354-3.1-4.765-6.319 0.504-2.751 3.758-2.789 6.016-3.319 17.339-4.073 35.01-5.712 51.316-7.058 10.474 0.119 18.926 1.786 25.516 8.829 6.88 7.354 12.632 15.489 16.919 24.601 2.691 5.72 4.477 11.786 4.479 18.169 2e-3 4.153-1.835 6.272-5.267 6.272-0.571 0-1.188-0.059-1.846-0.177" fill="url(#radialGradient4371)"/></g><g transform="matrix(1.3333 0 0 -1.3333 0 1024)"><path d="m475.92 419.25c-0.367-0.104-0.71-0.292-1.065-0.437-22.555-9.166-44.21-20.249-65.865-31.321-5.464-2.792-10.748-5.939-15.684-9.652-5.297-3.984-8.316-8.98-7.651-15.694 0-0.89-0.035-1.662 5e-3 -2.431 0.397-7.627 4.249-12.981 10.812-16.559 6.098-3.323 11.35-1.172 16.365 2.52 1.236 0.911 2.498 1.799 3.649 2.811 22.843 20.104 43.28 42.565 63.347 65.381 1.264 1.437 3.447 3.141 2.012 5.299-0.613 0.922-1.333 1.232-2.102 1.231-1.232 0-2.589-0.797-3.823-1.148" fill="url(#radialGradient4391)"/></g><g transform="matrix(1.3333 0 0 -1.3333 0 1024)"><path d="m589.81 520.12c-2.155-1.598-4.18-3.381-6.195-5.159-14.338-12.647-27.085-26.892-40.259-40.697-5.472-5.734-10.87-11.588-15.617-17.981-0.828-1.116-1.856-2.369-0.83-3.824 0.972-1.379 2.519-1.147 3.87-0.737 1.831 0.556 3.62 1.272 5.386 2.018 23.705 10.024 46.647 21.655 69.496 33.464 6.568 3.394 13.185 6.854 18.588 12.131 2.02 1.974 3.277 4.24 3.342 7.436-0.072 2.307-0.943 4.698-2.221 6.96-4.063 7.191-12.308 11.156-20.705 11.156-5.215 0-10.489-1.53-14.855-4.767" fill="url(#radialGradient4413)"/></g><g transform="matrix(1.3333 0 0 -1.3333 0 1024)"><path d="m413.44 432.07c-5.861-6.67-11.407-13.524-15.914-21.172-2.874-4.876-5.175-10.064-6.355-15.632-0.819-3.866 0.412-5.458 3.711-5.517 2.078 0.023 4.038 0.607 5.971 1.3 18.942 6.786 36.964 15.693 55.17 24.173 7.774 3.62 15.486 7.375 22.781 11.914 2.187 1.361 4.634 2.997 4.191 5.914-0.396 2.604-3.356 2.319-5.257 3.169-0.692 0.308-1.469 0.436-2.215 0.612-11.727 2.765-23.679 4.039-35.625 5.305-1.381 0.147-2.734 0.221-4.058 0.221-8.707 0-16.182-3.211-22.4-10.287" fill="url(#radialGradient4433)"/></g><g transform="matrix(1.3333 0 0 -1.3333 0 1024)"><path d="m567.31 526.82c-14.169-1.46-27.39-6.06-40.199-12.109-7.73-3.652-12.567-9.493-14.146-17.954-1.793-9.597-3.042-19.253-2.904-29.045 0-2.697-0.068-5.395 0.022-8.089 0.067-1.997-0.399-4.391 1.83-5.503 2.219-1.108 4.351 0.084 6.055 1.392 2.834 2.177 5.567 4.529 8.099 7.054 14.061 14.024 27.283 28.841 40.42 43.726 4.051 4.589 8.321 9.1 10.964 14.772 1.594 3.423 0.812 4.978-2.88 5.7-1.112 0.218-2.231 0.3-3.353 0.3-1.302 0-2.607-0.11-3.908-0.244" fill="url(#radialGradient4453)"/></g><g transform="matrix(1.3333 0 0 -1.3333 0 1024)"><path d="m493.43 497.7c-3.943-1.236-7.454-3.334-11.002-5.368-14.602-8.364-28.162-18.128-39.782-30.405-5.392-5.696-4.536-9.429 2.935-11.564 13.892-3.97 27.998-7.036 42.519-7.395 7.646-0.189 11.495 3.378 12.86 10.836 1.395 7.615 1.69 15.275 1.485 25.395-0.22 3.072 0.784 8.645-0.687 14.051-0.943 3.472-2.527 5.026-5.134 5.025-0.929 0-1.988-0.197-3.194-0.575" fill="url(#radialGradient4475)"/></g><g transform="matrix(1.3333 0 0 -1.3333 0 1024)"><path d="m641.75 509.34c0.237-0.588 0.284-0.774 0.375-1.057 0.174-0.52 0.277-0.819 0.368-1.099 0.09-0.28 0.019-0.115 0.107-0.392 0.087-0.277 0.181-0.577 0.264-0.852 0.082-0.276 0.15-0.659 0.224-0.934 0.076-0.276 0.167-0.604 0.233-0.881 0.066-0.276 0.257-1.337 0.312-1.615 0.316-1.599 0.241-2.12 0.401-3.701 0.16-1.58 0.244-3.151 0.26-4.712 0.016-1.563-0.038-3.115-0.154-4.66-0.117-1.545-0.296-3.081-0.533-4.61-0.237-1.528-0.531-3.049-0.877-4.562-0.345-1.513-0.742-3.02-1.184-4.518-0.442-1.5-0.93-2.992-1.457-4.478-0.527-1.485-1.094-2.964-1.694-4.438-1.181-2.896-2.451-5.732-3.805-8.51-1.353-2.778-2.789-5.5-4.302-8.169-1.512-2.671-3.099-5.288-4.755-7.858-1.656-2.571-3.38-5.093-5.165-7.574-1.785-2.48-3.631-4.918-5.53-7.318-1.9-2.4-3.853-4.762-5.852-7.09s-4.044-4.624-6.129-6.89c-2.084-2.267-4.207-4.506-6.362-6.72-2.218-2.28-4.472-4.518-6.76-6.714-2.287-2.197-4.608-4.353-6.961-6.47s-4.737-4.196-7.152-6.237-4.86-4.046-7.334-6.016c-2.473-1.969-4.975-3.904-7.504-5.806-2.528-1.902-5.084-3.771-7.664-5.609-2.581-1.838-5.187-3.645-7.816-5.423-2.63-1.778-5.282-3.527-7.957-5.247-3.325-2.142-6.677-4.217-10.058-6.225-3.381-2.007-6.792-3.946-10.232-5.813-3.441-1.867-6.912-3.661-10.416-5.38s-7.041-3.362-10.611-4.924c-3.571-1.563-7.177-3.046-10.818-4.446-1.846-0.709-3.702-1.398-5.566-2.064-1.814-0.648-3.636-1.274-5.468-1.88-3.716-1.228-7.47-2.37-11.263-3.42-3.794-1.049-7.627-2.009-11.502-2.872-1.624-0.362-3.249-0.695-4.876-1-1.627-0.304-3.255-0.578-4.886-0.823-1.631-0.244-3.262-0.457-4.896-0.638s-3.269-0.33-4.907-0.445c-1.636-0.116-3.275-0.197-4.915-0.245-1.639-0.046-3.28-0.058-4.923-0.034-1.641 0.024-3.285 0.085-4.929 0.183-1.645 0.097-3.29 0.233-4.937 0.408-1.337 0.142-2.664 0.317-3.979 0.531s-2.617 0.466-3.906 0.759c-1.288 0.294-2.562 0.627-3.819 1.007-1.256 0.38-2.496 0.805-3.717 1.278-1.22 0.475-2.422 0.997-3.602 1.571-0.749 0.365-1.489 0.751-2.22 1.159-0.522 0.286-1.038 0.583-1.55 0.891-1.134 0.683-2.243 1.421-3.327 2.219-0.744 0.549-1.475 1.125-2.194 1.731-0.282 0.225-0.554 0.448-0.806 0.662 5.18-5.701 10.508-11.008 15.987-15.904 5.538-4.95 11.227-9.48 17.07-13.573 5.843-4.094 11.839-7.751 17.987-10.956 6.148-3.204 12.449-5.954 18.901-8.236 6.453-2.28 13.059-4.091 19.816-5.415 6.758-1.325 13.668-2.162 20.731-2.495s14.278-0.163 21.645 0.528c7.368 0.692 14.887 1.904 22.56 3.654 8.186 1.866 15.978 4.164 23.376 6.884 7.397 2.719 14.401 5.861 21.012 9.414 6.611 3.555 12.828 7.522 18.655 11.891 5.826 4.371 11.261 9.143 16.304 14.311 5.045 5.167 9.698 10.729 13.962 16.675 4.264 5.947 8.14 12.278 11.626 18.985 3.487 6.707 6.586 13.789 9.296 21.238 2.712 7.449 5.037 15.264 6.975 23.436 0.548 2.31 1.025 4.624 1.432 6.944 0.408 2.318 0.747 4.641 1.023 6.968 0.275 2.327 0.486 4.658 0.637 6.991 0.15 2.333 0.241 4.671 0.276 7.01 0.034 2.339 0.013 4.681-0.061 7.024-0.074 2.345-0.2 4.69-0.374 7.037-0.174 2.348-0.396 4.697-0.664 7.046-0.266 2.349-0.577 4.699-0.928 7.049-0.39 2.617-0.912 5.345-1.535 8.125-0.623 2.779-1.346 5.609-2.142 8.427-0.795 2.819-1.66 5.626-2.569 8.359-0.907 2.733-1.855 5.391-2.816 7.915-0.959 2.523-1.931 4.909-2.883 7.098-0.953 2.189-1.888 4.179-2.773 5.909-0.886 1.73-1.723 3.199-2.481 4.346-0.475 0.717-1.25 1.831-1.946 2.775 0.215-0.462 0.433-0.967 0.627-1.49" fill="url(#radialGradient4297)"/></g><g transform="matrix(1.3333 0 0 -1.3333 0 1024)" fill="url(#linearGradient7248-9)"><g clip-path="url(#clipPath4307-2)" fill="url(#linearGradient7248-9)"><g transform="translate(382.92 331.6)" fill="url(#linearGradient7248-9)"><path d="m0 0c6.615-7.221 13.678-14.033 21.222-20.267 7.539-6.237 15.577-11.868 24.046-16.747 8.468-4.879 17.397-8.961 26.656-12.07 9.256-3.117 18.837-5.252 28.533-6.374 9.697-1.126 19.503-1.24 29.23-0.453 9.731 0.785 19.379 2.465 28.846 4.853 18.922 4.75 37.215 12.333 53.364 23.285 8.065 5.472 15.569 11.768 22.33 18.787 6.768 7.012 12.789 14.739 18.015 22.967 10.462 16.478 17.733 34.859 22.474 53.789l0.439 1.778 0.209 0.872 0.186 0.899 0.747 3.597 0.373 1.798 0.093 0.45 0.024 0.112c-7e-3 -0.038 7e-3 0.055 0.01 0.076l0.034 0.227 0.134 0.908 0.539 3.634 0.269 1.817c0.077 0.534 0.118 1.199 0.18 1.791l0.338 3.658 0.164 1.796 0.075 1.835 0.15 3.671c0.264 9.752-0.472 19.521-1.882 29.184-1.502 9.646-4.173 19.05-7.353 28.281l-1.228 3.463-0.307 0.865-0.144 0.409-0.173 0.426-0.689 1.702-1.379 3.405-0.685 1.671-0.778 1.664-1.557 3.327-0.371 0.788-0.455 0.798-0.909 1.596-1.8039 3.187c-12.883 30.325-179.5-236.54-259.07-177.56zm-8e-3 8e-3c92.186-40.479 231.35 107.76 257.54 180.96 0.828-0.982 1.523-1.975 2.238-2.986 0.351-0.52 0.707-0.981 1.036-1.569l0.913-1.594 0.912-1.594 0.457-0.797c0.183-0.358 0.275-0.576 0.417-0.871l2.346-4.986c0.271-0.617 0.473-1.148 0.713-1.726l1.387-3.402 0.693-1.701 0.173-0.425 0.164-0.457 0.309-0.865 1.235-3.459c3.22-9.271 5.937-18.782 7.475-28.508 1.444-9.716 2.213-19.555 1.972-29.393l-0.14-3.671-0.069-1.836-0.164-1.863-0.329-3.659c-0.063-0.625-0.09-1.185-0.183-1.864l-0.264-1.818-0.528-3.635-0.132-0.909-0.033-0.227-0.024-0.152-0.023-0.112-0.092-0.45-0.369-1.8-0.736-3.599-0.185-0.9-0.216-0.915-0.436-1.79c-2.367-9.534-5.355-18.932-9.077-28.033-3.717-9.105-8.196-17.911-13.474-26.221-5.276-8.31-11.358-16.118-18.197-23.206-6.832-7.094-14.416-13.459-22.563-18.989-8.15-5.528-16.852-10.229-25.9-14.096-9.054-3.857-18.44-6.901-27.983-9.247-9.54-2.354-19.27-3.997-29.075-4.737-9.804-0.741-19.683-0.576-29.444 0.61-9.761 1.181-19.397 3.381-28.694 6.565-9.3 3.177-18.256 7.329-26.737 12.275-8.488 4.941-16.474 10.71-23.972 17.028-7.497 6.325-14.5 13.22-21.044 20.512z" fill="url(#linearGradient7248-9)"/></g></g></g><path d="m853.83 340.91z" fill="none" stroke="#000" stroke-width="1px"/><path d="m512.59 583.25c24.074 19.047 61.695 18.896 102.99 7.7902 41.299-11.105 86.278-33.164 124.63-62.438 43.462-33.178 74.436-61.06 97.049-98.852 10.211-17.065 16.881-32.679 20.405-47.4 3.5243-14.721 2.1355-27.046-3.4171-40.626 0 0 22.052 31.822 28.025 81.607 2.9864 24.893 1.4477 59.407-11.26 93.337-11.843 31.622-27.743 61.223-54.874 85.55-1.0301 0.92357-12.207 12.478-32.938 24.374-25.596 14.688-46.884 22.5-75.534 27.634-34.926 6.2583-59.469 4.4522-85.833-1.8111-36.815-8.7462-71.794-28.542-110.24-69.041" fill="url(#radialGradient7556)"/></g></g></svg>
3 Copyright 2014 Citra Emulator Project
4 Licensed under GPLv2 or any later version
5 Refer to the license.txt file included.
6-->
7<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 341.071 338.846">
8 <radialGradient id="a" cx="170.5356" cy="167.271" r="170.5332" gradientTransform="matrix(1 0 0 0.9935 0 3.2396)" gradientUnits="userSpaceOnUse">
9 <stop offset="0.5193" stop-color="#FFFFFF" stop-opacity="0.1"/>
10 <stop offset="0.9415" stop-color="#000000" stop-opacity="0.5"/>
11 <stop offset="1" stop-color="#1A1818" stop-opacity="0"/>
12 </radialGradient>
13 <ellipse fill="url(#a)" cx="170.535" cy="169.423" rx="170.535" ry="169.423"/>
14 <circle fill="#D16F17" cx="170.536" cy="167.885" r="161.557"/>
15 <linearGradient id="b" gradientUnits="userSpaceOnUse" x1="234.4458" y1="33.5771" x2="97.5655" y2="321.2358">
16 <stop offset="0" stop-color="#FFF8BD"/>
17 <stop offset="1" stop-color="#F6DCAE"/>
18 </linearGradient>
19 <circle fill="url(#b)" cx="170.536" cy="167.885" r="155.295"/>
20 <g>
21 <linearGradient id="c" gradientUnits="userSpaceOnUse" x1="332.436" y1="91.7446" x2="111.1593" y2="342.0988">
22 <stop offset="0" stop-color="#F7A076"/>
23 <stop offset="0.4455" stop-color="#F3816C"/>
24 <stop offset="1" stop-color="#F06878"/>
25 </linearGradient>
26 <path fill="url(#c)" stroke="#F06564" stroke-miterlimit="10" d="M309.704,123.138
27 c-5.9-7.802-128.517,44.681-128.517,44.681S303.803,221.01,309.704,212.5C322.434,194.142,323.182,140.957,309.704,123.138z"/>
28 <linearGradient id="d" gradientUnits="userSpaceOnUse" x1="285.5845" y1="50.3345" x2="64.3074" y2="300.6891">
29 <stop offset="0" stop-color="#9DC63B"/>
30 <stop offset="1" stop-color="#9BC183"/>
31 </linearGradient>
32 <path fill="url(#d)" stroke="#72AA42" stroke-miterlimit="10" d="M300.518,100.96c-3.98-21.983-41.059-60.12-63.189-63.188
33 c-9.688-1.345-59.28,122.469-59.28,122.469S302.364,111.149,300.518,100.96z"/>
34 <linearGradient id="e" gradientUnits="userSpaceOnUse" x1="229.4995" y1="0.7637" x2="8.2231" y2="251.1176">
35 <stop offset="0" stop-color="#D5DE26"/>
36 <stop offset="1" stop-color="#C5D94B"/>
37 </linearGradient>
38 <path fill="url(#e)" stroke="#BECD30" stroke-miterlimit="10" d="M215.151,28.584c-18.357-12.73-71.543-13.478-89.362,0.001
39 c-7.801,5.899,44.682,128.516,44.682,128.516S223.663,34.484,215.151,28.584z"/>
40 <linearGradient id="f" gradientUnits="userSpaceOnUse" x1="219.3823" y1="-8.1782" x2="-1.8941" y2="242.1756">
41 <stop offset="0" stop-color="#F2D200"/>
42 <stop offset="1" stop-color="#FDEF52"/>
43 </linearGradient>
44 <path fill="url(#f)" stroke="#E1BE29" stroke-miterlimit="10" d="M162.893,160.239c0,0-49.092-124.315-59.281-122.469
45 c-21.982,3.979-60.12,41.058-63.188,63.189C39.078,110.646,162.893,160.239,162.893,160.239z"/>
46 <linearGradient id="g" gradientUnits="userSpaceOnUse" x1="226.0718" y1="-2.2656" x2="4.7951" y2="248.0886">
47 <stop offset="0" stop-color="#FFCD10"/>
48 <stop offset="1" stop-color="#F29634"/>
49 </linearGradient>
50 <path fill="url(#g)" stroke="#F79421" stroke-miterlimit="10" d="M31.236,123.136c-12.73,18.357-13.479,71.543,0,89.362
51 c5.898,7.801,128.516-44.682,128.516-44.682S37.135,114.625,31.236,123.136z"/>
52 <linearGradient id="h" gradientUnits="userSpaceOnUse" x1="272.9214" y1="39.144" x2="51.6446" y2="289.4984">
53 <stop offset="0" stop-color="#F79F1C"/>
54 <stop offset="0.4455" stop-color="#F08021"/>
55 <stop offset="1" stop-color="#ED693C"/>
56 </linearGradient>
57 <path fill="url(#h)" stroke="#F16622" stroke-miterlimit="10" d="M40.422,234.676c3.979,21.982,41.057,60.12,63.188,63.188
58 c9.687,1.346,59.279-122.468,59.279-122.468S38.574,224.487,40.422,234.676z"/>
59 <linearGradient id="i" gradientUnits="userSpaceOnUse" x1="329.0083" y1="88.7129" x2="107.7311" y2="339.0677">
60 <stop offset="0" stop-color="#E47C26"/>
61 <stop offset="0.4455" stop-color="#DF5B27"/>
62 <stop offset="1" stop-color="#DD3A3A"/>
63 </linearGradient>
64 <path fill="url(#i)" stroke="#E03827" stroke-miterlimit="10" d="M125.787,307.051c18.357,12.73,71.543,13.48,89.362,0
65 c7.801-5.898-44.681-128.515-44.681-128.515S117.275,301.153,125.787,307.051z"/>
66 <linearGradient id="j" gradientUnits="userSpaceOnUse" x1="339.1245" y1="97.6562" x2="117.8478" y2="348.0104">
67 <stop offset="0" stop-color="#F3783C"/>
68 <stop offset="0.4455" stop-color="#EF5339"/>
69 <stop offset="1" stop-color="#ED294A"/>
70 </linearGradient>
71 <path fill="url(#j)" stroke="#ED2836" stroke-miterlimit="10" d="M178.047,175.398c0,0,49.09,124.315,59.28,122.467
72 c21.982-3.979,60.121-41.057,63.189-63.188C301.86,224.991,178.047,175.398,178.047,175.398z"/>
73 </g>
74 <linearGradient id="k" gradientUnits="userSpaceOnUse" x1="170.5352" y1="6.3281" x2="170.5351" y2="329.4424">
75 <stop offset="0" stop-color="#FFFFFF" stop-opacity="0.2"/>
76 <stop offset="0.4504" stop-color="#908E8E" stop-opacity="0.05"/>
77 <stop offset="1" stop-color="#030003" stop-opacity="0.2"/>
78 </linearGradient>
79 <circle fill="url(#k)" cx="170.536" cy="167.885" r="161.557"/>
80</svg>
diff --git a/dist/doc-icon.png b/dist/doc-icon.png
index 420b1546f..9b5773214 100644
--- a/dist/doc-icon.png
+++ b/dist/doc-icon.png
Binary files differ
diff --git a/externals/soundtouch b/externals/soundtouch
Subproject 5274ec4dec498bd88ccbcd28862a0f78a3b95ef Subproject 019d2089bbadf70d73ba85aa8ea51490b071262
diff --git a/src/audio_core/hle/source.cpp b/src/audio_core/hle/source.cpp
index 92484c526..de4e88cae 100644
--- a/src/audio_core/hle/source.cpp
+++ b/src/audio_core/hle/source.cpp
@@ -244,17 +244,27 @@ void Source::GenerateFrame() {
244 break; 244 break;
245 } 245 }
246 246
247 const size_t size_to_copy = 247 switch (state.interpolation_mode) {
248 std::min(state.current_buffer.size(), current_frame.size() - frame_position); 248 case InterpolationMode::None:
249 249 AudioInterp::None(state.interp_state, state.current_buffer, state.rate_multiplier,
250 std::copy(state.current_buffer.begin(), state.current_buffer.begin() + size_to_copy, 250 current_frame, frame_position);
251 current_frame.begin() + frame_position); 251 break;
252 state.current_buffer.erase(state.current_buffer.begin(), 252 case InterpolationMode::Linear:
253 state.current_buffer.begin() + size_to_copy); 253 AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier,
254 254 current_frame, frame_position);
255 frame_position += size_to_copy; 255 break;
256 state.next_sample_number += static_cast<u32>(size_to_copy); 256 case InterpolationMode::Polyphase:
257 // TODO(merry): Implement polyphase interpolation
258 LOG_DEBUG(Audio_DSP, "Polyphase interpolation unimplemented; falling back to linear");
259 AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier,
260 current_frame, frame_position);
261 break;
262 default:
263 UNIMPLEMENTED();
264 break;
265 }
257 } 266 }
267 state.next_sample_number += frame_position;
258 268
259 state.filters.ProcessFrame(current_frame); 269 state.filters.ProcessFrame(current_frame);
260} 270}
@@ -305,25 +315,6 @@ bool Source::DequeueBuffer() {
305 return true; 315 return true;
306 } 316 }
307 317
308 switch (state.interpolation_mode) {
309 case InterpolationMode::None:
310 state.current_buffer =
311 AudioInterp::None(state.interp_state, state.current_buffer, state.rate_multiplier);
312 break;
313 case InterpolationMode::Linear:
314 state.current_buffer =
315 AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier);
316 break;
317 case InterpolationMode::Polyphase:
318 // TODO(merry): Implement polyphase interpolation
319 state.current_buffer =
320 AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier);
321 break;
322 default:
323 UNIMPLEMENTED();
324 break;
325 }
326
327 // the first playthrough starts at play_position, loops start at the beginning of the buffer 318 // the first playthrough starts at play_position, loops start at the beginning of the buffer
328 state.current_sample_number = (!buf.has_played) ? buf.play_position : 0; 319 state.current_sample_number = (!buf.has_played) ? buf.play_position : 0;
329 state.next_sample_number = state.current_sample_number; 320 state.next_sample_number = state.current_sample_number;
diff --git a/src/audio_core/interpolate.cpp b/src/audio_core/interpolate.cpp
index 8a5d4181a..16e68bc5c 100644
--- a/src/audio_core/interpolate.cpp
+++ b/src/audio_core/interpolate.cpp
@@ -13,74 +13,64 @@ namespace AudioInterp {
13constexpr u64 scale_factor = 1 << 24; 13constexpr u64 scale_factor = 1 << 24;
14constexpr u64 scale_mask = scale_factor - 1; 14constexpr u64 scale_mask = scale_factor - 1;
15 15
16/// Here we step over the input in steps of rate_multiplier, until we consume all of the input. 16/// Here we step over the input in steps of rate, until we consume all of the input.
17/// Three adjacent samples are passed to fn each step. 17/// Three adjacent samples are passed to fn each step.
18template <typename Function> 18template <typename Function>
19static StereoBuffer16 StepOverSamples(State& state, const StereoBuffer16& input, 19static void StepOverSamples(State& state, StereoBuffer16& input, float rate,
20 float rate_multiplier, Function fn) { 20 DSP::HLE::StereoFrame16& output, size_t& outputi, Function fn) {
21 ASSERT(rate_multiplier > 0); 21 ASSERT(rate > 0);
22 22
23 if (input.size() < 2) 23 if (input.empty())
24 return {}; 24 return;
25 25
26 StereoBuffer16 output; 26 input.insert(input.begin(), {state.xn2, state.xn1});
27 output.reserve(static_cast<size_t>(input.size() / rate_multiplier));
28 27
29 u64 step_size = static_cast<u64>(rate_multiplier * scale_factor); 28 const u64 step_size = static_cast<u64>(rate * scale_factor);
29 u64 fposition = state.fposition;
30 size_t inputi = 0;
30 31
31 u64 fposition = 0; 32 while (outputi < output.size()) {
32 const u64 max_fposition = input.size() * scale_factor; 33 inputi = static_cast<size_t>(fposition / scale_factor);
33 34
34 while (fposition < 1 * scale_factor) { 35 if (inputi + 2 >= input.size()) {
35 u64 fraction = fposition & scale_mask; 36 inputi = input.size() - 2;
36 37 break;
37 output.push_back(fn(fraction, state.xn2, state.xn1, input[0])); 38 }
38
39 fposition += step_size;
40 }
41
42 while (fposition < 2 * scale_factor) {
43 u64 fraction = fposition & scale_mask;
44
45 output.push_back(fn(fraction, state.xn1, input[0], input[1]));
46
47 fposition += step_size;
48 }
49 39
50 while (fposition < max_fposition) {
51 u64 fraction = fposition & scale_mask; 40 u64 fraction = fposition & scale_mask;
52 41 output[outputi++] = fn(fraction, input[inputi], input[inputi + 1], input[inputi + 2]);
53 size_t index = static_cast<size_t>(fposition / scale_factor);
54 output.push_back(fn(fraction, input[index - 2], input[index - 1], input[index]));
55 42
56 fposition += step_size; 43 fposition += step_size;
57 } 44 }
58 45
59 state.xn2 = input[input.size() - 2]; 46 state.xn2 = input[inputi];
60 state.xn1 = input[input.size() - 1]; 47 state.xn1 = input[inputi + 1];
48 state.fposition = fposition - inputi * scale_factor;
61 49
62 return output; 50 input.erase(input.begin(), input.begin() + inputi + 2);
63} 51}
64 52
65StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multiplier) { 53void None(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output,
66 return StepOverSamples( 54 size_t& outputi) {
67 state, input, rate_multiplier, 55 StepOverSamples(
56 state, input, rate, output, outputi,
68 [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) { return x0; }); 57 [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) { return x0; });
69} 58}
70 59
71StereoBuffer16 Linear(State& state, const StereoBuffer16& input, float rate_multiplier) { 60void Linear(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output,
61 size_t& outputi) {
72 // Note on accuracy: Some values that this produces are +/- 1 from the actual firmware. 62 // Note on accuracy: Some values that this produces are +/- 1 from the actual firmware.
73 return StepOverSamples(state, input, rate_multiplier, 63 StepOverSamples(state, input, rate, output, outputi,
74 [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) { 64 [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) {
75 // This is a saturated subtraction. (Verified by black-box fuzzing.) 65 // This is a saturated subtraction. (Verified by black-box fuzzing.)
76 s64 delta0 = MathUtil::Clamp<s64>(x1[0] - x0[0], -32768, 32767); 66 s64 delta0 = MathUtil::Clamp<s64>(x1[0] - x0[0], -32768, 32767);
77 s64 delta1 = MathUtil::Clamp<s64>(x1[1] - x0[1], -32768, 32767); 67 s64 delta1 = MathUtil::Clamp<s64>(x1[1] - x0[1], -32768, 32767);
78 68
79 return std::array<s16, 2>{ 69 return std::array<s16, 2>{
80 static_cast<s16>(x0[0] + fraction * delta0 / scale_factor), 70 static_cast<s16>(x0[0] + fraction * delta0 / scale_factor),
81 static_cast<s16>(x0[1] + fraction * delta1 / scale_factor), 71 static_cast<s16>(x0[1] + fraction * delta1 / scale_factor),
82 }; 72 };
83 }); 73 });
84} 74}
85 75
86} // namespace AudioInterp 76} // namespace AudioInterp
diff --git a/src/audio_core/interpolate.h b/src/audio_core/interpolate.h
index 19a7b66cb..59f59bc14 100644
--- a/src/audio_core/interpolate.h
+++ b/src/audio_core/interpolate.h
@@ -6,6 +6,7 @@
6 6
7#include <array> 7#include <array>
8#include <vector> 8#include <vector>
9#include "audio_core/hle/common.h"
9#include "common/common_types.h" 10#include "common/common_types.h"
10 11
11namespace AudioInterp { 12namespace AudioInterp {
@@ -14,31 +15,35 @@ namespace AudioInterp {
14using StereoBuffer16 = std::vector<std::array<s16, 2>>; 15using StereoBuffer16 = std::vector<std::array<s16, 2>>;
15 16
16struct State { 17struct State {
17 // Two historical samples. 18 /// Two historical samples.
18 std::array<s16, 2> xn1 = {}; ///< x[n-1] 19 std::array<s16, 2> xn1 = {}; ///< x[n-1]
19 std::array<s16, 2> xn2 = {}; ///< x[n-2] 20 std::array<s16, 2> xn2 = {}; ///< x[n-2]
21 /// Current fractional position.
22 u64 fposition = 0;
20}; 23};
21 24
22/** 25/**
23 * No interpolation. This is equivalent to a zero-order hold. There is a two-sample predelay. 26 * No interpolation. This is equivalent to a zero-order hold. There is a two-sample predelay.
24 * @param state Interpolation state. 27 * @param state Interpolation state.
25 * @param input Input buffer. 28 * @param input Input buffer.
26 * @param rate_multiplier Stretch factor. Must be a positive non-zero value. 29 * @param rate Stretch factor. Must be a positive non-zero value.
27 * rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0 30 * rate > 1.0 performs decimation and rate < 1.0 performs upsampling.
28 * performs upsampling. 31 * @param output The resampled audio buffer.
29 * @return The resampled audio buffer. 32 * @param outputi The index of output to start writing to.
30 */ 33 */
31StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multiplier); 34void None(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output,
35 size_t& outputi);
32 36
33/** 37/**
34 * Linear interpolation. This is equivalent to a first-order hold. There is a two-sample predelay. 38 * Linear interpolation. This is equivalent to a first-order hold. There is a two-sample predelay.
35 * @param state Interpolation state. 39 * @param state Interpolation state.
36 * @param input Input buffer. 40 * @param input Input buffer.
37 * @param rate_multiplier Stretch factor. Must be a positive non-zero value. 41 * @param rate Stretch factor. Must be a positive non-zero value.
38 * rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0 42 * rate > 1.0 performs decimation and rate < 1.0 performs upsampling.
39 * performs upsampling. 43 * @param output The resampled audio buffer.
40 * @return The resampled audio buffer. 44 * @param outputi The index of output to start writing to.
41 */ 45 */
42StereoBuffer16 Linear(State& state, const StereoBuffer16& input, float rate_multiplier); 46void Linear(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output,
47 size_t& outputi);
43 48
44} // namespace AudioInterp 49} // namespace AudioInterp
diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp
index 14574e56c..e524c5535 100644
--- a/src/citra/citra.cpp
+++ b/src/citra/citra.cpp
@@ -165,6 +165,8 @@ int main(int argc, char** argv) {
165 break; // Expected case 165 break; // Expected case
166 } 166 }
167 167
168 Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "SDL");
169
168 while (emu_window->IsOpen()) { 170 while (emu_window->IsOpen()) {
169 system.RunLoop(); 171 system.RunLoop();
170 } 172 }
diff --git a/src/citra/citra.rc b/src/citra/citra.rc
index fea603004..c490ef302 100644
--- a/src/citra/citra.rc
+++ b/src/citra/citra.rc
@@ -1,3 +1,4 @@
1#include "winresrc.h"
1///////////////////////////////////////////////////////////////////////////// 2/////////////////////////////////////////////////////////////////////////////
2// 3//
3// Icon 4// Icon
@@ -7,3 +8,10 @@
7// remains consistent on all systems. 8// remains consistent on all systems.
8CITRA_ICON ICON "../../dist/citra.ico" 9CITRA_ICON ICON "../../dist/citra.ico"
9 10
11
12/////////////////////////////////////////////////////////////////////////////
13//
14// RT_MANIFEST
15//
16
171 RT_MANIFEST "../../dist/citra.manifest"
diff --git a/src/citra/config.cpp b/src/citra/config.cpp
index 69247b166..a48ef08c7 100644
--- a/src/citra/config.cpp
+++ b/src/citra/config.cpp
@@ -76,6 +76,11 @@ void Config::ReadValues() {
76 Settings::values.analogs[i] = default_param; 76 Settings::values.analogs[i] = default_param;
77 } 77 }
78 78
79 Settings::values.motion_device = sdl2_config->Get(
80 "Controls", "motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01");
81 Settings::values.touch_device =
82 sdl2_config->Get("Controls", "touch_device", "engine:emu_window");
83
79 // Core 84 // Core
80 Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true); 85 Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
81 86
@@ -153,8 +158,12 @@ void Config::ReadValues() {
153 static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689)); 158 static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689));
154 159
155 // Web Service 160 // Web Service
161 Settings::values.enable_telemetry =
162 sdl2_config->GetBoolean("WebService", "enable_telemetry", true);
156 Settings::values.telemetry_endpoint_url = sdl2_config->Get( 163 Settings::values.telemetry_endpoint_url = sdl2_config->Get(
157 "WebService", "telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry"); 164 "WebService", "telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry");
165 Settings::values.citra_username = sdl2_config->Get("WebService", "citra_username", "");
166 Settings::values.citra_token = sdl2_config->Get("WebService", "citra_token", "");
158} 167}
159 168
160void Config::Reload() { 169void Config::Reload() {
diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h
index a12498e0f..4b13a2e1b 100644
--- a/src/citra/default_ini.h
+++ b/src/citra/default_ini.h
@@ -12,7 +12,7 @@ const char* sdl2_config_file = R"(
12# It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..." 12# It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..."
13# Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values 13# Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values
14 14
15# for button input, the following devices are avaible: 15# for button input, the following devices are available:
16# - "keyboard" (default) for keyboard input. Required parameters: 16# - "keyboard" (default) for keyboard input. Required parameters:
17# - "code": the code of the key to bind 17# - "code": the code of the key to bind
18# - "sdl" for joystick input using SDL. Required parameters: 18# - "sdl" for joystick input using SDL. Required parameters:
@@ -21,7 +21,7 @@ const char* sdl2_config_file = R"(
21# - "hat"(optional): the index of the hat to bind as direction buttons 21# - "hat"(optional): the index of the hat to bind as direction buttons
22# - "axis"(optional): the index of the axis to bind 22# - "axis"(optional): the index of the axis to bind
23# - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", "down", "left" or "right" 23# - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", "down", "left" or "right"
24# - "threshould"(only used for axis): a float value in (-1.0, 1.0) which the button is 24# - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is
25# triggered if the axis value crosses 25# triggered if the axis value crosses
26# - "direction"(only used for axis): "+" means the button is triggered when the axis value 26# - "direction"(only used for axis): "+" means the button is triggered when the axis value
27# is greater than the threshold; "-" means the button is triggered when the axis value 27# is greater than the threshold; "-" means the button is triggered when the axis value
@@ -42,8 +42,8 @@ button_zl=
42button_zr= 42button_zr=
43button_home= 43button_home=
44 44
45# for analog input, the following devices are avaible: 45# for analog input, the following devices are available:
46# - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters: 46# - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters:
47# - "up", "down", "left", "right": sub-devices for each direction. 47# - "up", "down", "left", "right": sub-devices for each direction.
48# Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00" 48# Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00"
49# - "modifier": sub-devices as a modifier. 49# - "modifier": sub-devices as a modifier.
@@ -56,6 +56,16 @@ button_home=
56circle_pad= 56circle_pad=
57c_stick= 57c_stick=
58 58
59# for motion input, the following devices are available:
60# - "motion_emu" (default) for emulating motion input from mouse input. Required parameters:
61# - "update_period": update period in milliseconds (default to 100)
62# - "sensitivity": the coefficient converting mouse movement to tilting angle (default to 0.01)
63motion_device=
64
65# for touch input, the following devices are available:
66# - "emu_window" (default) for emulating touch input from mouse input to the emulation window. No parameters required
67touch_device=
68
59[Core] 69[Core]
60# Whether to use the Just-In-Time (JIT) compiler for CPU emulation 70# Whether to use the Just-In-Time (JIT) compiler for CPU emulation
61# 0: Interpreter (slow), 1 (default): JIT (fast) 71# 0: Interpreter (slow), 1 (default): JIT (fast)
@@ -170,7 +180,14 @@ use_gdbstub=false
170gdbstub_port=24689 180gdbstub_port=24689
171 181
172[WebService] 182[WebService]
183# Whether or not to enable telemetry
184# 0: No, 1 (default): Yes
185enable_telemetry =
173# Endpoint URL for submitting telemetry data 186# Endpoint URL for submitting telemetry data
174telemetry_endpoint_url = 187telemetry_endpoint_url = https://services.citra-emu.org/api/telemetry
188# Username and token for Citra Web Service
189# See https://services.citra-emu.org/ for more info
190citra_username =
191citra_token =
175)"; 192)";
176} 193}
diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp
index b0f808399..25643715a 100644
--- a/src/citra/emu_window/emu_window_sdl2.cpp
+++ b/src/citra/emu_window/emu_window_sdl2.cpp
@@ -16,11 +16,12 @@
16#include "core/settings.h" 16#include "core/settings.h"
17#include "input_common/keyboard.h" 17#include "input_common/keyboard.h"
18#include "input_common/main.h" 18#include "input_common/main.h"
19#include "input_common/motion_emu.h"
19#include "network/network.h" 20#include "network/network.h"
20 21
21void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { 22void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
22 TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); 23 TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
23 motion_emu->Tilt(x, y); 24 InputCommon::GetMotionEmu()->Tilt(x, y);
24} 25}
25 26
26void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { 27void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
@@ -32,9 +33,9 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
32 } 33 }
33 } else if (button == SDL_BUTTON_RIGHT) { 34 } else if (button == SDL_BUTTON_RIGHT) {
34 if (state == SDL_PRESSED) { 35 if (state == SDL_PRESSED) {
35 motion_emu->BeginTilt(x, y); 36 InputCommon::GetMotionEmu()->BeginTilt(x, y);
36 } else { 37 } else {
37 motion_emu->EndTilt(); 38 InputCommon::GetMotionEmu()->EndTilt();
38 } 39 }
39 } 40 }
40} 41}
@@ -61,8 +62,6 @@ EmuWindow_SDL2::EmuWindow_SDL2() {
61 InputCommon::Init(); 62 InputCommon::Init();
62 Network::Init(); 63 Network::Init();
63 64
64 motion_emu = std::make_unique<Motion::MotionEmu>(*this);
65
66 SDL_SetMainReady(); 65 SDL_SetMainReady();
67 66
68 // Initialize the window 67 // Initialize the window
@@ -117,7 +116,6 @@ EmuWindow_SDL2::EmuWindow_SDL2() {
117EmuWindow_SDL2::~EmuWindow_SDL2() { 116EmuWindow_SDL2::~EmuWindow_SDL2() {
118 SDL_GL_DeleteContext(gl_context); 117 SDL_GL_DeleteContext(gl_context);
119 SDL_Quit(); 118 SDL_Quit();
120 motion_emu = nullptr;
121 119
122 Network::Shutdown(); 120 Network::Shutdown();
123 InputCommon::Shutdown(); 121 InputCommon::Shutdown();
diff --git a/src/citra/emu_window/emu_window_sdl2.h b/src/citra/emu_window/emu_window_sdl2.h
index 1ce2991f7..3664d2fbe 100644
--- a/src/citra/emu_window/emu_window_sdl2.h
+++ b/src/citra/emu_window/emu_window_sdl2.h
@@ -7,7 +7,6 @@
7#include <memory> 7#include <memory>
8#include <utility> 8#include <utility>
9#include "core/frontend/emu_window.h" 9#include "core/frontend/emu_window.h"
10#include "core/frontend/motion_emu.h"
11 10
12struct SDL_Window; 11struct SDL_Window;
13 12
@@ -57,7 +56,4 @@ private:
57 using SDL_GLContext = void*; 56 using SDL_GLContext = void*;
58 /// The OpenGL context associated with the window 57 /// The OpenGL context associated with the window
59 SDL_GLContext gl_context; 58 SDL_GLContext gl_context;
60
61 /// Motion sensors emulation
62 std::unique_ptr<Motion::MotionEmu> motion_emu;
63}; 59};
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index f364b2284..e0a19fd9e 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -12,6 +12,7 @@ set(SRCS
12 configuration/configure_graphics.cpp 12 configuration/configure_graphics.cpp
13 configuration/configure_input.cpp 13 configuration/configure_input.cpp
14 configuration/configure_system.cpp 14 configuration/configure_system.cpp
15 configuration/configure_web.cpp
15 debugger/graphics/graphics.cpp 16 debugger/graphics/graphics.cpp
16 debugger/graphics/graphics_breakpoint_observer.cpp 17 debugger/graphics/graphics_breakpoint_observer.cpp
17 debugger/graphics/graphics_breakpoints.cpp 18 debugger/graphics/graphics_breakpoints.cpp
@@ -42,6 +43,7 @@ set(HEADERS
42 configuration/configure_graphics.h 43 configuration/configure_graphics.h
43 configuration/configure_input.h 44 configuration/configure_input.h
44 configuration/configure_system.h 45 configuration/configure_system.h
46 configuration/configure_web.h
45 debugger/graphics/graphics.h 47 debugger/graphics/graphics.h
46 debugger/graphics/graphics_breakpoint_observer.h 48 debugger/graphics/graphics_breakpoint_observer.h
47 debugger/graphics/graphics_breakpoints.h 49 debugger/graphics/graphics_breakpoints.h
@@ -71,6 +73,7 @@ set(UIS
71 configuration/configure_graphics.ui 73 configuration/configure_graphics.ui
72 configuration/configure_input.ui 74 configuration/configure_input.ui
73 configuration/configure_system.ui 75 configuration/configure_system.ui
76 configuration/configure_web.ui
74 debugger/registers.ui 77 debugger/registers.ui
75 hotkeys.ui 78 hotkeys.ui
76 main.ui 79 main.ui
diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp
index 30554890f..7107bfc60 100644
--- a/src/citra_qt/bootmanager.cpp
+++ b/src/citra_qt/bootmanager.cpp
@@ -17,6 +17,7 @@
17#include "core/settings.h" 17#include "core/settings.h"
18#include "input_common/keyboard.h" 18#include "input_common/keyboard.h"
19#include "input_common/main.h" 19#include "input_common/main.h"
20#include "input_common/motion_emu.h"
20#include "network/network.h" 21#include "network/network.h"
21 22
22EmuThread::EmuThread(GRenderWindow* render_window) 23EmuThread::EmuThread(GRenderWindow* render_window)
@@ -201,7 +202,6 @@ qreal GRenderWindow::windowPixelRatio() {
201} 202}
202 203
203void GRenderWindow::closeEvent(QCloseEvent* event) { 204void GRenderWindow::closeEvent(QCloseEvent* event) {
204 motion_emu = nullptr;
205 emit Closed(); 205 emit Closed();
206 QWidget::closeEvent(event); 206 QWidget::closeEvent(event);
207} 207}
@@ -221,7 +221,7 @@ void GRenderWindow::mousePressEvent(QMouseEvent* event) {
221 this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio), 221 this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio),
222 static_cast<unsigned>(pos.y() * pixelRatio)); 222 static_cast<unsigned>(pos.y() * pixelRatio));
223 } else if (event->button() == Qt::RightButton) { 223 } else if (event->button() == Qt::RightButton) {
224 motion_emu->BeginTilt(pos.x(), pos.y()); 224 InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
225 } 225 }
226} 226}
227 227
@@ -230,14 +230,14 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
230 qreal pixelRatio = windowPixelRatio(); 230 qreal pixelRatio = windowPixelRatio();
231 this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * pixelRatio), 0u), 231 this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * pixelRatio), 0u),
232 std::max(static_cast<unsigned>(pos.y() * pixelRatio), 0u)); 232 std::max(static_cast<unsigned>(pos.y() * pixelRatio), 0u));
233 motion_emu->Tilt(pos.x(), pos.y()); 233 InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
234} 234}
235 235
236void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { 236void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
237 if (event->button() == Qt::LeftButton) 237 if (event->button() == Qt::LeftButton)
238 this->TouchReleased(); 238 this->TouchReleased();
239 else if (event->button() == Qt::RightButton) 239 else if (event->button() == Qt::RightButton)
240 motion_emu->EndTilt(); 240 InputCommon::GetMotionEmu()->EndTilt();
241} 241}
242 242
243void GRenderWindow::focusOutEvent(QFocusEvent* event) { 243void GRenderWindow::focusOutEvent(QFocusEvent* event) {
@@ -290,13 +290,11 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(
290} 290}
291 291
292void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) { 292void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
293 motion_emu = std::make_unique<Motion::MotionEmu>(*this);
294 this->emu_thread = emu_thread; 293 this->emu_thread = emu_thread;
295 child->DisablePainting(); 294 child->DisablePainting();
296} 295}
297 296
298void GRenderWindow::OnEmulationStopping() { 297void GRenderWindow::OnEmulationStopping() {
299 motion_emu = nullptr;
300 emu_thread = nullptr; 298 emu_thread = nullptr;
301 child->EnablePainting(); 299 child->EnablePainting();
302} 300}
diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h
index 4b3a3b3cc..6974edcbb 100644
--- a/src/citra_qt/bootmanager.h
+++ b/src/citra_qt/bootmanager.h
@@ -12,7 +12,6 @@
12#include "common/thread.h" 12#include "common/thread.h"
13#include "core/core.h" 13#include "core/core.h"
14#include "core/frontend/emu_window.h" 14#include "core/frontend/emu_window.h"
15#include "core/frontend/motion_emu.h"
16 15
17class QKeyEvent; 16class QKeyEvent;
18class QScreen; 17class QScreen;
@@ -158,9 +157,6 @@ private:
158 157
159 EmuThread* emu_thread; 158 EmuThread* emu_thread;
160 159
161 /// Motion sensors emulation
162 std::unique_ptr<Motion::MotionEmu> motion_emu;
163
164protected: 160protected:
165 void showEvent(QShowEvent* event) override; 161 void showEvent(QShowEvent* event) override;
166}; 162};
diff --git a/src/citra_qt/citra-qt.rc b/src/citra_qt/citra-qt.rc
index fea603004..a48a9440d 100644
--- a/src/citra_qt/citra-qt.rc
+++ b/src/citra_qt/citra-qt.rc
@@ -1,3 +1,4 @@
1#include "winresrc.h"
1///////////////////////////////////////////////////////////////////////////// 2/////////////////////////////////////////////////////////////////////////////
2// 3//
3// Icon 4// Icon
@@ -5,5 +6,14 @@
5 6
6// Icon with lowest ID value placed first to ensure application icon 7// Icon with lowest ID value placed first to ensure application icon
7// remains consistent on all systems. 8// remains consistent on all systems.
8CITRA_ICON ICON "../../dist/citra.ico" 9// QT requires that the default application icon is named IDI_ICON1
9 10
11IDI_ICON1 ICON "../../dist/citra.ico"
12
13
14/////////////////////////////////////////////////////////////////////////////
15//
16// RT_MANIFEST
17//
18
191 RT_MANIFEST "../../dist/citra.manifest"
diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp
index 75abb4ce6..ef114aad3 100644
--- a/src/citra_qt/configuration/config.cpp
+++ b/src/citra_qt/configuration/config.cpp
@@ -57,6 +57,13 @@ void Config::ReadValues() {
57 Settings::values.analogs[i] = default_param; 57 Settings::values.analogs[i] = default_param;
58 } 58 }
59 59
60 Settings::values.motion_device =
61 qt_config->value("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01")
62 .toString()
63 .toStdString();
64 Settings::values.touch_device =
65 qt_config->value("touch_device", "engine:emu_window").toString().toStdString();
66
60 qt_config->endGroup(); 67 qt_config->endGroup();
61 68
62 qt_config->beginGroup("Core"); 69 qt_config->beginGroup("Core");
@@ -134,10 +141,13 @@ void Config::ReadValues() {
134 qt_config->endGroup(); 141 qt_config->endGroup();
135 142
136 qt_config->beginGroup("WebService"); 143 qt_config->beginGroup("WebService");
144 Settings::values.enable_telemetry = qt_config->value("enable_telemetry", true).toBool();
137 Settings::values.telemetry_endpoint_url = 145 Settings::values.telemetry_endpoint_url =
138 qt_config->value("telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry") 146 qt_config->value("telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry")
139 .toString() 147 .toString()
140 .toStdString(); 148 .toStdString();
149 Settings::values.citra_username = qt_config->value("citra_username").toString().toStdString();
150 Settings::values.citra_token = qt_config->value("citra_token").toString().toStdString();
141 qt_config->endGroup(); 151 qt_config->endGroup();
142 152
143 qt_config->beginGroup("UI"); 153 qt_config->beginGroup("UI");
@@ -189,6 +199,7 @@ void Config::ReadValues() {
189 UISettings::values.show_status_bar = qt_config->value("showStatusBar", true).toBool(); 199 UISettings::values.show_status_bar = qt_config->value("showStatusBar", true).toBool();
190 UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool(); 200 UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool();
191 UISettings::values.first_start = qt_config->value("firstStart", true).toBool(); 201 UISettings::values.first_start = qt_config->value("firstStart", true).toBool();
202 UISettings::values.callout_flags = qt_config->value("calloutFlags", 0).toUInt();
192 203
193 qt_config->endGroup(); 204 qt_config->endGroup();
194} 205}
@@ -203,6 +214,8 @@ void Config::SaveValues() {
203 qt_config->setValue(QString::fromStdString(Settings::NativeAnalog::mapping[i]), 214 qt_config->setValue(QString::fromStdString(Settings::NativeAnalog::mapping[i]),
204 QString::fromStdString(Settings::values.analogs[i])); 215 QString::fromStdString(Settings::values.analogs[i]));
205 } 216 }
217 qt_config->setValue("motion_device", QString::fromStdString(Settings::values.motion_device));
218 qt_config->setValue("touch_device", QString::fromStdString(Settings::values.touch_device));
206 qt_config->endGroup(); 219 qt_config->endGroup();
207 220
208 qt_config->beginGroup("Core"); 221 qt_config->beginGroup("Core");
@@ -277,8 +290,11 @@ void Config::SaveValues() {
277 qt_config->endGroup(); 290 qt_config->endGroup();
278 291
279 qt_config->beginGroup("WebService"); 292 qt_config->beginGroup("WebService");
293 qt_config->setValue("enable_telemetry", Settings::values.enable_telemetry);
280 qt_config->setValue("telemetry_endpoint_url", 294 qt_config->setValue("telemetry_endpoint_url",
281 QString::fromStdString(Settings::values.telemetry_endpoint_url)); 295 QString::fromStdString(Settings::values.telemetry_endpoint_url));
296 qt_config->setValue("citra_username", QString::fromStdString(Settings::values.citra_username));
297 qt_config->setValue("citra_token", QString::fromStdString(Settings::values.citra_token));
282 qt_config->endGroup(); 298 qt_config->endGroup();
283 299
284 qt_config->beginGroup("UI"); 300 qt_config->beginGroup("UI");
@@ -314,6 +330,7 @@ void Config::SaveValues() {
314 qt_config->setValue("showStatusBar", UISettings::values.show_status_bar); 330 qt_config->setValue("showStatusBar", UISettings::values.show_status_bar);
315 qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing); 331 qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing);
316 qt_config->setValue("firstStart", UISettings::values.first_start); 332 qt_config->setValue("firstStart", UISettings::values.first_start);
333 qt_config->setValue("calloutFlags", UISettings::values.callout_flags);
317 334
318 qt_config->endGroup(); 335 qt_config->endGroup();
319} 336}
diff --git a/src/citra_qt/configuration/configure.ui b/src/citra_qt/configuration/configure.ui
index 85e206e42..6abd1917e 100644
--- a/src/citra_qt/configuration/configure.ui
+++ b/src/citra_qt/configuration/configure.ui
@@ -6,8 +6,8 @@
6 <rect> 6 <rect>
7 <x>0</x> 7 <x>0</x>
8 <y>0</y> 8 <y>0</y>
9 <width>441</width> 9 <width>740</width>
10 <height>501</height> 10 <height>500</height>
11 </rect> 11 </rect>
12 </property> 12 </property>
13 <property name="windowTitle"> 13 <property name="windowTitle">
@@ -49,6 +49,11 @@
49 <string>Debug</string> 49 <string>Debug</string>
50 </attribute> 50 </attribute>
51 </widget> 51 </widget>
52 <widget class="ConfigureWeb" name="webTab">
53 <attribute name="title">
54 <string>Web</string>
55 </attribute>
56 </widget>
52 </widget> 57 </widget>
53 </item> 58 </item>
54 <item> 59 <item>
@@ -97,6 +102,12 @@
97 <header>configuration/configure_graphics.h</header> 102 <header>configuration/configure_graphics.h</header>
98 <container>1</container> 103 <container>1</container>
99 </customwidget> 104 </customwidget>
105 <customwidget>
106 <class>ConfigureWeb</class>
107 <extends>QWidget</extends>
108 <header>configuration/configure_web.h</header>
109 <container>1</container>
110 </customwidget>
100 </customwidgets> 111 </customwidgets>
101 <resources/> 112 <resources/>
102 <connections> 113 <connections>
diff --git a/src/citra_qt/configuration/configure_dialog.cpp b/src/citra_qt/configuration/configure_dialog.cpp
index dfc8c03a7..b87dc0e6c 100644
--- a/src/citra_qt/configuration/configure_dialog.cpp
+++ b/src/citra_qt/configuration/configure_dialog.cpp
@@ -23,5 +23,6 @@ void ConfigureDialog::applyConfiguration() {
23 ui->graphicsTab->applyConfiguration(); 23 ui->graphicsTab->applyConfiguration();
24 ui->audioTab->applyConfiguration(); 24 ui->audioTab->applyConfiguration();
25 ui->debugTab->applyConfiguration(); 25 ui->debugTab->applyConfiguration();
26 ui->webTab->applyConfiguration();
26 Settings::Apply(); 27 Settings::Apply();
27} 28}
diff --git a/src/citra_qt/configuration/configure_graphics.ui b/src/citra_qt/configuration/configure_graphics.ui
index 228f2a869..b340149d5 100644
--- a/src/citra_qt/configuration/configure_graphics.ui
+++ b/src/citra_qt/configuration/configure_graphics.ui
@@ -146,17 +146,22 @@
146 <widget class="QComboBox" name="layout_combobox"> 146 <widget class="QComboBox" name="layout_combobox">
147 <item> 147 <item>
148 <property name="text"> 148 <property name="text">
149 <string notr="true">Default</string> 149 <string>Default</string>
150 </property> 150 </property>
151 </item> 151 </item>
152 <item> 152 <item>
153 <property name="text"> 153 <property name="text">
154 <string notr="true">Single Screen</string> 154 <string>Single Screen</string>
155 </property> 155 </property>
156 </item> 156 </item>
157 <item> 157 <item>
158 <property name="text"> 158 <property name="text">
159 <string notr="true">Large Screen</string> 159 <string>Large Screen</string>
160 </property>
161 </item>
162 <item>
163 <property name="text">
164 <string>Side by Side</string>
160 </property> 165 </property>
161 </item> 166 </item>
162 </widget> 167 </widget>
diff --git a/src/citra_qt/configuration/configure_web.cpp b/src/citra_qt/configuration/configure_web.cpp
new file mode 100644
index 000000000..8715fb018
--- /dev/null
+++ b/src/citra_qt/configuration/configure_web.cpp
@@ -0,0 +1,52 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "citra_qt/configuration/configure_web.h"
6#include "core/settings.h"
7#include "core/telemetry_session.h"
8#include "ui_configure_web.h"
9
10ConfigureWeb::ConfigureWeb(QWidget* parent)
11 : QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) {
12 ui->setupUi(this);
13 connect(ui->button_regenerate_telemetry_id, &QPushButton::clicked, this,
14 &ConfigureWeb::refreshTelemetryID);
15
16 this->setConfiguration();
17}
18
19ConfigureWeb::~ConfigureWeb() {}
20
21void ConfigureWeb::setConfiguration() {
22 ui->web_credentials_disclaimer->setWordWrap(true);
23 ui->telemetry_learn_more->setOpenExternalLinks(true);
24 ui->telemetry_learn_more->setText("<a "
25 "href='https://citra-emu.org/entry/"
26 "telemetry-and-why-thats-a-good-thing/'>Learn more</a>");
27
28 ui->web_signup_link->setOpenExternalLinks(true);
29 ui->web_signup_link->setText("<a href='https://services.citra-emu.org/'>Sign up</a>");
30 ui->web_token_info_link->setOpenExternalLinks(true);
31 ui->web_token_info_link->setText(
32 "<a href='https://citra-emu.org/wiki/citra-web-service/'>What is my token?</a>");
33
34 ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry);
35 ui->edit_username->setText(QString::fromStdString(Settings::values.citra_username));
36 ui->edit_token->setText(QString::fromStdString(Settings::values.citra_token));
37 ui->label_telemetry_id->setText("Telemetry ID: 0x" +
38 QString::number(Core::GetTelemetryId(), 16).toUpper());
39}
40
41void ConfigureWeb::applyConfiguration() {
42 Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked();
43 Settings::values.citra_username = ui->edit_username->text().toStdString();
44 Settings::values.citra_token = ui->edit_token->text().toStdString();
45 Settings::Apply();
46}
47
48void ConfigureWeb::refreshTelemetryID() {
49 const u64 new_telemetry_id{Core::RegenerateTelemetryId()};
50 ui->label_telemetry_id->setText("Telemetry ID: 0x" +
51 QString::number(new_telemetry_id, 16).toUpper());
52}
diff --git a/src/citra_qt/configuration/configure_web.h b/src/citra_qt/configuration/configure_web.h
new file mode 100644
index 000000000..20bc254b9
--- /dev/null
+++ b/src/citra_qt/configuration/configure_web.h
@@ -0,0 +1,30 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <QWidget>
9
10namespace Ui {
11class ConfigureWeb;
12}
13
14class ConfigureWeb : public QWidget {
15 Q_OBJECT
16
17public:
18 explicit ConfigureWeb(QWidget* parent = nullptr);
19 ~ConfigureWeb();
20
21 void applyConfiguration();
22
23public slots:
24 void refreshTelemetryID();
25
26private:
27 void setConfiguration();
28
29 std::unique_ptr<Ui::ConfigureWeb> ui;
30};
diff --git a/src/citra_qt/configuration/configure_web.ui b/src/citra_qt/configuration/configure_web.ui
new file mode 100644
index 000000000..d8d283fad
--- /dev/null
+++ b/src/citra_qt/configuration/configure_web.ui
@@ -0,0 +1,153 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>ConfigureWeb</class>
4 <widget class="QWidget" name="ConfigureWeb">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>400</width>
10 <height>300</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>Form</string>
15 </property>
16 <layout class="QVBoxLayout" name="verticalLayout">
17 <item>
18 <layout class="QVBoxLayout" name="verticalLayout_3">
19 <item>
20 <widget class="QGroupBox" name="groupBoxWebConfig">
21 <property name="title">
22 <string>Citra Web Service</string>
23 </property>
24 <layout class="QVBoxLayout" name="verticalLayoutCitraWebService">
25 <item>
26 <widget class="QLabel" name="web_credentials_disclaimer">
27 <property name="text">
28 <string>By providing your username and token, you agree to allow Citra to collect additional usage data, which may include user identifying information.</string>
29 </property>
30 </widget>
31 </item>
32 <item>
33 <layout class="QGridLayout" name="gridLayoutCitraUsername">
34 <item row="0" column="0">
35 <widget class="QLabel" name="label_username">
36 <property name="text">
37 <string>Username: </string>
38 </property>
39 </widget>
40 </item>
41 <item row="0" column="1">
42 <widget class="QLineEdit" name="edit_username">
43 <property name="maxLength">
44 <number>36</number>
45 </property>
46 </widget>
47 </item>
48 <item row="1" column="0">
49 <widget class="QLabel" name="label_token">
50 <property name="text">
51 <string>Token: </string>
52 </property>
53 </widget>
54 </item>
55 <item row="1" column="1">
56 <widget class="QLineEdit" name="edit_token">
57 <property name="maxLength">
58 <number>36</number>
59 </property>
60 <property name="echoMode">
61 <enum>QLineEdit::Password</enum>
62 </property>
63 </widget>
64 </item>
65 <item row="2" column="0">
66 <widget class="QLabel" name="web_signup_link">
67 <property name="text">
68 <string>Sign up</string>
69 </property>
70 </widget>
71 </item>
72 <item row="2" column="1">
73 <widget class="QLabel" name="web_token_info_link">
74 <property name="text">
75 <string>What is my token?</string>
76 </property>
77 </widget>
78 </item>
79 </layout>
80 </item>
81 </layout>
82 </widget>
83 </item>
84 <item>
85 <widget class="QGroupBox" name="groupBox">
86 <property name="title">
87 <string>Telemetry</string>
88 </property>
89 <layout class="QVBoxLayout" name="verticalLayout_2">
90 <item>
91 <widget class="QCheckBox" name="toggle_telemetry">
92 <property name="text">
93 <string>Share anonymous usage data with the Citra team</string>
94 </property>
95 </widget>
96 </item>
97 <item>
98 <widget class="QLabel" name="telemetry_learn_more">
99 <property name="text">
100 <string>Learn more</string>
101 </property>
102 </widget>
103 </item>
104 <item>
105 <layout class="QGridLayout" name="gridLayoutTelemetryId">
106 <item row="0" column="0">
107 <widget class="QLabel" name="label_telemetry_id">
108 <property name="text">
109 <string>Telemetry ID:</string>
110 </property>
111 </widget>
112 </item>
113 <item row="0" column="1">
114 <widget class="QPushButton" name="button_regenerate_telemetry_id">
115 <property name="sizePolicy">
116 <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
117 <horstretch>0</horstretch>
118 <verstretch>0</verstretch>
119 </sizepolicy>
120 </property>
121 <property name="layoutDirection">
122 <enum>Qt::RightToLeft</enum>
123 </property>
124 <property name="text">
125 <string>Regenerate</string>
126 </property>
127 </widget>
128 </item>
129 </layout>
130 </item>
131 </layout>
132 </widget>
133 </item>
134 </layout>
135 </item>
136 <item>
137 <spacer name="verticalSpacer">
138 <property name="orientation">
139 <enum>Qt::Vertical</enum>
140 </property>
141 <property name="sizeHint" stdset="0">
142 <size>
143 <width>20</width>
144 <height>40</height>
145 </size>
146 </property>
147 </spacer>
148 </item>
149 </layout>
150 </widget>
151 <resources/>
152 <connections/>
153</ui>
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index c1ae0ccc8..8adbcfe86 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -48,6 +48,47 @@
48Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); 48Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
49#endif 49#endif
50 50
51/**
52 * "Callouts" are one-time instructional messages shown to the user. In the config settings, there
53 * is a bitfield "callout_flags" options, used to track if a message has already been shown to the
54 * user. This is 32-bits - if we have more than 32 callouts, we should retire and recyle old ones.
55 */
56enum class CalloutFlag : uint32_t {
57 Telemetry = 0x1,
58};
59
60static void ShowCalloutMessage(const QString& message, CalloutFlag flag) {
61 if (UISettings::values.callout_flags & static_cast<uint32_t>(flag)) {
62 return;
63 }
64
65 UISettings::values.callout_flags |= static_cast<uint32_t>(flag);
66
67 QMessageBox msg;
68 msg.setText(message);
69 msg.setStandardButtons(QMessageBox::Ok);
70 msg.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
71 msg.setStyleSheet("QLabel{min-width: 900px;}");
72 msg.exec();
73}
74
75void GMainWindow::ShowCallouts() {
76 static const QString telemetry_message =
77 tr("To help improve Citra, the Citra Team collects anonymous usage data. No private or "
78 "personally identifying information is collected. This data helps us to understand how "
79 "people use Citra and prioritize our efforts. Furthermore, it helps us to more easily "
80 "identify emulation bugs and performance issues. This data includes:<ul><li>Information"
81 " about the version of Citra you are using</li><li>Performance data about the games you "
82 "play</li><li>Your configuration settings</li><li>Information about your computer "
83 "hardware</li><li>Emulation errors and crash information</li></ul>By default, this "
84 "feature is enabled. To disable this feature, click 'Emulation' from the menu and then "
85 "select 'Configure...'. Then, on the 'Web' tab, uncheck 'Share anonymous usage data with"
86 " the Citra team'. <br/><br/>By using this software, you agree to the above terms.<br/>"
87 "<br/><a href='https://citra-emu.org/entry/telemetry-and-why-thats-a-good-thing/'>Learn "
88 "more</a>");
89 ShowCalloutMessage(telemetry_message, CalloutFlag::Telemetry);
90}
91
51GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { 92GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
52 Pica::g_debug_context = Pica::DebugContext::Construct(); 93 Pica::g_debug_context = Pica::DebugContext::Construct();
53 setAcceptDrops(true); 94 setAcceptDrops(true);
@@ -73,6 +114,9 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
73 114
74 UpdateUITheme(); 115 UpdateUITheme();
75 116
117 // Show one-time "callout" messages to the user
118 ShowCallouts();
119
76 QStringList args = QApplication::arguments(); 120 QStringList args = QApplication::arguments();
77 if (args.length() >= 2) { 121 if (args.length() >= 2) {
78 BootGame(args[1]); 122 BootGame(args[1]);
@@ -320,6 +364,8 @@ bool GMainWindow::LoadROM(const QString& filename) {
320 364
321 const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())}; 365 const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())};
322 366
367 Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "Qt");
368
323 if (result != Core::System::ResultStatus::Success) { 369 if (result != Core::System::ResultStatus::Success) {
324 switch (result) { 370 switch (result) {
325 case Core::System::ResultStatus::ErrorGetLoader: 371 case Core::System::ResultStatus::ErrorGetLoader:
diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h
index 360de2ced..d59a6d67d 100644
--- a/src/citra_qt/main.h
+++ b/src/citra_qt/main.h
@@ -80,6 +80,8 @@ private:
80 void BootGame(const QString& filename); 80 void BootGame(const QString& filename);
81 void ShutdownGame(); 81 void ShutdownGame();
82 82
83 void ShowCallouts();
84
83 /** 85 /**
84 * Stores the filename in the recently loaded files list. 86 * Stores the filename in the recently loaded files list.
85 * The new filename is stored at the beginning of the recently loaded files list. 87 * The new filename is stored at the beginning of the recently loaded files list.
diff --git a/src/citra_qt/ui_settings.h b/src/citra_qt/ui_settings.h
index 025c73f84..d85c92765 100644
--- a/src/citra_qt/ui_settings.h
+++ b/src/citra_qt/ui_settings.h
@@ -48,6 +48,8 @@ struct Values {
48 48
49 // Shortcut name <Shortcut, context> 49 // Shortcut name <Shortcut, context>
50 std::vector<Shortcut> shortcuts; 50 std::vector<Shortcut> shortcuts;
51
52 uint32_t callout_flags;
51}; 53};
52 54
53extern Values values; 55extern Values values;
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 360f407f3..78dec8600 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -33,7 +33,6 @@ set(SRCS
33 frontend/camera/interface.cpp 33 frontend/camera/interface.cpp
34 frontend/emu_window.cpp 34 frontend/emu_window.cpp
35 frontend/framebuffer_layout.cpp 35 frontend/framebuffer_layout.cpp
36 frontend/motion_emu.cpp
37 gdbstub/gdbstub.cpp 36 gdbstub/gdbstub.cpp
38 hle/config_mem.cpp 37 hle/config_mem.cpp
39 hle/applets/applet.cpp 38 hle/applets/applet.cpp
@@ -60,6 +59,7 @@ set(SRCS
60 hle/kernel/timer.cpp 59 hle/kernel/timer.cpp
61 hle/kernel/vm_manager.cpp 60 hle/kernel/vm_manager.cpp
62 hle/kernel/wait_object.cpp 61 hle/kernel/wait_object.cpp
62 hle/lock.cpp
63 hle/romfs.cpp 63 hle/romfs.cpp
64 hle/service/ac/ac.cpp 64 hle/service/ac/ac.cpp
65 hle/service/ac/ac_i.cpp 65 hle/service/ac/ac_i.cpp
@@ -145,6 +145,7 @@ set(SRCS
145 hle/service/nwm/nwm_tst.cpp 145 hle/service/nwm/nwm_tst.cpp
146 hle/service/nwm/nwm_uds.cpp 146 hle/service/nwm/nwm_uds.cpp
147 hle/service/nwm/uds_beacon.cpp 147 hle/service/nwm/uds_beacon.cpp
148 hle/service/nwm/uds_connection.cpp
148 hle/service/nwm/uds_data.cpp 149 hle/service/nwm/uds_data.cpp
149 hle/service/pm_app.cpp 150 hle/service/pm_app.cpp
150 hle/service/ptm/ptm.cpp 151 hle/service/ptm/ptm.cpp
@@ -226,7 +227,6 @@ set(HEADERS
226 frontend/emu_window.h 227 frontend/emu_window.h
227 frontend/framebuffer_layout.h 228 frontend/framebuffer_layout.h
228 frontend/input.h 229 frontend/input.h
229 frontend/motion_emu.h
230 gdbstub/gdbstub.h 230 gdbstub/gdbstub.h
231 hle/config_mem.h 231 hle/config_mem.h
232 hle/function_wrappers.h 232 hle/function_wrappers.h
@@ -258,6 +258,7 @@ set(HEADERS
258 hle/kernel/timer.h 258 hle/kernel/timer.h
259 hle/kernel/vm_manager.h 259 hle/kernel/vm_manager.h
260 hle/kernel/wait_object.h 260 hle/kernel/wait_object.h
261 hle/lock.h
261 hle/result.h 262 hle/result.h
262 hle/romfs.h 263 hle/romfs.h
263 hle/service/ac/ac.h 264 hle/service/ac/ac.h
@@ -344,6 +345,7 @@ set(HEADERS
344 hle/service/nwm/nwm_tst.h 345 hle/service/nwm/nwm_tst.h
345 hle/service/nwm/nwm_uds.h 346 hle/service/nwm/nwm_uds.h
346 hle/service/nwm/uds_beacon.h 347 hle/service/nwm/uds_beacon.h
348 hle/service/nwm/uds_connection.h
347 hle/service/nwm/uds_data.h 349 hle/service/nwm/uds_data.h
348 hle/service/pm_app.h 350 hle/service/pm_app.h
349 hle/service/ptm/ptm.h 351 hle/service/ptm/ptm.h
@@ -388,7 +390,7 @@ set(HEADERS
388 390
389create_directory_groups(${SRCS} ${HEADERS}) 391create_directory_groups(${SRCS} ${HEADERS})
390add_library(core STATIC ${SRCS} ${HEADERS}) 392add_library(core STATIC ${SRCS} ${HEADERS})
391target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) 393target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core)
392target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp dynarmic fmt) 394target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp dynarmic fmt)
393if (ENABLE_WEB_SERVICE) 395if (ENABLE_WEB_SERVICE)
394 target_link_libraries(core PUBLIC json-headers web_service) 396 target_link_libraries(core PUBLIC json-headers web_service)
diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp
index 7d2790b08..0a0b91590 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic.cpp
@@ -136,7 +136,7 @@ MICROPROFILE_DEFINE(ARM_Jit, "ARM JIT", "ARM JIT", MP_RGB(255, 64, 64));
136void ARM_Dynarmic::ExecuteInstructions(int num_instructions) { 136void ARM_Dynarmic::ExecuteInstructions(int num_instructions) {
137 MICROPROFILE_SCOPE(ARM_Jit); 137 MICROPROFILE_SCOPE(ARM_Jit);
138 138
139 unsigned ticks_executed = jit->Run(static_cast<unsigned>(num_instructions)); 139 std::size_t ticks_executed = jit->Run(static_cast<unsigned>(num_instructions));
140 140
141 AddTicks(ticks_executed); 141 AddTicks(ticks_executed);
142} 142}
diff --git a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp
index f4fbb8d04..3522d1e82 100644
--- a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp
+++ b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp
@@ -759,7 +759,7 @@ static ThumbDecodeStatus DecodeThumbInstruction(u32 inst, u32 addr, u32* arm_ins
759 ThumbDecodeStatus ret = TranslateThumbInstruction(addr, inst, arm_inst, inst_size); 759 ThumbDecodeStatus ret = TranslateThumbInstruction(addr, inst, arm_inst, inst_size);
760 if (ret == ThumbDecodeStatus::BRANCH) { 760 if (ret == ThumbDecodeStatus::BRANCH) {
761 int inst_index; 761 int inst_index;
762 int table_length = arm_instruction_trans_len; 762 int table_length = static_cast<int>(arm_instruction_trans_len);
763 u32 tinstr = GetThumbInstruction(inst, addr); 763 u32 tinstr = GetThumbInstruction(inst, addr);
764 764
765 switch ((tinstr & 0xF800) >> 11) { 765 switch ((tinstr & 0xF800) >> 11) {
@@ -838,7 +838,7 @@ static unsigned int InterpreterTranslateInstruction(const ARMul_State* cpu, cons
838 return inst_size; 838 return inst_size;
839} 839}
840 840
841static int InterpreterTranslateBlock(ARMul_State* cpu, int& bb_start, u32 addr) { 841static int InterpreterTranslateBlock(ARMul_State* cpu, std::size_t& bb_start, u32 addr) {
842 MICROPROFILE_SCOPE(DynCom_Decode); 842 MICROPROFILE_SCOPE(DynCom_Decode);
843 843
844 // Decode instruction, get index 844 // Decode instruction, get index
@@ -871,7 +871,7 @@ static int InterpreterTranslateBlock(ARMul_State* cpu, int& bb_start, u32 addr)
871 return KEEP_GOING; 871 return KEEP_GOING;
872} 872}
873 873
874static int InterpreterTranslateSingle(ARMul_State* cpu, int& bb_start, u32 addr) { 874static int InterpreterTranslateSingle(ARMul_State* cpu, std::size_t& bb_start, u32 addr) {
875 MICROPROFILE_SCOPE(DynCom_Decode); 875 MICROPROFILE_SCOPE(DynCom_Decode);
876 876
877 ARM_INST_PTR inst_base = nullptr; 877 ARM_INST_PTR inst_base = nullptr;
@@ -1620,7 +1620,7 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) {
1620 unsigned int addr; 1620 unsigned int addr;
1621 unsigned int num_instrs = 0; 1621 unsigned int num_instrs = 0;
1622 1622
1623 int ptr; 1623 std::size_t ptr;
1624 1624
1625 LOAD_NZCVT; 1625 LOAD_NZCVT;
1626DISPATCH : { 1626DISPATCH : {
diff --git a/src/core/arm/skyeye_common/armstate.h b/src/core/arm/skyeye_common/armstate.h
index 1a707ff7e..893877797 100644
--- a/src/core/arm/skyeye_common/armstate.h
+++ b/src/core/arm/skyeye_common/armstate.h
@@ -230,7 +230,7 @@ public:
230 230
231 // TODO(bunnei): Move this cache to a better place - it should be per codeset (likely per 231 // TODO(bunnei): Move this cache to a better place - it should be per codeset (likely per
232 // process for our purposes), not per ARMul_State (which tracks CPU core state). 232 // process for our purposes), not per ARMul_State (which tracks CPU core state).
233 std::unordered_map<u32, int> instruction_cache; 233 std::unordered_map<u32, std::size_t> instruction_cache;
234 234
235private: 235private:
236 void ResetMPCoreCP15Registers(); 236 void ResetMPCoreCP15Registers();
diff --git a/src/core/core.cpp b/src/core/core.cpp
index d08f18623..5332318cf 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -19,6 +19,7 @@
19#include "core/loader/loader.h" 19#include "core/loader/loader.h"
20#include "core/memory_setup.h" 20#include "core/memory_setup.h"
21#include "core/settings.h" 21#include "core/settings.h"
22#include "network/network.h"
22#include "video_core/video_core.h" 23#include "video_core/video_core.h"
23 24
24namespace Core { 25namespace Core {
@@ -188,6 +189,10 @@ void System::Shutdown() {
188 cpu_core = nullptr; 189 cpu_core = nullptr;
189 app_loader = nullptr; 190 app_loader = nullptr;
190 telemetry_session = nullptr; 191 telemetry_session = nullptr;
192 if (auto room_member = Network::GetRoomMember().lock()) {
193 Network::GameInfo game_info{};
194 room_member->SendGameInfo(game_info);
195 }
191 196
192 LOG_DEBUG(Core, "Shutdown OK"); 197 LOG_DEBUG(Core, "Shutdown OK");
193} 198}
diff --git a/src/core/file_sys/archive_backend.cpp b/src/core/file_sys/archive_backend.cpp
index 1fae0ede0..87a240d7a 100644
--- a/src/core/file_sys/archive_backend.cpp
+++ b/src/core/file_sys/archive_backend.cpp
@@ -90,6 +90,8 @@ std::u16string Path::AsU16Str() const {
90 LOG_ERROR(Service_FS, "LowPathType cannot be converted to u16string!"); 90 LOG_ERROR(Service_FS, "LowPathType cannot be converted to u16string!");
91 return {}; 91 return {};
92 } 92 }
93
94 UNREACHABLE();
93} 95}
94 96
95std::vector<u8> Path::AsBinary() const { 97std::vector<u8> Path::AsBinary() const {
diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp
index 4f7d54a33..e67394177 100644
--- a/src/core/frontend/emu_window.cpp
+++ b/src/core/frontend/emu_window.cpp
@@ -2,14 +2,55 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <algorithm>
6#include <cmath> 5#include <cmath>
7#include "common/assert.h" 6#include <mutex>
8#include "core/3ds.h"
9#include "core/core.h"
10#include "core/frontend/emu_window.h" 7#include "core/frontend/emu_window.h"
8#include "core/frontend/input.h"
11#include "core/settings.h" 9#include "core/settings.h"
12 10
11class EmuWindow::TouchState : public Input::Factory<Input::TouchDevice>,
12 public std::enable_shared_from_this<TouchState> {
13public:
14 std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage&) override {
15 return std::make_unique<Device>(shared_from_this());
16 }
17
18 std::mutex mutex;
19
20 bool touch_pressed = false; ///< True if touchpad area is currently pressed, otherwise false
21
22 float touch_x = 0.0f; ///< Touchpad X-position
23 float touch_y = 0.0f; ///< Touchpad Y-position
24
25private:
26 class Device : public Input::TouchDevice {
27 public:
28 explicit Device(std::weak_ptr<TouchState>&& touch_state) : touch_state(touch_state) {}
29 std::tuple<float, float, bool> GetStatus() const override {
30 if (auto state = touch_state.lock()) {
31 std::lock_guard<std::mutex> guard(state->mutex);
32 return std::make_tuple(state->touch_x, state->touch_y, state->touch_pressed);
33 }
34 return std::make_tuple(0.0f, 0.0f, false);
35 }
36
37 private:
38 std::weak_ptr<TouchState> touch_state;
39 };
40};
41
42EmuWindow::EmuWindow() {
43 // TODO: Find a better place to set this.
44 config.min_client_area_size = std::make_pair(400u, 480u);
45 active_config = config;
46 touch_state = std::make_shared<TouchState>();
47 Input::RegisterFactory<Input::TouchDevice>("emu_window", touch_state);
48}
49
50EmuWindow::~EmuWindow() {
51 Input::UnregisterFactory<Input::TouchDevice>("emu_window");
52}
53
13/** 54/**
14 * Check if the given x/y coordinates are within the touchpad specified by the framebuffer layout 55 * Check if the given x/y coordinates are within the touchpad specified by the framebuffer layout
15 * @param layout FramebufferLayout object describing the framebuffer size and screen positions 56 * @param layout FramebufferLayout object describing the framebuffer size and screen positions
@@ -38,22 +79,26 @@ void EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) {
38 if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y)) 79 if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y))
39 return; 80 return;
40 81
41 touch_x = Core::kScreenBottomWidth * (framebuffer_x - framebuffer_layout.bottom_screen.left) / 82 std::lock_guard<std::mutex> guard(touch_state->mutex);
42 (framebuffer_layout.bottom_screen.right - framebuffer_layout.bottom_screen.left); 83 touch_state->touch_x =
43 touch_y = Core::kScreenBottomHeight * (framebuffer_y - framebuffer_layout.bottom_screen.top) / 84 static_cast<float>(framebuffer_x - framebuffer_layout.bottom_screen.left) /
44 (framebuffer_layout.bottom_screen.bottom - framebuffer_layout.bottom_screen.top); 85 (framebuffer_layout.bottom_screen.right - framebuffer_layout.bottom_screen.left);
86 touch_state->touch_y =
87 static_cast<float>(framebuffer_y - framebuffer_layout.bottom_screen.top) /
88 (framebuffer_layout.bottom_screen.bottom - framebuffer_layout.bottom_screen.top);
45 89
46 touch_pressed = true; 90 touch_state->touch_pressed = true;
47} 91}
48 92
49void EmuWindow::TouchReleased() { 93void EmuWindow::TouchReleased() {
50 touch_pressed = false; 94 std::lock_guard<std::mutex> guard(touch_state->mutex);
51 touch_x = 0; 95 touch_state->touch_pressed = false;
52 touch_y = 0; 96 touch_state->touch_x = 0;
97 touch_state->touch_y = 0;
53} 98}
54 99
55void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) { 100void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) {
56 if (!touch_pressed) 101 if (!touch_state->touch_pressed)
57 return; 102 return;
58 103
59 if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y)) 104 if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y))
@@ -62,29 +107,6 @@ void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) {
62 TouchPressed(framebuffer_x, framebuffer_y); 107 TouchPressed(framebuffer_x, framebuffer_y);
63} 108}
64 109
65void EmuWindow::AccelerometerChanged(float x, float y, float z) {
66 constexpr float coef = 512;
67
68 std::lock_guard<std::mutex> lock(accel_mutex);
69
70 // TODO(wwylele): do a time stretch as it in GyroscopeChanged
71 // The time stretch formula should be like
72 // stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity
73 accel_x = static_cast<s16>(x * coef);
74 accel_y = static_cast<s16>(y * coef);
75 accel_z = static_cast<s16>(z * coef);
76}
77
78void EmuWindow::GyroscopeChanged(float x, float y, float z) {
79 constexpr float FULL_FPS = 60;
80 float coef = GetGyroscopeRawToDpsCoefficient();
81 float stretch = Core::System::GetInstance().perf_stats.GetLastFrameTimeScale();
82 std::lock_guard<std::mutex> lock(gyro_mutex);
83 gyro_x = static_cast<s16>(x * coef * stretch);
84 gyro_y = static_cast<s16>(y * coef * stretch);
85 gyro_z = static_cast<s16>(z * coef * stretch);
86}
87
88void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height) { 110void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height) {
89 Layout::FramebufferLayout layout; 111 Layout::FramebufferLayout layout;
90 if (Settings::values.custom_layout == true) { 112 if (Settings::values.custom_layout == true) {
@@ -97,6 +119,9 @@ void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height)
97 case Settings::LayoutOption::LargeScreen: 119 case Settings::LayoutOption::LargeScreen:
98 layout = Layout::LargeFrameLayout(width, height, Settings::values.swap_screen); 120 layout = Layout::LargeFrameLayout(width, height, Settings::values.swap_screen);
99 break; 121 break;
122 case Settings::LayoutOption::SideScreen:
123 layout = Layout::SideFrameLayout(width, height, Settings::values.swap_screen);
124 break;
100 case Settings::LayoutOption::Default: 125 case Settings::LayoutOption::Default:
101 default: 126 default:
102 layout = Layout::DefaultFrameLayout(width, height, Settings::values.swap_screen); 127 layout = Layout::DefaultFrameLayout(width, height, Settings::values.swap_screen);
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index 9414123a4..c10dee51b 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -4,11 +4,10 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <mutex> 7#include <memory>
8#include <tuple> 8#include <tuple>
9#include <utility> 9#include <utility>
10#include "common/common_types.h" 10#include "common/common_types.h"
11#include "common/math_util.h"
12#include "core/frontend/framebuffer_layout.h" 11#include "core/frontend/framebuffer_layout.h"
13 12
14/** 13/**
@@ -69,84 +68,6 @@ public:
69 void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y); 68 void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y);
70 69
71 /** 70 /**
72 * Signal accelerometer state has changed.
73 * @param x X-axis accelerometer value
74 * @param y Y-axis accelerometer value
75 * @param z Z-axis accelerometer value
76 * @note all values are in unit of g (gravitational acceleration).
77 * e.g. x = 1.0 means 9.8m/s^2 in x direction.
78 * @see GetAccelerometerState for axis explanation.
79 */
80 void AccelerometerChanged(float x, float y, float z);
81
82 /**
83 * Signal gyroscope state has changed.
84 * @param x X-axis accelerometer value
85 * @param y Y-axis accelerometer value
86 * @param z Z-axis accelerometer value
87 * @note all values are in deg/sec.
88 * @see GetGyroscopeState for axis explanation.
89 */
90 void GyroscopeChanged(float x, float y, float z);
91
92 /**
93 * Gets the current touch screen state (touch X/Y coordinates and whether or not it is pressed).
94 * @note This should be called by the core emu thread to get a state set by the window thread.
95 * @todo Fix this function to be thread-safe.
96 * @return std::tuple of (x, y, pressed) where `x` and `y` are the touch coordinates and
97 * `pressed` is true if the touch screen is currently being pressed
98 */
99 std::tuple<u16, u16, bool> GetTouchState() const {
100 return std::make_tuple(touch_x, touch_y, touch_pressed);
101 }
102
103 /**
104 * Gets the current accelerometer state (acceleration along each three axis).
105 * Axis explained:
106 * +x is the same direction as LEFT on D-pad.
107 * +y is normal to the touch screen, pointing outward.
108 * +z is the same direction as UP on D-pad.
109 * Units:
110 * 1 unit of return value = 1/512 g (measured by hw test),
111 * where g is the gravitational acceleration (9.8 m/sec2).
112 * @note This should be called by the core emu thread to get a state set by the window thread.
113 * @return std::tuple of (x, y, z)
114 */
115 std::tuple<s16, s16, s16> GetAccelerometerState() {
116 std::lock_guard<std::mutex> lock(accel_mutex);
117 return std::make_tuple(accel_x, accel_y, accel_z);
118 }
119
120 /**
121 * Gets the current gyroscope state (angular rates about each three axis).
122 * Axis explained:
123 * +x is the same direction as LEFT on D-pad.
124 * +y is normal to the touch screen, pointing outward.
125 * +z is the same direction as UP on D-pad.
126 * Orientation is determined by right-hand rule.
127 * Units:
128 * 1 unit of return value = (1/coef) deg/sec,
129 * where coef is the return value of GetGyroscopeRawToDpsCoefficient().
130 * @note This should be called by the core emu thread to get a state set by the window thread.
131 * @return std::tuple of (x, y, z)
132 */
133 std::tuple<s16, s16, s16> GetGyroscopeState() {
134 std::lock_guard<std::mutex> lock(gyro_mutex);
135 return std::make_tuple(gyro_x, gyro_y, gyro_z);
136 }
137
138 /**
139 * Gets the coefficient for units conversion of gyroscope state.
140 * The conversion formula is r = coefficient * v,
141 * where v is angular rate in deg/sec,
142 * and r is the gyroscope state.
143 * @return float-type coefficient
144 */
145 f32 GetGyroscopeRawToDpsCoefficient() const {
146 return 14.375f; // taken from hw test, and gyroscope's document
147 }
148
149 /**
150 * Returns currently active configuration. 71 * Returns currently active configuration.
151 * @note Accesses to the returned object need not be consistent because it may be modified in 72 * @note Accesses to the returned object need not be consistent because it may be modified in
152 * another thread 73 * another thread
@@ -180,21 +101,8 @@ public:
180 void UpdateCurrentFramebufferLayout(unsigned width, unsigned height); 101 void UpdateCurrentFramebufferLayout(unsigned width, unsigned height);
181 102
182protected: 103protected:
183 EmuWindow() { 104 EmuWindow();
184 // TODO: Find a better place to set this. 105 virtual ~EmuWindow();
185 config.min_client_area_size = std::make_pair(400u, 480u);
186 active_config = config;
187 touch_x = 0;
188 touch_y = 0;
189 touch_pressed = false;
190 accel_x = 0;
191 accel_y = -512;
192 accel_z = 0;
193 gyro_x = 0;
194 gyro_y = 0;
195 gyro_z = 0;
196 }
197 virtual ~EmuWindow() {}
198 106
199 /** 107 /**
200 * Processes any pending configuration changes from the last SetConfig call. 108 * Processes any pending configuration changes from the last SetConfig call.
@@ -250,20 +158,8 @@ private:
250 /// ProcessConfigurationChanges) 158 /// ProcessConfigurationChanges)
251 WindowConfig active_config; ///< Internal active configuration 159 WindowConfig active_config; ///< Internal active configuration
252 160
253 bool touch_pressed; ///< True if touchpad area is currently pressed, otherwise false 161 class TouchState;
254 162 std::shared_ptr<TouchState> touch_state;
255 u16 touch_x; ///< Touchpad X-position in native 3DS pixel coordinates (0-320)
256 u16 touch_y; ///< Touchpad Y-position in native 3DS pixel coordinates (0-240)
257
258 std::mutex accel_mutex;
259 s16 accel_x; ///< Accelerometer X-axis value in native 3DS units
260 s16 accel_y; ///< Accelerometer Y-axis value in native 3DS units
261 s16 accel_z; ///< Accelerometer Z-axis value in native 3DS units
262
263 std::mutex gyro_mutex;
264 s16 gyro_x; ///< Gyroscope X-axis value in native 3DS units
265 s16 gyro_y; ///< Gyroscope Y-axis value in native 3DS units
266 s16 gyro_z; ///< Gyroscope Z-axis value in native 3DS units
267 163
268 /** 164 /**
269 * Clip the provided coordinates to be inside the touchscreen area. 165 * Clip the provided coordinates to be inside the touchscreen area.
diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp
index d2d02f9ff..e9f778fcb 100644
--- a/src/core/frontend/framebuffer_layout.cpp
+++ b/src/core/frontend/framebuffer_layout.cpp
@@ -141,6 +141,40 @@ FramebufferLayout LargeFrameLayout(unsigned width, unsigned height, bool swapped
141 return res; 141 return res;
142} 142}
143 143
144FramebufferLayout SideFrameLayout(unsigned width, unsigned height, bool swapped) {
145 ASSERT(width > 0);
146 ASSERT(height > 0);
147
148 FramebufferLayout res{width, height, true, true, {}, {}};
149 // Aspect ratio of both screens side by side
150 const float emulation_aspect_ratio = static_cast<float>(Core::kScreenTopHeight) /
151 (Core::kScreenTopWidth + Core::kScreenBottomWidth);
152 float window_aspect_ratio = static_cast<float>(height) / width;
153 MathUtil::Rectangle<unsigned> screen_window_area{0, 0, width, height};
154 // Find largest Rectangle that can fit in the window size with the given aspect ratio
155 MathUtil::Rectangle<unsigned> screen_rect =
156 maxRectangle(screen_window_area, emulation_aspect_ratio);
157 // Find sizes of top and bottom screen
158 MathUtil::Rectangle<unsigned> top_screen = maxRectangle(screen_rect, TOP_SCREEN_ASPECT_RATIO);
159 MathUtil::Rectangle<unsigned> bot_screen = maxRectangle(screen_rect, BOT_SCREEN_ASPECT_RATIO);
160
161 if (window_aspect_ratio < emulation_aspect_ratio) {
162 // Apply borders to the left and right sides of the window.
163 u32 shift_horizontal = (screen_window_area.GetWidth() - screen_rect.GetWidth()) / 2;
164 top_screen = top_screen.TranslateX(shift_horizontal);
165 bot_screen = bot_screen.TranslateX(shift_horizontal);
166 } else {
167 // Window is narrower than the emulation content => apply borders to the top and bottom
168 u32 shift_vertical = (screen_window_area.GetHeight() - screen_rect.GetHeight()) / 2;
169 top_screen = top_screen.TranslateY(shift_vertical);
170 bot_screen = bot_screen.TranslateY(shift_vertical);
171 }
172 // Move the top screen to the right if we are swapped.
173 res.top_screen = swapped ? top_screen.TranslateX(bot_screen.GetWidth()) : top_screen;
174 res.bottom_screen = swapped ? bot_screen : bot_screen.TranslateX(top_screen.GetWidth());
175 return res;
176}
177
144FramebufferLayout CustomFrameLayout(unsigned width, unsigned height) { 178FramebufferLayout CustomFrameLayout(unsigned width, unsigned height) {
145 ASSERT(width > 0); 179 ASSERT(width > 0);
146 ASSERT(height > 0); 180 ASSERT(height > 0);
@@ -158,4 +192,4 @@ FramebufferLayout CustomFrameLayout(unsigned width, unsigned height) {
158 res.bottom_screen = bot_screen; 192 res.bottom_screen = bot_screen;
159 return res; 193 return res;
160} 194}
161} 195} // namespace Layout
diff --git a/src/core/frontend/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h
index 9a7738969..4983cf103 100644
--- a/src/core/frontend/framebuffer_layout.h
+++ b/src/core/frontend/framebuffer_layout.h
@@ -54,6 +54,17 @@ FramebufferLayout SingleFrameLayout(unsigned width, unsigned height, bool is_swa
54FramebufferLayout LargeFrameLayout(unsigned width, unsigned height, bool is_swapped); 54FramebufferLayout LargeFrameLayout(unsigned width, unsigned height, bool is_swapped);
55 55
56/** 56/**
57* Factory method for constructing a Frame with the Top screen and bottom
58* screen side by side
59* This is useful for devices with small screens, like the GPDWin
60* @param width Window framebuffer width in pixels
61* @param height Window framebuffer height in pixels
62* @param is_swapped if true, the bottom screen will be the left display
63* @return Newly created FramebufferLayout object with default screen regions initialized
64*/
65FramebufferLayout SideFrameLayout(unsigned width, unsigned height, bool is_swapped);
66
67/**
57 * Factory method for constructing a custom FramebufferLayout 68 * Factory method for constructing a custom FramebufferLayout
58 * @param width Window framebuffer width in pixels 69 * @param width Window framebuffer width in pixels
59 * @param height Window framebuffer height in pixels 70 * @param height Window framebuffer height in pixels
diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h
index 0a5713dc0..8c256beb5 100644
--- a/src/core/frontend/input.h
+++ b/src/core/frontend/input.h
@@ -11,6 +11,7 @@
11#include <utility> 11#include <utility>
12#include "common/logging/log.h" 12#include "common/logging/log.h"
13#include "common/param_package.h" 13#include "common/param_package.h"
14#include "common/vector_math.h"
14 15
15namespace Input { 16namespace Input {
16 17
@@ -107,4 +108,28 @@ using ButtonDevice = InputDevice<bool>;
107 */ 108 */
108using AnalogDevice = InputDevice<std::tuple<float, float>>; 109using AnalogDevice = InputDevice<std::tuple<float, float>>;
109 110
111/**
112 * A motion device is an input device that returns a tuple of accelerometer state vector and
113 * gyroscope state vector.
114 *
115 * For both vectors:
116 * x+ is the same direction as LEFT on D-pad.
117 * y+ is normal to the touch screen, pointing outward.
118 * z+ is the same direction as UP on D-pad.
119 *
120 * For accelerometer state vector
121 * Units: g (gravitational acceleration)
122 *
123 * For gyroscope state vector:
124 * Orientation is determined by right-hand rule.
125 * Units: deg/sec
126 */
127using MotionDevice = InputDevice<std::tuple<Math::Vec3<float>, Math::Vec3<float>>>;
128
129/**
130 * A touch device is an input device that returns a tuple of two floats and a bool. The floats are
131 * x and y coordinates in the range 0.0 - 1.0, and the bool indicates whether it is pressed.
132 */
133using TouchDevice = InputDevice<std::tuple<float, float, bool>>;
134
110} // namespace Input 135} // namespace Input
diff --git a/src/core/frontend/motion_emu.cpp b/src/core/frontend/motion_emu.cpp
deleted file mode 100644
index 9a5b3185d..000000000
--- a/src/core/frontend/motion_emu.cpp
+++ /dev/null
@@ -1,89 +0,0 @@
1// Copyright 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/math_util.h"
6#include "common/quaternion.h"
7#include "core/frontend/emu_window.h"
8#include "core/frontend/motion_emu.h"
9
10namespace Motion {
11
12static constexpr int update_millisecond = 100;
13static constexpr auto update_duration =
14 std::chrono::duration_cast<std::chrono::steady_clock::duration>(
15 std::chrono::milliseconds(update_millisecond));
16
17MotionEmu::MotionEmu(EmuWindow& emu_window)
18 : motion_emu_thread(&MotionEmu::MotionEmuThread, this, std::ref(emu_window)) {}
19
20MotionEmu::~MotionEmu() {
21 if (motion_emu_thread.joinable()) {
22 shutdown_event.Set();
23 motion_emu_thread.join();
24 }
25}
26
27void MotionEmu::MotionEmuThread(EmuWindow& emu_window) {
28 auto update_time = std::chrono::steady_clock::now();
29 Math::Quaternion<float> q = MakeQuaternion(Math::Vec3<float>(), 0);
30 Math::Quaternion<float> old_q;
31
32 while (!shutdown_event.WaitUntil(update_time)) {
33 update_time += update_duration;
34 old_q = q;
35
36 {
37 std::lock_guard<std::mutex> guard(tilt_mutex);
38
39 // Find the quaternion describing current 3DS tilting
40 q = MakeQuaternion(Math::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x),
41 tilt_angle);
42 }
43
44 auto inv_q = q.Inverse();
45
46 // Set the gravity vector in world space
47 auto gravity = Math::MakeVec(0.0f, -1.0f, 0.0f);
48
49 // Find the angular rate vector in world space
50 auto angular_rate = ((q - old_q) * inv_q).xyz * 2;
51 angular_rate *= 1000 / update_millisecond / MathUtil::PI * 180;
52
53 // Transform the two vectors from world space to 3DS space
54 gravity = QuaternionRotate(inv_q, gravity);
55 angular_rate = QuaternionRotate(inv_q, angular_rate);
56
57 // Update the sensor state
58 emu_window.AccelerometerChanged(gravity.x, gravity.y, gravity.z);
59 emu_window.GyroscopeChanged(angular_rate.x, angular_rate.y, angular_rate.z);
60 }
61}
62
63void MotionEmu::BeginTilt(int x, int y) {
64 mouse_origin = Math::MakeVec(x, y);
65 is_tilting = true;
66}
67
68void MotionEmu::Tilt(int x, int y) {
69 constexpr float SENSITIVITY = 0.01f;
70 auto mouse_move = Math::MakeVec(x, y) - mouse_origin;
71 if (is_tilting) {
72 std::lock_guard<std::mutex> guard(tilt_mutex);
73 if (mouse_move.x == 0 && mouse_move.y == 0) {
74 tilt_angle = 0;
75 } else {
76 tilt_direction = mouse_move.Cast<float>();
77 tilt_angle = MathUtil::Clamp(tilt_direction.Normalize() * SENSITIVITY, 0.0f,
78 MathUtil::PI * 0.5f);
79 }
80 }
81}
82
83void MotionEmu::EndTilt() {
84 std::lock_guard<std::mutex> guard(tilt_mutex);
85 tilt_angle = 0;
86 is_tilting = false;
87}
88
89} // namespace Motion
diff --git a/src/core/frontend/motion_emu.h b/src/core/frontend/motion_emu.h
deleted file mode 100644
index 99d41a726..000000000
--- a/src/core/frontend/motion_emu.h
+++ /dev/null
@@ -1,52 +0,0 @@
1// Copyright 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6#include "common/thread.h"
7#include "common/vector_math.h"
8
9class EmuWindow;
10
11namespace Motion {
12
13class MotionEmu final {
14public:
15 MotionEmu(EmuWindow& emu_window);
16 ~MotionEmu();
17
18 /**
19 * Signals that a motion sensor tilt has begun.
20 * @param x the x-coordinate of the cursor
21 * @param y the y-coordinate of the cursor
22 */
23 void BeginTilt(int x, int y);
24
25 /**
26 * Signals that a motion sensor tilt is occurring.
27 * @param x the x-coordinate of the cursor
28 * @param y the y-coordinate of the cursor
29 */
30 void Tilt(int x, int y);
31
32 /**
33 * Signals that a motion sensor tilt has ended.
34 */
35 void EndTilt();
36
37private:
38 Math::Vec2<int> mouse_origin;
39
40 std::mutex tilt_mutex;
41 Math::Vec2<float> tilt_direction;
42 float tilt_angle = 0;
43
44 bool is_tilting = false;
45
46 Common::Event shutdown_event;
47 std::thread motion_emu_thread;
48
49 void MotionEmuThread(EmuWindow& emu_window);
50};
51
52} // namespace Motion
diff --git a/src/core/hle/applets/erreula.cpp b/src/core/hle/applets/erreula.cpp
index 75d7fd9fc..518f371f5 100644
--- a/src/core/hle/applets/erreula.cpp
+++ b/src/core/hle/applets/erreula.cpp
@@ -31,8 +31,8 @@ ResultCode ErrEula::ReceiveParameter(const Service::APT::MessageParameter& param
31 heap_memory = std::make_shared<std::vector<u8>>(capture_info.size); 31 heap_memory = std::make_shared<std::vector<u8>>(capture_info.size);
32 // Create a SharedMemory that directly points to this heap block. 32 // Create a SharedMemory that directly points to this heap block.
33 framebuffer_memory = Kernel::SharedMemory::CreateForApplet( 33 framebuffer_memory = Kernel::SharedMemory::CreateForApplet(
34 heap_memory, 0, heap_memory->size(), MemoryPermission::ReadWrite, 34 heap_memory, 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite,
35 MemoryPermission::ReadWrite, "ErrEula Memory"); 35 "ErrEula Memory");
36 36
37 // Send the response message with the newly created SharedMemory 37 // Send the response message with the newly created SharedMemory
38 Service::APT::MessageParameter result; 38 Service::APT::MessageParameter result;
diff --git a/src/core/hle/applets/mii_selector.cpp b/src/core/hle/applets/mii_selector.cpp
index 89f08daa2..f225c23a5 100644
--- a/src/core/hle/applets/mii_selector.cpp
+++ b/src/core/hle/applets/mii_selector.cpp
@@ -38,8 +38,8 @@ ResultCode MiiSelector::ReceiveParameter(const Service::APT::MessageParameter& p
38 heap_memory = std::make_shared<std::vector<u8>>(capture_info.size); 38 heap_memory = std::make_shared<std::vector<u8>>(capture_info.size);
39 // Create a SharedMemory that directly points to this heap block. 39 // Create a SharedMemory that directly points to this heap block.
40 framebuffer_memory = Kernel::SharedMemory::CreateForApplet( 40 framebuffer_memory = Kernel::SharedMemory::CreateForApplet(
41 heap_memory, 0, heap_memory->size(), MemoryPermission::ReadWrite, 41 heap_memory, 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite,
42 MemoryPermission::ReadWrite, "MiiSelector Memory"); 42 "MiiSelector Memory");
43 43
44 // Send the response message with the newly created SharedMemory 44 // Send the response message with the newly created SharedMemory
45 Service::APT::MessageParameter result; 45 Service::APT::MessageParameter result;
@@ -66,7 +66,7 @@ ResultCode MiiSelector::StartImpl(const Service::APT::AppletStartupParameter& pa
66 // continue. 66 // continue.
67 MiiResult result; 67 MiiResult result;
68 memset(&result, 0, sizeof(result)); 68 memset(&result, 0, sizeof(result));
69 result.result_code = 0; 69 result.return_code = 0;
70 70
71 // Let the application know that we're closing 71 // Let the application know that we're closing
72 Service::APT::MessageParameter message; 72 Service::APT::MessageParameter message;
@@ -82,5 +82,5 @@ ResultCode MiiSelector::StartImpl(const Service::APT::AppletStartupParameter& pa
82} 82}
83 83
84void MiiSelector::Update() {} 84void MiiSelector::Update() {}
85} 85} // namespace Applets
86} // namespace 86} // namespace HLE
diff --git a/src/core/hle/applets/mii_selector.h b/src/core/hle/applets/mii_selector.h
index ec00e29d2..136ce8948 100644
--- a/src/core/hle/applets/mii_selector.h
+++ b/src/core/hle/applets/mii_selector.h
@@ -16,51 +16,46 @@ namespace HLE {
16namespace Applets { 16namespace Applets {
17 17
18struct MiiConfig { 18struct MiiConfig {
19 u8 unk_000; 19 u8 enable_cancel_button;
20 u8 unk_001; 20 u8 enable_guest_mii;
21 u8 unk_002; 21 u8 show_on_top_screen;
22 u8 unk_003; 22 INSERT_PADDING_BYTES(5);
23 u8 unk_004; 23 u16 title[0x40];
24 INSERT_PADDING_BYTES(4);
25 u8 show_guest_miis;
24 INSERT_PADDING_BYTES(3); 26 INSERT_PADDING_BYTES(3);
25 u16 unk_008; 27 u32 initially_selected_mii_index;
26 INSERT_PADDING_BYTES(0x82); 28 u8 guest_mii_whitelist[6];
27 u8 unk_08C; 29 u8 user_mii_whitelist[0x64];
28 INSERT_PADDING_BYTES(3);
29 u16 unk_090;
30 INSERT_PADDING_BYTES(2); 30 INSERT_PADDING_BYTES(2);
31 u32 unk_094; 31 u32 magic_value;
32 u16 unk_098;
33 u8 unk_09A[0x64];
34 u8 unk_0FE;
35 u8 unk_0FF;
36 u32 unk_100;
37}; 32};
38
39static_assert(sizeof(MiiConfig) == 0x104, "MiiConfig structure has incorrect size"); 33static_assert(sizeof(MiiConfig) == 0x104, "MiiConfig structure has incorrect size");
40#define ASSERT_REG_POSITION(field_name, position) \ 34#define ASSERT_REG_POSITION(field_name, position) \
41 static_assert(offsetof(MiiConfig, field_name) == position, \ 35 static_assert(offsetof(MiiConfig, field_name) == position, \
42 "Field " #field_name " has invalid position") 36 "Field " #field_name " has invalid position")
43ASSERT_REG_POSITION(unk_008, 0x08); 37ASSERT_REG_POSITION(title, 0x08);
44ASSERT_REG_POSITION(unk_08C, 0x8C); 38ASSERT_REG_POSITION(show_guest_miis, 0x8C);
45ASSERT_REG_POSITION(unk_090, 0x90); 39ASSERT_REG_POSITION(initially_selected_mii_index, 0x90);
46ASSERT_REG_POSITION(unk_094, 0x94); 40ASSERT_REG_POSITION(guest_mii_whitelist, 0x94);
47ASSERT_REG_POSITION(unk_0FE, 0xFE);
48#undef ASSERT_REG_POSITION 41#undef ASSERT_REG_POSITION
49 42
50struct MiiResult { 43struct MiiResult {
51 u32 result_code; 44 u32 return_code;
52 u8 unk_04; 45 u32 is_guest_mii_selected;
53 INSERT_PADDING_BYTES(7); 46 u32 selected_guest_mii_index;
54 u8 unk_0C[0x60]; 47 // TODO(mailwl): expand to Mii Format structure: https://www.3dbrew.org/wiki/Mii
55 u8 unk_6C[0x16]; 48 u8 selected_mii_data[0x5C];
56 INSERT_PADDING_BYTES(2); 49 INSERT_PADDING_BYTES(2);
50 u16 mii_data_checksum;
51 u16 guest_mii_name[0xC];
57}; 52};
58static_assert(sizeof(MiiResult) == 0x84, "MiiResult structure has incorrect size"); 53static_assert(sizeof(MiiResult) == 0x84, "MiiResult structure has incorrect size");
59#define ASSERT_REG_POSITION(field_name, position) \ 54#define ASSERT_REG_POSITION(field_name, position) \
60 static_assert(offsetof(MiiResult, field_name) == position, \ 55 static_assert(offsetof(MiiResult, field_name) == position, \
61 "Field " #field_name " has invalid position") 56 "Field " #field_name " has invalid position")
62ASSERT_REG_POSITION(unk_0C, 0x0C); 57ASSERT_REG_POSITION(selected_mii_data, 0x0C);
63ASSERT_REG_POSITION(unk_6C, 0x6C); 58ASSERT_REG_POSITION(guest_mii_name, 0x6C);
64#undef ASSERT_REG_POSITION 59#undef ASSERT_REG_POSITION
65 60
66class MiiSelector final : public Applet { 61class MiiSelector final : public Applet {
@@ -79,5 +74,5 @@ private:
79 74
80 MiiConfig config; 75 MiiConfig config;
81}; 76};
82} 77} // namespace Applets
83} // namespace 78} // namespace HLE
diff --git a/src/core/hle/applets/mint.cpp b/src/core/hle/applets/mint.cpp
index 31a79ea17..50d79190b 100644
--- a/src/core/hle/applets/mint.cpp
+++ b/src/core/hle/applets/mint.cpp
@@ -31,8 +31,8 @@ ResultCode Mint::ReceiveParameter(const Service::APT::MessageParameter& paramete
31 heap_memory = std::make_shared<std::vector<u8>>(capture_info.size); 31 heap_memory = std::make_shared<std::vector<u8>>(capture_info.size);
32 // Create a SharedMemory that directly points to this heap block. 32 // Create a SharedMemory that directly points to this heap block.
33 framebuffer_memory = Kernel::SharedMemory::CreateForApplet( 33 framebuffer_memory = Kernel::SharedMemory::CreateForApplet(
34 heap_memory, 0, heap_memory->size(), MemoryPermission::ReadWrite, 34 heap_memory, 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite,
35 MemoryPermission::ReadWrite, "Mint Memory"); 35 "Mint Memory");
36 36
37 // Send the response message with the newly created SharedMemory 37 // Send the response message with the newly created SharedMemory
38 Service::APT::MessageParameter result; 38 Service::APT::MessageParameter result;
diff --git a/src/core/hle/applets/swkbd.cpp b/src/core/hle/applets/swkbd.cpp
index fdf8807b0..0bc471a3a 100644
--- a/src/core/hle/applets/swkbd.cpp
+++ b/src/core/hle/applets/swkbd.cpp
@@ -41,8 +41,8 @@ ResultCode SoftwareKeyboard::ReceiveParameter(Service::APT::MessageParameter con
41 heap_memory = std::make_shared<std::vector<u8>>(capture_info.size); 41 heap_memory = std::make_shared<std::vector<u8>>(capture_info.size);
42 // Create a SharedMemory that directly points to this heap block. 42 // Create a SharedMemory that directly points to this heap block.
43 framebuffer_memory = Kernel::SharedMemory::CreateForApplet( 43 framebuffer_memory = Kernel::SharedMemory::CreateForApplet(
44 heap_memory, 0, heap_memory->size(), MemoryPermission::ReadWrite, 44 heap_memory, 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite,
45 MemoryPermission::ReadWrite, "SoftwareKeyboard Memory"); 45 "SoftwareKeyboard Memory");
46 46
47 // Send the response message with the newly created SharedMemory 47 // Send the response message with the newly created SharedMemory
48 Service::APT::MessageParameter result; 48 Service::APT::MessageParameter result;
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index 9cf288b08..73fab3981 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -8,6 +8,7 @@
8#include <string> 8#include <string>
9#include <utility> 9#include <utility>
10#include <boost/smart_ptr/intrusive_ptr.hpp> 10#include <boost/smart_ptr/intrusive_ptr.hpp>
11#include "common/assert.h"
11#include "common/common_types.h" 12#include "common/common_types.h"
12 13
13namespace Kernel { 14namespace Kernel {
@@ -84,6 +85,8 @@ public:
84 case HandleType::ClientSession: 85 case HandleType::ClientSession:
85 return false; 86 return false;
86 } 87 }
88
89 UNREACHABLE();
87 } 90 }
88 91
89public: 92public:
@@ -129,4 +132,4 @@ void Init(u32 system_mode);
129/// Shutdown the kernel 132/// Shutdown the kernel
130void Shutdown(); 133void Shutdown();
131 134
132} // namespace 135} // namespace Kernel
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index f5f2eb2f7..b957c45dd 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -478,8 +478,6 @@ void Thread::BoostPriority(s32 priority) {
478} 478}
479 479
480SharedPtr<Thread> SetupMainThread(u32 entry_point, s32 priority) { 480SharedPtr<Thread> SetupMainThread(u32 entry_point, s32 priority) {
481 DEBUG_ASSERT(!GetCurrentThread());
482
483 // Initialize new "main" thread 481 // Initialize new "main" thread
484 auto thread_res = Thread::Create("main", entry_point, priority, 0, THREADPROCESSORID_0, 482 auto thread_res = Thread::Create("main", entry_point, priority, 0, THREADPROCESSORID_0,
485 Memory::HEAP_VADDR_END); 483 Memory::HEAP_VADDR_END);
@@ -489,9 +487,7 @@ SharedPtr<Thread> SetupMainThread(u32 entry_point, s32 priority) {
489 thread->context.fpscr = 487 thread->context.fpscr =
490 FPSCR_DEFAULT_NAN | FPSCR_FLUSH_TO_ZERO | FPSCR_ROUND_TOZERO | FPSCR_IXC; // 0x03C00010 488 FPSCR_DEFAULT_NAN | FPSCR_FLUSH_TO_ZERO | FPSCR_ROUND_TOZERO | FPSCR_IXC; // 0x03C00010
491 489
492 // Run new "main" thread 490 // Note: The newly created thread will be run when the scheduler fires.
493 SwitchContext(thread.get());
494
495 return thread; 491 return thread;
496} 492}
497 493
diff --git a/src/core/hle/lock.cpp b/src/core/hle/lock.cpp
new file mode 100644
index 000000000..1c24c7ce9
--- /dev/null
+++ b/src/core/hle/lock.cpp
@@ -0,0 +1,11 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <core/hle/lock.h>
8
9namespace HLE {
10std::recursive_mutex g_hle_lock;
11}
diff --git a/src/core/hle/lock.h b/src/core/hle/lock.h
new file mode 100644
index 000000000..5c99fe996
--- /dev/null
+++ b/src/core/hle/lock.h
@@ -0,0 +1,18 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <mutex>
8
9namespace HLE {
10/*
11 * Synchronizes access to the internal HLE kernel structures, it is acquired when a guest
12 * application thread performs a syscall. It should be acquired by any host threads that read or
13 * modify the HLE kernel state. Note: Any operation that directly or indirectly reads from or writes
14 * to the emulated memory is not protected by this mutex, and should be avoided in any threads other
15 * than the CPU thread.
16 */
17extern std::recursive_mutex g_hle_lock;
18} // namespace HLE
diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp
index 0109fa2b2..58d94768c 100644
--- a/src/core/hle/service/apt/apt.cpp
+++ b/src/core/hle/service/apt/apt.cpp
@@ -34,8 +34,6 @@ static bool shared_font_loaded = false;
34static bool shared_font_relocated = false; 34static bool shared_font_relocated = false;
35 35
36static Kernel::SharedPtr<Kernel::Mutex> lock; 36static Kernel::SharedPtr<Kernel::Mutex> lock;
37static Kernel::SharedPtr<Kernel::Event> notification_event; ///< APT notification event
38static Kernel::SharedPtr<Kernel::Event> parameter_event; ///< APT parameter event
39 37
40static u32 cpu_percent; ///< CPU time available to the running application 38static u32 cpu_percent; ///< CPU time available to the running application
41 39
@@ -44,32 +42,160 @@ static u8 unknown_ns_state_field;
44 42
45static ScreencapPostPermission screen_capture_post_permission; 43static ScreencapPostPermission screen_capture_post_permission;
46 44
47/// Parameter data to be returned in the next call to Glance/ReceiveParameter 45/// Parameter data to be returned in the next call to Glance/ReceiveParameter.
46/// TODO(Subv): Use std::optional once we migrate to C++17.
48static boost::optional<MessageParameter> next_parameter; 47static boost::optional<MessageParameter> next_parameter;
49 48
49enum class AppletPos { Application = 0, Library = 1, System = 2, SysLibrary = 3, Resident = 4 };
50
51static constexpr size_t NumAppletSlot = 4;
52
53enum class AppletSlot : u8 {
54 Application,
55 SystemApplet,
56 HomeMenu,
57 LibraryApplet,
58
59 // An invalid tag
60 Error,
61};
62
63union AppletAttributes {
64 u32 raw;
65
66 BitField<0, 3, u32> applet_pos;
67
68 AppletAttributes() : raw(0) {}
69 AppletAttributes(u32 attributes) : raw(attributes) {}
70};
71
72struct AppletSlotData {
73 AppletId applet_id;
74 AppletSlot slot;
75 bool registered;
76 AppletAttributes attributes;
77 Kernel::SharedPtr<Kernel::Event> notification_event;
78 Kernel::SharedPtr<Kernel::Event> parameter_event;
79};
80
81// Holds data about the concurrently running applets in the system.
82static std::array<AppletSlotData, NumAppletSlot> applet_slots = {};
83
84// This overload returns nullptr if no applet with the specified id has been started.
85static AppletSlotData* GetAppletSlotData(AppletId id) {
86 auto GetSlot = [](AppletSlot slot) -> AppletSlotData* {
87 return &applet_slots[static_cast<size_t>(slot)];
88 };
89
90 if (id == AppletId::Application) {
91 auto* slot = GetSlot(AppletSlot::Application);
92 if (slot->applet_id != AppletId::None)
93 return slot;
94
95 return nullptr;
96 }
97
98 if (id == AppletId::AnySystemApplet) {
99 auto* system_slot = GetSlot(AppletSlot::SystemApplet);
100 if (system_slot->applet_id != AppletId::None)
101 return system_slot;
102
103 // The Home Menu is also a system applet, but it lives in its own slot to be able to run
104 // concurrently with other system applets.
105 auto* home_slot = GetSlot(AppletSlot::HomeMenu);
106 if (home_slot->applet_id != AppletId::None)
107 return home_slot;
108
109 return nullptr;
110 }
111
112 if (id == AppletId::AnyLibraryApplet || id == AppletId::AnySysLibraryApplet) {
113 auto* slot = GetSlot(AppletSlot::LibraryApplet);
114 if (slot->applet_id == AppletId::None)
115 return nullptr;
116
117 u32 applet_pos = slot->attributes.applet_pos;
118
119 if (id == AppletId::AnyLibraryApplet && applet_pos == static_cast<u32>(AppletPos::Library))
120 return slot;
121
122 if (id == AppletId::AnySysLibraryApplet &&
123 applet_pos == static_cast<u32>(AppletPos::SysLibrary))
124 return slot;
125
126 return nullptr;
127 }
128
129 if (id == AppletId::HomeMenu || id == AppletId::AlternateMenu) {
130 auto* slot = GetSlot(AppletSlot::HomeMenu);
131 if (slot->applet_id != AppletId::None)
132 return slot;
133
134 return nullptr;
135 }
136
137 for (auto& slot : applet_slots) {
138 if (slot.applet_id == id)
139 return &slot;
140 }
141
142 return nullptr;
143}
144
145static AppletSlotData* GetAppletSlotData(AppletAttributes attributes) {
146 // Mapping from AppletPos to AppletSlot
147 static constexpr std::array<AppletSlot, 6> applet_position_slots = {
148 AppletSlot::Application, AppletSlot::LibraryApplet, AppletSlot::SystemApplet,
149 AppletSlot::LibraryApplet, AppletSlot::Error, AppletSlot::LibraryApplet};
150
151 u32 applet_pos = attributes.applet_pos;
152 if (applet_pos >= applet_position_slots.size())
153 return nullptr;
154
155 AppletSlot slot = applet_position_slots[applet_pos];
156
157 if (slot == AppletSlot::Error)
158 return nullptr;
159
160 return &applet_slots[static_cast<size_t>(slot)];
161}
162
50void SendParameter(const MessageParameter& parameter) { 163void SendParameter(const MessageParameter& parameter) {
51 next_parameter = parameter; 164 next_parameter = parameter;
52 // Signal the event to let the application know that a new parameter is ready to be read 165 // Signal the event to let the receiver know that a new parameter is ready to be read
53 parameter_event->Signal(); 166 auto* const slot_data = GetAppletSlotData(static_cast<AppletId>(parameter.destination_id));
167 ASSERT(slot_data);
168
169 slot_data->parameter_event->Signal();
54} 170}
55 171
56void Initialize(Service::Interface* self) { 172void Initialize(Service::Interface* self) {
57 IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x2, 2, 0); // 0x20080 173 IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x2, 2, 0); // 0x20080
58 u32 app_id = rp.Pop<u32>(); 174 u32 app_id = rp.Pop<u32>();
59 u32 flags = rp.Pop<u32>(); 175 u32 attributes = rp.Pop<u32>();
60 IPC::RequestBuilder rb = rp.MakeBuilder(1, 3);
61 rb.Push(RESULT_SUCCESS);
62 rb.PushCopyHandles(Kernel::g_handle_table.Create(notification_event).Unwrap(),
63 Kernel::g_handle_table.Create(parameter_event).Unwrap());
64 176
65 // TODO(bunnei): Check if these events are cleared every time Initialize is called. 177 LOG_DEBUG(Service_APT, "called app_id=0x%08X, attributes=0x%08X", app_id, attributes);
66 notification_event->Clear(); 178
67 parameter_event->Clear(); 179 auto* const slot_data = GetAppletSlotData(attributes);
180
181 // Note: The real NS service does not check if the attributes value is valid before accessing
182 // the data in the array
183 ASSERT_MSG(slot_data, "Invalid application attributes");
184
185 if (slot_data->registered) {
186 IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
187 rb.Push(ResultCode(ErrorDescription::AlreadyExists, ErrorModule::Applet,
188 ErrorSummary::InvalidState, ErrorLevel::Status));
189 return;
190 }
68 191
69 ASSERT_MSG((nullptr != lock), "Cannot initialize without lock"); 192 slot_data->applet_id = static_cast<AppletId>(app_id);
70 lock->Release(); 193 slot_data->attributes.raw = attributes;
71 194
72 LOG_DEBUG(Service_APT, "called app_id=0x%08X, flags=0x%08X", app_id, flags); 195 IPC::RequestBuilder rb = rp.MakeBuilder(1, 3);
196 rb.Push(RESULT_SUCCESS);
197 rb.PushCopyHandles(Kernel::g_handle_table.Create(slot_data->notification_event).Unwrap(),
198 Kernel::g_handle_table.Create(slot_data->parameter_event).Unwrap());
73} 199}
74 200
75void GetSharedFont(Service::Interface* self) { 201void GetSharedFont(Service::Interface* self) {
@@ -120,7 +246,12 @@ void GetLockHandle(Service::Interface* self) {
120 // this will cause the app to wait until parameter_event is signaled. 246 // this will cause the app to wait until parameter_event is signaled.
121 u32 applet_attributes = rp.Pop<u32>(); 247 u32 applet_attributes = rp.Pop<u32>();
122 IPC::RequestBuilder rb = rp.MakeBuilder(3, 2); 248 IPC::RequestBuilder rb = rp.MakeBuilder(3, 2);
123 rb.Push(RESULT_SUCCESS); // No error 249 rb.Push(RESULT_SUCCESS); // No error
250
251 // TODO(Subv): The output attributes should have an AppletPos of either Library or System |
252 // Library (depending on the type of the last launched applet) if the input attributes'
253 // AppletPos has the Library bit set.
254
124 rb.Push(applet_attributes); // Applet Attributes, this value is passed to Enable. 255 rb.Push(applet_attributes); // Applet Attributes, this value is passed to Enable.
125 rb.Push<u32>(0); // Least significant bit = power button state 256 rb.Push<u32>(0); // Least significant bit = power button state
126 Kernel::Handle handle_copy = Kernel::g_handle_table.Create(lock).Unwrap(); 257 Kernel::Handle handle_copy = Kernel::g_handle_table.Create(lock).Unwrap();
@@ -133,10 +264,22 @@ void GetLockHandle(Service::Interface* self) {
133void Enable(Service::Interface* self) { 264void Enable(Service::Interface* self) {
134 IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x3, 1, 0); // 0x30040 265 IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x3, 1, 0); // 0x30040
135 u32 attributes = rp.Pop<u32>(); 266 u32 attributes = rp.Pop<u32>();
267
268 LOG_DEBUG(Service_APT, "called attributes=0x%08X", attributes);
269
136 IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); 270 IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
137 rb.Push(RESULT_SUCCESS); // No error 271
138 parameter_event->Signal(); // Let the application know that it has been started 272 auto* const slot_data = GetAppletSlotData(attributes);
139 LOG_WARNING(Service_APT, "(STUBBED) called attributes=0x%08X", attributes); 273
274 if (!slot_data) {
275 rb.Push(ResultCode(ErrCodes::InvalidAppletSlot, ErrorModule::Applet,
276 ErrorSummary::InvalidState, ErrorLevel::Status));
277 return;
278 }
279
280 slot_data->registered = true;
281
282 rb.Push(RESULT_SUCCESS);
140} 283}
141 284
142void GetAppletManInfo(Service::Interface* self) { 285void GetAppletManInfo(Service::Interface* self) {
@@ -154,22 +297,27 @@ void GetAppletManInfo(Service::Interface* self) {
154 297
155void IsRegistered(Service::Interface* self) { 298void IsRegistered(Service::Interface* self) {
156 IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x9, 1, 0); // 0x90040 299 IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x9, 1, 0); // 0x90040
157 u32 app_id = rp.Pop<u32>(); 300 AppletId app_id = static_cast<AppletId>(rp.Pop<u32>());
158 IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); 301 IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
159 rb.Push(RESULT_SUCCESS); // No error 302 rb.Push(RESULT_SUCCESS); // No error
160 303
161 // TODO(Subv): An application is considered "registered" if it has already called APT::Enable 304 auto* const slot_data = GetAppletSlotData(app_id);
162 // handle this properly once we implement multiprocess support. 305
163 bool is_registered = false; // Set to not registered by default 306 // Check if an LLE applet was registered first, then fallback to HLE applets
307 bool is_registered = slot_data && slot_data->registered;
164 308
165 if (app_id == static_cast<u32>(AppletId::AnyLibraryApplet)) { 309 if (!is_registered) {
166 is_registered = HLE::Applets::IsLibraryAppletRunning(); 310 if (app_id == AppletId::AnyLibraryApplet) {
167 } else if (auto applet = HLE::Applets::Applet::Get(static_cast<AppletId>(app_id))) { 311 is_registered = HLE::Applets::IsLibraryAppletRunning();
168 is_registered = true; // Set to registered 312 } else if (auto applet = HLE::Applets::Applet::Get(app_id)) {
313 // The applet exists, set it as registered.
314 is_registered = true;
315 }
169 } 316 }
317
170 rb.Push(is_registered); 318 rb.Push(is_registered);
171 319
172 LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X", app_id); 320 LOG_DEBUG(Service_APT, "called app_id=0x%08X", static_cast<u32>(app_id));
173} 321}
174 322
175void InquireNotification(Service::Interface* self) { 323void InquireNotification(Service::Interface* self) {
@@ -864,14 +1012,23 @@ void Init() {
864 screen_capture_post_permission = 1012 screen_capture_post_permission =
865 ScreencapPostPermission::CleanThePermission; // TODO(JamePeng): verify the initial value 1013 ScreencapPostPermission::CleanThePermission; // TODO(JamePeng): verify the initial value
866 1014
867 // TODO(bunnei): Check if these are created in Initialize or on APT process startup. 1015 for (size_t slot = 0; slot < applet_slots.size(); ++slot) {
868 notification_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "APT_U:Notification"); 1016 auto& slot_data = applet_slots[slot];
869 parameter_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "APT_U:Start"); 1017 slot_data.slot = static_cast<AppletSlot>(slot);
1018 slot_data.applet_id = AppletId::None;
1019 slot_data.attributes.raw = 0;
1020 slot_data.registered = false;
1021 slot_data.notification_event =
1022 Kernel::Event::Create(Kernel::ResetType::OneShot, "APT:Notification");
1023 slot_data.parameter_event =
1024 Kernel::Event::Create(Kernel::ResetType::OneShot, "APT:Parameter");
1025 }
870 1026
871 // Initialize the parameter to wake up the application. 1027 // Initialize the parameter to wake up the application.
872 next_parameter.emplace(); 1028 next_parameter.emplace();
873 next_parameter->signal = static_cast<u32>(SignalType::Wakeup); 1029 next_parameter->signal = static_cast<u32>(SignalType::Wakeup);
874 next_parameter->destination_id = static_cast<u32>(AppletId::Application); 1030 next_parameter->destination_id = static_cast<u32>(AppletId::Application);
1031 applet_slots[static_cast<size_t>(AppletSlot::Application)].parameter_event->Signal();
875} 1032}
876 1033
877void Shutdown() { 1034void Shutdown() {
@@ -879,8 +1036,12 @@ void Shutdown() {
879 shared_font_loaded = false; 1036 shared_font_loaded = false;
880 shared_font_relocated = false; 1037 shared_font_relocated = false;
881 lock = nullptr; 1038 lock = nullptr;
882 notification_event = nullptr; 1039
883 parameter_event = nullptr; 1040 for (auto& slot : applet_slots) {
1041 slot.registered = false;
1042 slot.notification_event = nullptr;
1043 slot.parameter_event = nullptr;
1044 }
884 1045
885 next_parameter = boost::none; 1046 next_parameter = boost::none;
886 1047
diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h
index 106754853..96b28b438 100644
--- a/src/core/hle/service/apt/apt.h
+++ b/src/core/hle/service/apt/apt.h
@@ -72,6 +72,8 @@ enum class SignalType : u32 {
72 72
73/// App Id's used by APT functions 73/// App Id's used by APT functions
74enum class AppletId : u32 { 74enum class AppletId : u32 {
75 None = 0,
76 AnySystemApplet = 0x100,
75 HomeMenu = 0x101, 77 HomeMenu = 0x101,
76 AlternateMenu = 0x103, 78 AlternateMenu = 0x103,
77 Camera = 0x110, 79 Camera = 0x110,
@@ -83,6 +85,7 @@ enum class AppletId : u32 {
83 Miiverse = 0x117, 85 Miiverse = 0x117,
84 MiiversePost = 0x118, 86 MiiversePost = 0x118,
85 AmiiboSettings = 0x119, 87 AmiiboSettings = 0x119,
88 AnySysLibraryApplet = 0x200,
86 SoftwareKeyboard1 = 0x201, 89 SoftwareKeyboard1 = 0x201,
87 Ed1 = 0x202, 90 Ed1 = 0x202,
88 PnoteApp = 0x204, 91 PnoteApp = 0x204,
@@ -119,8 +122,9 @@ enum class ScreencapPostPermission : u32 {
119namespace ErrCodes { 122namespace ErrCodes {
120enum { 123enum {
121 ParameterPresent = 2, 124 ParameterPresent = 2,
125 InvalidAppletSlot = 4,
122}; 126};
123} 127} // namespace ErrCodes
124 128
125/// Send a parameter to the currently-running application, which will read it via ReceiveParameter 129/// Send a parameter to the currently-running application, which will read it via ReceiveParameter
126void SendParameter(const MessageParameter& parameter); 130void SendParameter(const MessageParameter& parameter);
diff --git a/src/core/hle/service/dsp_dsp.cpp b/src/core/hle/service/dsp_dsp.cpp
index 7d746054f..42f8950f9 100644
--- a/src/core/hle/service/dsp_dsp.cpp
+++ b/src/core/hle/service/dsp_dsp.cpp
@@ -147,9 +147,10 @@ static void LoadComponent(Service::Interface* self) {
147 LOG_INFO(Service_DSP, "Firmware hash: %#" PRIx64, 147 LOG_INFO(Service_DSP, "Firmware hash: %#" PRIx64,
148 Common::ComputeHash64(component_data.data(), component_data.size())); 148 Common::ComputeHash64(component_data.data(), component_data.size()));
149 // Some versions of the firmware have the location of DSP structures listed here. 149 // Some versions of the firmware have the location of DSP structures listed here.
150 ASSERT(size > 0x37C); 150 if (size > 0x37C) {
151 LOG_INFO(Service_DSP, "Structures hash: %#" PRIx64, 151 LOG_INFO(Service_DSP, "Structures hash: %#" PRIx64,
152 Common::ComputeHash64(component_data.data() + 0x340, 60)); 152 Common::ComputeHash64(component_data.data() + 0x340, 60));
153 }
153 154
154 LOG_WARNING(Service_DSP, 155 LOG_WARNING(Service_DSP,
155 "(STUBBED) called size=0x%X, prog_mask=0x%08X, data_mask=0x%08X, buffer=0x%08X", 156 "(STUBBED) called size=0x%X, prog_mask=0x%08X, data_mask=0x%08X, buffer=0x%08X",
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 2014b8461..aa5d821f9 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -7,8 +7,9 @@
7#include <cmath> 7#include <cmath>
8#include <memory> 8#include <memory>
9#include "common/logging/log.h" 9#include "common/logging/log.h"
10#include "core/3ds.h"
11#include "core/core.h"
10#include "core/core_timing.h" 12#include "core/core_timing.h"
11#include "core/frontend/emu_window.h"
12#include "core/frontend/input.h" 13#include "core/frontend/input.h"
13#include "core/hle/ipc.h" 14#include "core/hle/ipc.h"
14#include "core/hle/kernel/event.h" 15#include "core/hle/kernel/event.h"
@@ -18,7 +19,6 @@
18#include "core/hle/service/hid/hid_spvr.h" 19#include "core/hle/service/hid/hid_spvr.h"
19#include "core/hle/service/hid/hid_user.h" 20#include "core/hle/service/hid/hid_user.h"
20#include "core/hle/service/service.h" 21#include "core/hle/service/service.h"
21#include "video_core/video_core.h"
22 22
23namespace Service { 23namespace Service {
24namespace HID { 24namespace HID {
@@ -50,10 +50,15 @@ constexpr u64 pad_update_ticks = BASE_CLOCK_RATE_ARM11 / 234;
50constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE_ARM11 / 104; 50constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE_ARM11 / 104;
51constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE_ARM11 / 101; 51constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE_ARM11 / 101;
52 52
53constexpr float accelerometer_coef = 512.0f; // measured from hw test result
54constexpr float gyroscope_coef = 14.375f; // got from hwtest GetGyroscopeLowRawToDpsCoefficient call
55
53static std::atomic<bool> is_device_reload_pending; 56static std::atomic<bool> is_device_reload_pending;
54static std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID> 57static std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
55 buttons; 58 buttons;
56static std::unique_ptr<Input::AnalogDevice> circle_pad; 59static std::unique_ptr<Input::AnalogDevice> circle_pad;
60static std::unique_ptr<Input::MotionDevice> motion_device;
61static std::unique_ptr<Input::TouchDevice> touch_device;
57 62
58DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) { 63DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
59 // 30 degree and 60 degree are angular thresholds for directions 64 // 30 degree and 60 degree are angular thresholds for directions
@@ -90,6 +95,8 @@ static void LoadInputDevices() {
90 buttons.begin(), Input::CreateDevice<Input::ButtonDevice>); 95 buttons.begin(), Input::CreateDevice<Input::ButtonDevice>);
91 circle_pad = Input::CreateDevice<Input::AnalogDevice>( 96 circle_pad = Input::CreateDevice<Input::AnalogDevice>(
92 Settings::values.analogs[Settings::NativeAnalog::CirclePad]); 97 Settings::values.analogs[Settings::NativeAnalog::CirclePad]);
98 motion_device = Input::CreateDevice<Input::MotionDevice>(Settings::values.motion_device);
99 touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touch_device);
93} 100}
94 101
95static void UnloadInputDevices() { 102static void UnloadInputDevices() {
@@ -97,6 +104,8 @@ static void UnloadInputDevices() {
97 button.reset(); 104 button.reset();
98 } 105 }
99 circle_pad.reset(); 106 circle_pad.reset();
107 motion_device.reset();
108 touch_device.reset();
100} 109}
101 110
102static void UpdatePadCallback(u64 userdata, int cycles_late) { 111static void UpdatePadCallback(u64 userdata, int cycles_late) {
@@ -165,8 +174,10 @@ static void UpdatePadCallback(u64 userdata, int cycles_late) {
165 // Get the current touch entry 174 // Get the current touch entry
166 TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index]; 175 TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index];
167 bool pressed = false; 176 bool pressed = false;
168 177 float x, y;
169 std::tie(touch_entry.x, touch_entry.y, pressed) = VideoCore::g_emu_window->GetTouchState(); 178 std::tie(x, y, pressed) = touch_device->GetStatus();
179 touch_entry.x = static_cast<u16>(x * Core::kScreenBottomWidth);
180 touch_entry.y = static_cast<u16>(y * Core::kScreenBottomHeight);
170 touch_entry.valid.Assign(pressed ? 1 : 0); 181 touch_entry.valid.Assign(pressed ? 1 : 0);
171 182
172 // TODO(bunnei): We're not doing anything with offset 0xA8 + 0x18 of HID SharedMemory, which 183 // TODO(bunnei): We're not doing anything with offset 0xA8 + 0x18 of HID SharedMemory, which
@@ -193,10 +204,19 @@ static void UpdateAccelerometerCallback(u64 userdata, int cycles_late) {
193 mem->accelerometer.index = next_accelerometer_index; 204 mem->accelerometer.index = next_accelerometer_index;
194 next_accelerometer_index = (next_accelerometer_index + 1) % mem->accelerometer.entries.size(); 205 next_accelerometer_index = (next_accelerometer_index + 1) % mem->accelerometer.entries.size();
195 206
207 Math::Vec3<float> accel;
208 std::tie(accel, std::ignore) = motion_device->GetStatus();
209 accel *= accelerometer_coef;
210 // TODO(wwylele): do a time stretch like the one in UpdateGyroscopeCallback
211 // The time stretch formula should be like
212 // stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity
213
196 AccelerometerDataEntry& accelerometer_entry = 214 AccelerometerDataEntry& accelerometer_entry =
197 mem->accelerometer.entries[mem->accelerometer.index]; 215 mem->accelerometer.entries[mem->accelerometer.index];
198 std::tie(accelerometer_entry.x, accelerometer_entry.y, accelerometer_entry.z) = 216
199 VideoCore::g_emu_window->GetAccelerometerState(); 217 accelerometer_entry.x = static_cast<s16>(accel.x);
218 accelerometer_entry.y = static_cast<s16>(accel.y);
219 accelerometer_entry.z = static_cast<s16>(accel.z);
200 220
201 // Make up "raw" entry 221 // Make up "raw" entry
202 // TODO(wwylele): 222 // TODO(wwylele):
@@ -227,8 +247,14 @@ static void UpdateGyroscopeCallback(u64 userdata, int cycles_late) {
227 next_gyroscope_index = (next_gyroscope_index + 1) % mem->gyroscope.entries.size(); 247 next_gyroscope_index = (next_gyroscope_index + 1) % mem->gyroscope.entries.size();
228 248
229 GyroscopeDataEntry& gyroscope_entry = mem->gyroscope.entries[mem->gyroscope.index]; 249 GyroscopeDataEntry& gyroscope_entry = mem->gyroscope.entries[mem->gyroscope.index];
230 std::tie(gyroscope_entry.x, gyroscope_entry.y, gyroscope_entry.z) = 250
231 VideoCore::g_emu_window->GetGyroscopeState(); 251 Math::Vec3<float> gyro;
252 std::tie(std::ignore, gyro) = motion_device->GetStatus();
253 double stretch = Core::System::GetInstance().perf_stats.GetLastFrameTimeScale();
254 gyro *= gyroscope_coef * stretch;
255 gyroscope_entry.x = static_cast<s16>(gyro.x);
256 gyroscope_entry.y = static_cast<s16>(gyro.y);
257 gyroscope_entry.z = static_cast<s16>(gyro.z);
232 258
233 // Make up "raw" entry 259 // Make up "raw" entry
234 mem->gyroscope.raw_entry.x = gyroscope_entry.x; 260 mem->gyroscope.raw_entry.x = gyroscope_entry.x;
@@ -326,7 +352,7 @@ void GetGyroscopeLowRawToDpsCoefficient(Service::Interface* self) {
326 352
327 cmd_buff[1] = RESULT_SUCCESS.raw; 353 cmd_buff[1] = RESULT_SUCCESS.raw;
328 354
329 f32 coef = VideoCore::g_emu_window->GetGyroscopeRawToDpsCoefficient(); 355 f32 coef = gyroscope_coef;
330 memcpy(&cmd_buff[2], &coef, 4); 356 memcpy(&cmd_buff[2], &coef, 4);
331} 357}
332 358
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index 1ef972e70..ef25926b5 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -24,7 +24,7 @@ namespace HID {
24 */ 24 */
25struct PadState { 25struct PadState {
26 union { 26 union {
27 u32 hex; 27 u32 hex{};
28 28
29 BitField<0, 1, u32> a; 29 BitField<0, 1, u32> a;
30 BitField<1, 1, u32> b; 30 BitField<1, 1, u32> b;
diff --git a/src/core/hle/service/ir/ir_rst.cpp b/src/core/hle/service/ir/ir_rst.cpp
index 837413f93..0912d5756 100644
--- a/src/core/hle/service/ir/ir_rst.cpp
+++ b/src/core/hle/service/ir/ir_rst.cpp
@@ -18,7 +18,7 @@ namespace Service {
18namespace IR { 18namespace IR {
19 19
20union PadState { 20union PadState {
21 u32_le hex; 21 u32_le hex{};
22 22
23 BitField<14, 1, u32_le> zl; 23 BitField<14, 1, u32_le> zl;
24 BitField<15, 1, u32_le> zr; 24 BitField<15, 1, u32_le> zr;
diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp
index 6dbdff044..893bbb1e7 100644
--- a/src/core/hle/service/nwm/nwm_uds.cpp
+++ b/src/core/hle/service/nwm/nwm_uds.cpp
@@ -4,6 +4,7 @@
4 4
5#include <array> 5#include <array>
6#include <cstring> 6#include <cstring>
7#include <mutex>
7#include <unordered_map> 8#include <unordered_map>
8#include <vector> 9#include <vector>
9#include "common/common_types.h" 10#include "common/common_types.h"
@@ -15,8 +16,10 @@
15#include "core/hle/result.h" 16#include "core/hle/result.h"
16#include "core/hle/service/nwm/nwm_uds.h" 17#include "core/hle/service/nwm/nwm_uds.h"
17#include "core/hle/service/nwm/uds_beacon.h" 18#include "core/hle/service/nwm/uds_beacon.h"
19#include "core/hle/service/nwm/uds_connection.h"
18#include "core/hle/service/nwm/uds_data.h" 20#include "core/hle/service/nwm/uds_data.h"
19#include "core/memory.h" 21#include "core/memory.h"
22#include "network/network.h"
20 23
21namespace Service { 24namespace Service {
22namespace NWM { 25namespace NWM {
@@ -51,6 +54,135 @@ static NetworkInfo network_info;
51// Event that will generate and send the 802.11 beacon frames. 54// Event that will generate and send the 802.11 beacon frames.
52static int beacon_broadcast_event; 55static int beacon_broadcast_event;
53 56
57// Mutex to synchronize access to the list of received beacons between the emulation thread and the
58// network thread.
59static std::mutex beacon_mutex;
60
61// Number of beacons to store before we start dropping the old ones.
62// TODO(Subv): Find a more accurate value for this limit.
63constexpr size_t MaxBeaconFrames = 15;
64
65// List of the last <MaxBeaconFrames> beacons received from the network.
66static std::deque<Network::WifiPacket> received_beacons;
67
68/**
69 * Returns a list of received 802.11 beacon frames from the specified sender since the last call.
70 */
71std::deque<Network::WifiPacket> GetReceivedBeacons(const MacAddress& sender) {
72 std::lock_guard<std::mutex> lock(beacon_mutex);
73 // TODO(Subv): Filter by sender.
74 return std::move(received_beacons);
75}
76
77/// Sends a WifiPacket to the room we're currently connected to.
78void SendPacket(Network::WifiPacket& packet) {
79 // TODO(Subv): Implement.
80}
81
82// Inserts the received beacon frame in the beacon queue and removes any older beacons if the size
83// limit is exceeded.
84void HandleBeaconFrame(const Network::WifiPacket& packet) {
85 std::lock_guard<std::mutex> lock(beacon_mutex);
86
87 received_beacons.emplace_back(packet);
88
89 // Discard old beacons if the buffer is full.
90 if (received_beacons.size() > MaxBeaconFrames)
91 received_beacons.pop_front();
92}
93
94/*
95 * Returns an available index in the nodes array for the
96 * currently-hosted UDS network.
97 */
98static u16 GetNextAvailableNodeId() {
99 ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost),
100 "Can not accept clients if we're not hosting a network");
101
102 for (u16 index = 0; index < connection_status.max_nodes; ++index) {
103 if ((connection_status.node_bitmask & (1 << index)) == 0)
104 return index;
105 }
106
107 // Any connection attempts to an already full network should have been refused.
108 ASSERT_MSG(false, "No available connection slots in the network");
109}
110
111/*
112 * Start a connection sequence with an UDS server. The sequence starts by sending an 802.11
113 * authentication frame with SEQ1.
114 */
115void StartConnectionSequence(const MacAddress& server) {
116 ASSERT(connection_status.status == static_cast<u32>(NetworkStatus::NotConnected));
117
118 // TODO(Subv): Handle timeout.
119
120 // Send an authentication frame with SEQ1
121 using Network::WifiPacket;
122 WifiPacket auth_request;
123 auth_request.channel = network_channel;
124 auth_request.data = GenerateAuthenticationFrame(AuthenticationSeq::SEQ1);
125 auth_request.destination_address = server;
126 auth_request.type = WifiPacket::PacketType::Authentication;
127
128 SendPacket(auth_request);
129}
130
131/// Sends an Association Response frame to the specified mac address
132void SendAssociationResponseFrame(const MacAddress& address) {
133 ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost));
134
135 using Network::WifiPacket;
136 WifiPacket assoc_response;
137 assoc_response.channel = network_channel;
138 // TODO(Subv): This will cause multiple clients to end up with the same association id, but
139 // we're not using that for anything.
140 u16 association_id = 1;
141 assoc_response.data = GenerateAssocResponseFrame(AssocStatus::Successful, association_id,
142 network_info.network_id);
143 assoc_response.destination_address = address;
144 assoc_response.type = WifiPacket::PacketType::AssociationResponse;
145
146 SendPacket(assoc_response);
147}
148
149/*
150 * Handles the authentication request frame and sends the authentication response and association
151 * response frames. Once an Authentication frame with SEQ1 is received by the server, it responds
152 * with an Authentication frame containing SEQ2, and immediately sends an Association response frame
153 * containing the details of the access point and the assigned association id for the new client.
154 */
155void HandleAuthenticationFrame(const Network::WifiPacket& packet) {
156 // Only the SEQ1 auth frame is handled here, the SEQ2 frame doesn't need any special behavior
157 if (GetAuthenticationSeqNumber(packet.data) == AuthenticationSeq::SEQ1) {
158 ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost));
159
160 // Respond with an authentication response frame with SEQ2
161 using Network::WifiPacket;
162 WifiPacket auth_request;
163 auth_request.channel = network_channel;
164 auth_request.data = GenerateAuthenticationFrame(AuthenticationSeq::SEQ2);
165 auth_request.destination_address = packet.transmitter_address;
166 auth_request.type = WifiPacket::PacketType::Authentication;
167
168 SendPacket(auth_request);
169
170 SendAssociationResponseFrame(packet.transmitter_address);
171 }
172}
173
174/// Callback to parse and handle a received wifi packet.
175void OnWifiPacketReceived(const Network::WifiPacket& packet) {
176 switch (packet.type) {
177 case Network::WifiPacket::PacketType::Beacon:
178 HandleBeaconFrame(packet);
179 break;
180 case Network::WifiPacket::PacketType::Authentication:
181 HandleAuthenticationFrame(packet);
182 break;
183 }
184}
185
54/** 186/**
55 * NWM_UDS::Shutdown service function 187 * NWM_UDS::Shutdown service function
56 * Inputs: 188 * Inputs:
@@ -111,8 +243,7 @@ static void RecvBeaconBroadcastData(Interface* self) {
111 u32 total_size = sizeof(BeaconDataReplyHeader); 243 u32 total_size = sizeof(BeaconDataReplyHeader);
112 244
113 // Retrieve all beacon frames that were received from the desired mac address. 245 // Retrieve all beacon frames that were received from the desired mac address.
114 std::deque<WifiPacket> beacons = 246 auto beacons = GetReceivedBeacons(mac_address);
115 GetReceivedPackets(WifiPacket::PacketType::Beacon, mac_address);
116 247
117 BeaconDataReplyHeader data_reply_header{}; 248 BeaconDataReplyHeader data_reply_header{};
118 data_reply_header.total_entries = beacons.size(); 249 data_reply_header.total_entries = beacons.size();
@@ -193,6 +324,9 @@ static void InitializeWithVersion(Interface* self) {
193 rb.Push(RESULT_SUCCESS); 324 rb.Push(RESULT_SUCCESS);
194 rb.PushCopyHandles(Kernel::g_handle_table.Create(connection_status_event).Unwrap()); 325 rb.PushCopyHandles(Kernel::g_handle_table.Create(connection_status_event).Unwrap());
195 326
327 // TODO(Subv): Connect the OnWifiPacketReceived function to the wifi packet received callback of
328 // the room we're currently in.
329
196 LOG_DEBUG(Service_NWM, "called sharedmem_size=0x%08X, version=0x%08X, sharedmem_handle=0x%08X", 330 LOG_DEBUG(Service_NWM, "called sharedmem_size=0x%08X, version=0x%08X, sharedmem_handle=0x%08X",
197 sharedmem_size, version, sharedmem_handle); 331 sharedmem_size, version, sharedmem_handle);
198} 332}
@@ -610,32 +744,23 @@ static void BeaconBroadcastCallback(u64 userdata, int cycles_late) {
610 if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) 744 if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost))
611 return; 745 return;
612 746
613 // TODO(Subv): Actually send the beacon.
614 std::vector<u8> frame = GenerateBeaconFrame(network_info, node_info); 747 std::vector<u8> frame = GenerateBeaconFrame(network_info, node_info);
615 748
749 using Network::WifiPacket;
750 WifiPacket packet;
751 packet.type = WifiPacket::PacketType::Beacon;
752 packet.data = std::move(frame);
753 packet.destination_address = Network::BroadcastMac;
754 packet.channel = network_channel;
755
756 SendPacket(packet);
757
616 // Start broadcasting the network, send a beacon frame every 102.4ms. 758 // Start broadcasting the network, send a beacon frame every 102.4ms.
617 CoreTiming::ScheduleEvent(msToCycles(DefaultBeaconInterval * MillisecondsPerTU) - cycles_late, 759 CoreTiming::ScheduleEvent(msToCycles(DefaultBeaconInterval * MillisecondsPerTU) - cycles_late,
618 beacon_broadcast_event, 0); 760 beacon_broadcast_event, 0);
619} 761}
620 762
621/* 763/*
622 * Returns an available index in the nodes array for the
623 * currently-hosted UDS network.
624 */
625static u32 GetNextAvailableNodeId() {
626 ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost),
627 "Can not accept clients if we're not hosting a network");
628
629 for (unsigned index = 0; index < connection_status.max_nodes; ++index) {
630 if ((connection_status.node_bitmask & (1 << index)) == 0)
631 return index;
632 }
633
634 // Any connection attempts to an already full network should have been refused.
635 ASSERT_MSG(false, "No available connection slots in the network");
636}
637
638/*
639 * Called when a client connects to an UDS network we're hosting, 764 * Called when a client connects to an UDS network we're hosting,
640 * updates the connection status and signals the update event. 765 * updates the connection status and signals the update event.
641 * @param network_node_id Network Node Id of the connecting client. 766 * @param network_node_id Network Node Id of the connecting client.
diff --git a/src/core/hle/service/nwm/nwm_uds.h b/src/core/hle/service/nwm/nwm_uds.h
index 141f49f9c..f1caaf974 100644
--- a/src/core/hle/service/nwm/nwm_uds.h
+++ b/src/core/hle/service/nwm/nwm_uds.h
@@ -42,6 +42,7 @@ using NodeList = std::vector<NodeInfo>;
42enum class NetworkStatus { 42enum class NetworkStatus {
43 NotConnected = 3, 43 NotConnected = 3,
44 ConnectedAsHost = 6, 44 ConnectedAsHost = 6,
45 Connecting = 7,
45 ConnectedAsClient = 9, 46 ConnectedAsClient = 9,
46 ConnectedAsSpectator = 10, 47 ConnectedAsSpectator = 10,
47}; 48};
@@ -85,6 +86,17 @@ static_assert(offsetof(NetworkInfo, oui_value) == 0xC, "oui_value is at the wron
85static_assert(offsetof(NetworkInfo, wlan_comm_id) == 0x10, "wlancommid is at the wrong offset."); 86static_assert(offsetof(NetworkInfo, wlan_comm_id) == 0x10, "wlancommid is at the wrong offset.");
86static_assert(sizeof(NetworkInfo) == 0x108, "NetworkInfo has incorrect size."); 87static_assert(sizeof(NetworkInfo) == 0x108, "NetworkInfo has incorrect size.");
87 88
89/// Additional block tag ids in the Beacon and Association Response frames
90enum class TagId : u8 {
91 SSID = 0,
92 SupportedRates = 1,
93 DSParameterSet = 2,
94 TrafficIndicationMap = 5,
95 CountryInformation = 7,
96 ERPInformation = 42,
97 VendorSpecific = 221
98};
99
88class NWM_UDS final : public Interface { 100class NWM_UDS final : public Interface {
89public: 101public:
90 NWM_UDS(); 102 NWM_UDS();
diff --git a/src/core/hle/service/nwm/uds_beacon.cpp b/src/core/hle/service/nwm/uds_beacon.cpp
index 6332b404c..552eaf65e 100644
--- a/src/core/hle/service/nwm/uds_beacon.cpp
+++ b/src/core/hle/service/nwm/uds_beacon.cpp
@@ -325,8 +325,5 @@ std::vector<u8> GenerateBeaconFrame(const NetworkInfo& network_info, const NodeL
325 return buffer; 325 return buffer;
326} 326}
327 327
328std::deque<WifiPacket> GetReceivedPackets(WifiPacket::PacketType type, const MacAddress& sender) {
329 return {};
330}
331} // namespace NWM 328} // namespace NWM
332} // namespace Service 329} // namespace Service
diff --git a/src/core/hle/service/nwm/uds_beacon.h b/src/core/hle/service/nwm/uds_beacon.h
index caacf4c6f..50cc76da2 100644
--- a/src/core/hle/service/nwm/uds_beacon.h
+++ b/src/core/hle/service/nwm/uds_beacon.h
@@ -17,17 +17,6 @@ namespace NWM {
17using MacAddress = std::array<u8, 6>; 17using MacAddress = std::array<u8, 6>;
18constexpr std::array<u8, 3> NintendoOUI = {0x00, 0x1F, 0x32}; 18constexpr std::array<u8, 3> NintendoOUI = {0x00, 0x1F, 0x32};
19 19
20/// Additional block tag ids in the Beacon frames
21enum class TagId : u8 {
22 SSID = 0,
23 SupportedRates = 1,
24 DSParameterSet = 2,
25 TrafficIndicationMap = 5,
26 CountryInformation = 7,
27 ERPInformation = 42,
28 VendorSpecific = 221
29};
30
31/** 20/**
32 * Internal vendor-specific tag ids as stored inside 21 * Internal vendor-specific tag ids as stored inside
33 * VendorSpecific blocks in the Beacon frames. 22 * VendorSpecific blocks in the Beacon frames.
@@ -135,20 +124,6 @@ struct BeaconData {
135 124
136static_assert(sizeof(BeaconData) == 0x12, "BeaconData has incorrect size."); 125static_assert(sizeof(BeaconData) == 0x12, "BeaconData has incorrect size.");
137 126
138/// Information about a received WiFi packet.
139/// Acts as our own 802.11 header.
140struct WifiPacket {
141 enum class PacketType { Beacon, Data };
142
143 PacketType type; ///< The type of 802.11 frame, Beacon / Data.
144
145 /// Raw 802.11 frame data, starting at the management frame header for management frames.
146 std::vector<u8> data;
147 MacAddress transmitter_address; ///< Mac address of the transmitter.
148 MacAddress destination_address; ///< Mac address of the receiver.
149 u8 channel; ///< WiFi channel where this frame was transmitted.
150};
151
152/** 127/**
153 * Decrypts the beacon data buffer for the network described by `network_info`. 128 * Decrypts the beacon data buffer for the network described by `network_info`.
154 */ 129 */
@@ -161,10 +136,5 @@ void DecryptBeaconData(const NetworkInfo& network_info, std::vector<u8>& buffer)
161 */ 136 */
162std::vector<u8> GenerateBeaconFrame(const NetworkInfo& network_info, const NodeList& nodes); 137std::vector<u8> GenerateBeaconFrame(const NetworkInfo& network_info, const NodeList& nodes);
163 138
164/**
165 * Returns a list of received 802.11 frames from the specified sender
166 * matching the type since the last call.
167 */
168std::deque<WifiPacket> GetReceivedPackets(WifiPacket::PacketType type, const MacAddress& sender);
169} // namespace NWM 139} // namespace NWM
170} // namespace Service 140} // namespace Service
diff --git a/src/core/hle/service/nwm/uds_connection.cpp b/src/core/hle/service/nwm/uds_connection.cpp
new file mode 100644
index 000000000..c8a76ec2a
--- /dev/null
+++ b/src/core/hle/service/nwm/uds_connection.cpp
@@ -0,0 +1,79 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "core/hle/service/nwm/nwm_uds.h"
6#include "core/hle/service/nwm/uds_connection.h"
7#include "fmt/format.h"
8
9namespace Service {
10namespace NWM {
11
12// Note: These values were taken from a packet capture of an o3DS XL
13// broadcasting a Super Smash Bros. 4 lobby.
14constexpr u16 DefaultExtraCapabilities = 0x0431;
15
16std::vector<u8> GenerateAuthenticationFrame(AuthenticationSeq seq) {
17 AuthenticationFrame frame{};
18 frame.auth_seq = static_cast<u16>(seq);
19
20 std::vector<u8> data(sizeof(frame));
21 std::memcpy(data.data(), &frame, sizeof(frame));
22
23 return data;
24}
25
26AuthenticationSeq GetAuthenticationSeqNumber(const std::vector<u8>& body) {
27 AuthenticationFrame frame;
28 std::memcpy(&frame, body.data(), sizeof(frame));
29
30 return static_cast<AuthenticationSeq>(frame.auth_seq);
31}
32
33/**
34 * Generates an SSID tag of an 802.11 Beacon frame with an 8-byte character representation of the
35 * specified network id as the SSID value.
36 * @param network_id The network id to use.
37 * @returns A buffer with the SSID tag.
38 */
39static std::vector<u8> GenerateSSIDTag(u32 network_id) {
40 constexpr u8 SSIDSize = 8;
41
42 struct {
43 u8 id = static_cast<u8>(TagId::SSID);
44 u8 size = SSIDSize;
45 } tag_header;
46
47 std::vector<u8> buffer(sizeof(tag_header) + SSIDSize);
48
49 std::memcpy(buffer.data(), &tag_header, sizeof(tag_header));
50
51 std::string network_name = fmt::format("{0:08X}", network_id);
52
53 std::memcpy(buffer.data() + sizeof(tag_header), network_name.c_str(), SSIDSize);
54
55 return buffer;
56}
57
58std::vector<u8> GenerateAssocResponseFrame(AssocStatus status, u16 association_id, u32 network_id) {
59 AssociationResponseFrame frame{};
60 frame.capabilities = DefaultExtraCapabilities;
61 frame.status_code = static_cast<u16>(status);
62 // The association id is ORed with this magic value (0xC000)
63 constexpr u16 AssociationIdMagic = 0xC000;
64 frame.assoc_id = association_id | AssociationIdMagic;
65
66 std::vector<u8> data(sizeof(frame));
67 std::memcpy(data.data(), &frame, sizeof(frame));
68
69 auto ssid_tag = GenerateSSIDTag(network_id);
70 data.insert(data.end(), ssid_tag.begin(), ssid_tag.end());
71
72 // TODO(Subv): Add the SupportedRates tag.
73 // TODO(Subv): Add the DSParameterSet tag.
74 // TODO(Subv): Add the ERPInformation tag.
75 return data;
76}
77
78} // namespace NWM
79} // namespace Service
diff --git a/src/core/hle/service/nwm/uds_connection.h b/src/core/hle/service/nwm/uds_connection.h
new file mode 100644
index 000000000..73f55a4fd
--- /dev/null
+++ b/src/core/hle/service/nwm/uds_connection.h
@@ -0,0 +1,51 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <vector>
8#include "common/common_types.h"
9#include "common/swap.h"
10#include "core/hle/service/service.h"
11
12namespace Service {
13namespace NWM {
14
15/// Sequence number of the 802.11 authentication frames.
16enum class AuthenticationSeq : u16 { SEQ1 = 1, SEQ2 = 2 };
17
18enum class AuthAlgorithm : u16 { OpenSystem = 0 };
19
20enum class AuthStatus : u16 { Successful = 0 };
21
22enum class AssocStatus : u16 { Successful = 0 };
23
24struct AuthenticationFrame {
25 u16_le auth_algorithm = static_cast<u16>(AuthAlgorithm::OpenSystem);
26 u16_le auth_seq;
27 u16_le status_code = static_cast<u16>(AuthStatus::Successful);
28};
29
30static_assert(sizeof(AuthenticationFrame) == 6, "AuthenticationFrame has wrong size");
31
32struct AssociationResponseFrame {
33 u16_le capabilities;
34 u16_le status_code;
35 u16_le assoc_id;
36};
37
38static_assert(sizeof(AssociationResponseFrame) == 6, "AssociationResponseFrame has wrong size");
39
40/// Generates an 802.11 authentication frame, starting at the frame body.
41std::vector<u8> GenerateAuthenticationFrame(AuthenticationSeq seq);
42
43/// Returns the sequence number from the body of an Authentication frame.
44AuthenticationSeq GetAuthenticationSeqNumber(const std::vector<u8>& body);
45
46/// Generates an 802.11 association response frame with the specified status, association id and
47/// network id, starting at the frame body.
48std::vector<u8> GenerateAssocResponseFrame(AssocStatus status, u16 association_id, u32 network_id);
49
50} // namespace NWM
51} // namespace Service
diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp
index e4b803046..dfc36748c 100644
--- a/src/core/hle/svc.cpp
+++ b/src/core/hle/svc.cpp
@@ -31,6 +31,7 @@
31#include "core/hle/kernel/timer.h" 31#include "core/hle/kernel/timer.h"
32#include "core/hle/kernel/vm_manager.h" 32#include "core/hle/kernel/vm_manager.h"
33#include "core/hle/kernel/wait_object.h" 33#include "core/hle/kernel/wait_object.h"
34#include "core/hle/lock.h"
34#include "core/hle/result.h" 35#include "core/hle/result.h"
35#include "core/hle/service/service.h" 36#include "core/hle/service/service.h"
36 37
@@ -1188,7 +1189,7 @@ struct FunctionDef {
1188 Func* func; 1189 Func* func;
1189 const char* name; 1190 const char* name;
1190}; 1191};
1191} 1192} // namespace
1192 1193
1193static const FunctionDef SVC_Table[] = { 1194static const FunctionDef SVC_Table[] = {
1194 {0x00, nullptr, "Unknown"}, 1195 {0x00, nullptr, "Unknown"},
@@ -1332,6 +1333,9 @@ MICROPROFILE_DEFINE(Kernel_SVC, "Kernel", "SVC", MP_RGB(70, 200, 70));
1332void CallSVC(u32 immediate) { 1333void CallSVC(u32 immediate) {
1333 MICROPROFILE_SCOPE(Kernel_SVC); 1334 MICROPROFILE_SCOPE(Kernel_SVC);
1334 1335
1336 // Lock the global kernel mutex when we enter the kernel HLE.
1337 std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
1338
1335 const FunctionDef* info = GetSVCInfo(immediate); 1339 const FunctionDef* info = GetSVCInfo(immediate);
1336 if (info) { 1340 if (info) {
1337 if (info->func) { 1341 if (info->func) {
@@ -1342,4 +1346,4 @@ void CallSVC(u32 immediate) {
1342 } 1346 }
1343} 1347}
1344 1348
1345} // namespace 1349} // namespace SVC
diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp
index 6838e449c..83ad9d898 100644
--- a/src/core/hw/gpu.cpp
+++ b/src/core/hw/gpu.cpp
@@ -29,7 +29,7 @@ namespace GPU {
29Regs g_regs; 29Regs g_regs;
30 30
31/// 268MHz CPU clocks / 60Hz frames per second 31/// 268MHz CPU clocks / 60Hz frames per second
32const u64 frame_ticks = BASE_CLOCK_RATE_ARM11 / SCREEN_REFRESH_RATE; 32const u64 frame_ticks = static_cast<u64>(BASE_CLOCK_RATE_ARM11 / SCREEN_REFRESH_RATE);
33/// Event id for CoreTiming 33/// Event id for CoreTiming
34static int vblank_event; 34static int vblank_event;
35 35
diff --git a/src/core/hw/gpu.h b/src/core/hw/gpu.h
index 21b127fee..e3d0a0e08 100644
--- a/src/core/hw/gpu.h
+++ b/src/core/hw/gpu.h
@@ -74,9 +74,9 @@ struct Regs {
74 case PixelFormat::RGB5A1: 74 case PixelFormat::RGB5A1:
75 case PixelFormat::RGBA4: 75 case PixelFormat::RGBA4:
76 return 2; 76 return 2;
77 default:
78 UNIMPLEMENTED();
79 } 77 }
78
79 UNREACHABLE();
80 } 80 }
81 81
82 INSERT_PADDING_WORDS(0x4); 82 INSERT_PADDING_WORDS(0x4);
diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp
index c007069a9..7aff7f29b 100644
--- a/src/core/loader/ncch.cpp
+++ b/src/core/loader/ncch.cpp
@@ -20,6 +20,7 @@
20#include "core/loader/ncch.h" 20#include "core/loader/ncch.h"
21#include "core/loader/smdh.h" 21#include "core/loader/smdh.h"
22#include "core/memory.h" 22#include "core/memory.h"
23#include "network/network.h"
23 24
24//////////////////////////////////////////////////////////////////////////////////////////////////// 25////////////////////////////////////////////////////////////////////////////////////////////////////
25// Loader namespace 26// Loader namespace
@@ -350,6 +351,13 @@ ResultStatus AppLoader_NCCH::Load() {
350 351
351 Core::Telemetry().AddField(Telemetry::FieldType::Session, "ProgramId", program_id); 352 Core::Telemetry().AddField(Telemetry::FieldType::Session, "ProgramId", program_id);
352 353
354 if (auto room_member = Network::GetRoomMember().lock()) {
355 Network::GameInfo game_info;
356 ReadTitle(game_info.name);
357 game_info.id = ncch_header.program_id;
358 room_member->SendGameInfo(game_info);
359 }
360
353 is_loaded = true; // Set state to loaded 361 is_loaded = true; // Set state to loaded
354 362
355 result = LoadExec(); // Load the executable into memory for booting 363 result = LoadExec(); // Load the executable into memory for booting
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 65649d9d7..097bc5b47 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -9,6 +9,7 @@
9#include "common/logging/log.h" 9#include "common/logging/log.h"
10#include "common/swap.h" 10#include "common/swap.h"
11#include "core/hle/kernel/process.h" 11#include "core/hle/kernel/process.h"
12#include "core/hle/lock.h"
12#include "core/memory.h" 13#include "core/memory.h"
13#include "core/memory_setup.h" 14#include "core/memory_setup.h"
14#include "core/mmio.h" 15#include "core/mmio.h"
@@ -181,6 +182,9 @@ T Read(const VAddr vaddr) {
181 return value; 182 return value;
182 } 183 }
183 184
185 // The memory access might do an MMIO or cached access, so we have to lock the HLE kernel state
186 std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
187
184 PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; 188 PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
185 switch (type) { 189 switch (type) {
186 case PageType::Unmapped: 190 case PageType::Unmapped:
@@ -219,6 +223,9 @@ void Write(const VAddr vaddr, const T data) {
219 return; 223 return;
220 } 224 }
221 225
226 // The memory access might do an MMIO or cached access, so we have to lock the HLE kernel state
227 std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
228
222 PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; 229 PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
223 switch (type) { 230 switch (type) {
224 case PageType::Unmapped: 231 case PageType::Unmapped:
@@ -746,4 +753,4 @@ boost::optional<VAddr> PhysicalToVirtualAddress(const PAddr addr) {
746 return boost::none; 753 return boost::none;
747} 754}
748 755
749} // namespace 756} // namespace Memory
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index d4f0429d1..efcf1267d 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -36,4 +36,4 @@ void Apply() {
36 Service::IR::ReloadInputDevices(); 36 Service::IR::ReloadInputDevices();
37} 37}
38 38
39} // namespace 39} // namespace Settings
diff --git a/src/core/settings.h b/src/core/settings.h
index ee16bb90a..024f14666 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -15,6 +15,7 @@ enum class LayoutOption {
15 Default, 15 Default,
16 SingleScreen, 16 SingleScreen,
17 LargeScreen, 17 LargeScreen,
18 SideScreen,
18}; 19};
19 20
20namespace NativeButton { 21namespace NativeButton {
@@ -70,7 +71,7 @@ enum Values {
70static const std::array<const char*, NumAnalogs> mapping = {{ 71static const std::array<const char*, NumAnalogs> mapping = {{
71 "circle_pad", "c_stick", 72 "circle_pad", "c_stick",
72}}; 73}};
73} // namespace NumAnalog 74} // namespace NativeAnalog
74 75
75struct Values { 76struct Values {
76 // CheckNew3DS 77 // CheckNew3DS
@@ -79,6 +80,8 @@ struct Values {
79 // Controls 80 // Controls
80 std::array<std::string, NativeButton::NumButtons> buttons; 81 std::array<std::string, NativeButton::NumButtons> buttons;
81 std::array<std::string, NativeAnalog::NumAnalogs> analogs; 82 std::array<std::string, NativeAnalog::NumAnalogs> analogs;
83 std::string motion_device;
84 std::string touch_device;
82 85
83 // Core 86 // Core
84 bool use_cpu_jit; 87 bool use_cpu_jit;
@@ -128,7 +131,10 @@ struct Values {
128 u16 gdbstub_port; 131 u16 gdbstub_port;
129 132
130 // WebService 133 // WebService
134 bool enable_telemetry;
131 std::string telemetry_endpoint_url; 135 std::string telemetry_endpoint_url;
136 std::string citra_username;
137 std::string citra_token;
132} extern values; 138} extern values;
133 139
134// a special value for Values::region_value indicating that citra will automatically select a region 140// a special value for Values::region_value indicating that citra will automatically select a region
@@ -136,4 +142,4 @@ struct Values {
136static constexpr int REGION_VALUE_AUTO_SELECT = -1; 142static constexpr int REGION_VALUE_AUTO_SELECT = -1;
137 143
138void Apply(); 144void Apply();
139} 145} // namespace Settings
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index 94483f385..104a16cc9 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -3,8 +3,10 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <cstring> 5#include <cstring>
6#include <cryptopp/osrng.h>
6 7
7#include "common/assert.h" 8#include "common/assert.h"
9#include "common/file_util.h"
8#include "common/scm_rev.h" 10#include "common/scm_rev.h"
9#include "common/x64/cpu_detect.h" 11#include "common/x64/cpu_detect.h"
10#include "core/core.h" 12#include "core/core.h"
@@ -29,12 +31,65 @@ static const char* CpuVendorToStr(Common::CPUVendor vendor) {
29 UNREACHABLE(); 31 UNREACHABLE();
30} 32}
31 33
34static u64 GenerateTelemetryId() {
35 u64 telemetry_id{};
36 CryptoPP::AutoSeededRandomPool rng;
37 rng.GenerateBlock(reinterpret_cast<CryptoPP::byte*>(&telemetry_id), sizeof(u64));
38 return telemetry_id;
39}
40
41u64 GetTelemetryId() {
42 u64 telemetry_id{};
43 static const std::string& filename{FileUtil::GetUserPath(D_CONFIG_IDX) + "telemetry_id"};
44
45 if (FileUtil::Exists(filename)) {
46 FileUtil::IOFile file(filename, "rb");
47 if (!file.IsOpen()) {
48 LOG_ERROR(Core, "failed to open telemetry_id: %s", filename.c_str());
49 return {};
50 }
51 file.ReadBytes(&telemetry_id, sizeof(u64));
52 } else {
53 FileUtil::IOFile file(filename, "wb");
54 if (!file.IsOpen()) {
55 LOG_ERROR(Core, "failed to open telemetry_id: %s", filename.c_str());
56 return {};
57 }
58 telemetry_id = GenerateTelemetryId();
59 file.WriteBytes(&telemetry_id, sizeof(u64));
60 }
61
62 return telemetry_id;
63}
64
65u64 RegenerateTelemetryId() {
66 const u64 new_telemetry_id{GenerateTelemetryId()};
67 static const std::string& filename{FileUtil::GetUserPath(D_CONFIG_IDX) + "telemetry_id"};
68
69 FileUtil::IOFile file(filename, "wb");
70 if (!file.IsOpen()) {
71 LOG_ERROR(Core, "failed to open telemetry_id: %s", filename.c_str());
72 return {};
73 }
74 file.WriteBytes(&new_telemetry_id, sizeof(u64));
75 return new_telemetry_id;
76}
77
32TelemetrySession::TelemetrySession() { 78TelemetrySession::TelemetrySession() {
33#ifdef ENABLE_WEB_SERVICE 79#ifdef ENABLE_WEB_SERVICE
34 backend = std::make_unique<WebService::TelemetryJson>(); 80 if (Settings::values.enable_telemetry) {
81 backend = std::make_unique<WebService::TelemetryJson>(
82 Settings::values.telemetry_endpoint_url, Settings::values.citra_username,
83 Settings::values.citra_token);
84 } else {
85 backend = std::make_unique<Telemetry::NullVisitor>();
86 }
35#else 87#else
36 backend = std::make_unique<Telemetry::NullVisitor>(); 88 backend = std::make_unique<Telemetry::NullVisitor>();
37#endif 89#endif
90 // Log one-time top-level information
91 AddField(Telemetry::FieldType::None, "TelemetryId", GetTelemetryId());
92
38 // Log one-time session start information 93 // Log one-time session start information
39 const s64 init_time{std::chrono::duration_cast<std::chrono::milliseconds>( 94 const s64 init_time{std::chrono::duration_cast<std::chrono::milliseconds>(
40 std::chrono::system_clock::now().time_since_epoch()) 95 std::chrono::system_clock::now().time_since_epoch())
diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h
index cf53835c3..65613daae 100644
--- a/src/core/telemetry_session.h
+++ b/src/core/telemetry_session.h
@@ -35,4 +35,16 @@ private:
35 std::unique_ptr<Telemetry::VisitorInterface> backend; ///< Backend interface that logs fields 35 std::unique_ptr<Telemetry::VisitorInterface> backend; ///< Backend interface that logs fields
36}; 36};
37 37
38/**
39 * Gets TelemetryId, a unique identifier used for the user's telemetry sessions.
40 * @returns The current TelemetryId for the session.
41 */
42u64 GetTelemetryId();
43
44/**
45 * Regenerates TelemetryId, a unique identifier used for the user's telemetry sessions.
46 * @returns The new TelemetryId that was generated.
47 */
48u64 RegenerateTelemetryId();
49
38} // namespace Core 50} // namespace Core
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index e3e36ada7..92792a702 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -2,12 +2,14 @@ set(SRCS
2 analog_from_button.cpp 2 analog_from_button.cpp
3 keyboard.cpp 3 keyboard.cpp
4 main.cpp 4 main.cpp
5 motion_emu.cpp
5 ) 6 )
6 7
7set(HEADERS 8set(HEADERS
8 analog_from_button.h 9 analog_from_button.h
9 keyboard.h 10 keyboard.h
10 main.h 11 main.h
12 motion_emu.h
11 ) 13 )
12 14
13if(SDL2_FOUND) 15if(SDL2_FOUND)
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index 699f41e6b..557353740 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -7,6 +7,7 @@
7#include "input_common/analog_from_button.h" 7#include "input_common/analog_from_button.h"
8#include "input_common/keyboard.h" 8#include "input_common/keyboard.h"
9#include "input_common/main.h" 9#include "input_common/main.h"
10#include "input_common/motion_emu.h"
10#ifdef HAVE_SDL2 11#ifdef HAVE_SDL2
11#include "input_common/sdl/sdl.h" 12#include "input_common/sdl/sdl.h"
12#endif 13#endif
@@ -14,12 +15,16 @@
14namespace InputCommon { 15namespace InputCommon {
15 16
16static std::shared_ptr<Keyboard> keyboard; 17static std::shared_ptr<Keyboard> keyboard;
18static std::shared_ptr<MotionEmu> motion_emu;
17 19
18void Init() { 20void Init() {
19 keyboard = std::make_shared<InputCommon::Keyboard>(); 21 keyboard = std::make_shared<Keyboard>();
20 Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard); 22 Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
21 Input::RegisterFactory<Input::AnalogDevice>("analog_from_button", 23 Input::RegisterFactory<Input::AnalogDevice>("analog_from_button",
22 std::make_shared<InputCommon::AnalogFromButton>()); 24 std::make_shared<AnalogFromButton>());
25 motion_emu = std::make_shared<MotionEmu>();
26 Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
27
23#ifdef HAVE_SDL2 28#ifdef HAVE_SDL2
24 SDL::Init(); 29 SDL::Init();
25#endif 30#endif
@@ -29,6 +34,8 @@ void Shutdown() {
29 Input::UnregisterFactory<Input::ButtonDevice>("keyboard"); 34 Input::UnregisterFactory<Input::ButtonDevice>("keyboard");
30 keyboard.reset(); 35 keyboard.reset();
31 Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button"); 36 Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
37 Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
38 motion_emu.reset();
32 39
33#ifdef HAVE_SDL2 40#ifdef HAVE_SDL2
34 SDL::Shutdown(); 41 SDL::Shutdown();
@@ -39,6 +46,10 @@ Keyboard* GetKeyboard() {
39 return keyboard.get(); 46 return keyboard.get();
40} 47}
41 48
49MotionEmu* GetMotionEmu() {
50 return motion_emu.get();
51}
52
42std::string GenerateKeyboardParam(int key_code) { 53std::string GenerateKeyboardParam(int key_code) {
43 Common::ParamPackage param{ 54 Common::ParamPackage param{
44 {"engine", "keyboard"}, {"code", std::to_string(key_code)}, 55 {"engine", "keyboard"}, {"code", std::to_string(key_code)},
diff --git a/src/input_common/main.h b/src/input_common/main.h
index 140bbd014..5604f0fa8 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -11,7 +11,7 @@ namespace InputCommon {
11/// Initializes and registers all built-in input device factories. 11/// Initializes and registers all built-in input device factories.
12void Init(); 12void Init();
13 13
14/// Unresisters all build-in input device factories and shut them down. 14/// Deregisters all built-in input device factories and shuts them down.
15void Shutdown(); 15void Shutdown();
16 16
17class Keyboard; 17class Keyboard;
@@ -19,6 +19,11 @@ class Keyboard;
19/// Gets the keyboard button device factory. 19/// Gets the keyboard button device factory.
20Keyboard* GetKeyboard(); 20Keyboard* GetKeyboard();
21 21
22class MotionEmu;
23
24/// Gets the motion emulation factory.
25MotionEmu* GetMotionEmu();
26
22/// Generates a serialized param package for creating a keyboard button device 27/// Generates a serialized param package for creating a keyboard button device
23std::string GenerateKeyboardParam(int key_code); 28std::string GenerateKeyboardParam(int key_code);
24 29
diff --git a/src/input_common/motion_emu.cpp b/src/input_common/motion_emu.cpp
new file mode 100644
index 000000000..59a035e70
--- /dev/null
+++ b/src/input_common/motion_emu.cpp
@@ -0,0 +1,168 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <chrono>
6#include <mutex>
7#include <thread>
8#include <tuple>
9#include "common/math_util.h"
10#include "common/quaternion.h"
11#include "common/thread.h"
12#include "common/vector_math.h"
13#include "input_common/motion_emu.h"
14
15namespace InputCommon {
16
17// Implementation class of the motion emulation device
18class MotionEmuDevice {
19public:
20 MotionEmuDevice(int update_millisecond, float sensitivity)
21 : update_millisecond(update_millisecond),
22 update_duration(std::chrono::duration_cast<std::chrono::steady_clock::duration>(
23 std::chrono::milliseconds(update_millisecond))),
24 sensitivity(sensitivity), motion_emu_thread(&MotionEmuDevice::MotionEmuThread, this) {}
25
26 ~MotionEmuDevice() {
27 if (motion_emu_thread.joinable()) {
28 shutdown_event.Set();
29 motion_emu_thread.join();
30 }
31 }
32
33 void BeginTilt(int x, int y) {
34 mouse_origin = Math::MakeVec(x, y);
35 is_tilting = true;
36 }
37
38 void Tilt(int x, int y) {
39 auto mouse_move = Math::MakeVec(x, y) - mouse_origin;
40 if (is_tilting) {
41 std::lock_guard<std::mutex> guard(tilt_mutex);
42 if (mouse_move.x == 0 && mouse_move.y == 0) {
43 tilt_angle = 0;
44 } else {
45 tilt_direction = mouse_move.Cast<float>();
46 tilt_angle = MathUtil::Clamp(tilt_direction.Normalize() * sensitivity, 0.0f,
47 MathUtil::PI * 0.5f);
48 }
49 }
50 }
51
52 void EndTilt() {
53 std::lock_guard<std::mutex> guard(tilt_mutex);
54 tilt_angle = 0;
55 is_tilting = false;
56 }
57
58 std::tuple<Math::Vec3<float>, Math::Vec3<float>> GetStatus() {
59 std::lock_guard<std::mutex> guard(status_mutex);
60 return status;
61 }
62
63private:
64 const int update_millisecond;
65 const std::chrono::steady_clock::duration update_duration;
66 const float sensitivity;
67
68 Math::Vec2<int> mouse_origin;
69
70 std::mutex tilt_mutex;
71 Math::Vec2<float> tilt_direction;
72 float tilt_angle = 0;
73
74 bool is_tilting = false;
75
76 Common::Event shutdown_event;
77
78 std::tuple<Math::Vec3<float>, Math::Vec3<float>> status;
79 std::mutex status_mutex;
80
81 // Note: always keep the thread declaration at the end so that other objects are initialized
82 // before this!
83 std::thread motion_emu_thread;
84
85 void MotionEmuThread() {
86 auto update_time = std::chrono::steady_clock::now();
87 Math::Quaternion<float> q = MakeQuaternion(Math::Vec3<float>(), 0);
88 Math::Quaternion<float> old_q;
89
90 while (!shutdown_event.WaitUntil(update_time)) {
91 update_time += update_duration;
92 old_q = q;
93
94 {
95 std::lock_guard<std::mutex> guard(tilt_mutex);
96
97 // Find the quaternion describing current 3DS tilting
98 q = MakeQuaternion(Math::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x),
99 tilt_angle);
100 }
101
102 auto inv_q = q.Inverse();
103
104 // Set the gravity vector in world space
105 auto gravity = Math::MakeVec(0.0f, -1.0f, 0.0f);
106
107 // Find the angular rate vector in world space
108 auto angular_rate = ((q - old_q) * inv_q).xyz * 2;
109 angular_rate *= 1000 / update_millisecond / MathUtil::PI * 180;
110
111 // Transform the two vectors from world space to 3DS space
112 gravity = QuaternionRotate(inv_q, gravity);
113 angular_rate = QuaternionRotate(inv_q, angular_rate);
114
115 // Update the sensor state
116 {
117 std::lock_guard<std::mutex> guard(status_mutex);
118 status = std::make_tuple(gravity, angular_rate);
119 }
120 }
121 }
122};
123
124// Interface wrapper held by input receiver as a unique_ptr. It holds the implementation class as
125// a shared_ptr, which is also observed by the factory class as a weak_ptr. In this way the factory
126// can forward all the inputs to the implementation only when it is valid.
127class MotionEmuDeviceWrapper : public Input::MotionDevice {
128public:
129 MotionEmuDeviceWrapper(int update_millisecond, float sensitivity) {
130 device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity);
131 }
132
133 std::tuple<Math::Vec3<float>, Math::Vec3<float>> GetStatus() const {
134 return device->GetStatus();
135 }
136
137 std::shared_ptr<MotionEmuDevice> device;
138};
139
140std::unique_ptr<Input::MotionDevice> MotionEmu::Create(const Common::ParamPackage& params) {
141 int update_period = params.Get("update_period", 100);
142 float sensitivity = params.Get("sensitivity", 0.01f);
143 auto device_wrapper = std::make_unique<MotionEmuDeviceWrapper>(update_period, sensitivity);
144 // Previously created device is disconnected here. Having two motion devices for 3DS is not
145 // expected.
146 current_device = device_wrapper->device;
147 return std::move(device_wrapper);
148}
149
150void MotionEmu::BeginTilt(int x, int y) {
151 if (auto ptr = current_device.lock()) {
152 ptr->BeginTilt(x, y);
153 }
154}
155
156void MotionEmu::Tilt(int x, int y) {
157 if (auto ptr = current_device.lock()) {
158 ptr->Tilt(x, y);
159 }
160}
161
162void MotionEmu::EndTilt() {
163 if (auto ptr = current_device.lock()) {
164 ptr->EndTilt();
165 }
166}
167
168} // namespace InputCommon
diff --git a/src/input_common/motion_emu.h b/src/input_common/motion_emu.h
new file mode 100644
index 000000000..7a7e22467
--- /dev/null
+++ b/src/input_common/motion_emu.h
@@ -0,0 +1,46 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "core/frontend/input.h"
8
9namespace InputCommon {
10
11class MotionEmuDevice;
12
13class MotionEmu : public Input::Factory<Input::MotionDevice> {
14public:
15 /**
16 * Creates a motion device emulated from mouse input
17 * @param params contains parameters for creating the device:
18 * - "update_period": update period in milliseconds
19 * - "sensitivity": the coefficient converting mouse movement to tilting angle
20 */
21 std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
22
23 /**
24 * Signals that a motion sensor tilt has begun.
25 * @param x the x-coordinate of the cursor
26 * @param y the y-coordinate of the cursor
27 */
28 void BeginTilt(int x, int y);
29
30 /**
31 * Signals that a motion sensor tilt is occurring.
32 * @param x the x-coordinate of the cursor
33 * @param y the y-coordinate of the cursor
34 */
35 void Tilt(int x, int y);
36
37 /**
38 * Signals that a motion sensor tilt has ended.
39 */
40 void EndTilt();
41
42private:
43 std::weak_ptr<MotionEmuDevice> current_device;
44};
45
46} // namespace InputCommon
diff --git a/src/input_common/sdl/sdl.cpp b/src/input_common/sdl/sdl.cpp
index 756ee58b7..d404afa89 100644
--- a/src/input_common/sdl/sdl.cpp
+++ b/src/input_common/sdl/sdl.cpp
@@ -159,7 +159,7 @@ public:
159 * - "axis"(optional): the index of the axis to bind 159 * - "axis"(optional): the index of the axis to bind
160 * - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", 160 * - "direction"(only used for hat): the direction name of the hat to bind. Can be "up",
161 * "down", "left" or "right" 161 * "down", "left" or "right"
162 * - "threshould"(only used for axis): a float value in (-1.0, 1.0) which the button is 162 * - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is
163 * triggered if the axis value crosses 163 * triggered if the axis value crosses
164 * - "direction"(only used for axis): "+" means the button is triggered when the axis value 164 * - "direction"(only used for axis): "+" means the button is triggered when the axis value
165 * is greater than the threshold; "-" means the button is triggered when the axis value 165 * is greater than the threshold; "-" means the button is triggered when the axis value
diff --git a/src/network/packet.cpp b/src/network/packet.cpp
index 660e92c0d..cc60f2fbc 100644
--- a/src/network/packet.cpp
+++ b/src/network/packet.cpp
@@ -13,6 +13,18 @@
13 13
14namespace Network { 14namespace Network {
15 15
16#ifndef htonll
17u64 htonll(u64 x) {
18 return ((1 == htonl(1)) ? (x) : ((uint64_t)htonl((x)&0xFFFFFFFF) << 32) | htonl((x) >> 32));
19}
20#endif
21
22#ifndef ntohll
23u64 ntohll(u64 x) {
24 return ((1 == ntohl(1)) ? (x) : ((uint64_t)ntohl((x)&0xFFFFFFFF) << 32) | ntohl((x) >> 32));
25}
26#endif
27
16void Packet::Append(const void* in_data, std::size_t size_in_bytes) { 28void Packet::Append(const void* in_data, std::size_t size_in_bytes) {
17 if (in_data && (size_in_bytes > 0)) { 29 if (in_data && (size_in_bytes > 0)) {
18 std::size_t start = data.size(); 30 std::size_t start = data.size();
@@ -100,6 +112,20 @@ Packet& Packet::operator>>(u32& out_data) {
100 return *this; 112 return *this;
101} 113}
102 114
115Packet& Packet::operator>>(s64& out_data) {
116 s64 value;
117 Read(&value, sizeof(value));
118 out_data = ntohll(value);
119 return *this;
120}
121
122Packet& Packet::operator>>(u64& out_data) {
123 u64 value;
124 Read(&value, sizeof(value));
125 out_data = ntohll(value);
126 return *this;
127}
128
103Packet& Packet::operator>>(float& out_data) { 129Packet& Packet::operator>>(float& out_data) {
104 Read(&out_data, sizeof(out_data)); 130 Read(&out_data, sizeof(out_data));
105 return *this; 131 return *this;
@@ -183,6 +209,18 @@ Packet& Packet::operator<<(u32 in_data) {
183 return *this; 209 return *this;
184} 210}
185 211
212Packet& Packet::operator<<(s64 in_data) {
213 s64 toWrite = htonll(in_data);
214 Append(&toWrite, sizeof(toWrite));
215 return *this;
216}
217
218Packet& Packet::operator<<(u64 in_data) {
219 u64 toWrite = htonll(in_data);
220 Append(&toWrite, sizeof(toWrite));
221 return *this;
222}
223
186Packet& Packet::operator<<(float in_data) { 224Packet& Packet::operator<<(float in_data) {
187 Append(&in_data, sizeof(in_data)); 225 Append(&in_data, sizeof(in_data));
188 return *this; 226 return *this;
diff --git a/src/network/packet.h b/src/network/packet.h
index 94b351ab1..5a2e58dc2 100644
--- a/src/network/packet.h
+++ b/src/network/packet.h
@@ -72,6 +72,8 @@ public:
72 Packet& operator>>(u16& out_data); 72 Packet& operator>>(u16& out_data);
73 Packet& operator>>(s32& out_data); 73 Packet& operator>>(s32& out_data);
74 Packet& operator>>(u32& out_data); 74 Packet& operator>>(u32& out_data);
75 Packet& operator>>(s64& out_data);
76 Packet& operator>>(u64& out_data);
75 Packet& operator>>(float& out_data); 77 Packet& operator>>(float& out_data);
76 Packet& operator>>(double& out_data); 78 Packet& operator>>(double& out_data);
77 Packet& operator>>(char* out_data); 79 Packet& operator>>(char* out_data);
@@ -89,6 +91,8 @@ public:
89 Packet& operator<<(u16 in_data); 91 Packet& operator<<(u16 in_data);
90 Packet& operator<<(s32 in_data); 92 Packet& operator<<(s32 in_data);
91 Packet& operator<<(u32 in_data); 93 Packet& operator<<(u32 in_data);
94 Packet& operator<<(s64 in_data);
95 Packet& operator<<(u64 in_data);
92 Packet& operator<<(float in_data); 96 Packet& operator<<(float in_data);
93 Packet& operator<<(double in_data); 97 Packet& operator<<(double in_data);
94 Packet& operator<<(const char* in_data); 98 Packet& operator<<(const char* in_data);
diff --git a/src/network/room.cpp b/src/network/room.cpp
index fbbaf8b93..261049ab0 100644
--- a/src/network/room.cpp
+++ b/src/network/room.cpp
@@ -4,9 +4,9 @@
4 4
5#include <algorithm> 5#include <algorithm>
6#include <atomic> 6#include <atomic>
7#include <mutex>
7#include <random> 8#include <random>
8#include <thread> 9#include <thread>
9#include <vector>
10#include "enet/enet.h" 10#include "enet/enet.h"
11#include "network/packet.h" 11#include "network/packet.h"
12#include "network/room.h" 12#include "network/room.h"
@@ -29,12 +29,14 @@ public:
29 29
30 struct Member { 30 struct Member {
31 std::string nickname; ///< The nickname of the member. 31 std::string nickname; ///< The nickname of the member.
32 std::string game_name; ///< The current game of the member 32 GameInfo game_info; ///< The current game of the member
33 MacAddress mac_address; ///< The assigned mac address of the member. 33 MacAddress mac_address; ///< The assigned mac address of the member.
34 ENetPeer* peer; ///< The remote peer. 34 ENetPeer* peer; ///< The remote peer.
35 }; 35 };
36 using MemberList = std::vector<Member>; 36 using MemberList = std::vector<Member>;
37 MemberList members; ///< Information about the members of this room. 37 MemberList members; ///< Information about the members of this room
38 mutable std::mutex member_mutex; ///< Mutex for locking the members list
39 /// This should be a std::shared_mutex as soon as C++17 is supported
38 40
39 RoomImpl() 41 RoomImpl()
40 : random_gen(std::random_device()()), NintendoOUI{0x00, 0x1F, 0x32, 0x00, 0x00, 0x00} {} 42 : random_gen(std::random_device()()), NintendoOUI{0x00, 0x1F, 0x32, 0x00, 0x00, 0x00} {}
@@ -147,7 +149,7 @@ void Room::RoomImpl::ServerLoop() {
147 case IdJoinRequest: 149 case IdJoinRequest:
148 HandleJoinRequest(&event); 150 HandleJoinRequest(&event);
149 break; 151 break;
150 case IdSetGameName: 152 case IdSetGameInfo:
151 HandleGameNamePacket(&event); 153 HandleGameNamePacket(&event);
152 break; 154 break;
153 case IdWifiPacket: 155 case IdWifiPacket:
@@ -213,7 +215,10 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {
213 member.nickname = nickname; 215 member.nickname = nickname;
214 member.peer = event->peer; 216 member.peer = event->peer;
215 217
216 members.push_back(std::move(member)); 218 {
219 std::lock_guard<std::mutex> lock(member_mutex);
220 members.push_back(std::move(member));
221 }
217 222
218 // Notify everyone that the room information has changed. 223 // Notify everyone that the room information has changed.
219 BroadcastRoomInformation(); 224 BroadcastRoomInformation();
@@ -223,12 +228,14 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {
223bool Room::RoomImpl::IsValidNickname(const std::string& nickname) const { 228bool Room::RoomImpl::IsValidNickname(const std::string& nickname) const {
224 // A nickname is valid if it is not already taken by anybody else in the room. 229 // A nickname is valid if it is not already taken by anybody else in the room.
225 // TODO(B3N30): Check for empty names, spaces, etc. 230 // TODO(B3N30): Check for empty names, spaces, etc.
231 std::lock_guard<std::mutex> lock(member_mutex);
226 return std::all_of(members.begin(), members.end(), 232 return std::all_of(members.begin(), members.end(),
227 [&nickname](const auto& member) { return member.nickname != nickname; }); 233 [&nickname](const auto& member) { return member.nickname != nickname; });
228} 234}
229 235
230bool Room::RoomImpl::IsValidMacAddress(const MacAddress& address) const { 236bool Room::RoomImpl::IsValidMacAddress(const MacAddress& address) const {
231 // A MAC address is valid if it is not already taken by anybody else in the room. 237 // A MAC address is valid if it is not already taken by anybody else in the room.
238 std::lock_guard<std::mutex> lock(member_mutex);
232 return std::all_of(members.begin(), members.end(), 239 return std::all_of(members.begin(), members.end(),
233 [&address](const auto& member) { return member.mac_address != address; }); 240 [&address](const auto& member) { return member.mac_address != address; });
234} 241}
@@ -279,6 +286,7 @@ void Room::RoomImpl::SendCloseMessage() {
279 packet << static_cast<u8>(IdCloseRoom); 286 packet << static_cast<u8>(IdCloseRoom);
280 ENetPacket* enet_packet = 287 ENetPacket* enet_packet =
281 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE); 288 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
289 std::lock_guard<std::mutex> lock(member_mutex);
282 for (auto& member : members) { 290 for (auto& member : members) {
283 enet_peer_send(member.peer, 0, enet_packet); 291 enet_peer_send(member.peer, 0, enet_packet);
284 } 292 }
@@ -295,10 +303,14 @@ void Room::RoomImpl::BroadcastRoomInformation() {
295 packet << room_information.member_slots; 303 packet << room_information.member_slots;
296 304
297 packet << static_cast<u32>(members.size()); 305 packet << static_cast<u32>(members.size());
298 for (const auto& member : members) { 306 {
299 packet << member.nickname; 307 std::lock_guard<std::mutex> lock(member_mutex);
300 packet << member.mac_address; 308 for (const auto& member : members) {
301 packet << member.game_name; 309 packet << member.nickname;
310 packet << member.mac_address;
311 packet << member.game_info.name;
312 packet << member.game_info.id;
313 }
302 } 314 }
303 315
304 ENetPacket* enet_packet = 316 ENetPacket* enet_packet =
@@ -335,11 +347,13 @@ void Room::RoomImpl::HandleWifiPacket(const ENetEvent* event) {
335 ENET_PACKET_FLAG_RELIABLE); 347 ENET_PACKET_FLAG_RELIABLE);
336 348
337 if (destination_address == BroadcastMac) { // Send the data to everyone except the sender 349 if (destination_address == BroadcastMac) { // Send the data to everyone except the sender
350 std::lock_guard<std::mutex> lock(member_mutex);
338 for (const auto& member : members) { 351 for (const auto& member : members) {
339 if (member.peer != event->peer) 352 if (member.peer != event->peer)
340 enet_peer_send(member.peer, 0, enet_packet); 353 enet_peer_send(member.peer, 0, enet_packet);
341 } 354 }
342 } else { // Send the data only to the destination client 355 } else { // Send the data only to the destination client
356 std::lock_guard<std::mutex> lock(member_mutex);
343 auto member = std::find_if(members.begin(), members.end(), 357 auto member = std::find_if(members.begin(), members.end(),
344 [destination_address](const Member& member) -> bool { 358 [destination_address](const Member& member) -> bool {
345 return member.mac_address == destination_address; 359 return member.mac_address == destination_address;
@@ -361,6 +375,8 @@ void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) {
361 auto CompareNetworkAddress = [event](const Member member) -> bool { 375 auto CompareNetworkAddress = [event](const Member member) -> bool {
362 return member.peer == event->peer; 376 return member.peer == event->peer;
363 }; 377 };
378
379 std::lock_guard<std::mutex> lock(member_mutex);
364 const auto sending_member = std::find_if(members.begin(), members.end(), CompareNetworkAddress); 380 const auto sending_member = std::find_if(members.begin(), members.end(), CompareNetworkAddress);
365 if (sending_member == members.end()) { 381 if (sending_member == members.end()) {
366 return; // Received a chat message from a unknown sender 382 return; // Received a chat message from a unknown sender
@@ -385,22 +401,32 @@ void Room::RoomImpl::HandleGameNamePacket(const ENetEvent* event) {
385 in_packet.Append(event->packet->data, event->packet->dataLength); 401 in_packet.Append(event->packet->data, event->packet->dataLength);
386 402
387 in_packet.IgnoreBytes(sizeof(u8)); // Igonore the message type 403 in_packet.IgnoreBytes(sizeof(u8)); // Igonore the message type
388 std::string game_name; 404 GameInfo game_info;
389 in_packet >> game_name; 405 in_packet >> game_info.name;
390 auto member = 406 in_packet >> game_info.id;
391 std::find_if(members.begin(), members.end(), 407
392 [event](const Member& member) -> bool { return member.peer == event->peer; }); 408 {
393 if (member != members.end()) { 409 std::lock_guard<std::mutex> lock(member_mutex);
394 member->game_name = game_name; 410 auto member =
395 BroadcastRoomInformation(); 411 std::find_if(members.begin(), members.end(), [event](const Member& member) -> bool {
412 return member.peer == event->peer;
413 });
414 if (member != members.end()) {
415 member->game_info = game_info;
416 }
396 } 417 }
418 BroadcastRoomInformation();
397} 419}
398 420
399void Room::RoomImpl::HandleClientDisconnection(ENetPeer* client) { 421void Room::RoomImpl::HandleClientDisconnection(ENetPeer* client) {
400 // Remove the client from the members list. 422 // Remove the client from the members list.
401 members.erase(std::remove_if(members.begin(), members.end(), 423 {
402 [client](const Member& member) { return member.peer == client; }), 424 std::lock_guard<std::mutex> lock(member_mutex);
403 members.end()); 425 members.erase(
426 std::remove_if(members.begin(), members.end(),
427 [client](const Member& member) { return member.peer == client; }),
428 members.end());
429 }
404 430
405 // Announce the change to all clients. 431 // Announce the change to all clients.
406 enet_peer_disconnect(client, 0); 432 enet_peer_disconnect(client, 0);
@@ -437,6 +463,19 @@ const RoomInformation& Room::GetRoomInformation() const {
437 return room_impl->room_information; 463 return room_impl->room_information;
438} 464}
439 465
466std::vector<Room::Member> Room::GetRoomMemberList() const {
467 std::vector<Room::Member> member_list;
468 std::lock_guard<std::mutex> lock(room_impl->member_mutex);
469 for (const auto& member_impl : room_impl->members) {
470 Member member;
471 member.nickname = member_impl.nickname;
472 member.mac_address = member_impl.mac_address;
473 member.game_info = member_impl.game_info;
474 member_list.push_back(member);
475 }
476 return member_list;
477};
478
440void Room::Destroy() { 479void Room::Destroy() {
441 room_impl->state = State::Closed; 480 room_impl->state = State::Closed;
442 room_impl->room_thread->join(); 481 room_impl->room_thread->join();
@@ -447,7 +486,10 @@ void Room::Destroy() {
447 } 486 }
448 room_impl->room_information = {}; 487 room_impl->room_information = {};
449 room_impl->server = nullptr; 488 room_impl->server = nullptr;
450 room_impl->members.clear(); 489 {
490 std::lock_guard<std::mutex> lock(room_impl->member_mutex);
491 room_impl->members.clear();
492 }
451 room_impl->room_information.member_slots = 0; 493 room_impl->room_information.member_slots = 0;
452 room_impl->room_information.name.clear(); 494 room_impl->room_information.name.clear();
453} 495}
diff --git a/src/network/room.h b/src/network/room.h
index 65b0d008a..8285a4d0c 100644
--- a/src/network/room.h
+++ b/src/network/room.h
@@ -7,6 +7,7 @@
7#include <array> 7#include <array>
8#include <memory> 8#include <memory>
9#include <string> 9#include <string>
10#include <vector>
10#include "common/common_types.h" 11#include "common/common_types.h"
11 12
12namespace Network { 13namespace Network {
@@ -21,6 +22,11 @@ struct RoomInformation {
21 u32 member_slots; ///< Maximum number of members in this room 22 u32 member_slots; ///< Maximum number of members in this room
22}; 23};
23 24
25struct GameInfo {
26 std::string name{""};
27 u64 id{0};
28};
29
24using MacAddress = std::array<u8, 6>; 30using MacAddress = std::array<u8, 6>;
25/// A special MAC address that tells the room we're joining to assign us a MAC address 31/// A special MAC address that tells the room we're joining to assign us a MAC address
26/// automatically. 32/// automatically.
@@ -34,7 +40,7 @@ enum RoomMessageTypes : u8 {
34 IdJoinRequest = 1, 40 IdJoinRequest = 1,
35 IdJoinSuccess, 41 IdJoinSuccess,
36 IdRoomInformation, 42 IdRoomInformation,
37 IdSetGameName, 43 IdSetGameInfo,
38 IdWifiPacket, 44 IdWifiPacket,
39 IdChatMessage, 45 IdChatMessage,
40 IdNameCollision, 46 IdNameCollision,
@@ -51,6 +57,12 @@ public:
51 Closed, ///< The room is not opened and can not accept connections. 57 Closed, ///< The room is not opened and can not accept connections.
52 }; 58 };
53 59
60 struct Member {
61 std::string nickname; ///< The nickname of the member.
62 GameInfo game_info; ///< The current game of the member
63 MacAddress mac_address; ///< The assigned mac address of the member.
64 };
65
54 Room(); 66 Room();
55 ~Room(); 67 ~Room();
56 68
@@ -65,6 +77,11 @@ public:
65 const RoomInformation& GetRoomInformation() const; 77 const RoomInformation& GetRoomInformation() const;
66 78
67 /** 79 /**
80 * Gets a list of the mbmers connected to the room.
81 */
82 std::vector<Member> GetRoomMemberList() const;
83
84 /**
68 * Creates the socket for this room. Will bind to default address if 85 * Creates the socket for this room. Will bind to default address if
69 * server is empty string. 86 * server is empty string.
70 */ 87 */
diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp
index dac9bacae..f229ec6fd 100644
--- a/src/network/room_member.cpp
+++ b/src/network/room_member.cpp
@@ -5,6 +5,7 @@
5#include <atomic> 5#include <atomic>
6#include <list> 6#include <list>
7#include <mutex> 7#include <mutex>
8#include <set>
8#include <thread> 9#include <thread>
9#include "common/assert.h" 10#include "common/assert.h"
10#include "enet/enet.h" 11#include "enet/enet.h"
@@ -25,6 +26,9 @@ public:
25 /// Information about the room we're connected to. 26 /// Information about the room we're connected to.
26 RoomInformation room_information; 27 RoomInformation room_information;
27 28
29 /// The current game name, id and version
30 GameInfo current_game_info;
31
28 std::atomic<State> state{State::Idle}; ///< Current state of the RoomMember. 32 std::atomic<State> state{State::Idle}; ///< Current state of the RoomMember.
29 void SetState(const State new_state); 33 void SetState(const State new_state);
30 bool IsConnected() const; 34 bool IsConnected() const;
@@ -37,6 +41,24 @@ public:
37 std::unique_ptr<std::thread> loop_thread; 41 std::unique_ptr<std::thread> loop_thread;
38 std::mutex send_list_mutex; ///< Mutex that controls access to the `send_list` variable. 42 std::mutex send_list_mutex; ///< Mutex that controls access to the `send_list` variable.
39 std::list<Packet> send_list; ///< A list that stores all packets to send the async 43 std::list<Packet> send_list; ///< A list that stores all packets to send the async
44
45 template <typename T>
46 using CallbackSet = std::set<CallbackHandle<T>>;
47 std::mutex callback_mutex; ///< The mutex used for handling callbacks
48
49 class Callbacks {
50 public:
51 template <typename T>
52 CallbackSet<T>& Get();
53
54 private:
55 CallbackSet<WifiPacket> callback_set_wifi_packet;
56 CallbackSet<ChatEntry> callback_set_chat_messages;
57 CallbackSet<RoomInformation> callback_set_room_information;
58 CallbackSet<State> callback_set_state;
59 };
60 Callbacks callbacks; ///< All CallbackSets to all events
61
40 void MemberLoop(); 62 void MemberLoop();
41 63
42 void StartLoop(); 64 void StartLoop();
@@ -84,12 +106,20 @@ public:
84 * Disconnects the RoomMember from the Room 106 * Disconnects the RoomMember from the Room
85 */ 107 */
86 void Disconnect(); 108 void Disconnect();
109
110 template <typename T>
111 void Invoke(const T& data);
112
113 template <typename T>
114 CallbackHandle<T> Bind(std::function<void(const T&)> callback);
87}; 115};
88 116
89// RoomMemberImpl 117// RoomMemberImpl
90void RoomMember::RoomMemberImpl::SetState(const State new_state) { 118void RoomMember::RoomMemberImpl::SetState(const State new_state) {
91 state = new_state; 119 if (state != new_state) {
92 // TODO(B3N30): Invoke the callback functions 120 state = new_state;
121 Invoke<State>(state);
122 }
93} 123}
94 124
95bool RoomMember::RoomMemberImpl::IsConnected() const { 125bool RoomMember::RoomMemberImpl::IsConnected() const {
@@ -195,9 +225,10 @@ void RoomMember::RoomMemberImpl::HandleRoomInformationPacket(const ENetEvent* ev
195 for (auto& member : member_information) { 225 for (auto& member : member_information) {
196 packet >> member.nickname; 226 packet >> member.nickname;
197 packet >> member.mac_address; 227 packet >> member.mac_address;
198 packet >> member.game_name; 228 packet >> member.game_info.name;
229 packet >> member.game_info.id;
199 } 230 }
200 // TODO(B3N30): Invoke callbacks 231 Invoke(room_information);
201} 232}
202 233
203void RoomMember::RoomMemberImpl::HandleJoinPacket(const ENetEvent* event) { 234void RoomMember::RoomMemberImpl::HandleJoinPacket(const ENetEvent* event) {
@@ -209,7 +240,7 @@ void RoomMember::RoomMemberImpl::HandleJoinPacket(const ENetEvent* event) {
209 240
210 // Parse the MAC Address from the packet 241 // Parse the MAC Address from the packet
211 packet >> mac_address; 242 packet >> mac_address;
212 // TODO(B3N30): Invoke callbacks 243 SetState(State::Joined);
213} 244}
214 245
215void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) { 246void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) {
@@ -235,7 +266,7 @@ void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) {
235 266
236 packet >> wifi_packet.data; 267 packet >> wifi_packet.data;
237 268
238 // TODO(B3N30): Invoke callbacks 269 Invoke<WifiPacket>(wifi_packet);
239} 270}
240 271
241void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) { 272void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) {
@@ -248,7 +279,7 @@ void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) {
248 ChatEntry chat_entry{}; 279 ChatEntry chat_entry{};
249 packet >> chat_entry.nickname; 280 packet >> chat_entry.nickname;
250 packet >> chat_entry.message; 281 packet >> chat_entry.message;
251 // TODO(B3N30): Invoke callbacks 282 Invoke<ChatEntry>(chat_entry);
252} 283}
253 284
254void RoomMember::RoomMemberImpl::Disconnect() { 285void RoomMember::RoomMemberImpl::Disconnect() {
@@ -276,6 +307,46 @@ void RoomMember::RoomMemberImpl::Disconnect() {
276 server = nullptr; 307 server = nullptr;
277} 308}
278 309
310template <>
311RoomMember::RoomMemberImpl::CallbackSet<WifiPacket>& RoomMember::RoomMemberImpl::Callbacks::Get() {
312 return callback_set_wifi_packet;
313}
314
315template <>
316RoomMember::RoomMemberImpl::CallbackSet<RoomMember::State>&
317RoomMember::RoomMemberImpl::Callbacks::Get() {
318 return callback_set_state;
319}
320
321template <>
322RoomMember::RoomMemberImpl::CallbackSet<RoomInformation>&
323RoomMember::RoomMemberImpl::Callbacks::Get() {
324 return callback_set_room_information;
325}
326
327template <>
328RoomMember::RoomMemberImpl::CallbackSet<ChatEntry>& RoomMember::RoomMemberImpl::Callbacks::Get() {
329 return callback_set_chat_messages;
330}
331
332template <typename T>
333void RoomMember::RoomMemberImpl::Invoke(const T& data) {
334 std::lock_guard<std::mutex> lock(callback_mutex);
335 CallbackSet<T> callback_set = callbacks.Get<T>();
336 for (auto const& callback : callback_set)
337 (*callback)(data);
338}
339
340template <typename T>
341RoomMember::CallbackHandle<T> RoomMember::RoomMemberImpl::Bind(
342 std::function<void(const T&)> callback) {
343 std::lock_guard<std::mutex> lock(callback_mutex);
344 CallbackHandle<T> handle;
345 handle = std::make_shared<std::function<void(const T&)>>(callback);
346 callbacks.Get<T>().insert(handle);
347 return handle;
348}
349
279// RoomMember 350// RoomMember
280RoomMember::RoomMember() : room_member_impl{std::make_unique<RoomMemberImpl>()} { 351RoomMember::RoomMember() : room_member_impl{std::make_unique<RoomMemberImpl>()} {
281 room_member_impl->client = enet_host_create(nullptr, 1, NumChannels, 0, 0); 352 room_member_impl->client = enet_host_create(nullptr, 1, NumChannels, 0, 0);
@@ -339,6 +410,7 @@ void RoomMember::Join(const std::string& nick, const char* server_addr, u16 serv
339 room_member_impl->SetState(State::Joining); 410 room_member_impl->SetState(State::Joining);
340 room_member_impl->StartLoop(); 411 room_member_impl->StartLoop();
341 room_member_impl->SendJoinRequest(nick, preferred_mac); 412 room_member_impl->SendJoinRequest(nick, preferred_mac);
413 SendGameInfo(room_member_impl->current_game_info);
342 } else { 414 } else {
343 room_member_impl->SetState(State::CouldNotConnect); 415 room_member_impl->SetState(State::CouldNotConnect);
344 } 416 }
@@ -366,17 +438,53 @@ void RoomMember::SendChatMessage(const std::string& message) {
366 room_member_impl->Send(std::move(packet)); 438 room_member_impl->Send(std::move(packet));
367} 439}
368 440
369void RoomMember::SendGameName(const std::string& game_name) { 441void RoomMember::SendGameInfo(const GameInfo& game_info) {
442 room_member_impl->current_game_info = game_info;
443 if (!IsConnected())
444 return;
445
370 Packet packet; 446 Packet packet;
371 packet << static_cast<u8>(IdSetGameName); 447 packet << static_cast<u8>(IdSetGameInfo);
372 packet << game_name; 448 packet << game_info.name;
449 packet << game_info.id;
373 room_member_impl->Send(std::move(packet)); 450 room_member_impl->Send(std::move(packet));
374} 451}
375 452
453RoomMember::CallbackHandle<RoomMember::State> RoomMember::BindOnStateChanged(
454 std::function<void(const RoomMember::State&)> callback) {
455 return room_member_impl->Bind(callback);
456}
457
458RoomMember::CallbackHandle<WifiPacket> RoomMember::BindOnWifiPacketReceived(
459 std::function<void(const WifiPacket&)> callback) {
460 return room_member_impl->Bind(callback);
461}
462
463RoomMember::CallbackHandle<RoomInformation> RoomMember::BindOnRoomInformationChanged(
464 std::function<void(const RoomInformation&)> callback) {
465 return room_member_impl->Bind(callback);
466}
467
468RoomMember::CallbackHandle<ChatEntry> RoomMember::BindOnChatMessageRecieved(
469 std::function<void(const ChatEntry&)> callback) {
470 return room_member_impl->Bind(callback);
471}
472
473template <typename T>
474void RoomMember::Unbind(CallbackHandle<T> handle) {
475 std::lock_guard<std::mutex> lock(room_member_impl->callback_mutex);
476 room_member_impl->callbacks.Get<T>().erase(handle);
477}
478
376void RoomMember::Leave() { 479void RoomMember::Leave() {
377 room_member_impl->SetState(State::Idle); 480 room_member_impl->SetState(State::Idle);
378 room_member_impl->loop_thread->join(); 481 room_member_impl->loop_thread->join();
379 room_member_impl->loop_thread.reset(); 482 room_member_impl->loop_thread.reset();
380} 483}
381 484
485template void RoomMember::Unbind(CallbackHandle<WifiPacket>);
486template void RoomMember::Unbind(CallbackHandle<RoomMember::State>);
487template void RoomMember::Unbind(CallbackHandle<RoomInformation>);
488template void RoomMember::Unbind(CallbackHandle<ChatEntry>);
489
382} // namespace Network 490} // namespace Network
diff --git a/src/network/room_member.h b/src/network/room_member.h
index bc1af3a7e..98770a234 100644
--- a/src/network/room_member.h
+++ b/src/network/room_member.h
@@ -4,6 +4,7 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <functional>
7#include <memory> 8#include <memory>
8#include <string> 9#include <string>
9#include <vector> 10#include <vector>
@@ -53,12 +54,23 @@ public:
53 54
54 struct MemberInformation { 55 struct MemberInformation {
55 std::string nickname; ///< Nickname of the member. 56 std::string nickname; ///< Nickname of the member.
56 std::string game_name; ///< Name of the game they're currently playing, or empty if they're 57 GameInfo game_info; ///< Name of the game they're currently playing, or empty if they're
57 /// not playing anything. 58 /// not playing anything.
58 MacAddress mac_address; ///< MAC address associated with this member. 59 MacAddress mac_address; ///< MAC address associated with this member.
59 }; 60 };
60 using MemberList = std::vector<MemberInformation>; 61 using MemberList = std::vector<MemberInformation>;
61 62
63 // The handle for the callback functions
64 template <typename T>
65 using CallbackHandle = std::shared_ptr<std::function<void(const T&)>>;
66
67 /**
68 * Unbinds a callback function from the events.
69 * @param handle The connection handle to disconnect
70 */
71 template <typename T>
72 void Unbind(CallbackHandle<T> handle);
73
62 RoomMember(); 74 RoomMember();
63 ~RoomMember(); 75 ~RoomMember();
64 76
@@ -113,10 +125,49 @@ public:
113 void SendChatMessage(const std::string& message); 125 void SendChatMessage(const std::string& message);
114 126
115 /** 127 /**
116 * Sends the current game name to the room. 128 * Sends the current game info to the room.
117 * @param game_name The game name. 129 * @param game_info The game information.
130 */
131 void SendGameInfo(const GameInfo& game_info);
132
133 /**
134 * Binds a function to an event that will be triggered every time the State of the member
135 * changed. The function wil be called every time the event is triggered. The callback function
136 * must not bind or unbind a function. Doing so will cause a deadlock
137 * @param callback The function to call
138 * @return A handle used for removing the function from the registered list
139 */
140 CallbackHandle<State> BindOnStateChanged(std::function<void(const State&)> callback);
141
142 /**
143 * Binds a function to an event that will be triggered every time a WifiPacket is received.
144 * The function wil be called everytime the event is triggered.
145 * The callback function must not bind or unbind a function. Doing so will cause a deadlock
146 * @param callback The function to call
147 * @return A handle used for removing the function from the registered list
148 */
149 CallbackHandle<WifiPacket> BindOnWifiPacketReceived(
150 std::function<void(const WifiPacket&)> callback);
151
152 /**
153 * Binds a function to an event that will be triggered every time the RoomInformation changes.
154 * The function wil be called every time the event is triggered.
155 * The callback function must not bind or unbind a function. Doing so will cause a deadlock
156 * @param callback The function to call
157 * @return A handle used for removing the function from the registered list
158 */
159 CallbackHandle<RoomInformation> BindOnRoomInformationChanged(
160 std::function<void(const RoomInformation&)> callback);
161
162 /**
163 * Binds a function to an event that will be triggered every time a ChatMessage is received.
164 * The function wil be called every time the event is triggered.
165 * The callback function must not bind or unbind a function. Doing so will cause a deadlock
166 * @param callback The function to call
167 * @return A handle used for removing the function from the registered list
118 */ 168 */
119 void SendGameName(const std::string& game_name); 169 CallbackHandle<ChatEntry> BindOnChatMessageRecieved(
170 std::function<void(const ChatEntry&)> callback);
120 171
121 /** 172 /**
122 * Leaves the current room. 173 * Leaves the current room.
diff --git a/src/video_core/regs_framebuffer.h b/src/video_core/regs_framebuffer.h
index a50bd4111..7b565f911 100644
--- a/src/video_core/regs_framebuffer.h
+++ b/src/video_core/regs_framebuffer.h
@@ -256,10 +256,9 @@ struct FramebufferRegs {
256 return 3; 256 return 3;
257 case DepthFormat::D24S8: 257 case DepthFormat::D24S8:
258 return 4; 258 return 4;
259 default:
260 LOG_CRITICAL(HW_GPU, "Unknown depth format %u", format);
261 UNIMPLEMENTED();
262 } 259 }
260
261 ASSERT_MSG(false, "Unknown depth format %u", format);
263 } 262 }
264 263
265 // Returns the number of bits per depth component of the specified depth format 264 // Returns the number of bits per depth component of the specified depth format
@@ -270,10 +269,9 @@ struct FramebufferRegs {
270 case DepthFormat::D24: 269 case DepthFormat::D24:
271 case DepthFormat::D24S8: 270 case DepthFormat::D24S8:
272 return 24; 271 return 24;
273 default:
274 LOG_CRITICAL(HW_GPU, "Unknown depth format %u", format);
275 UNIMPLEMENTED();
276 } 272 }
273
274 ASSERT_MSG(false, "Unknown depth format %u", format);
277 } 275 }
278 276
279 INSERT_PADDING_WORDS(0x20); 277 INSERT_PADDING_WORDS(0x20);
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 1c6c15a58..aa95ef21d 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -28,6 +28,9 @@ MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(100, 100, 255));
28MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100)); 28MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100));
29 29
30RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) { 30RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) {
31 // Clipping plane 0 is always enabled for PICA fixed clip plane z <= 0
32 state.clip_distance[0] = true;
33
31 // Create sampler objects 34 // Create sampler objects
32 for (size_t i = 0; i < texture_samplers.size(); ++i) { 35 for (size_t i = 0; i < texture_samplers.size(); ++i) {
33 texture_samplers[i].Create(); 36 texture_samplers[i].Create();
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index bb192affd..c536e61e1 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -8,6 +8,7 @@
8#include "common/assert.h" 8#include "common/assert.h"
9#include "common/bit_field.h" 9#include "common/bit_field.h"
10#include "common/logging/log.h" 10#include "common/logging/log.h"
11#include "core/core.h"
11#include "video_core/regs_framebuffer.h" 12#include "video_core/regs_framebuffer.h"
12#include "video_core/regs_lighting.h" 13#include "video_core/regs_lighting.h"
13#include "video_core/regs_rasterizer.h" 14#include "video_core/regs_rasterizer.h"
@@ -525,11 +526,12 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
525 "float geo_factor = 1.0;\n"; 526 "float geo_factor = 1.0;\n";
526 527
527 // Compute fragment normals and tangents 528 // Compute fragment normals and tangents
528 const std::string pertubation = 529 auto Perturbation = [&]() {
529 "2.0 * (" + SampleTexture(config, lighting.bump_selector) + ").rgb - 1.0"; 530 return "2.0 * (" + SampleTexture(config, lighting.bump_selector) + ").rgb - 1.0";
531 };
530 if (lighting.bump_mode == LightingRegs::LightingBumpMode::NormalMap) { 532 if (lighting.bump_mode == LightingRegs::LightingBumpMode::NormalMap) {
531 // Bump mapping is enabled using a normal map 533 // Bump mapping is enabled using a normal map
532 out += "vec3 surface_normal = " + pertubation + ";\n"; 534 out += "vec3 surface_normal = " + Perturbation() + ";\n";
533 535
534 // Recompute Z-component of perturbation if 'renorm' is enabled, this provides a higher 536 // Recompute Z-component of perturbation if 'renorm' is enabled, this provides a higher
535 // precision result 537 // precision result
@@ -543,7 +545,7 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
543 out += "vec3 surface_tangent = vec3(1.0, 0.0, 0.0);\n"; 545 out += "vec3 surface_tangent = vec3(1.0, 0.0, 0.0);\n";
544 } else if (lighting.bump_mode == LightingRegs::LightingBumpMode::TangentMap) { 546 } else if (lighting.bump_mode == LightingRegs::LightingBumpMode::TangentMap) {
545 // Bump mapping is enabled using a tangent map 547 // Bump mapping is enabled using a tangent map
546 out += "vec3 surface_tangent = " + pertubation + ";\n"; 548 out += "vec3 surface_tangent = " + Perturbation() + ";\n";
547 // Mathematically, recomputing Z-component of the tangent vector won't affect the relevant 549 // Mathematically, recomputing Z-component of the tangent vector won't affect the relevant
548 // computation below, which is also confirmed on 3DS. So we don't bother recomputing here 550 // computation below, which is also confirmed on 3DS. So we don't bother recomputing here
549 // even if 'renorm' is enabled. 551 // even if 'renorm' is enabled.
@@ -593,8 +595,8 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
593 // Note: even if the normal vector is modified by normal map, which is not the 595 // Note: even if the normal vector is modified by normal map, which is not the
594 // normal of the tangent plane anymore, the half angle vector is still projected 596 // normal of the tangent plane anymore, the half angle vector is still projected
595 // using the modified normal vector. 597 // using the modified normal vector.
596 std::string half_angle_proj = "normalize(half_vector) - normal / dot(normal, " 598 std::string half_angle_proj =
597 "normal) * dot(normal, normalize(half_vector))"; 599 "normalize(half_vector) - normal * dot(normal, normalize(half_vector))";
598 // Note: the half angle vector projection is confirmed not normalized before the dot 600 // Note: the half angle vector projection is confirmed not normalized before the dot
599 // product. The result is in fact not cos(phi) as the name suggested. 601 // product. The result is in fact not cos(phi) as the name suggested.
600 index = "dot(" + half_angle_proj + ", tangent)"; 602 index = "dot(" + half_angle_proj + ", tangent)";
@@ -749,7 +751,8 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
749 } 751 }
750 752
751 // Fresnel 753 // Fresnel
752 if (lighting.lut_fr.enable && 754 // Note: only the last entry in the light slots applies the Fresnel factor
755 if (light_index == lighting.src_num - 1 && lighting.lut_fr.enable &&
753 LightingRegs::IsLightingSamplerSupported(lighting.config, 756 LightingRegs::IsLightingSamplerSupported(lighting.config,
754 LightingRegs::LightingSampler::Fresnel)) { 757 LightingRegs::LightingSampler::Fresnel)) {
755 // Lookup fresnel LUT value 758 // Lookup fresnel LUT value
@@ -758,17 +761,17 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
758 lighting.lut_fr.type, lighting.lut_fr.abs_input); 761 lighting.lut_fr.type, lighting.lut_fr.abs_input);
759 value = "(" + std::to_string(lighting.lut_fr.scale) + " * " + value + ")"; 762 value = "(" + std::to_string(lighting.lut_fr.scale) + " * " + value + ")";
760 763
761 // Enabled for difffuse lighting alpha component 764 // Enabled for diffuse lighting alpha component
762 if (lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::PrimaryAlpha || 765 if (lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::PrimaryAlpha ||
763 lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) { 766 lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) {
764 out += "diffuse_sum.a *= " + value + ";\n"; 767 out += "diffuse_sum.a = " + value + ";\n";
765 } 768 }
766 769
767 // Enabled for the specular lighting alpha component 770 // Enabled for the specular lighting alpha component
768 if (lighting.fresnel_selector == 771 if (lighting.fresnel_selector ==
769 LightingRegs::LightingFresnelSelector::SecondaryAlpha || 772 LightingRegs::LightingFresnelSelector::SecondaryAlpha ||
770 lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) { 773 lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) {
771 out += "specular_sum.a *= " + value + ";\n"; 774 out += "specular_sum.a = " + value + ";\n";
772 } 775 }
773 } 776 }
774 777
@@ -1111,7 +1114,10 @@ vec4 secondary_fragment_color = vec4(0.0);
1111 "gl_FragCoord.y < scissor_y2)) discard;\n"; 1114 "gl_FragCoord.y < scissor_y2)) discard;\n";
1112 } 1115 }
1113 1116
1114 out += "float z_over_w = 1.0 - gl_FragCoord.z * 2.0;\n"; 1117 // After perspective divide, OpenGL transform z_over_w from [-1, 1] to [near, far]. Here we use
1118 // default near = 0 and far = 1, and undo the transformation to get the original z_over_w, then
1119 // do our own transformation according to PICA specification.
1120 out += "float z_over_w = 2.0 * gl_FragCoord.z - 1.0;\n";
1115 out += "float depth = z_over_w * depth_scale + depth_offset;\n"; 1121 out += "float depth = z_over_w * depth_scale + depth_offset;\n";
1116 if (state.depthmap_enable == RasterizerRegs::DepthBuffering::WBuffering) { 1122 if (state.depthmap_enable == RasterizerRegs::DepthBuffering::WBuffering) {
1117 out += "depth /= gl_FragCoord.w;\n"; 1123 out += "depth /= gl_FragCoord.w;\n";
@@ -1151,6 +1157,11 @@ vec4 secondary_fragment_color = vec4(0.0);
1151 1157
1152 // Blend the fog 1158 // Blend the fog
1153 out += "last_tex_env_out.rgb = mix(fog_color.rgb, last_tex_env_out.rgb, fog_factor);\n"; 1159 out += "last_tex_env_out.rgb = mix(fog_color.rgb, last_tex_env_out.rgb, fog_factor);\n";
1160 } else if (state.fog_mode == TexturingRegs::FogMode::Gas) {
1161 Core::Telemetry().AddField(Telemetry::FieldType::Session, "VideoCore_Pica_UseGasMode",
1162 true);
1163 LOG_CRITICAL(Render_OpenGL, "Unimplemented gas mode");
1164 UNIMPLEMENTED();
1154 } 1165 }
1155 1166
1156 out += "gl_FragDepth = depth;\n"; 1167 out += "gl_FragDepth = depth;\n";
@@ -1194,7 +1205,9 @@ void main() {
1194 texcoord0_w = vert_texcoord0_w; 1205 texcoord0_w = vert_texcoord0_w;
1195 normquat = vert_normquat; 1206 normquat = vert_normquat;
1196 view = vert_view; 1207 view = vert_view;
1197 gl_Position = vec4(vert_position.x, vert_position.y, -vert_position.z, vert_position.w); 1208 gl_Position = vert_position;
1209 gl_ClipDistance[0] = -vert_position.z; // fixed PICA clipping plane z <= 0
1210 // TODO (wwylele): calculate gl_ClipDistance[1] from user-defined clipping plane
1198} 1211}
1199)"; 1212)";
1200 1213
diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp
index bc9d34b84..06a905766 100644
--- a/src/video_core/renderer_opengl/gl_state.cpp
+++ b/src/video_core/renderer_opengl/gl_state.cpp
@@ -68,6 +68,8 @@ OpenGLState::OpenGLState() {
68 draw.vertex_buffer = 0; 68 draw.vertex_buffer = 0;
69 draw.uniform_buffer = 0; 69 draw.uniform_buffer = 0;
70 draw.shader_program = 0; 70 draw.shader_program = 0;
71
72 clip_distance = {};
71} 73}
72 74
73void OpenGLState::Apply() const { 75void OpenGLState::Apply() const {
@@ -261,6 +263,17 @@ void OpenGLState::Apply() const {
261 glUseProgram(draw.shader_program); 263 glUseProgram(draw.shader_program);
262 } 264 }
263 265
266 // Clip distance
267 for (size_t i = 0; i < clip_distance.size(); ++i) {
268 if (clip_distance[i] != cur_state.clip_distance[i]) {
269 if (clip_distance[i]) {
270 glEnable(GL_CLIP_DISTANCE0 + i);
271 } else {
272 glDisable(GL_CLIP_DISTANCE0 + i);
273 }
274 }
275 }
276
264 cur_state = *this; 277 cur_state = *this;
265} 278}
266 279
diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h
index 745a74479..437fe34c4 100644
--- a/src/video_core/renderer_opengl/gl_state.h
+++ b/src/video_core/renderer_opengl/gl_state.h
@@ -4,6 +4,7 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <array>
7#include <glad/glad.h> 8#include <glad/glad.h>
8 9
9namespace TextureUnits { 10namespace TextureUnits {
@@ -123,6 +124,8 @@ public:
123 GLuint shader_program; // GL_CURRENT_PROGRAM 124 GLuint shader_program; // GL_CURRENT_PROGRAM
124 } draw; 125 } draw;
125 126
127 std::array<bool, 2> clip_distance; // GL_CLIP_DISTANCE
128
126 OpenGLState(); 129 OpenGLState();
127 130
128 /// Get the currently active OpenGL state 131 /// Get the currently active OpenGL state
diff --git a/src/video_core/swrasterizer/clipper.cpp b/src/video_core/swrasterizer/clipper.cpp
index 7537689b7..cdbc71502 100644
--- a/src/video_core/swrasterizer/clipper.cpp
+++ b/src/video_core/swrasterizer/clipper.cpp
@@ -125,10 +125,6 @@ void ProcessTriangle(const OutputVertex& v0, const OutputVertex& v1, const Outpu
125 {Math::MakeVec(f0, f0, f0, -f1), Math::Vec4<float24>(f0, f0, f0, EPSILON)}, // w = EPSILON 125 {Math::MakeVec(f0, f0, f0, -f1), Math::Vec4<float24>(f0, f0, f0, EPSILON)}, // w = EPSILON
126 }}; 126 }};
127 127
128 // TODO: If one vertex lies outside one of the depth clipping planes, some platforms (e.g. Wii)
129 // drop the whole primitive instead of clipping the primitive properly. We should test if
130 // this happens on the 3DS, too.
131
132 // Simple implementation of the Sutherland-Hodgman clipping algorithm. 128 // Simple implementation of the Sutherland-Hodgman clipping algorithm.
133 // TODO: Make this less inefficient (currently lots of useless buffering overhead happens here) 129 // TODO: Make this less inefficient (currently lots of useless buffering overhead happens here)
134 for (auto edge : clipping_edges) { 130 for (auto edge : clipping_edges) {
diff --git a/src/video_core/swrasterizer/framebuffer.cpp b/src/video_core/swrasterizer/framebuffer.cpp
index 7de3aac75..f34eab6cf 100644
--- a/src/video_core/swrasterizer/framebuffer.cpp
+++ b/src/video_core/swrasterizer/framebuffer.cpp
@@ -352,6 +352,8 @@ u8 LogicOp(u8 src, u8 dest, FramebufferRegs::LogicOp op) {
352 case FramebufferRegs::LogicOp::OrInverted: 352 case FramebufferRegs::LogicOp::OrInverted:
353 return ~src | dest; 353 return ~src | dest;
354 } 354 }
355
356 UNREACHABLE();
355}; 357};
356 358
357} // namespace Rasterizer 359} // namespace Rasterizer
diff --git a/src/video_core/swrasterizer/lighting.cpp b/src/video_core/swrasterizer/lighting.cpp
index 63088eee8..5fa748611 100644
--- a/src/video_core/swrasterizer/lighting.cpp
+++ b/src/video_core/swrasterizer/lighting.cpp
@@ -22,18 +22,37 @@ static float LookupLightingLut(const Pica::State::Lighting& lighting, size_t lut
22 22
23std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors( 23std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors(
24 const Pica::LightingRegs& lighting, const Pica::State::Lighting& lighting_state, 24 const Pica::LightingRegs& lighting, const Pica::State::Lighting& lighting_state,
25 const Math::Quaternion<float>& normquat, const Math::Vec3<float>& view) { 25 const Math::Quaternion<float>& normquat, const Math::Vec3<float>& view,
26 const Math::Vec4<u8> (&texture_color)[4]) {
26 27
27 // TODO(Subv): Bump mapping 28 Math::Vec3<float> surface_normal;
28 Math::Vec3<float> surface_normal = {0.0f, 0.0f, 1.0f}; 29 Math::Vec3<float> surface_tangent;
29 30
30 if (lighting.config0.bump_mode != LightingRegs::LightingBumpMode::None) { 31 if (lighting.config0.bump_mode != LightingRegs::LightingBumpMode::None) {
31 LOG_CRITICAL(HW_GPU, "unimplemented bump mapping"); 32 Math::Vec3<float> perturbation =
32 UNIMPLEMENTED(); 33 texture_color[lighting.config0.bump_selector].xyz().Cast<float>() / 127.5f -
34 Math::MakeVec(1.0f, 1.0f, 1.0f);
35 if (lighting.config0.bump_mode == LightingRegs::LightingBumpMode::NormalMap) {
36 if (!lighting.config0.disable_bump_renorm) {
37 const float z_square = 1 - perturbation.xy().Length2();
38 perturbation.z = std::sqrt(std::max(z_square, 0.0f));
39 }
40 surface_normal = perturbation;
41 surface_tangent = Math::MakeVec(1.0f, 0.0f, 0.0f);
42 } else if (lighting.config0.bump_mode == LightingRegs::LightingBumpMode::TangentMap) {
43 surface_normal = Math::MakeVec(0.0f, 0.0f, 1.0f);
44 surface_tangent = perturbation;
45 } else {
46 LOG_ERROR(HW_GPU, "Unknown bump mode %u", lighting.config0.bump_mode.Value());
47 }
48 } else {
49 surface_normal = Math::MakeVec(0.0f, 0.0f, 1.0f);
50 surface_tangent = Math::MakeVec(1.0f, 0.0f, 0.0f);
33 } 51 }
34 52
35 // Use the normalized the quaternion when performing the rotation 53 // Use the normalized the quaternion when performing the rotation
36 auto normal = Math::QuaternionRotate(normquat, surface_normal); 54 auto normal = Math::QuaternionRotate(normquat, surface_normal);
55 auto tangent = Math::QuaternionRotate(normquat, surface_tangent);
37 56
38 Math::Vec4<float> diffuse_sum = {0.0f, 0.0f, 0.0f, 1.0f}; 57 Math::Vec4<float> diffuse_sum = {0.0f, 0.0f, 0.0f, 1.0f};
39 Math::Vec4<float> specular_sum = {0.0f, 0.0f, 0.0f, 1.0f}; 58 Math::Vec4<float> specular_sum = {0.0f, 0.0f, 0.0f, 1.0f};
@@ -55,6 +74,9 @@ std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors(
55 74
56 light_vector.Normalize(); 75 light_vector.Normalize();
57 76
77 Math::Vec3<float> norm_view = view.Normalized();
78 Math::Vec3<float> half_vector = norm_view + light_vector;
79
58 float dist_atten = 1.0f; 80 float dist_atten = 1.0f;
59 if (!lighting.IsDistAttenDisabled(num)) { 81 if (!lighting.IsDistAttenDisabled(num)) {
60 auto distance = (-view - position).Length(); 82 auto distance = (-view - position).Length();
@@ -74,17 +96,15 @@ std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors(
74 auto GetLutValue = [&](LightingRegs::LightingLutInput input, bool abs, 96 auto GetLutValue = [&](LightingRegs::LightingLutInput input, bool abs,
75 LightingRegs::LightingScale scale_enum, 97 LightingRegs::LightingScale scale_enum,
76 LightingRegs::LightingSampler sampler) { 98 LightingRegs::LightingSampler sampler) {
77 Math::Vec3<float> norm_view = view.Normalized();
78 Math::Vec3<float> half_angle = (norm_view + light_vector).Normalized();
79 float result = 0.0f; 99 float result = 0.0f;
80 100
81 switch (input) { 101 switch (input) {
82 case LightingRegs::LightingLutInput::NH: 102 case LightingRegs::LightingLutInput::NH:
83 result = Math::Dot(normal, half_angle); 103 result = Math::Dot(normal, half_vector.Normalized());
84 break; 104 break;
85 105
86 case LightingRegs::LightingLutInput::VH: 106 case LightingRegs::LightingLutInput::VH:
87 result = Math::Dot(norm_view, half_angle); 107 result = Math::Dot(norm_view, half_vector.Normalized());
88 break; 108 break;
89 109
90 case LightingRegs::LightingLutInput::NV: 110 case LightingRegs::LightingLutInput::NV:
@@ -95,6 +115,22 @@ std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors(
95 result = Math::Dot(light_vector, normal); 115 result = Math::Dot(light_vector, normal);
96 break; 116 break;
97 117
118 case LightingRegs::LightingLutInput::SP: {
119 Math::Vec3<s32> spot_dir{light_config.spot_x.Value(), light_config.spot_y.Value(),
120 light_config.spot_z.Value()};
121 result = Math::Dot(light_vector, spot_dir.Cast<float>() / 2047.0f);
122 break;
123 }
124 case LightingRegs::LightingLutInput::CP:
125 if (lighting.config0.config == LightingRegs::LightingConfig::Config7) {
126 const Math::Vec3<float> norm_half_vector = half_vector.Normalized();
127 const Math::Vec3<float> half_vector_proj =
128 norm_half_vector - normal * Math::Dot(normal, norm_half_vector);
129 result = Math::Dot(half_vector_proj, tangent);
130 } else {
131 result = 0.0f;
132 }
133 break;
98 default: 134 default:
99 LOG_CRITICAL(HW_GPU, "Unknown lighting LUT input %u\n", static_cast<u32>(input)); 135 LOG_CRITICAL(HW_GPU, "Unknown lighting LUT input %u\n", static_cast<u32>(input));
100 UNIMPLEMENTED(); 136 UNIMPLEMENTED();
@@ -125,6 +161,16 @@ std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors(
125 LookupLightingLut(lighting_state, static_cast<size_t>(sampler), index, delta); 161 LookupLightingLut(lighting_state, static_cast<size_t>(sampler), index, delta);
126 }; 162 };
127 163
164 // If enabled, compute spot light attenuation value
165 float spot_atten = 1.0f;
166 if (!lighting.IsSpotAttenDisabled(num) &&
167 LightingRegs::IsLightingSamplerSupported(
168 lighting.config0.config, LightingRegs::LightingSampler::SpotlightAttenuation)) {
169 auto lut = LightingRegs::SpotlightAttenuationSampler(num);
170 spot_atten = GetLutValue(lighting.lut_input.sp, lighting.abs_lut_input.disable_sp == 0,
171 lighting.lut_scale.sp, lut);
172 }
173
128 // Specular 0 component 174 // Specular 0 component
129 float d0_lut_value = 1.0f; 175 float d0_lut_value = 1.0f;
130 if (lighting.config1.disable_lut_d0 == 0 && 176 if (lighting.config1.disable_lut_d0 == 0 &&
@@ -184,7 +230,8 @@ std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors(
184 d1_lut_value * refl_value * light_config.specular_1.ToVec3f(); 230 d1_lut_value * refl_value * light_config.specular_1.ToVec3f();
185 231
186 // Fresnel 232 // Fresnel
187 if (lighting.config1.disable_lut_fr == 0 && 233 // Note: only the last entry in the light slots applies the Fresnel factor
234 if (light_index == lighting.max_light_index && lighting.config1.disable_lut_fr == 0 &&
188 LightingRegs::IsLightingSamplerSupported(lighting.config0.config, 235 LightingRegs::IsLightingSamplerSupported(lighting.config0.config,
189 LightingRegs::LightingSampler::Fresnel)) { 236 LightingRegs::LightingSampler::Fresnel)) {
190 237
@@ -196,14 +243,14 @@ std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors(
196 if (lighting.config0.fresnel_selector == 243 if (lighting.config0.fresnel_selector ==
197 LightingRegs::LightingFresnelSelector::PrimaryAlpha || 244 LightingRegs::LightingFresnelSelector::PrimaryAlpha ||
198 lighting.config0.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) { 245 lighting.config0.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) {
199 diffuse_sum.a() *= lut_value; 246 diffuse_sum.a() = lut_value;
200 } 247 }
201 248
202 // Enabled for the specular lighting alpha component 249 // Enabled for the specular lighting alpha component
203 if (lighting.config0.fresnel_selector == 250 if (lighting.config0.fresnel_selector ==
204 LightingRegs::LightingFresnelSelector::SecondaryAlpha || 251 LightingRegs::LightingFresnelSelector::SecondaryAlpha ||
205 lighting.config0.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) { 252 lighting.config0.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) {
206 specular_sum.a() *= lut_value; 253 specular_sum.a() = lut_value;
207 } 254 }
208 } 255 }
209 256
@@ -224,12 +271,23 @@ std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors(
224 else 271 else
225 dot_product = std::max(dot_product, 0.0f); 272 dot_product = std::max(dot_product, 0.0f);
226 273
274 if (light_config.config.geometric_factor_0 || light_config.config.geometric_factor_1) {
275 float geo_factor = half_vector.Length2();
276 geo_factor = geo_factor == 0.0f ? 0.0f : std::min(dot_product / geo_factor, 1.0f);
277 if (light_config.config.geometric_factor_0) {
278 specular_0 *= geo_factor;
279 }
280 if (light_config.config.geometric_factor_1) {
281 specular_1 *= geo_factor;
282 }
283 }
284
227 auto diffuse = 285 auto diffuse =
228 light_config.diffuse.ToVec3f() * dot_product + light_config.ambient.ToVec3f(); 286 light_config.diffuse.ToVec3f() * dot_product + light_config.ambient.ToVec3f();
229 diffuse_sum += Math::MakeVec(diffuse * dist_atten, 0.0f); 287 diffuse_sum += Math::MakeVec(diffuse * dist_atten * spot_atten, 0.0f);
230 288
231 specular_sum += 289 specular_sum += Math::MakeVec(
232 Math::MakeVec((specular_0 + specular_1) * clamp_highlights * dist_atten, 0.0f); 290 (specular_0 + specular_1) * clamp_highlights * dist_atten * spot_atten, 0.0f);
233 } 291 }
234 292
235 diffuse_sum += Math::MakeVec(lighting.global_ambient.ToVec3f(), 0.0f); 293 diffuse_sum += Math::MakeVec(lighting.global_ambient.ToVec3f(), 0.0f);
@@ -244,7 +302,7 @@ std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors(
244 MathUtil::Clamp(specular_sum.z, 0.0f, 1.0f) * 255, 302 MathUtil::Clamp(specular_sum.z, 0.0f, 1.0f) * 255,
245 MathUtil::Clamp(specular_sum.w, 0.0f, 1.0f) * 255) 303 MathUtil::Clamp(specular_sum.w, 0.0f, 1.0f) * 255)
246 .Cast<u8>(); 304 .Cast<u8>();
247 return {diffuse, specular}; 305 return std::make_tuple(diffuse, specular);
248} 306}
249 307
250} // namespace Pica 308} // namespace Pica
diff --git a/src/video_core/swrasterizer/lighting.h b/src/video_core/swrasterizer/lighting.h
index 438dca926..d807a3d94 100644
--- a/src/video_core/swrasterizer/lighting.h
+++ b/src/video_core/swrasterizer/lighting.h
@@ -13,6 +13,7 @@ namespace Pica {
13 13
14std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors( 14std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors(
15 const Pica::LightingRegs& lighting, const Pica::State::Lighting& lighting_state, 15 const Pica::LightingRegs& lighting, const Pica::State::Lighting& lighting_state,
16 const Math::Quaternion<float>& normquat, const Math::Vec3<float>& view); 16 const Math::Quaternion<float>& normquat, const Math::Vec3<float>& view,
17 const Math::Vec4<u8> (&texture_color)[4]);
17 18
18} // namespace Pica 19} // namespace Pica
diff --git a/src/video_core/swrasterizer/rasterizer.cpp b/src/video_core/swrasterizer/rasterizer.cpp
index fdc1df199..862135614 100644
--- a/src/video_core/swrasterizer/rasterizer.cpp
+++ b/src/video_core/swrasterizer/rasterizer.cpp
@@ -437,8 +437,8 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
437 GetInterpolatedAttribute(v0.view.y, v1.view.y, v2.view.y).ToFloat32(), 437 GetInterpolatedAttribute(v0.view.y, v1.view.y, v2.view.y).ToFloat32(),
438 GetInterpolatedAttribute(v0.view.z, v1.view.z, v2.view.z).ToFloat32(), 438 GetInterpolatedAttribute(v0.view.z, v1.view.z, v2.view.z).ToFloat32(),
439 }; 439 };
440 std::tie(primary_fragment_color, secondary_fragment_color) = 440 std::tie(primary_fragment_color, secondary_fragment_color) = ComputeFragmentsColors(
441 ComputeFragmentsColors(g_state.regs.lighting, g_state.lighting, normquat, view); 441 g_state.regs.lighting, g_state.lighting, normquat, view, texture_color);
442 } 442 }
443 443
444 for (unsigned tev_stage_index = 0; tev_stage_index < tev_stages.size(); 444 for (unsigned tev_stage_index = 0; tev_stage_index < tev_stages.size();
diff --git a/src/video_core/swrasterizer/rasterizer.h b/src/video_core/swrasterizer/rasterizer.h
index 2f0877581..66cd6cfd4 100644
--- a/src/video_core/swrasterizer/rasterizer.h
+++ b/src/video_core/swrasterizer/rasterizer.h
@@ -19,10 +19,9 @@ struct Vertex : Shader::OutputVertex {
19 19
20 // Linear interpolation 20 // Linear interpolation
21 // factor: 0=this, 1=vtx 21 // factor: 0=this, 1=vtx
22 // Note: This function cannot be called after perspective divide
22 void Lerp(float24 factor, const Vertex& vtx) { 23 void Lerp(float24 factor, const Vertex& vtx) {
23 pos = pos * factor + vtx.pos * (float24::FromFloat32(1) - factor); 24 pos = pos * factor + vtx.pos * (float24::FromFloat32(1) - factor);
24
25 // TODO: Should perform perspective correct interpolation here...
26 quat = quat * factor + vtx.quat * (float24::FromFloat32(1) - factor); 25 quat = quat * factor + vtx.quat * (float24::FromFloat32(1) - factor);
27 color = color * factor + vtx.color * (float24::FromFloat32(1) - factor); 26 color = color * factor + vtx.color * (float24::FromFloat32(1) - factor);
28 tc0 = tc0 * factor + vtx.tc0 * (float24::FromFloat32(1) - factor); 27 tc0 = tc0 * factor + vtx.tc0 * (float24::FromFloat32(1) - factor);
@@ -30,12 +29,11 @@ struct Vertex : Shader::OutputVertex {
30 tc0_w = tc0_w * factor + vtx.tc0_w * (float24::FromFloat32(1) - factor); 29 tc0_w = tc0_w * factor + vtx.tc0_w * (float24::FromFloat32(1) - factor);
31 view = view * factor + vtx.view * (float24::FromFloat32(1) - factor); 30 view = view * factor + vtx.view * (float24::FromFloat32(1) - factor);
32 tc2 = tc2 * factor + vtx.tc2 * (float24::FromFloat32(1) - factor); 31 tc2 = tc2 * factor + vtx.tc2 * (float24::FromFloat32(1) - factor);
33
34 screenpos = screenpos * factor + vtx.screenpos * (float24::FromFloat32(1) - factor);
35 } 32 }
36 33
37 // Linear interpolation 34 // Linear interpolation
38 // factor: 0=v0, 1=v1 35 // factor: 0=v0, 1=v1
36 // Note: This function cannot be called after perspective divide
39 static Vertex Lerp(float24 factor, const Vertex& v0, const Vertex& v1) { 37 static Vertex Lerp(float24 factor, const Vertex& v0, const Vertex& v1) {
40 Vertex ret = v0; 38 Vertex ret = v0;
41 ret.Lerp(factor, v1); 39 ret.Lerp(factor, v1);
diff --git a/src/video_core/swrasterizer/texturing.cpp b/src/video_core/swrasterizer/texturing.cpp
index 4f02b93f2..79b1ce841 100644
--- a/src/video_core/swrasterizer/texturing.cpp
+++ b/src/video_core/swrasterizer/texturing.cpp
@@ -89,6 +89,8 @@ Math::Vec3<u8> GetColorModifier(TevStageConfig::ColorModifier factor,
89 case ColorModifier::OneMinusSourceBlue: 89 case ColorModifier::OneMinusSourceBlue:
90 return (Math::Vec3<u8>(255, 255, 255) - values.bbb()).Cast<u8>(); 90 return (Math::Vec3<u8>(255, 255, 255) - values.bbb()).Cast<u8>();
91 } 91 }
92
93 UNREACHABLE();
92}; 94};
93 95
94u8 GetAlphaModifier(TevStageConfig::AlphaModifier factor, const Math::Vec4<u8>& values) { 96u8 GetAlphaModifier(TevStageConfig::AlphaModifier factor, const Math::Vec4<u8>& values) {
@@ -119,6 +121,8 @@ u8 GetAlphaModifier(TevStageConfig::AlphaModifier factor, const Math::Vec4<u8>&
119 case AlphaModifier::OneMinusSourceBlue: 121 case AlphaModifier::OneMinusSourceBlue:
120 return 255 - values.b(); 122 return 255 - values.b();
121 } 123 }
124
125 UNREACHABLE();
122}; 126};
123 127
124Math::Vec3<u8> ColorCombine(TevStageConfig::Operation op, const Math::Vec3<u8> input[3]) { 128Math::Vec3<u8> ColorCombine(TevStageConfig::Operation op, const Math::Vec3<u8> input[3]) {
diff --git a/src/web_service/telemetry_json.cpp b/src/web_service/telemetry_json.cpp
index a2d007e77..6ad2ffcd4 100644
--- a/src/web_service/telemetry_json.cpp
+++ b/src/web_service/telemetry_json.cpp
@@ -3,7 +3,6 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include "common/assert.h" 5#include "common/assert.h"
6#include "core/settings.h"
7#include "web_service/telemetry_json.h" 6#include "web_service/telemetry_json.h"
8#include "web_service/web_backend.h" 7#include "web_service/web_backend.h"
9 8
@@ -81,7 +80,7 @@ void TelemetryJson::Complete() {
81 SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback"); 80 SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback");
82 SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig"); 81 SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig");
83 SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem"); 82 SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem");
84 PostJson(Settings::values.telemetry_endpoint_url, TopSection().dump()); 83 PostJson(endpoint_url, TopSection().dump(), true, username, token);
85} 84}
86 85
87} // namespace WebService 86} // namespace WebService
diff --git a/src/web_service/telemetry_json.h b/src/web_service/telemetry_json.h
index 39038b4f9..9e78c6803 100644
--- a/src/web_service/telemetry_json.h
+++ b/src/web_service/telemetry_json.h
@@ -17,7 +17,9 @@ namespace WebService {
17 */ 17 */
18class TelemetryJson : public Telemetry::VisitorInterface { 18class TelemetryJson : public Telemetry::VisitorInterface {
19public: 19public:
20 TelemetryJson() = default; 20 TelemetryJson(const std::string& endpoint_url, const std::string& username,
21 const std::string& token)
22 : endpoint_url(endpoint_url), username(username), token(token) {}
21 ~TelemetryJson() = default; 23 ~TelemetryJson() = default;
22 24
23 void Visit(const Telemetry::Field<bool>& field) override; 25 void Visit(const Telemetry::Field<bool>& field) override;
@@ -49,6 +51,9 @@ private:
49 51
50 nlohmann::json output; 52 nlohmann::json output;
51 std::array<nlohmann::json, 7> sections; 53 std::array<nlohmann::json, 7> sections;
54 std::string endpoint_url;
55 std::string username;
56 std::string token;
52}; 57};
53 58
54} // namespace WebService 59} // namespace WebService
diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp
index 13e4555ac..d28a3f757 100644
--- a/src/web_service/web_backend.cpp
+++ b/src/web_service/web_backend.cpp
@@ -2,51 +2,62 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#ifdef _WIN32
6#include <winsock.h>
7#endif
8
9#include <cstdlib>
10#include <thread>
5#include <cpr/cpr.h> 11#include <cpr/cpr.h>
6#include <stdlib.h>
7#include "common/logging/log.h" 12#include "common/logging/log.h"
8#include "web_service/web_backend.h" 13#include "web_service/web_backend.h"
9 14
10namespace WebService { 15namespace WebService {
11 16
12static constexpr char API_VERSION[]{"1"}; 17static constexpr char API_VERSION[]{"1"};
13static constexpr char ENV_VAR_USERNAME[]{"CITRA_WEB_SERVICES_USERNAME"};
14static constexpr char ENV_VAR_TOKEN[]{"CITRA_WEB_SERVICES_TOKEN"};
15
16static std::string GetEnvironmentVariable(const char* name) {
17 const char* value{getenv(name)};
18 if (value) {
19 return value;
20 }
21 return {};
22}
23
24const std::string& GetUsername() {
25 static const std::string username{GetEnvironmentVariable(ENV_VAR_USERNAME)};
26 return username;
27}
28 18
29const std::string& GetToken() { 19static std::unique_ptr<cpr::Session> g_session;
30 static const std::string token{GetEnvironmentVariable(ENV_VAR_TOKEN)};
31 return token;
32}
33 20
34void PostJson(const std::string& url, const std::string& data) { 21void PostJson(const std::string& url, const std::string& data, bool allow_anonymous,
22 const std::string& username, const std::string& token) {
35 if (url.empty()) { 23 if (url.empty()) {
36 LOG_ERROR(WebService, "URL is invalid"); 24 LOG_ERROR(WebService, "URL is invalid");
37 return; 25 return;
38 } 26 }
39 27
40 if (GetUsername().empty() || GetToken().empty()) { 28 const bool are_credentials_provided{!token.empty() && !username.empty()};
41 LOG_ERROR(WebService, "Environment variables %s and %s must be set to POST JSON", 29 if (!allow_anonymous && !are_credentials_provided) {
42 ENV_VAR_USERNAME, ENV_VAR_TOKEN); 30 LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
43 return; 31 return;
44 } 32 }
45 33
46 cpr::PostAsync(cpr::Url{url}, cpr::Body{data}, cpr::Header{{"Content-Type", "application/json"}, 34#ifdef _WIN32
47 {"x-username", GetUsername()}, 35 // On Windows, CPR/libcurl does not properly initialize Winsock. The below code is used to
48 {"x-token", GetToken()}, 36 // initialize Winsock globally, which fixes this problem. Without this, only the first CPR
49 {"api-version", API_VERSION}}); 37 // session will properly be created, and subsequent ones will fail.
38 WSADATA wsa_data;
39 const int wsa_result{WSAStartup(MAKEWORD(2, 2), &wsa_data)};
40 if (wsa_result) {
41 LOG_CRITICAL(WebService, "WSAStartup failed: %d", wsa_result);
42 }
43#endif
44
45 // Built request header
46 cpr::Header header;
47 if (are_credentials_provided) {
48 // Authenticated request if credentials are provided
49 header = {{"Content-Type", "application/json"},
50 {"x-username", username.c_str()},
51 {"x-token", token.c_str()},
52 {"api-version", API_VERSION}};
53 } else {
54 // Otherwise, anonymous request
55 header = cpr::Header{{"Content-Type", "application/json"}, {"api-version", API_VERSION}};
56 }
57
58 // Post JSON asynchronously
59 static cpr::AsyncResponse future;
60 future = cpr::PostAsync(cpr::Url{url.c_str()}, cpr::Body{data.c_str()}, header);
50} 61}
51 62
52} // namespace WebService 63} // namespace WebService
diff --git a/src/web_service/web_backend.h b/src/web_service/web_backend.h
index 2753d3b68..d17100398 100644
--- a/src/web_service/web_backend.h
+++ b/src/web_service/web_backend.h
@@ -10,22 +10,14 @@
10namespace WebService { 10namespace WebService {
11 11
12/** 12/**
13 * Gets the current username for accessing services.citra-emu.org.
14 * @returns Username as a string, empty if not set.
15 */
16const std::string& GetUsername();
17
18/**
19 * Gets the current token for accessing services.citra-emu.org.
20 * @returns Token as a string, empty if not set.
21 */
22const std::string& GetToken();
23
24/**
25 * Posts JSON to services.citra-emu.org. 13 * Posts JSON to services.citra-emu.org.
26 * @param url URL of the services.citra-emu.org endpoint to post data to. 14 * @param url URL of the services.citra-emu.org endpoint to post data to.
27 * @param data String of JSON data to use for the body of the POST request. 15 * @param data String of JSON data to use for the body of the POST request.
16 * @param allow_anonymous If true, allow anonymous unauthenticated requests.
17 * @param username Citra username to use for authentication.
18 * @param token Citra token to use for authentication.
28 */ 19 */
29void PostJson(const std::string& url, const std::string& data); 20void PostJson(const std::string& url, const std::string& data, bool allow_anonymous,
21 const std::string& username = {}, const std::string& token = {});
30 22
31} // namespace WebService 23} // namespace WebService