Преглед изворни кода

Merge remote-tracking branch 'upstream/master' into nx

# Conflicts:
#	src/core/CMakeLists.txt
#	src/core/arm/dynarmic/arm_dynarmic.cpp
#	src/core/arm/dyncom/arm_dyncom.cpp
#	src/core/hle/kernel/process.cpp
#	src/core/hle/kernel/thread.cpp
#	src/core/hle/kernel/thread.h
#	src/core/hle/kernel/vm_manager.cpp
#	src/core/loader/3dsx.cpp
#	src/core/loader/elf.cpp
#	src/core/loader/ncch.cpp
#	src/core/memory.cpp
#	src/core/memory.h
#	src/core/memory_setup.h
bunnei пре 8 година
родитељ
комит
b1d5db1cf6
100 измењених фајлова са 2613 додато и 1017 уклоњено
  1. 4 0
      .gitignore
  2. 0 67
      .travis-build.sh
  3. 0 40
      .travis-deps.sh
  4. 0 129
      .travis-upload.sh
  5. 23 22
      .travis.yml
  6. 37 0
      .travis/clang-format/script.sh
  7. 22 0
      .travis/common/post-upload.sh
  8. 6 0
      .travis/common/pre-upload.sh
  9. 3 0
      .travis/linux/build.sh
  10. 3 0
      .travis/linux/deps.sh
  11. 17 0
      .travis/linux/docker.sh
  12. 14 0
      .travis/linux/upload.sh
  13. 12 0
      .travis/macos/build.sh
  14. 4 0
      .travis/macos/deps.sh
  15. 110 0
      .travis/macos/upload.sh
  16. 21 22
      CMakeLists.txt
  17. 18 0
      CMakeModules/DownloadExternals.cmake
  18. 1 1
      README.md
  19. 132 35
      appveyor.yml
  20. BIN
      dist/citra.icns
  21. BIN
      dist/citra.ico
  22. 24 0
      dist/citra.manifest
  23. 0 79
      dist/citra.svg
  24. BIN
      dist/doc-icon.png
  25. BIN
      dist/icons/checked.png
  26. BIN
      dist/icons/failed.png
  27. 6 0
      dist/icons/icons.qrc
  28. 21 2
      externals/CMakeLists.txt
  29. 1 1
      externals/cryptopp/cryptopp
  30. 1 1
      externals/dynarmic
  31. 1 1
      externals/enet
  32. 1 1
      externals/soundtouch
  33. 3 1
      src/audio_core/codec.cpp
  34. 2 2
      src/audio_core/codec.h
  35. 20 29
      src/audio_core/hle/source.cpp
  36. 1 1
      src/audio_core/hle/source.h
  37. 38 48
      src/audio_core/interpolate.cpp
  38. 18 13
      src/audio_core/interpolate.h
  39. 2 0
      src/citra/citra.cpp
  40. 8 0
      src/citra/citra.rc
  41. 11 0
      src/citra/config.cpp
  42. 24 5
      src/citra/default_ini.h
  43. 4 6
      src/citra/emu_window/emu_window_sdl2.cpp
  44. 0 4
      src/citra/emu_window/emu_window_sdl2.h
  45. 6 2
      src/citra_qt/CMakeLists.txt
  46. 4 6
      src/citra_qt/bootmanager.cpp
  47. 0 4
      src/citra_qt/bootmanager.h
  48. 11 1
      src/citra_qt/citra-qt.rc
  49. 23 0
      src/citra_qt/configuration/config.cpp
  50. 13 2
      src/citra_qt/configuration/configure.ui
  51. 1 0
      src/citra_qt/configuration/configure_dialog.cpp
  52. 19 14
      src/citra_qt/configuration/configure_graphics.ui
  53. 2 1
      src/citra_qt/configuration/configure_system.cpp
  54. 102 0
      src/citra_qt/configuration/configure_web.cpp
  55. 40 0
      src/citra_qt/configuration/configure_web.h
  56. 190 0
      src/citra_qt/configuration/configure_web.ui
  57. 2 2
      src/citra_qt/debugger/graphics/graphics_cmdlists.cpp
  58. 2 1
      src/citra_qt/debugger/graphics/graphics_surface.cpp
  59. 18 23
      src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp
  60. 47 1
      src/citra_qt/main.cpp
  61. 2 0
      src/citra_qt/main.h
  62. 2 0
      src/citra_qt/ui_settings.h
  63. 4 0
      src/common/common_types.h
  64. 5 0
      src/common/quaternion.h
  65. 2 0
      src/common/scm_rev.cpp.in
  66. 1 0
      src/common/scm_rev.h
  67. 1 1
      src/common/string_util.cpp
  68. 1 1
      src/common/string_util.h
  69. 13 16
      src/common/vector_math.h
  70. 15 5
      src/core/CMakeLists.txt
  71. 12 10
      src/core/arm/arm_interface.h
  72. 35 58
      src/core/arm/dynarmic/arm_dynarmic.cpp
  73. 13 4
      src/core/arm/dynarmic/arm_dynarmic.h
  74. 12 7
      src/core/arm/dyncom/arm_dyncom.cpp
  75. 3 2
      src/core/arm/dyncom/arm_dyncom.h
  76. 4 4
      src/core/arm/dyncom/arm_dyncom_interpreter.cpp
  77. 1 1
      src/core/arm/skyeye_common/armstate.h
  78. 9 3
      src/core/core.cpp
  79. 5 4
      src/core/core.h
  80. 23 13
      src/core/core_timing.cpp
  81. 6 0
      src/core/core_timing.h
  82. 2 0
      src/core/file_sys/archive_backend.cpp
  83. 23 6
      src/core/file_sys/archive_ncch.cpp
  84. 39 2
      src/core/file_sys/archive_sdmc.cpp
  85. 50 10
      src/core/file_sys/archive_selfncch.cpp
  86. 11 2
      src/core/file_sys/archive_selfncch.h
  87. 423 0
      src/core/file_sys/ncch_container.cpp
  88. 274 0
      src/core/file_sys/ncch_container.h
  89. 39 2
      src/core/file_sys/savedata_archive.cpp
  90. 212 0
      src/core/file_sys/title_metadata.cpp
  91. 125 0
      src/core/file_sys/title_metadata.h
  92. 61 36
      src/core/frontend/emu_window.cpp
  93. 5 109
      src/core/frontend/emu_window.h
  94. 35 1
      src/core/frontend/framebuffer_layout.cpp
  95. 11 0
      src/core/frontend/framebuffer_layout.h
  96. 25 0
      src/core/frontend/input.h
  97. 0 89
      src/core/frontend/motion_emu.cpp
  98. 0 52
      src/core/frontend/motion_emu.h
  99. 14 10
      src/core/gdbstub/gdbstub.cpp
  100. 2 2
      src/core/hle/applets/erreula.cpp

+ 4 - 0
.gitignore

@@ -9,12 +9,16 @@ src/common/scm_rev.cpp
 # Project/editor files
 # Project/editor files
 *.swp
 *.swp
 .idea/
 .idea/
+.vs/
 .vscode/
 .vscode/
 
 
 # *nix related
 # *nix related
 # Common convention for backup or temporary files
 # Common convention for backup or temporary files
 *~
 *~
 
 
+# Visual Studio CMake settings
+CMakeSettings.json
+
 # OSX global filetypes
 # OSX global filetypes
 # Created by Finder or Spotlight in directories for various OS functionality (indexing, etc)
 # Created by Finder or Spotlight in directories for various OS functionality (indexing, etc)
 .DS_Store
 .DS_Store

+ 0 - 67
.travis-build.sh

@@ -1,67 +0,0 @@
-#!/bin/bash
-
-set -e
-set -x
-
-if grep -nr '\s$' src *.yml *.txt *.md Doxyfile .gitignore .gitmodules .travis* dist/*.desktop \
-                 dist/*.svg dist/*.xml; then
-    echo Trailing whitespace found, aborting
-    exit 1
-fi
-
-# Only run clang-format on Linux because we don't have 4.0 on OS X images
-if [ "$TRAVIS_OS_NAME" = "linux" ]; then
-    # Default clang-format points to default 3.5 version one
-    CLANG_FORMAT=clang-format-3.9
-    $CLANG_FORMAT --version
-
-    if [ "$TRAVIS_EVENT_TYPE" = "pull_request" ]; then
-        # Get list of every file modified in this pull request
-        files_to_lint="$(git diff --name-only --diff-filter=ACMRTUXB $TRAVIS_COMMIT_RANGE | grep '^src/[^.]*[.]\(cpp\|h\)$' || true)"
-    else
-        # Check everything for branch pushes
-        files_to_lint="$(find src/ -name '*.cpp' -or -name '*.h')"
-    fi
-
-    # Turn off tracing for this because it's too verbose
-    set +x
-
-    for f in $files_to_lint; do
-        d=$(diff -u "$f" <($CLANG_FORMAT "$f") || true)
-        if ! [ -z "$d" ]; then
-            echo "!!! $f not compliant to coding style, here is the fix:"
-            echo "$d"
-            fail=1
-        fi
-    done
-
-    set -x
-
-    if [ "$fail" = 1 ]; then
-        exit 1
-    fi
-fi
-
-#if OS is linux or is not set
-if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then
-    export CC=gcc-6
-    export CXX=g++-6
-    export PKG_CONFIG_PATH=$HOME/.local/lib/pkgconfig:$PKG_CONFIG_PATH
-
-    mkdir build && cd build
-    cmake ..
-    make -j4
-
-    ctest -VV -C Release
-elif [ "$TRAVIS_OS_NAME" = "osx" ]; then
-    set -o pipefail
-
-    export MACOSX_DEPLOYMENT_TARGET=10.9
-    export Qt5_DIR=$(brew --prefix)/opt/qt5
-
-    mkdir build && cd build
-    cmake .. -GXcode
-    xcodebuild -configuration Release
-
-    ctest -VV -C Release
-fi

+ 0 - 40
.travis-deps.sh

@@ -1,40 +0,0 @@
-#!/bin/sh
-
-set -e
-set -x
-
-#if OS is linux or is not set
-if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then
-    export CC=gcc-6
-    export CXX=g++-6
-    mkdir -p $HOME/.local
-
-    if [ ! -e $HOME/.local/bin/cmake ]; then
-        echo "CMake not found in the cache, get and extract it..."
-        curl -L http://www.cmake.org/files/v3.6/cmake-3.6.3-Linux-x86_64.tar.gz \
-            | tar -xz -C $HOME/.local --strip-components=1
-    else
-        echo "Using cached CMake"
-    fi
-
-    if [ ! -e $HOME/.local/lib/libSDL2.la ]; then
-        echo "SDL2 not found in cache, get and build it..."
-        wget http://libsdl.org/release/SDL2-2.0.5.tar.gz -O - | tar xz
-        cd SDL2-2.0.5
-        ./configure --prefix=$HOME/.local
-        make -j4 && make install
-    else
-        echo "Using cached SDL2"
-    fi
-
-    export DEBIAN_FRONTEND=noninteractive
-    # Amazing placebo security
-    curl http://apt.llvm.org/llvm-snapshot.gpg.key | sudo -E apt-key add -
-    sudo -E add-apt-repository "deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-3.9 main"
-    sudo -E apt-get -yq update
-    sudo -E apt-get -yq install clang-format-3.9
-
-elif [ "$TRAVIS_OS_NAME" = "osx" ]; then
-    brew update
-    brew install qt5 sdl2 dylibbundler
-fi

+ 0 - 129
.travis-upload.sh

@@ -1,129 +0,0 @@
-if [ "$TRAVIS_EVENT_TYPE" = "push" ]&&[ "$TRAVIS_BRANCH" = "master" ]; then
-    GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
-    GITREV="`git show -s --format='%h'`"
-    mkdir -p artifacts
-
-    if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then
-        REV_NAME="citra-linux-${GITDATE}-${GITREV}"
-        ARCHIVE_NAME="${REV_NAME}.tar.xz"
-        COMPRESSION_FLAGS="-cJvf"
-        mkdir "$REV_NAME"
-
-        cp build/src/citra/citra "$REV_NAME"
-        cp build/src/citra_qt/citra-qt "$REV_NAME"
-    elif [ "$TRAVIS_OS_NAME" = "osx" ]; then
-        REV_NAME="citra-osx-${GITDATE}-${GITREV}"
-        ARCHIVE_NAME="${REV_NAME}.tar.gz"
-        COMPRESSION_FLAGS="-czvf"
-        mkdir "$REV_NAME"
-
-        cp build/src/citra/Release/citra "$REV_NAME"
-        cp -r build/src/citra_qt/Release/citra-qt.app "$REV_NAME"
-
-        # move qt libs into app bundle for deployment
-        $(brew --prefix)/opt/qt5/bin/macdeployqt "${REV_NAME}/citra-qt.app"
-
-        # move SDL2 libs into folder for deployment
-        dylibbundler -b -x "${REV_NAME}/citra" -cd -d "${REV_NAME}/libs" -p "@executable_path/libs/"
-
-        # Make the changes to make the citra-qt app standalone (i.e. not dependent on the current brew installation).
-        # To do this, the absolute references to each and every QT framework must be re-written to point to the local frameworks
-        # (in the Contents/Frameworks folder).
-        # The "install_name_tool" is used to do so.
-
-        # Coreutils is a hack to coerce Homebrew to point to the absolute Cellar path (symlink dereferenced). i.e:
-        # ls -l /usr/local/opt/qt5:: /usr/local/opt/qt5 -> ../Cellar/qt5/5.6.1-1
-        # grealpath ../Cellar/qt5/5.6.1-1:: /usr/local/Cellar/qt5/5.6.1-1
-        brew install coreutils
-
-        REV_NAME_ALT=$REV_NAME/
-        # grealpath is located in coreutils, there is no "realpath" for OS X :(
-        QT_BREWS_PATH=$(grealpath "$(brew --prefix qt5)")
-        BREW_PATH=$(brew --prefix)
-        QT_VERSION_NUM=5
-
-        $BREW_PATH/opt/qt5/bin/macdeployqt "${REV_NAME_ALT}citra-qt.app" \
-            -executable="${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt"
-
-        # These are the files that macdeployqt packed into Contents/Frameworks/ - we don't want those, so we replace them.
-        declare -a macos_libs=("QtCore" "QtWidgets" "QtGui" "QtOpenGL" "QtPrintSupport")
-
-        for macos_lib in "${macos_libs[@]}"
-        do
-            SC_FRAMEWORK_PART=$macos_lib.framework/Versions/$QT_VERSION_NUM/$macos_lib
-            # Replace macdeployqt versions of the Frameworks with our own (from /usr/local/opt/qt5/lib/)
-            cp "$BREW_PATH/opt/qt5/lib/$SC_FRAMEWORK_PART" "${REV_NAME_ALT}citra-qt.app/Contents/Frameworks/$SC_FRAMEWORK_PART"
-
-            # Replace references within the embedded Framework files with "internal" versions.
-            for macos_lib2 in "${macos_libs[@]}"
-            do
-                # Since brew references both the non-symlinked and symlink paths of QT5, it needs to be duplicated.
-                # /usr/local/Cellar/qt5/5.6.1-1/lib and /usr/local/opt/qt5/lib both resolve to the same files.
-                # So the two lines below are effectively duplicates when resolved as a path, but as strings, they aren't.
-                RM_FRAMEWORK_PART=$macos_lib2.framework/Versions/$QT_VERSION_NUM/$macos_lib2
-                install_name_tool -change \
-                    $QT_BREWS_PATH/lib/$RM_FRAMEWORK_PART \
-                    @executable_path/../Frameworks/$RM_FRAMEWORK_PART \
-                    "${REV_NAME_ALT}citra-qt.app/Contents/Frameworks/$SC_FRAMEWORK_PART"
-                install_name_tool -change \
-                    "$BREW_PATH/opt/qt5/lib/$RM_FRAMEWORK_PART" \
-                    @executable_path/../Frameworks/$RM_FRAMEWORK_PART \
-                    "${REV_NAME_ALT}citra-qt.app/Contents/Frameworks/$SC_FRAMEWORK_PART"
-            done
-        done
-
-        # Handles `This application failed to start because it could not find or load the Qt platform plugin "cocoa"`
-        # Which manifests itself as:
-        # "Exception Type: EXC_CRASH (SIGABRT) | Exception Codes: 0x0000000000000000, 0x0000000000000000 | Exception Note: EXC_CORPSE_NOTIFY"
-        # There may be more dylibs needed to be fixed...
-        declare -a macos_plugins=("Plugins/platforms/libqcocoa.dylib")
-
-        for macos_lib in "${macos_plugins[@]}"
-        do
-            install_name_tool -id @executable_path/../$macos_lib "${REV_NAME_ALT}citra-qt.app/Contents/$macos_lib"
-            for macos_lib2 in "${macos_libs[@]}"
-            do
-                RM_FRAMEWORK_PART=$macos_lib2.framework/Versions/$QT_VERSION_NUM/$macos_lib2
-                install_name_tool -change \
-                    $QT_BREWS_PATH/lib/$RM_FRAMEWORK_PART \
-                    @executable_path/../Frameworks/$RM_FRAMEWORK_PART \
-                    "${REV_NAME_ALT}citra-qt.app/Contents/$macos_lib"
-                install_name_tool -change \
-                    "$BREW_PATH/opt/qt5/lib/$RM_FRAMEWORK_PART" \
-                    @executable_path/../Frameworks/$RM_FRAMEWORK_PART \
-                    "${REV_NAME_ALT}citra-qt.app/Contents/$macos_lib"
-            done
-        done
-
-        for macos_lib in "${macos_libs[@]}"
-        do
-            # Debugging info for Travis-CI
-            otool -L "${REV_NAME_ALT}citra-qt.app/Contents/Frameworks/$macos_lib.framework/Versions/$QT_VERSION_NUM/$macos_lib"
-        done
-
-        # Make the citra-qt.app application launch a debugging terminal.
-        # Store away the actual binary
-        mv ${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt ${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt-bin
-
-        cat > ${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt <<EOL
-#!/usr/bin/env bash
-cd "\`dirname "\$0"\`"
-chmod +x citra-qt-bin
-open citra-qt-bin --args "\$@"
-EOL
-        # Content that will serve as the launching script for citra (within the .app folder)
-
-        # Make the launching script executable
-        chmod +x ${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt
-
-    fi
-
-    # Copy documentation
-    cp license.txt "$REV_NAME"
-    cp README.md "$REV_NAME"
-
-    tar $COMPRESSION_FLAGS "$ARCHIVE_NAME" "$REV_NAME"
-
-    # move the compiled archive into the artifacts directory to be uploaded by travis releases
-    mv "$ARCHIVE_NAME" artifacts/
-fi

+ 23 - 22
.travis.yml

@@ -2,38 +2,39 @@ language: cpp
 matrix:
 matrix:
   include:
   include:
     - os: linux
     - os: linux
+      env: NAME="linux build"
       sudo: required
       sudo: required
       dist: trusty
       dist: trusty
+      services: docker
+      addons:
+        apt:
+          packages:
+            - p7zip-full
+      install: "./.travis/linux/deps.sh"
+      script: "./.travis/linux/build.sh"
+      after_success: "./.travis/linux/upload.sh"
     - os: osx
     - os: osx
+      env: NAME="macos build"
       sudo: false
       sudo: false
       osx_image: xcode7.3
       osx_image: xcode7.3
-
-addons:
-  apt:
-    sources:
-      - ubuntu-toolchain-r-test
-    packages:
-      - gcc-6
-      - g++-6
-      - qt5-default
-      - libqt5opengl5-dev
-      - xorg-dev
-      - lib32stdc++6 # For CMake
-
-cache:
-  directories:
-    - "$HOME/.local"
-
-install: "./.travis-deps.sh"
-script: "./.travis-build.sh"
-after_success: "./.travis-upload.sh"
+      install: "./.travis/macos/deps.sh"
+      script: "./.travis/macos/build.sh"
+      after_success: "./.travis/macos/upload.sh"
+    - os: linux
+      env: NAME="clang-format"
+      dist: trusty
+      addons:
+        apt:
+          packages:
+            - clang-format-3.9
+      script: "./.travis/clang-format/script.sh"
 
 
 deploy:
 deploy:
   provider: releases
   provider: releases
   api_key:
   api_key:
     secure: Mck15DIWaJdxDiS3aYVlM9N3G6y8VKUI1rnwII7/iolfm1s94U+tgvbheZDmT7SSbFyaGaYO/E8HrV/uZR9Vvs7ev20sHsTN1u60OTWfDIIyHs9SqjhcGbtq95m9/dMFschOYqTOR+gAs5BsxjuoeAotHdhpQEwvkO2oo5oR0zhGy45gjFnVvtcxT/IfpZBIpVgcK3aLb9zT6ekcJbSiPmEB15iLq3xXd0nFUNtEZdX3D6Veye4n5jB6n72qN8JVoKvPZAwaC2K0pZxpcGJaXDchLsw1q+4eCvdz6UJfUemeQ/uMAmjfeQ3wrzYGXe3nCM3WmX5wosCsB0mw4zYatzl3si6CZ1W+0GkV4Rwlx03dfp7v3EeFhTsXYCaXqhwuLZnWOLUik8t9vaSoFUx4nUIRwfO9kAMUJQSpLuHNO2nT01s3GxvqxzczuLQ9he5nGSi0RRodUzDwek1qUp6I4uV3gRHKz4B07YIc1i2fK88NLXjyQ0uLVZ+7Oq1+kgDp6+N7vvXXZ5qZ17tdaysSbKEE0Y8zsoXw7Rk1tPN19vrCS+TSpomNMyQyne1k+I5iZ/qkxPTLAS5qI6Utc2dL3GJdxWRAEfGNO9AIX3GV/jmmKfdcvwGsCYP8hxqs5vLYfgacw3D8NLf1941lQUwavC17jm9EV9g5G3Pn1Cp516E=
     secure: Mck15DIWaJdxDiS3aYVlM9N3G6y8VKUI1rnwII7/iolfm1s94U+tgvbheZDmT7SSbFyaGaYO/E8HrV/uZR9Vvs7ev20sHsTN1u60OTWfDIIyHs9SqjhcGbtq95m9/dMFschOYqTOR+gAs5BsxjuoeAotHdhpQEwvkO2oo5oR0zhGy45gjFnVvtcxT/IfpZBIpVgcK3aLb9zT6ekcJbSiPmEB15iLq3xXd0nFUNtEZdX3D6Veye4n5jB6n72qN8JVoKvPZAwaC2K0pZxpcGJaXDchLsw1q+4eCvdz6UJfUemeQ/uMAmjfeQ3wrzYGXe3nCM3WmX5wosCsB0mw4zYatzl3si6CZ1W+0GkV4Rwlx03dfp7v3EeFhTsXYCaXqhwuLZnWOLUik8t9vaSoFUx4nUIRwfO9kAMUJQSpLuHNO2nT01s3GxvqxzczuLQ9he5nGSi0RRodUzDwek1qUp6I4uV3gRHKz4B07YIc1i2fK88NLXjyQ0uLVZ+7Oq1+kgDp6+N7vvXXZ5qZ17tdaysSbKEE0Y8zsoXw7Rk1tPN19vrCS+TSpomNMyQyne1k+I5iZ/qkxPTLAS5qI6Utc2dL3GJdxWRAEfGNO9AIX3GV/jmmKfdcvwGsCYP8hxqs5vLYfgacw3D8NLf1941lQUwavC17jm9EV9g5G3Pn1Cp516E=
   file_glob: true
   file_glob: true
-  file: "artifacts/*.tar.*"
+  file: "artifacts/*"
   skip_cleanup: true
   skip_cleanup: true
   on:
   on:
-    repo: citra-emu/citra-nightly
+    tags: true

+ 37 - 0
.travis/clang-format/script.sh

@@ -0,0 +1,37 @@
+#!/bin/bash -ex
+
+if grep -nr '\s$' src *.yml *.txt *.md Doxyfile .gitignore .gitmodules .travis* dist/*.desktop \
+                 dist/*.svg dist/*.xml; then
+    echo Trailing whitespace found, aborting
+    exit 1
+fi
+
+# Default clang-format points to default 3.5 version one
+CLANG_FORMAT=clang-format-3.9
+$CLANG_FORMAT --version
+
+if [ "$TRAVIS_EVENT_TYPE" = "pull_request" ]; then
+    # Get list of every file modified in this pull request
+    files_to_lint="$(git diff --name-only --diff-filter=ACMRTUXB $TRAVIS_COMMIT_RANGE | grep '^src/[^.]*[.]\(cpp\|h\)$' || true)"
+else
+    # Check everything for branch pushes
+    files_to_lint="$(find src/ -name '*.cpp' -or -name '*.h')"
+fi
+
+# Turn off tracing for this because it's too verbose
+set +x
+
+for f in $files_to_lint; do
+    d=$(diff -u "$f" <($CLANG_FORMAT "$f") || true)
+    if ! [ -z "$d" ]; then
+        echo "!!! $f not compliant to coding style, here is the fix:"
+        echo "$d"
+        fail=1
+    fi
+done
+
+set -x
+
+if [ "$fail" = 1 ]; then
+    exit 1
+fi

+ 22 - 0
.travis/common/post-upload.sh

@@ -0,0 +1,22 @@
+#!/bin/bash -ex
+
+# Copy documentation
+cp license.txt "$REV_NAME"
+cp README.md "$REV_NAME"
+
+tar $COMPRESSION_FLAGS "$ARCHIVE_NAME" "$REV_NAME"
+
+# Find out what release we are building
+if [ -z $TRAVIS_TAG ]; then
+    RELEASE_NAME=head
+else
+    RELEASE_NAME=$(echo $TRAVIS_TAG | cut -d- -f1)
+fi
+
+mv "$REV_NAME" $RELEASE_NAME
+
+7z a "$REV_NAME.7z" $RELEASE_NAME
+
+# move the compiled archive into the artifacts directory to be uploaded by travis releases
+mv "$ARCHIVE_NAME" artifacts/
+mv "$REV_NAME.7z" artifacts/

+ 6 - 0
.travis/common/pre-upload.sh

@@ -0,0 +1,6 @@
+#!/bin/bash -ex
+
+GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
+GITREV="`git show -s --format='%h'`"
+
+mkdir -p artifacts

+ 3 - 0
.travis/linux/build.sh

@@ -0,0 +1,3 @@
+#!/bin/bash -ex
+
+docker run -v $(pwd):/citra ubuntu:16.04 /bin/bash /citra/.travis/linux/docker.sh

+ 3 - 0
.travis/linux/deps.sh

@@ -0,0 +1,3 @@
+#!/bin/sh -ex
+
+docker pull ubuntu:16.04

+ 17 - 0
.travis/linux/docker.sh

@@ -0,0 +1,17 @@
+#!/bin/bash -ex
+
+cd /citra
+
+apt-get update
+apt-get install -y build-essential libsdl2-dev qtbase5-dev libqt5opengl5-dev libcurl4-openssl-dev libssl-dev wget git
+
+# Get a recent version of CMake
+wget https://cmake.org/files/v3.9/cmake-3.9.0-Linux-x86_64.sh
+echo y | sh cmake-3.9.0-Linux-x86_64.sh --prefix=cmake
+export PATH=/citra/cmake/cmake-3.9.0-Linux-x86_64/bin:$PATH
+
+mkdir build && cd build
+cmake .. -DUSE_SYSTEM_CURL=ON -DCMAKE_BUILD_TYPE=Release
+make -j4
+
+ctest -VV -C Release

+ 14 - 0
.travis/linux/upload.sh

@@ -0,0 +1,14 @@
+#!/bin/bash -ex
+
+. .travis/common/pre-upload.sh
+
+REV_NAME="citra-linux-${GITDATE}-${GITREV}"
+ARCHIVE_NAME="${REV_NAME}.tar.xz"
+COMPRESSION_FLAGS="-cJvf"
+
+mkdir "$REV_NAME"
+
+cp build/src/citra/citra "$REV_NAME"
+cp build/src/citra_qt/citra-qt "$REV_NAME"
+
+. .travis/common/post-upload.sh

+ 12 - 0
.travis/macos/build.sh

@@ -0,0 +1,12 @@
+#!/bin/bash -ex
+
+set -o pipefail
+
+export MACOSX_DEPLOYMENT_TARGET=10.9
+export Qt5_DIR=$(brew --prefix)/opt/qt5
+
+mkdir build && cd build
+cmake .. -DUSE_SYSTEM_CURL=ON -DCMAKE_OSX_ARCHITECTURES="x86_64;x86_64h" -DCMAKE_BUILD_TYPE=Release
+make -j4
+
+ctest -VV -C Release

+ 4 - 0
.travis/macos/deps.sh

@@ -0,0 +1,4 @@
+#!/bin/sh -ex
+
+brew update
+brew install qt5 sdl2 dylibbundler p7zip

+ 110 - 0
.travis/macos/upload.sh

@@ -0,0 +1,110 @@
+#!/bin/bash -ex
+
+. .travis/common/pre-upload.sh
+
+REV_NAME="citra-osx-${GITDATE}-${GITREV}"
+ARCHIVE_NAME="${REV_NAME}.tar.gz"
+COMPRESSION_FLAGS="-czvf"
+
+mkdir "$REV_NAME"
+
+cp build/src/citra/citra "$REV_NAME"
+cp -r build/src/citra_qt/citra-qt.app "$REV_NAME"
+
+# move qt libs into app bundle for deployment
+$(brew --prefix)/opt/qt5/bin/macdeployqt "${REV_NAME}/citra-qt.app"
+
+# move SDL2 libs into folder for deployment
+dylibbundler -b -x "${REV_NAME}/citra" -cd -d "${REV_NAME}/libs" -p "@executable_path/libs/"
+
+# Make the changes to make the citra-qt app standalone (i.e. not dependent on the current brew installation).
+# To do this, the absolute references to each and every QT framework must be re-written to point to the local frameworks
+# (in the Contents/Frameworks folder).
+# The "install_name_tool" is used to do so.
+
+# Coreutils is a hack to coerce Homebrew to point to the absolute Cellar path (symlink dereferenced). i.e:
+# ls -l /usr/local/opt/qt5:: /usr/local/opt/qt5 -> ../Cellar/qt5/5.6.1-1
+# grealpath ../Cellar/qt5/5.6.1-1:: /usr/local/Cellar/qt5/5.6.1-1
+brew install coreutils || brew upgrade coreutils || true
+
+REV_NAME_ALT=$REV_NAME/
+# grealpath is located in coreutils, there is no "realpath" for OS X :(
+QT_BREWS_PATH=$(grealpath "$(brew --prefix qt5)")
+BREW_PATH=$(brew --prefix)
+QT_VERSION_NUM=5
+
+$BREW_PATH/opt/qt5/bin/macdeployqt "${REV_NAME_ALT}citra-qt.app" \
+    -executable="${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt"
+
+# These are the files that macdeployqt packed into Contents/Frameworks/ - we don't want those, so we replace them.
+declare -a macos_libs=("QtCore" "QtWidgets" "QtGui" "QtOpenGL" "QtPrintSupport")
+
+for macos_lib in "${macos_libs[@]}"
+do
+    SC_FRAMEWORK_PART=$macos_lib.framework/Versions/$QT_VERSION_NUM/$macos_lib
+    # Replace macdeployqt versions of the Frameworks with our own (from /usr/local/opt/qt5/lib/)
+    cp "$BREW_PATH/opt/qt5/lib/$SC_FRAMEWORK_PART" "${REV_NAME_ALT}citra-qt.app/Contents/Frameworks/$SC_FRAMEWORK_PART"
+
+    # Replace references within the embedded Framework files with "internal" versions.
+    for macos_lib2 in "${macos_libs[@]}"
+    do
+        # Since brew references both the non-symlinked and symlink paths of QT5, it needs to be duplicated.
+        # /usr/local/Cellar/qt5/5.6.1-1/lib and /usr/local/opt/qt5/lib both resolve to the same files.
+        # So the two lines below are effectively duplicates when resolved as a path, but as strings, they aren't.
+        RM_FRAMEWORK_PART=$macos_lib2.framework/Versions/$QT_VERSION_NUM/$macos_lib2
+        install_name_tool -change \
+            $QT_BREWS_PATH/lib/$RM_FRAMEWORK_PART \
+            @executable_path/../Frameworks/$RM_FRAMEWORK_PART \
+            "${REV_NAME_ALT}citra-qt.app/Contents/Frameworks/$SC_FRAMEWORK_PART"
+        install_name_tool -change \
+            "$BREW_PATH/opt/qt5/lib/$RM_FRAMEWORK_PART" \
+            @executable_path/../Frameworks/$RM_FRAMEWORK_PART \
+            "${REV_NAME_ALT}citra-qt.app/Contents/Frameworks/$SC_FRAMEWORK_PART"
+    done
+done
+
+# Handles `This application failed to start because it could not find or load the Qt platform plugin "cocoa"`
+# Which manifests itself as:
+# "Exception Type: EXC_CRASH (SIGABRT) | Exception Codes: 0x0000000000000000, 0x0000000000000000 | Exception Note: EXC_CORPSE_NOTIFY"
+# There may be more dylibs needed to be fixed...
+declare -a macos_plugins=("Plugins/platforms/libqcocoa.dylib")
+
+for macos_lib in "${macos_plugins[@]}"
+do
+    install_name_tool -id @executable_path/../$macos_lib "${REV_NAME_ALT}citra-qt.app/Contents/$macos_lib"
+    for macos_lib2 in "${macos_libs[@]}"
+    do
+        RM_FRAMEWORK_PART=$macos_lib2.framework/Versions/$QT_VERSION_NUM/$macos_lib2
+        install_name_tool -change \
+            $QT_BREWS_PATH/lib/$RM_FRAMEWORK_PART \
+            @executable_path/../Frameworks/$RM_FRAMEWORK_PART \
+            "${REV_NAME_ALT}citra-qt.app/Contents/$macos_lib"
+        install_name_tool -change \
+            "$BREW_PATH/opt/qt5/lib/$RM_FRAMEWORK_PART" \
+            @executable_path/../Frameworks/$RM_FRAMEWORK_PART \
+            "${REV_NAME_ALT}citra-qt.app/Contents/$macos_lib"
+    done
+done
+
+for macos_lib in "${macos_libs[@]}"
+do
+    # Debugging info for Travis-CI
+    otool -L "${REV_NAME_ALT}citra-qt.app/Contents/Frameworks/$macos_lib.framework/Versions/$QT_VERSION_NUM/$macos_lib"
+done
+
+# Make the citra-qt.app application launch a debugging terminal.
+# Store away the actual binary
+mv ${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt ${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt-bin
+
+cat > ${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt <<EOL
+#!/usr/bin/env bash
+cd "\`dirname "\$0"\`"
+chmod +x citra-qt-bin
+open citra-qt-bin --args "\$@"
+EOL
+# Content that will serve as the launching script for citra (within the .app folder)
+
+# Make the launching script executable
+chmod +x ${REV_NAME_ALT}citra-qt.app/Contents/MacOS/citra-qt
+
+. .travis/common/post-upload.sh

+ 21 - 22
CMakeLists.txt

@@ -2,6 +2,7 @@
 cmake_minimum_required(VERSION 3.6)
 cmake_minimum_required(VERSION 3.6)
 list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
 list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
 list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
 list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
+include(DownloadExternals)
 
 
 project(citra)
 project(citra)
 
 
@@ -12,6 +13,15 @@ option(ENABLE_QT "Enable the Qt frontend" ON)
 option(CITRA_USE_BUNDLED_QT "Download bundled Qt binaries" OFF)
 option(CITRA_USE_BUNDLED_QT "Download bundled Qt binaries" OFF)
 
 
 option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
 option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
+option(CITRA_USE_BUNDLED_CURL "FOR MINGW ONLY: Download curl configured against winssl instead of openssl" OFF)
+if (ENABLE_WEB_SERVICE AND CITRA_USE_BUNDLED_CURL AND WINDOWS AND MSVC)
+    message("Turning off use bundled curl as msvc can compile curl on cpr")
+    SET(CITRA_USE_BUNDLED_CURL OFF CACHE BOOL "" FORCE)
+endif()
+if (ENABLE_WEB_SERVICE AND NOT CITRA_USE_BUNDLED_CURL AND MINGW)
+    message(AUTHOR_WARNING "Turning on CITRA_USE_BUNDLED_CURL. Override it only if you know what you are doing.")
+    SET(CITRA_USE_BUNDLED_CURL ON CACHE BOOL "" FORCE)
+endif()
 
 
 if(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git/hooks/pre-commit)
 if(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git/hooks/pre-commit)
     message(STATUS "Copying pre-commit hook")
     message(STATUS "Copying pre-commit hook")
@@ -129,8 +139,8 @@ else()
     set(CMAKE_C_FLAGS_RELEASE   "/O2 /GS- /MD" CACHE STRING "" FORCE)
     set(CMAKE_C_FLAGS_RELEASE   "/O2 /GS- /MD" CACHE STRING "" FORCE)
     set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}" CACHE STRING "" FORCE)
     set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}" CACHE STRING "" FORCE)
 
 
-    set(CMAKE_EXE_LINKER_FLAGS_DEBUG   "/DEBUG" CACHE STRING "" FORCE)
-    set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE)
+    set(CMAKE_EXE_LINKER_FLAGS_DEBUG   "/DEBUG /MANIFEST:NO" CACHE STRING "" FORCE)
+    set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE)
 endif()
 endif()
 
 
 # Set file offset size to 64 bits.
 # Set file offset size to 64 bits.
@@ -151,24 +161,6 @@ set_property(DIRECTORY APPEND PROPERTY
 # System imported libraries
 # System imported libraries
 # ======================
 # ======================
 
 
-# This function downloads a binary library package from our external repo.
-# Params:
-#   remote_path: path to the file to download, relative to the remote repository root
-#   prefix_var: name of a variable which will be set with the path to the extracted contents
-function(download_bundled_external remote_path lib_name prefix_var)
-    set(prefix "${CMAKE_BINARY_DIR}/externals/${lib_name}")
-    if (NOT EXISTS "${prefix}")
-        message(STATUS "Downloading binaries for ${lib_name}...")
-        file(DOWNLOAD
-            https://github.com/citra-emu/ext-windows-bin/raw/master/${remote_path}${lib_name}.7z
-            "${CMAKE_BINARY_DIR}/externals/${lib_name}.7z" SHOW_PROGRESS)
-        execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${CMAKE_BINARY_DIR}/externals/${lib_name}.7z"
-            WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals")
-    endif()
-    message(STATUS "Using bundled binaries at ${prefix}")
-    set(${prefix_var} "${prefix}" PARENT_SCOPE)
-endfunction()
-
 find_package(PNG QUIET)
 find_package(PNG QUIET)
 if (NOT PNG_FOUND)
 if (NOT PNG_FOUND)
     message(STATUS "libpng not found. Some debugging features have been disabled.")
     message(STATUS "libpng not found. Some debugging features have been disabled.")
@@ -295,15 +287,22 @@ function(create_directory_groups)
     endforeach()
     endforeach()
 endfunction()
 endfunction()
 
 
-# generate git revision information
+# Gets a UTC timstamp and sets the provided variable to it
+function(get_timestamp _var)
+    string(TIMESTAMP timestamp UTC)
+    set(${_var} "${timestamp}" PARENT_SCOPE)
+endfunction()
+
+# generate git/build information
 include(GetGitRevisionDescription)
 include(GetGitRevisionDescription)
 get_git_head_revision(GIT_REF_SPEC GIT_REV)
 get_git_head_revision(GIT_REF_SPEC GIT_REV)
 git_describe(GIT_DESC --always --long --dirty)
 git_describe(GIT_DESC --always --long --dirty)
 git_branch_name(GIT_BRANCH)
 git_branch_name(GIT_BRANCH)
+get_timestamp(BUILD_DATE)
 
 
+enable_testing()
 add_subdirectory(externals)
 add_subdirectory(externals)
 add_subdirectory(src)
 add_subdirectory(src)
-enable_testing()
 
 
 
 
 # Installation instructions
 # Installation instructions

+ 18 - 0
CMakeModules/DownloadExternals.cmake

@@ -0,0 +1,18 @@
+
+# This function downloads a binary library package from our external repo.
+# Params:
+#   remote_path: path to the file to download, relative to the remote repository root
+#   prefix_var: name of a variable which will be set with the path to the extracted contents
+function(download_bundled_external remote_path lib_name prefix_var)
+set(prefix "${CMAKE_BINARY_DIR}/externals/${lib_name}")
+if (NOT EXISTS "${prefix}")
+    message(STATUS "Downloading binaries for ${lib_name}...")
+    file(DOWNLOAD
+        https://github.com/citra-emu/ext-windows-bin/raw/master/${remote_path}${lib_name}.7z
+        "${CMAKE_BINARY_DIR}/externals/${lib_name}.7z" SHOW_PROGRESS)
+    execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${CMAKE_BINARY_DIR}/externals/${lib_name}.7z"
+        WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals")
+endif()
+message(STATUS "Using bundled binaries at ${prefix}")
+set(${prefix_var} "${prefix}" PARENT_SCOPE)
+endfunction()

+ 1 - 1
README.md

@@ -17,7 +17,7 @@ For development discussion, please join us @ #citra on freenode.
 
 
 Most of the development happens on GitHub. It's also where [our central repository](https://github.com/citra-emu/citra) is hosted.
 Most of the development happens on GitHub. It's also where [our central repository](https://github.com/citra-emu/citra) is hosted.
 
 
-If 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.
+If you want to contribute please take a look at the [Contributor's Guide](CONTRIBUTING.md) and [Developer Information](https://github.com/citra-emu/citra/wiki/Developer-Information). You should as well contact any of the developers in the forum in order to know about the current state of the emulator because the [TODO list](https://docs.google.com/document/d/1SWIop0uBI9IW8VGg97TAtoT_CHNoP42FzYmvG1F4QDA) isn't maintained anymore.
 
 
 ### Building
 ### Building
 
 

+ 132 - 35
appveyor.yml

@@ -1,15 +1,21 @@
 # shallow clone
 # shallow clone
 clone_depth: 10
 clone_depth: 10
 
 
-# don't build on tag
-skip_tags: true
-
 cache:
 cache:
   - C:\ProgramData\chocolatey\bin -> appveyor.yml
   - C:\ProgramData\chocolatey\bin -> appveyor.yml
   - C:\ProgramData\chocolatey\lib -> appveyor.yml
   - C:\ProgramData\chocolatey\lib -> appveyor.yml
 
 
 os: Visual Studio 2017
 os: Visual Studio 2017
 
 
+environment:
+  # Tell msys2 to add mingw64 to the path
+  MSYSTEM: MINGW64
+  # Tell msys2 to inherit the current directory when starting the shell
+  CHERE_INVOKING: 1
+  matrix:
+    - BUILD_TYPE: mingw
+    - BUILD_TYPE: msvc
+
 platform:
 platform:
   - x64
   - x64
 
 
@@ -18,59 +24,150 @@ configuration:
 
 
 install:
 install:
   - git submodule update --init --recursive
   - git submodule update --init --recursive
+  - ps: |
+        if ($env:BUILD_TYPE -eq 'mingw') {
+          $dependencies = "mingw64/mingw-w64-x86_64-cmake",
+                          "mingw64/mingw-w64-x86_64-qt5",
+                          "mingw64/mingw-w64-x86_64-curl",
+                          "mingw64/mingw-w64-x86_64-SDL2"
+          # redirect err to null to prevent warnings from becoming errors
+          # workaround to prevent pacman from failing due to cyclical dependencies
+          C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S mingw64/mingw-w64-x86_64-freetype mingw64/mingw-w64-x86_64-fontconfig" 2> $null
+          C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S $dependencies" 2> $null
+        }
 
 
 before_build:
 before_build:
-  - mkdir build
-  - cd build
-  - cmake -G "Visual Studio 15 2017 Win64" -DCITRA_USE_BUNDLED_QT=1 -DCITRA_USE_BUNDLED_SDL2=1 -DCMAKE_USE_OPENSSL=0 ..
+  - mkdir %BUILD_TYPE%_build
+  - cd %BUILD_TYPE%_build
+  - ps: |
+        if ($env:BUILD_TYPE -eq 'msvc') {
+          # redirect stderr and change the exit code to prevent powershell from cancelling the build if cmake prints a warning
+          cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DCITRA_USE_BUNDLED_QT=1 -DCITRA_USE_BUNDLED_SDL2=1 -DCMAKE_USE_OPENSSL=0 .. 2>&1 && exit 0'
+        } else {
+          C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DUSE_SYSTEM_CURL=1 -DCITRA_USE_BUNDLED_CURL=1 -DCMAKE_BUILD_TYPE=Release .. 2>&1"
+        }
   - cd ..
   - cd ..
 
 
-build:
-  project: build/citra.sln
-  parallel: true
+build_script:
+  - ps: |
+        if ($env:BUILD_TYPE -eq 'msvc') {
+          # https://www.appveyor.com/docs/build-phase
+          msbuild msvc_build/citra.sln /maxcpucount /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
+        } else {
+          C:\msys64\usr\bin\bash.exe -lc 'mingw32-make -C mingw_build/ 2>&1'
+        }
 
 
 after_build:
 after_build:
   - ps: |
   - ps: |
         $GITDATE = $(git show -s --date=short --format='%ad') -replace "-",""
         $GITDATE = $(git show -s --date=short --format='%ad') -replace "-",""
         $GITREV = $(git show -s --format='%h')
         $GITREV = $(git show -s --format='%h')
-        $GIT_LONG_HASH = $(git rev-parse HEAD)
-        # Where are these spaces coming from? Regardless, let's remove them
-        $MSVC_BUILD_NAME = "citra-windows-msvc-$GITDATE-$GITREV.zip" -replace " ", ""
-        $MSVC_BUILD_PDB = "citra-windows-msvc-$GITDATE-$GITREV-debugsymbols.zip" -replace " ", ""
-        $BINTRAY_VERSION = "nightly-$GIT_LONG_HASH" -replace " ", ""
 
 
-        # set the build names as env vars so the artifacts can upload them
-        $env:MSVC_BUILD_NAME = $MSVC_BUILD_NAME
-        $env:MSVC_BUILD_PDB = $MSVC_BUILD_PDB
-        $env:GITREV = $GITREV
+        # Find out which kind of release we are producing by tag name
+        if ($env:APPVEYOR_REPO_TAG_NAME) {
+          $RELEASE_DIST, $RELEASE_VERSION = $env:APPVEYOR_REPO_TAG_NAME.split('-')
+        } else {
+          # There is no repo tag - make assumptions
+          $RELEASE_DIST = "head"
+        }
+
+        if ($env:BUILD_TYPE -eq 'msvc') {
+          # Where are these spaces coming from? Regardless, let's remove them
+          $MSVC_BUILD_ZIP = "citra-windows-msvc-$GITDATE-$GITREV.zip" -replace " ", ""
+          $MSVC_BUILD_PDB = "citra-windows-msvc-$GITDATE-$GITREV-debugsymbols.zip" -replace " ", ""
+          $MSVC_SEVENZIP = "citra-windows-msvc-$GITDATE-$GITREV.7z" -replace " ", ""
+
+          # set the build names as env vars so the artifacts can upload them
+          $env:BUILD_ZIP = $MSVC_BUILD_ZIP
+          $env:BUILD_SYMBOLS = $MSVC_BUILD_PDB
+          $env:BUILD_UPDATE = $MSVC_SEVENZIP
+
+          7z a -tzip $MSVC_BUILD_PDB .\msvc_build\bin\release\*.pdb
+          rm .\msvc_build\bin\release\*.pdb
+
+          mkdir $RELEASE_DIST
+          Copy-Item .\msvc_build\bin\release\* -Destination $RELEASE_DIST -Recurse
+          Copy-Item .\license.txt -Destination $RELEASE_DIST
+          Copy-Item .\README.md -Destination $RELEASE_DIST
+          7z a -tzip $MSVC_BUILD_ZIP $RELEASE_DIST\*
+          7z a $MSVC_SEVENZIP $RELEASE_DIST
+        } else {
+          $MINGW_BUILD_ZIP = "citra-windows-mingw-$GITDATE-$GITREV.zip" -replace " ", ""
+          $MINGW_SEVENZIP = "citra-windows-mingw-$GITDATE-$GITREV.7z" -replace " ", ""
+          # not going to bother adding separate debug symbols for mingw, so just upload a README for it
+          # if someone wants to add them, change mingw to compile with -g and use objdump and strip to separate the symbols from the binary
+          $MINGW_NO_DEBUG_SYMBOLS = "README_No_Debug_Symbols.txt"
+          Set-Content -Path $MINGW_NO_DEBUG_SYMBOLS -Value "This is a workaround for Appveyor since msvc has debug symbols but mingw doesnt" -Force
 
 
-        7z a -tzip $MSVC_BUILD_PDB .\build\bin\release\*.pdb
-        rm .\build\bin\release\*.pdb
-        7z a -tzip $MSVC_BUILD_NAME .\build\bin\release\* .\license.txt .\README.md
+          # store the build information in env vars so we can use them as artifacts
+          $env:BUILD_ZIP = $MINGW_BUILD_ZIP
+          $env:BUILD_SYMBOLS = $MINGW_NO_DEBUG_SYMBOLS
+          $env:BUILD_UPDATE = $MINGW_SEVENZIP
+
+          $CMAKE_SOURCE_DIR = "$env:APPVEYOR_BUILD_FOLDER"
+          $CMAKE_BINARY_DIR = "$CMAKE_SOURCE_DIR/mingw_build"
+          $RELEASE_DIST = $RELEASE_DIST + "-mingw"
+
+          mkdir $RELEASE_DIST
+          mkdir $RELEASE_DIST/platforms
+
+          # copy the compiled binaries and other release files to the release folder
+          Get-ChildItem "$CMAKE_BINARY_DIR" -Recurse -Filter "citra*.exe" | Copy-Item -destination $RELEASE_DIST
+          # copy the libcurl dll
+          Get-ChildItem "$CMAKE_BINARY_DIR" -Recurse -Filter "libcurl.dll" | Copy-Item -destination $RELEASE_DIST
+          Copy-Item -path "$CMAKE_SOURCE_DIR/license.txt" -destination $RELEASE_DIST
+          Copy-Item -path "$CMAKE_SOURCE_DIR/README.md" -destination $RELEASE_DIST
+          # copy all the dll dependencies to the release folder
+          # hardcoded list because we don't build static and determining the list of dlls from the binary is a pain.
+          $MingwDLLs = "Qt5Core.dll","Qt5Widgets.dll","Qt5Gui.dll","Qt5OpenGL.dll",
+                       # QT dll dependencies
+                       "libbz2-*.dll","libicudt*.dll","libicuin*.dll","libicuuc*.dll","libffi-*.dll",
+                       "libfreetype-*.dll","libglib-*.dll","libgobject-*.dll","libgraphite2.dll","libiconv-*.dll",
+                       "libharfbuzz-*.dll","libintl-*.dll","libpcre-*.dll","libpcre16-*.dll","libpng16-*.dll",
+                       # Runtime/Other dependencies
+                       "libgcc_s_seh-*.dll","libstdc++-*.dll","libwinpthread-*.dll","SDL2.dll","zlib1.dll"
+          foreach ($file in $MingwDLLs) {
+            Copy-Item -path "C:/msys64/mingw64/bin/$file" -force -destination "$RELEASE_DIST"
+          }
+          # the above list copies a few extra debug dlls that aren't needed (thanks globbing patterns!)
+          # so we can remove them by hardcoding another list of extra dlls to remove
+          $DebugDLLs = "libicudtd*.dll","libicuind*.dll","libicuucd*.dll"
+          foreach ($file in $DebugDLLs) {
+            Remove-Item -path "$RELEASE_DIST/$file"
+          }
+
+          # copy the qt windows plugin dll to platforms
+          Copy-Item -path "C:/msys64/mingw64/share/qt5/plugins/platforms/qwindows.dll" -force -destination "$RELEASE_DIST/platforms"
+
+          7z a -tzip $MINGW_BUILD_ZIP $RELEASE_DIST\*
+          7z a $MINGW_SEVENZIP $RELEASE_DIST
+        }
 
 
 test_script:
 test_script:
-  - cd build && ctest -VV -C Release && cd ..
+  - cd %BUILD_TYPE%_build
+  - ps: |
+        if ($env:BUILD_TYPE -eq 'msvc') {
+          ctest -VV -C Release
+        } else {
+          C:\msys64\usr\bin\bash.exe -lc "ctest -VV -C Release"
+        }
+  - cd ..
 
 
 artifacts:
 artifacts:
-  - path: $(MSVC_BUILD_NAME)
-    name: msvcbuild
-    type: zip
-  - path: $(MSVC_BUILD_PDB)
-    name: msvcdebug
+  - path: $(BUILD_ZIP)
+    name: build
     type: zip
     type: zip
+  - path: $(BUILD_SYMBOLS)
+    name: debugsymbols
+  - path: $(BUILD_UPDATE)
+    name: update
 
 
 deploy:
 deploy:
   provider: GitHub
   provider: GitHub
-  release: nightly-$(appveyor_build_number)
-  description: |
-    Citra nightly releases. Please choose the correct download for your operating system from the list below.
-
-    Short Commit Hash $(GITREV)
+  release: $(appveyor_repo_tag_name)
   auth_token:
   auth_token:
     secure: "dbpsMC/MgPKWFNJCXpQl4cR8FYhepkPLjgNp/pRMktZ8oLKTqPYErfreaIxb/4P1"
     secure: "dbpsMC/MgPKWFNJCXpQl4cR8FYhepkPLjgNp/pRMktZ8oLKTqPYErfreaIxb/4P1"
-  artifact: msvcbuild
+  artifact: update,build
   draft: false
   draft: false
   prerelease: false
   prerelease: false
   on:
   on:
-    branch: master
-    appveyor_repo_name: citra-emu/citra-nightly
+    appveyor_repo_tag: true



+ 24 - 0
dist/citra.manifest

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
+  <security>
+   <requestedPrivileges>
+    <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
+   </requestedPrivileges>
+  </security>
+ </trustInfo>
+ <application xmlns="urn:schemas-microsoft-com:asm.v3">
+  <windowsSettings>
+   <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
+   <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
+  </windowsSettings>
+ </application>
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+  <application>
+   <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+   <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+   <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+   <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+  </application>
+ </compatibility>
+</assembly>

Разлика између датотеке није приказан због своје велике величине
+ 0 - 79
dist/citra.svg


BIN
dist/doc-icon.png


BIN
dist/icons/checked.png


BIN
dist/icons/failed.png


+ 6 - 0
dist/icons/icons.qrc

@@ -0,0 +1,6 @@
+<RCC>
+  <qresource prefix="icons">
+    <file>checked.png</file>
+    <file>failed.png</file>
+  </qresource>
+</RCC>

+ 21 - 2
externals/CMakeLists.txt

@@ -1,5 +1,8 @@
 # Definitions for all external bundled libraries
 # Definitions for all external bundled libraries
 
 
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
+include(DownloadExternals)
+
 # Catch
 # Catch
 add_library(catch-single-include INTERFACE)
 add_library(catch-single-include INTERFACE)
 target_include_directories(catch-single-include INTERFACE catch/single_include)
 target_include_directories(catch-single-include INTERFACE catch/single_include)
@@ -46,6 +49,10 @@ add_subdirectory(soundtouch)
 # The SoundTouch target doesn't export the necessary include paths as properties by default
 # The SoundTouch target doesn't export the necessary include paths as properties by default
 target_include_directories(SoundTouch INTERFACE ./soundtouch/include)
 target_include_directories(SoundTouch INTERFACE ./soundtouch/include)
 
 
+# Unicorn
+add_library(unicorn-headers INTERFACE)
+target_include_directories(unicorn-headers INTERFACE ./unicorn/include)
+
 # Xbyak
 # Xbyak
 if (ARCHITECTURE_x86_64)
 if (ARCHITECTURE_x86_64)
     # Defined before "dynarmic" above
     # Defined before "dynarmic" above
@@ -59,9 +66,21 @@ add_subdirectory(enet)
 target_include_directories(enet INTERFACE ./enet/include)
 target_include_directories(enet INTERFACE ./enet/include)
 
 
 if (ENABLE_WEB_SERVICE)
 if (ENABLE_WEB_SERVICE)
+    # msys installed curl is configured to use openssl, but that isn't portable
+    # since it relies on having the bundled certs install in the home folder for SSL
+    # by default on mingw, download the precompiled curl thats linked against windows native ssl
+    if (MINGW AND CITRA_USE_BUNDLED_CURL)
+        download_bundled_external("curl/" "curl-7_55_1" CURL_PREFIX)
+        set(CURL_PREFIX "${CMAKE_BINARY_DIR}/externals/curl-7_55_1")
+        set(CURL_FOUND YES)
+        set(CURL_INCLUDE_DIR "${CURL_PREFIX}/include" CACHE PATH "Path to curl headers")
+        set(CURL_LIBRARY "${CURL_PREFIX}/lib/libcurldll.a" CACHE PATH "Path to curl library")
+        set(CURL_DLL_DIR "${CURL_PREFIX}/lib/" CACHE PATH "Path to curl.dll")
+        set(USE_SYSTEM_CURL ON CACHE BOOL "")
+    endif()
     # CPR
     # CPR
-    option(BUILD_TESTING OFF)
-    option(BUILD_CPR_TESTS OFF)
+    set(BUILD_TESTING OFF CACHE BOOL "")
+    set(BUILD_CPR_TESTS OFF CACHE BOOL "")
     add_subdirectory(cpr)
     add_subdirectory(cpr)
     target_include_directories(cpr INTERFACE ./cpr/include)
     target_include_directories(cpr INTERFACE ./cpr/include)
 
 

+ 1 - 1
externals/cryptopp/cryptopp

@@ -1 +1 @@
-Subproject commit 841c37e34765487a2968357369ab74db8b10a62d
+Subproject commit 24bc2b85674254fb294e717eb5b47d9f53e786b8

+ 1 - 1
externals/dynarmic

@@ -1 +1 @@
-Subproject commit 8f15e3f70cb96e56705e5de6ba97b5d09423a56b
+Subproject commit 69eccf826d657a6cfb1d731b00629939d230ec5f

+ 1 - 1
externals/enet

@@ -1 +1 @@
-Subproject commit a84c120eff13d2fa3eadb41ef7afe0f7819f4d6c
+Subproject commit 9d9ba122d4818f7ae1aef2197933ac696edb2331

+ 1 - 1
externals/soundtouch

@@ -1 +1 @@
-Subproject commit 5274ec4dec498bd88ccbcd28862a0f78a3b95eff
+Subproject commit 019d2089bbadf70d73ba85aa8ea51490b071262c

+ 3 - 1
src/audio_core/codec.cpp

@@ -117,7 +117,9 @@ StereoBuffer16 DecodePCM16(const unsigned num_channels, const u8* const data,
             ret[i].fill(sample);
             ret[i].fill(sample);
         }
         }
     } else {
     } else {
-        std::memcpy(ret.data(), data, sample_count * 2 * sizeof(u16));
+        for (size_t i = 0; i < sample_count; ++i) {
+            std::memcpy(&ret[i], data + i * sizeof(s16) * 2, 2 * sizeof(s16));
+        }
     }
     }
 
 
     return ret;
     return ret;

+ 2 - 2
src/audio_core/codec.h

@@ -5,13 +5,13 @@
 #pragma once
 #pragma once
 
 
 #include <array>
 #include <array>
-#include <vector>
+#include <deque>
 #include "common/common_types.h"
 #include "common/common_types.h"
 
 
 namespace Codec {
 namespace Codec {
 
 
 /// A variable length buffer of signed PCM16 stereo samples.
 /// A variable length buffer of signed PCM16 stereo samples.
-using StereoBuffer16 = std::vector<std::array<s16, 2>>;
+using StereoBuffer16 = std::deque<std::array<s16, 2>>;
 
 
 /// See: Codec::DecodeADPCM
 /// See: Codec::DecodeADPCM
 struct ADPCMState {
 struct ADPCMState {

+ 20 - 29
src/audio_core/hle/source.cpp

@@ -244,17 +244,27 @@ void Source::GenerateFrame() {
             break;
             break;
         }
         }
 
 
-        const size_t size_to_copy =
-            std::min(state.current_buffer.size(), current_frame.size() - frame_position);
-
-        std::copy(state.current_buffer.begin(), state.current_buffer.begin() + size_to_copy,
-                  current_frame.begin() + frame_position);
-        state.current_buffer.erase(state.current_buffer.begin(),
-                                   state.current_buffer.begin() + size_to_copy);
-
-        frame_position += size_to_copy;
-        state.next_sample_number += static_cast<u32>(size_to_copy);
+        switch (state.interpolation_mode) {
+        case InterpolationMode::None:
+            AudioInterp::None(state.interp_state, state.current_buffer, state.rate_multiplier,
+                              current_frame, frame_position);
+            break;
+        case InterpolationMode::Linear:
+            AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier,
+                                current_frame, frame_position);
+            break;
+        case InterpolationMode::Polyphase:
+            // TODO(merry): Implement polyphase interpolation
+            LOG_DEBUG(Audio_DSP, "Polyphase interpolation unimplemented; falling back to linear");
+            AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier,
+                                current_frame, frame_position);
+            break;
+        default:
+            UNIMPLEMENTED();
+            break;
+        }
     }
     }
+    state.next_sample_number += static_cast<u32>(frame_position);
 
 
     state.filters.ProcessFrame(current_frame);
     state.filters.ProcessFrame(current_frame);
 }
 }
@@ -305,25 +315,6 @@ bool Source::DequeueBuffer() {
         return true;
         return true;
     }
     }
 
 
-    switch (state.interpolation_mode) {
-    case InterpolationMode::None:
-        state.current_buffer =
-            AudioInterp::None(state.interp_state, state.current_buffer, state.rate_multiplier);
-        break;
-    case InterpolationMode::Linear:
-        state.current_buffer =
-            AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier);
-        break;
-    case InterpolationMode::Polyphase:
-        // TODO(merry): Implement polyphase interpolation
-        state.current_buffer =
-            AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier);
-        break;
-    default:
-        UNIMPLEMENTED();
-        break;
-    }
-
     // the first playthrough starts at play_position, loops start at the beginning of the buffer
     // the first playthrough starts at play_position, loops start at the beginning of the buffer
     state.current_sample_number = (!buf.has_played) ? buf.play_position : 0;
     state.current_sample_number = (!buf.has_played) ? buf.play_position : 0;
     state.next_sample_number = state.current_sample_number;
     state.next_sample_number = state.current_sample_number;

+ 1 - 1
src/audio_core/hle/source.h

@@ -108,7 +108,7 @@ private:
 
 
         u32 current_sample_number = 0;
         u32 current_sample_number = 0;
         u32 next_sample_number = 0;
         u32 next_sample_number = 0;
-        std::vector<std::array<s16, 2>> current_buffer;
+        AudioInterp::StereoBuffer16 current_buffer;
 
 
         // buffer_id state
         // buffer_id state
 
 

+ 38 - 48
src/audio_core/interpolate.cpp

@@ -13,74 +13,64 @@ namespace AudioInterp {
 constexpr u64 scale_factor = 1 << 24;
 constexpr u64 scale_factor = 1 << 24;
 constexpr u64 scale_mask = scale_factor - 1;
 constexpr u64 scale_mask = scale_factor - 1;
 
 
-/// Here we step over the input in steps of rate_multiplier, until we consume all of the input.
+/// Here we step over the input in steps of rate, until we consume all of the input.
 /// Three adjacent samples are passed to fn each step.
 /// Three adjacent samples are passed to fn each step.
 template <typename Function>
 template <typename Function>
-static StereoBuffer16 StepOverSamples(State& state, const StereoBuffer16& input,
-                                      float rate_multiplier, Function fn) {
-    ASSERT(rate_multiplier > 0);
+static void StepOverSamples(State& state, StereoBuffer16& input, float rate,
+                            DSP::HLE::StereoFrame16& output, size_t& outputi, Function fn) {
+    ASSERT(rate > 0);
 
 
-    if (input.size() < 2)
-        return {};
+    if (input.empty())
+        return;
 
 
-    StereoBuffer16 output;
-    output.reserve(static_cast<size_t>(input.size() / rate_multiplier));
+    input.insert(input.begin(), {state.xn2, state.xn1});
 
 
-    u64 step_size = static_cast<u64>(rate_multiplier * scale_factor);
+    const u64 step_size = static_cast<u64>(rate * scale_factor);
+    u64 fposition = state.fposition;
+    size_t inputi = 0;
 
 
-    u64 fposition = 0;
-    const u64 max_fposition = input.size() * scale_factor;
+    while (outputi < output.size()) {
+        inputi = static_cast<size_t>(fposition / scale_factor);
 
 
-    while (fposition < 1 * scale_factor) {
-        u64 fraction = fposition & scale_mask;
-
-        output.push_back(fn(fraction, state.xn2, state.xn1, input[0]));
-
-        fposition += step_size;
-    }
-
-    while (fposition < 2 * scale_factor) {
-        u64 fraction = fposition & scale_mask;
-
-        output.push_back(fn(fraction, state.xn1, input[0], input[1]));
-
-        fposition += step_size;
-    }
+        if (inputi + 2 >= input.size()) {
+            inputi = input.size() - 2;
+            break;
+        }
 
 
-    while (fposition < max_fposition) {
         u64 fraction = fposition & scale_mask;
         u64 fraction = fposition & scale_mask;
-
-        size_t index = static_cast<size_t>(fposition / scale_factor);
-        output.push_back(fn(fraction, input[index - 2], input[index - 1], input[index]));
+        output[outputi++] = fn(fraction, input[inputi], input[inputi + 1], input[inputi + 2]);
 
 
         fposition += step_size;
         fposition += step_size;
     }
     }
 
 
-    state.xn2 = input[input.size() - 2];
-    state.xn1 = input[input.size() - 1];
+    state.xn2 = input[inputi];
+    state.xn1 = input[inputi + 1];
+    state.fposition = fposition - inputi * scale_factor;
 
 
-    return output;
+    input.erase(input.begin(), std::next(input.begin(), inputi + 2));
 }
 }
 
 
-StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multiplier) {
-    return StepOverSamples(
-        state, input, rate_multiplier,
+void None(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output,
+          size_t& outputi) {
+    StepOverSamples(
+        state, input, rate, output, outputi,
         [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) { return x0; });
         [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) { return x0; });
 }
 }
 
 
-StereoBuffer16 Linear(State& state, const StereoBuffer16& input, float rate_multiplier) {
+void Linear(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output,
+            size_t& outputi) {
     // Note on accuracy: Some values that this produces are +/- 1 from the actual firmware.
     // Note on accuracy: Some values that this produces are +/- 1 from the actual firmware.
-    return StepOverSamples(state, input, rate_multiplier,
-                           [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) {
-                               // This is a saturated subtraction. (Verified by black-box fuzzing.)
-                               s64 delta0 = MathUtil::Clamp<s64>(x1[0] - x0[0], -32768, 32767);
-                               s64 delta1 = MathUtil::Clamp<s64>(x1[1] - x0[1], -32768, 32767);
-
-                               return std::array<s16, 2>{
-                                   static_cast<s16>(x0[0] + fraction * delta0 / scale_factor),
-                                   static_cast<s16>(x0[1] + fraction * delta1 / scale_factor),
-                               };
-                           });
+    StepOverSamples(state, input, rate, output, outputi,
+                    [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) {
+                        // This is a saturated subtraction. (Verified by black-box fuzzing.)
+                        s64 delta0 = MathUtil::Clamp<s64>(x1[0] - x0[0], -32768, 32767);
+                        s64 delta1 = MathUtil::Clamp<s64>(x1[1] - x0[1], -32768, 32767);
+
+                        return std::array<s16, 2>{
+                            static_cast<s16>(x0[0] + fraction * delta0 / scale_factor),
+                            static_cast<s16>(x0[1] + fraction * delta1 / scale_factor),
+                        };
+                    });
 }
 }
 
 
 } // namespace AudioInterp
 } // namespace AudioInterp

+ 18 - 13
src/audio_core/interpolate.h

@@ -5,40 +5,45 @@
 #pragma once
 #pragma once
 
 
 #include <array>
 #include <array>
-#include <vector>
+#include <deque>
+#include "audio_core/hle/common.h"
 #include "common/common_types.h"
 #include "common/common_types.h"
 
 
 namespace AudioInterp {
 namespace AudioInterp {
 
 
 /// A variable length buffer of signed PCM16 stereo samples.
 /// A variable length buffer of signed PCM16 stereo samples.
-using StereoBuffer16 = std::vector<std::array<s16, 2>>;
+using StereoBuffer16 = std::deque<std::array<s16, 2>>;
 
 
 struct State {
 struct State {
-    // Two historical samples.
+    /// Two historical samples.
     std::array<s16, 2> xn1 = {}; ///< x[n-1]
     std::array<s16, 2> xn1 = {}; ///< x[n-1]
     std::array<s16, 2> xn2 = {}; ///< x[n-2]
     std::array<s16, 2> xn2 = {}; ///< x[n-2]
+    /// Current fractional position.
+    u64 fposition = 0;
 };
 };
 
 
 /**
 /**
  * No interpolation. This is equivalent to a zero-order hold. There is a two-sample predelay.
  * No interpolation. This is equivalent to a zero-order hold. There is a two-sample predelay.
  * @param state Interpolation state.
  * @param state Interpolation state.
  * @param input Input buffer.
  * @param input Input buffer.
- * @param rate_multiplier Stretch factor. Must be a positive non-zero value.
- *                        rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0
- *                        performs upsampling.
- * @return The resampled audio buffer.
+ * @param rate Stretch factor. Must be a positive non-zero value.
+ *             rate > 1.0 performs decimation and rate < 1.0 performs upsampling.
+ * @param output The resampled audio buffer.
+ * @param outputi The index of output to start writing to.
  */
  */
-StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multiplier);
+void None(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output,
+          size_t& outputi);
 
 
 /**
 /**
  * Linear interpolation. This is equivalent to a first-order hold. There is a two-sample predelay.
  * Linear interpolation. This is equivalent to a first-order hold. There is a two-sample predelay.
  * @param state Interpolation state.
  * @param state Interpolation state.
  * @param input Input buffer.
  * @param input Input buffer.
- * @param rate_multiplier Stretch factor. Must be a positive non-zero value.
- *                        rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0
- *                        performs upsampling.
- * @return The resampled audio buffer.
+ * @param rate Stretch factor. Must be a positive non-zero value.
+ *             rate > 1.0 performs decimation and rate < 1.0 performs upsampling.
+ * @param output The resampled audio buffer.
+ * @param outputi The index of output to start writing to.
  */
  */
-StereoBuffer16 Linear(State& state, const StereoBuffer16& input, float rate_multiplier);
+void Linear(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output,
+            size_t& outputi);
 
 
 } // namespace AudioInterp
 } // namespace AudioInterp

+ 2 - 0
src/citra/citra.cpp

@@ -165,6 +165,8 @@ int main(int argc, char** argv) {
         break; // Expected case
         break; // Expected case
     }
     }
 
 
+    Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "SDL");
+
     while (emu_window->IsOpen()) {
     while (emu_window->IsOpen()) {
         system.RunLoop();
         system.RunLoop();
     }
     }

+ 8 - 0
src/citra/citra.rc

@@ -1,3 +1,4 @@
+#include "winresrc.h"
 /////////////////////////////////////////////////////////////////////////////
 /////////////////////////////////////////////////////////////////////////////
 //
 //
 // Icon
 // Icon
@@ -7,3 +8,10 @@
 // remains consistent on all systems.
 // remains consistent on all systems.
 CITRA_ICON              ICON                    "../../dist/citra.ico"
 CITRA_ICON              ICON                    "../../dist/citra.ico"
 
 
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// RT_MANIFEST
+//
+
+1                       RT_MANIFEST             "../../dist/citra.manifest"

+ 11 - 0
src/citra/config.cpp

@@ -76,6 +76,11 @@ void Config::ReadValues() {
             Settings::values.analogs[i] = default_param;
             Settings::values.analogs[i] = default_param;
     }
     }
 
 
+    Settings::values.motion_device = sdl2_config->Get(
+        "Controls", "motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01");
+    Settings::values.touch_device =
+        sdl2_config->Get("Controls", "touch_device", "engine:emu_window");
+
     // Core
     // Core
     Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
     Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
 
 
@@ -153,8 +158,14 @@ void Config::ReadValues() {
         static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689));
         static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689));
 
 
     // Web Service
     // Web Service
+    Settings::values.enable_telemetry =
+        sdl2_config->GetBoolean("WebService", "enable_telemetry", true);
     Settings::values.telemetry_endpoint_url = sdl2_config->Get(
     Settings::values.telemetry_endpoint_url = sdl2_config->Get(
         "WebService", "telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry");
         "WebService", "telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry");
+    Settings::values.verify_endpoint_url = sdl2_config->Get(
+        "WebService", "verify_endpoint_url", "https://services.citra-emu.org/api/profile");
+    Settings::values.citra_username = sdl2_config->Get("WebService", "citra_username", "");
+    Settings::values.citra_token = sdl2_config->Get("WebService", "citra_token", "");
 }
 }
 
 
 void Config::Reload() {
 void Config::Reload() {

+ 24 - 5
src/citra/default_ini.h

@@ -12,7 +12,7 @@ const char* sdl2_config_file = R"(
 # It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..."
 # It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..."
 # Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values
 # Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values
 
 
-# for button input, the following devices are avaible:
+# for button input, the following devices are available:
 #  - "keyboard" (default) for keyboard input. Required parameters:
 #  - "keyboard" (default) for keyboard input. Required parameters:
 #      - "code": the code of the key to bind
 #      - "code": the code of the key to bind
 #  - "sdl" for joystick input using SDL. Required parameters:
 #  - "sdl" for joystick input using SDL. Required parameters:
@@ -21,7 +21,7 @@ const char* sdl2_config_file = R"(
 #      - "hat"(optional): the index of the hat to bind as direction buttons
 #      - "hat"(optional): the index of the hat to bind as direction buttons
 #      - "axis"(optional): the index of the axis to bind
 #      - "axis"(optional): the index of the axis to bind
 #      - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", "down", "left" or "right"
 #      - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", "down", "left" or "right"
-#      - "threshould"(only used for axis): a float value in (-1.0, 1.0) which the button is
+#      - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is
 #          triggered if the axis value crosses
 #          triggered if the axis value crosses
 #      - "direction"(only used for axis): "+" means the button is triggered when the axis value
 #      - "direction"(only used for axis): "+" means the button is triggered when the axis value
 #          is greater than the threshold; "-" means the button is triggered when the axis value
 #          is greater than the threshold; "-" means the button is triggered when the axis value
@@ -42,8 +42,8 @@ button_zl=
 button_zr=
 button_zr=
 button_home=
 button_home=
 
 
-# for analog input, the following devices are avaible:
-#  - "analog_from_button" (default) for emulating analog input from direction buttons.  Required parameters:
+# for analog input, the following devices are available:
+#  - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters:
 #      - "up", "down", "left", "right": sub-devices for each direction.
 #      - "up", "down", "left", "right": sub-devices for each direction.
 #          Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00"
 #          Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00"
 #      - "modifier": sub-devices as a modifier.
 #      - "modifier": sub-devices as a modifier.
@@ -56,6 +56,16 @@ button_home=
 circle_pad=
 circle_pad=
 c_stick=
 c_stick=
 
 
+# for motion input, the following devices are available:
+#  - "motion_emu" (default) for emulating motion input from mouse input. Required parameters:
+#      - "update_period": update period in milliseconds (default to 100)
+#      - "sensitivity": the coefficient converting mouse movement to tilting angle (default to 0.01)
+motion_device=
+
+# for touch input, the following devices are available:
+#  - "emu_window" (default) for emulating touch input from mouse input to the emulation window. No parameters required
+touch_device=
+
 [Core]
 [Core]
 # Whether to use the Just-In-Time (JIT) compiler for CPU emulation
 # Whether to use the Just-In-Time (JIT) compiler for CPU emulation
 # 0: Interpreter (slow), 1 (default): JIT (fast)
 # 0: Interpreter (slow), 1 (default): JIT (fast)
@@ -170,7 +180,16 @@ use_gdbstub=false
 gdbstub_port=24689
 gdbstub_port=24689
 
 
 [WebService]
 [WebService]
+# Whether or not to enable telemetry
+# 0: No, 1 (default): Yes
+enable_telemetry =
 # Endpoint URL for submitting telemetry data
 # Endpoint URL for submitting telemetry data
-telemetry_endpoint_url =
+telemetry_endpoint_url = https://services.citra-emu.org/api/telemetry
+# Endpoint URL to verify the username and token
+verify_endpoint_url = https://services.citra-emu.org/api/profile
+# Username and token for Citra Web Service
+# See https://services.citra-emu.org/ for more info
+citra_username =
+citra_token =
 )";
 )";
 }
 }

+ 4 - 6
src/citra/emu_window/emu_window_sdl2.cpp

@@ -16,11 +16,12 @@
 #include "core/settings.h"
 #include "core/settings.h"
 #include "input_common/keyboard.h"
 #include "input_common/keyboard.h"
 #include "input_common/main.h"
 #include "input_common/main.h"
+#include "input_common/motion_emu.h"
 #include "network/network.h"
 #include "network/network.h"
 
 
 void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
 void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
     TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
     TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
-    motion_emu->Tilt(x, y);
+    InputCommon::GetMotionEmu()->Tilt(x, y);
 }
 }
 
 
 void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
 void 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) {
         }
         }
     } else if (button == SDL_BUTTON_RIGHT) {
     } else if (button == SDL_BUTTON_RIGHT) {
         if (state == SDL_PRESSED) {
         if (state == SDL_PRESSED) {
-            motion_emu->BeginTilt(x, y);
+            InputCommon::GetMotionEmu()->BeginTilt(x, y);
         } else {
         } else {
-            motion_emu->EndTilt();
+            InputCommon::GetMotionEmu()->EndTilt();
         }
         }
     }
     }
 }
 }
@@ -61,8 +62,6 @@ EmuWindow_SDL2::EmuWindow_SDL2() {
     InputCommon::Init();
     InputCommon::Init();
     Network::Init();
     Network::Init();
 
 
-    motion_emu = std::make_unique<Motion::MotionEmu>(*this);
-
     SDL_SetMainReady();
     SDL_SetMainReady();
 
 
     // Initialize the window
     // Initialize the window
@@ -117,7 +116,6 @@ EmuWindow_SDL2::EmuWindow_SDL2() {
 EmuWindow_SDL2::~EmuWindow_SDL2() {
 EmuWindow_SDL2::~EmuWindow_SDL2() {
     SDL_GL_DeleteContext(gl_context);
     SDL_GL_DeleteContext(gl_context);
     SDL_Quit();
     SDL_Quit();
-    motion_emu = nullptr;
 
 
     Network::Shutdown();
     Network::Shutdown();
     InputCommon::Shutdown();
     InputCommon::Shutdown();

+ 0 - 4
src/citra/emu_window/emu_window_sdl2.h

@@ -7,7 +7,6 @@
 #include <memory>
 #include <memory>
 #include <utility>
 #include <utility>
 #include "core/frontend/emu_window.h"
 #include "core/frontend/emu_window.h"
-#include "core/frontend/motion_emu.h"
 
 
 struct SDL_Window;
 struct SDL_Window;
 
 
@@ -57,7 +56,4 @@ private:
     using SDL_GLContext = void*;
     using SDL_GLContext = void*;
     /// The OpenGL context associated with the window
     /// The OpenGL context associated with the window
     SDL_GLContext gl_context;
     SDL_GLContext gl_context;
-
-    /// Motion sensors emulation
-    std::unique_ptr<Motion::MotionEmu> motion_emu;
 };
 };

+ 6 - 2
src/citra_qt/CMakeLists.txt

@@ -12,6 +12,7 @@ set(SRCS
             configuration/configure_graphics.cpp
             configuration/configure_graphics.cpp
             configuration/configure_input.cpp
             configuration/configure_input.cpp
             configuration/configure_system.cpp
             configuration/configure_system.cpp
+            configuration/configure_web.cpp
             debugger/graphics/graphics.cpp
             debugger/graphics/graphics.cpp
             debugger/graphics/graphics_breakpoint_observer.cpp
             debugger/graphics/graphics_breakpoint_observer.cpp
             debugger/graphics/graphics_breakpoints.cpp
             debugger/graphics/graphics_breakpoints.cpp
@@ -42,6 +43,7 @@ set(HEADERS
             configuration/configure_graphics.h
             configuration/configure_graphics.h
             configuration/configure_input.h
             configuration/configure_input.h
             configuration/configure_system.h
             configuration/configure_system.h
+            configuration/configure_web.h
             debugger/graphics/graphics.h
             debugger/graphics/graphics.h
             debugger/graphics/graphics_breakpoint_observer.h
             debugger/graphics/graphics_breakpoint_observer.h
             debugger/graphics/graphics_breakpoints.h
             debugger/graphics/graphics_breakpoints.h
@@ -71,11 +73,13 @@ set(UIS
             configuration/configure_graphics.ui
             configuration/configure_graphics.ui
             configuration/configure_input.ui
             configuration/configure_input.ui
             configuration/configure_system.ui
             configuration/configure_system.ui
+            configuration/configure_web.ui
             debugger/registers.ui
             debugger/registers.ui
             hotkeys.ui
             hotkeys.ui
             main.ui
             main.ui
             )
             )
 
 
+file(GLOB_RECURSE ICONS ${CMAKE_SOURCE_DIR}/dist/icons/*)
 file(GLOB_RECURSE THEMES ${CMAKE_SOURCE_DIR}/dist/qt_themes/*)
 file(GLOB_RECURSE THEMES ${CMAKE_SOURCE_DIR}/dist/qt_themes/*)
 
 
 create_directory_groups(${SRCS} ${HEADERS} ${UIS})
 create_directory_groups(${SRCS} ${HEADERS} ${UIS})
@@ -89,10 +93,10 @@ endif()
 if (APPLE)
 if (APPLE)
     set(MACOSX_ICON "../../dist/citra.icns")
     set(MACOSX_ICON "../../dist/citra.icns")
     set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
     set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
-    add_executable(citra-qt MACOSX_BUNDLE ${SRCS} ${HEADERS} ${UI_HDRS} ${THEMES} ${MACOSX_ICON})
+    add_executable(citra-qt MACOSX_BUNDLE ${SRCS} ${HEADERS} ${UI_HDRS} ${ICONS} ${THEMES} ${MACOSX_ICON})
     set_target_properties(citra-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
     set_target_properties(citra-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
 else()
 else()
-    add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS} ${THEMES})
+    add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS} ${ICONS} ${THEMES})
 endif()
 endif()
 target_link_libraries(citra-qt PRIVATE audio_core common core input_common network video_core)
 target_link_libraries(citra-qt PRIVATE audio_core common core input_common network video_core)
 target_link_libraries(citra-qt PRIVATE Boost::boost glad nihstro-headers Qt5::OpenGL Qt5::Widgets)
 target_link_libraries(citra-qt PRIVATE Boost::boost glad nihstro-headers Qt5::OpenGL Qt5::Widgets)

+ 4 - 6
src/citra_qt/bootmanager.cpp

@@ -17,6 +17,7 @@
 #include "core/settings.h"
 #include "core/settings.h"
 #include "input_common/keyboard.h"
 #include "input_common/keyboard.h"
 #include "input_common/main.h"
 #include "input_common/main.h"
+#include "input_common/motion_emu.h"
 #include "network/network.h"
 #include "network/network.h"
 
 
 EmuThread::EmuThread(GRenderWindow* render_window)
 EmuThread::EmuThread(GRenderWindow* render_window)
@@ -201,7 +202,6 @@ qreal GRenderWindow::windowPixelRatio() {
 }
 }
 
 
 void GRenderWindow::closeEvent(QCloseEvent* event) {
 void GRenderWindow::closeEvent(QCloseEvent* event) {
-    motion_emu = nullptr;
     emit Closed();
     emit Closed();
     QWidget::closeEvent(event);
     QWidget::closeEvent(event);
 }
 }
@@ -221,7 +221,7 @@ void GRenderWindow::mousePressEvent(QMouseEvent* event) {
         this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio),
         this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio),
                            static_cast<unsigned>(pos.y() * pixelRatio));
                            static_cast<unsigned>(pos.y() * pixelRatio));
     } else if (event->button() == Qt::RightButton) {
     } else if (event->button() == Qt::RightButton) {
-        motion_emu->BeginTilt(pos.x(), pos.y());
+        InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
     }
     }
 }
 }
 
 
@@ -230,14 +230,14 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
     qreal pixelRatio = windowPixelRatio();
     qreal pixelRatio = windowPixelRatio();
     this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * pixelRatio), 0u),
     this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * pixelRatio), 0u),
                      std::max(static_cast<unsigned>(pos.y() * pixelRatio), 0u));
                      std::max(static_cast<unsigned>(pos.y() * pixelRatio), 0u));
-    motion_emu->Tilt(pos.x(), pos.y());
+    InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
 }
 }
 
 
 void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
 void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
     if (event->button() == Qt::LeftButton)
     if (event->button() == Qt::LeftButton)
         this->TouchReleased();
         this->TouchReleased();
     else if (event->button() == Qt::RightButton)
     else if (event->button() == Qt::RightButton)
-        motion_emu->EndTilt();
+        InputCommon::GetMotionEmu()->EndTilt();
 }
 }
 
 
 void GRenderWindow::focusOutEvent(QFocusEvent* event) {
 void GRenderWindow::focusOutEvent(QFocusEvent* event) {
@@ -290,13 +290,11 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(
 }
 }
 
 
 void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
 void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
-    motion_emu = std::make_unique<Motion::MotionEmu>(*this);
     this->emu_thread = emu_thread;
     this->emu_thread = emu_thread;
     child->DisablePainting();
     child->DisablePainting();
 }
 }
 
 
 void GRenderWindow::OnEmulationStopping() {
 void GRenderWindow::OnEmulationStopping() {
-    motion_emu = nullptr;
     emu_thread = nullptr;
     emu_thread = nullptr;
     child->EnablePainting();
     child->EnablePainting();
 }
 }

+ 0 - 4
src/citra_qt/bootmanager.h

@@ -12,7 +12,6 @@
 #include "common/thread.h"
 #include "common/thread.h"
 #include "core/core.h"
 #include "core/core.h"
 #include "core/frontend/emu_window.h"
 #include "core/frontend/emu_window.h"
-#include "core/frontend/motion_emu.h"
 
 
 class QKeyEvent;
 class QKeyEvent;
 class QScreen;
 class QScreen;
@@ -158,9 +157,6 @@ private:
 
 
     EmuThread* emu_thread;
     EmuThread* emu_thread;
 
 
-    /// Motion sensors emulation
-    std::unique_ptr<Motion::MotionEmu> motion_emu;
-
 protected:
 protected:
     void showEvent(QShowEvent* event) override;
     void showEvent(QShowEvent* event) override;
 };
 };

+ 11 - 1
src/citra_qt/citra-qt.rc

@@ -1,3 +1,4 @@
+#include "winresrc.h"
 /////////////////////////////////////////////////////////////////////////////
 /////////////////////////////////////////////////////////////////////////////
 //
 //
 // Icon
 // Icon
@@ -5,5 +6,14 @@
 
 
 // Icon with lowest ID value placed first to ensure application icon
 // Icon with lowest ID value placed first to ensure application icon
 // remains consistent on all systems.
 // remains consistent on all systems.
-CITRA_ICON              ICON                    "../../dist/citra.ico"
+// QT requires that the default application icon is named IDI_ICON1
 
 
+IDI_ICON1               ICON                    "../../dist/citra.ico"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// RT_MANIFEST
+//
+
+1                       RT_MANIFEST             "../../dist/citra.manifest"

+ 23 - 0
src/citra_qt/configuration/config.cpp

@@ -57,6 +57,13 @@ void Config::ReadValues() {
             Settings::values.analogs[i] = default_param;
             Settings::values.analogs[i] = default_param;
     }
     }
 
 
+    Settings::values.motion_device =
+        qt_config->value("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01")
+            .toString()
+            .toStdString();
+    Settings::values.touch_device =
+        qt_config->value("touch_device", "engine:emu_window").toString().toStdString();
+
     qt_config->endGroup();
     qt_config->endGroup();
 
 
     qt_config->beginGroup("Core");
     qt_config->beginGroup("Core");
@@ -134,10 +141,17 @@ void Config::ReadValues() {
     qt_config->endGroup();
     qt_config->endGroup();
 
 
     qt_config->beginGroup("WebService");
     qt_config->beginGroup("WebService");
+    Settings::values.enable_telemetry = qt_config->value("enable_telemetry", true).toBool();
     Settings::values.telemetry_endpoint_url =
     Settings::values.telemetry_endpoint_url =
         qt_config->value("telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry")
         qt_config->value("telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry")
             .toString()
             .toString()
             .toStdString();
             .toStdString();
+    Settings::values.verify_endpoint_url =
+        qt_config->value("verify_endpoint_url", "https://services.citra-emu.org/api/profile")
+            .toString()
+            .toStdString();
+    Settings::values.citra_username = qt_config->value("citra_username").toString().toStdString();
+    Settings::values.citra_token = qt_config->value("citra_token").toString().toStdString();
     qt_config->endGroup();
     qt_config->endGroup();
 
 
     qt_config->beginGroup("UI");
     qt_config->beginGroup("UI");
@@ -189,6 +203,7 @@ void Config::ReadValues() {
     UISettings::values.show_status_bar = qt_config->value("showStatusBar", true).toBool();
     UISettings::values.show_status_bar = qt_config->value("showStatusBar", true).toBool();
     UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool();
     UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool();
     UISettings::values.first_start = qt_config->value("firstStart", true).toBool();
     UISettings::values.first_start = qt_config->value("firstStart", true).toBool();
+    UISettings::values.callout_flags = qt_config->value("calloutFlags", 0).toUInt();
 
 
     qt_config->endGroup();
     qt_config->endGroup();
 }
 }
@@ -203,6 +218,8 @@ void Config::SaveValues() {
         qt_config->setValue(QString::fromStdString(Settings::NativeAnalog::mapping[i]),
         qt_config->setValue(QString::fromStdString(Settings::NativeAnalog::mapping[i]),
                             QString::fromStdString(Settings::values.analogs[i]));
                             QString::fromStdString(Settings::values.analogs[i]));
     }
     }
+    qt_config->setValue("motion_device", QString::fromStdString(Settings::values.motion_device));
+    qt_config->setValue("touch_device", QString::fromStdString(Settings::values.touch_device));
     qt_config->endGroup();
     qt_config->endGroup();
 
 
     qt_config->beginGroup("Core");
     qt_config->beginGroup("Core");
@@ -277,8 +294,13 @@ void Config::SaveValues() {
     qt_config->endGroup();
     qt_config->endGroup();
 
 
     qt_config->beginGroup("WebService");
     qt_config->beginGroup("WebService");
+    qt_config->setValue("enable_telemetry", Settings::values.enable_telemetry);
     qt_config->setValue("telemetry_endpoint_url",
     qt_config->setValue("telemetry_endpoint_url",
                         QString::fromStdString(Settings::values.telemetry_endpoint_url));
                         QString::fromStdString(Settings::values.telemetry_endpoint_url));
+    qt_config->setValue("verify_endpoint_url",
+                        QString::fromStdString(Settings::values.verify_endpoint_url));
+    qt_config->setValue("citra_username", QString::fromStdString(Settings::values.citra_username));
+    qt_config->setValue("citra_token", QString::fromStdString(Settings::values.citra_token));
     qt_config->endGroup();
     qt_config->endGroup();
 
 
     qt_config->beginGroup("UI");
     qt_config->beginGroup("UI");
@@ -314,6 +336,7 @@ void Config::SaveValues() {
     qt_config->setValue("showStatusBar", UISettings::values.show_status_bar);
     qt_config->setValue("showStatusBar", UISettings::values.show_status_bar);
     qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing);
     qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing);
     qt_config->setValue("firstStart", UISettings::values.first_start);
     qt_config->setValue("firstStart", UISettings::values.first_start);
+    qt_config->setValue("calloutFlags", UISettings::values.callout_flags);
 
 
     qt_config->endGroup();
     qt_config->endGroup();
 }
 }

+ 13 - 2
src/citra_qt/configuration/configure.ui

@@ -6,8 +6,8 @@
    <rect>
    <rect>
     <x>0</x>
     <x>0</x>
     <y>0</y>
     <y>0</y>
-    <width>441</width>
-    <height>501</height>
+    <width>740</width>
+    <height>500</height>
    </rect>
    </rect>
   </property>
   </property>
   <property name="windowTitle">
   <property name="windowTitle">
@@ -49,6 +49,11 @@
        <string>Debug</string>
        <string>Debug</string>
       </attribute>
       </attribute>
      </widget>
      </widget>
+     <widget class="ConfigureWeb" name="webTab">
+      <attribute name="title">
+       <string>Web</string>
+      </attribute>
+     </widget>
     </widget>
     </widget>
    </item>
    </item>
    <item>
    <item>
@@ -97,6 +102,12 @@
    <header>configuration/configure_graphics.h</header>
    <header>configuration/configure_graphics.h</header>
    <container>1</container>
    <container>1</container>
   </customwidget>
   </customwidget>
+  <customwidget>
+   <class>ConfigureWeb</class>
+   <extends>QWidget</extends>
+   <header>configuration/configure_web.h</header>
+   <container>1</container>
+  </customwidget>
  </customwidgets>
  </customwidgets>
  <resources/>
  <resources/>
  <connections>
  <connections>

+ 1 - 0
src/citra_qt/configuration/configure_dialog.cpp

@@ -23,5 +23,6 @@ void ConfigureDialog::applyConfiguration() {
     ui->graphicsTab->applyConfiguration();
     ui->graphicsTab->applyConfiguration();
     ui->audioTab->applyConfiguration();
     ui->audioTab->applyConfiguration();
     ui->debugTab->applyConfiguration();
     ui->debugTab->applyConfiguration();
+    ui->webTab->applyConfiguration();
     Settings::Apply();
     Settings::Apply();
 }
 }

+ 19 - 14
src/citra_qt/configuration/configure_graphics.ui

@@ -63,57 +63,57 @@
               <widget class="QComboBox" name="resolution_factor_combobox">
               <widget class="QComboBox" name="resolution_factor_combobox">
                 <item>
                 <item>
                   <property name="text">
                   <property name="text">
-                    <string notr="true">Auto (Window Size)</string>
+                    <string>Auto (Window Size)</string>
                   </property>
                   </property>
                 </item>
                 </item>
                 <item>
                 <item>
                   <property name="text">
                   <property name="text">
-                    <string notr="true">Native (400x240)</string>
+                    <string>Native (400x240)</string>
                   </property>
                   </property>
                 </item>
                 </item>
                 <item>
                 <item>
                   <property name="text">
                   <property name="text">
-                    <string notr="true">2x Native (800x480)</string>
+                    <string>2x Native (800x480)</string>
                   </property>
                   </property>
                 </item>
                 </item>
                 <item>
                 <item>
                   <property name="text">
                   <property name="text">
-                    <string notr="true">3x Native (1200x720)</string>
+                    <string>3x Native (1200x720)</string>
                   </property>
                   </property>
                 </item>
                 </item>
                 <item>
                 <item>
                   <property name="text">
                   <property name="text">
-                    <string notr="true">4x Native (1600x960)</string>
+                    <string>4x Native (1600x960)</string>
                   </property>
                   </property>
                 </item>
                 </item>
                 <item>
                 <item>
                   <property name="text">
                   <property name="text">
-                    <string notr="true">5x Native (2000x1200)</string>
+                    <string>5x Native (2000x1200)</string>
                   </property>
                   </property>
                 </item>
                 </item>
                 <item>
                 <item>
                   <property name="text">
                   <property name="text">
-                    <string notr="true">6x Native (2400x1440)</string>
+                    <string>6x Native (2400x1440)</string>
                   </property>
                   </property>
                 </item>
                 </item>
                 <item>
                 <item>
                   <property name="text">
                   <property name="text">
-                    <string notr="true">7x Native (2800x1680)</string>
+                    <string>7x Native (2800x1680)</string>
                   </property>
                   </property>
                 </item>
                 </item>
                 <item>
                 <item>
                   <property name="text">
                   <property name="text">
-                    <string notr="true">8x Native (3200x1920)</string>
+                    <string>8x Native (3200x1920)</string>
                   </property>
                   </property>
                 </item>
                 </item>
                 <item>
                 <item>
                   <property name="text">
                   <property name="text">
-                    <string notr="true">9x Native (3600x2160)</string>
+                    <string>9x Native (3600x2160)</string>
                   </property>
                   </property>
                 </item>
                 </item>
                 <item>
                 <item>
                   <property name="text">
                   <property name="text">
-                    <string notr="true">10x Native (4000x2400)</string>
+                    <string>10x Native (4000x2400)</string>
                   </property>
                   </property>
                 </item>
                 </item>
               </widget>
               </widget>
@@ -146,17 +146,22 @@
            <widget class="QComboBox" name="layout_combobox">
            <widget class="QComboBox" name="layout_combobox">
             <item>
             <item>
              <property name="text">
              <property name="text">
-              <string notr="true">Default</string>
+              <string>Default</string>
              </property>
              </property>
             </item>
             </item>
             <item>
             <item>
              <property name="text">
              <property name="text">
-              <string notr="true">Single Screen</string>
+              <string>Single Screen</string>
              </property>
              </property>
             </item>
             </item>
             <item>
             <item>
              <property name="text">
              <property name="text">
-              <string notr="true">Large Screen</string>
+              <string>Large Screen</string>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>Side by Side</string>
              </property>
              </property>
             </item>
             </item>
            </widget>
            </widget>

+ 2 - 1
src/citra_qt/configuration/configure_system.cpp

@@ -78,7 +78,8 @@ void ConfigureSystem::ReadSystemSettings() {
 
 
     // set the console id
     // set the console id
     u64 console_id = Service::CFG::GetConsoleUniqueId();
     u64 console_id = Service::CFG::GetConsoleUniqueId();
-    ui->label_console_id->setText("Console ID: 0x" + QString::number(console_id, 16).toUpper());
+    ui->label_console_id->setText(
+        tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper()));
 }
 }
 
 
 void ConfigureSystem::applyConfiguration() {
 void ConfigureSystem::applyConfiguration() {

+ 102 - 0
src/citra_qt/configuration/configure_web.cpp

@@ -0,0 +1,102 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QMessageBox>
+#include "citra_qt/configuration/configure_web.h"
+#include "core/settings.h"
+#include "core/telemetry_session.h"
+#include "ui_configure_web.h"
+
+ConfigureWeb::ConfigureWeb(QWidget* parent)
+    : QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) {
+    ui->setupUi(this);
+    connect(ui->button_regenerate_telemetry_id, &QPushButton::clicked, this,
+            &ConfigureWeb::RefreshTelemetryID);
+    connect(ui->button_verify_login, &QPushButton::clicked, this, &ConfigureWeb::VerifyLogin);
+    connect(this, &ConfigureWeb::LoginVerified, this, &ConfigureWeb::OnLoginVerified);
+
+    this->setConfiguration();
+}
+
+ConfigureWeb::~ConfigureWeb() {}
+
+void ConfigureWeb::setConfiguration() {
+    ui->web_credentials_disclaimer->setWordWrap(true);
+    ui->telemetry_learn_more->setOpenExternalLinks(true);
+    ui->telemetry_learn_more->setText(tr("<a "
+                                         "href='https://citra-emu.org/entry/"
+                                         "telemetry-and-why-thats-a-good-thing/'>Learn more</a>"));
+
+    ui->web_signup_link->setOpenExternalLinks(true);
+    ui->web_signup_link->setText(tr("<a href='https://services.citra-emu.org/'>Sign up</a>"));
+    ui->web_token_info_link->setOpenExternalLinks(true);
+    ui->web_token_info_link->setText(
+        tr("<a href='https://citra-emu.org/wiki/citra-web-service/'>What is my token?</a>"));
+
+    ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry);
+    ui->edit_username->setText(QString::fromStdString(Settings::values.citra_username));
+    ui->edit_token->setText(QString::fromStdString(Settings::values.citra_token));
+    // Connect after setting the values, to avoid calling OnLoginChanged now
+    connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
+    connect(ui->edit_username, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
+    ui->label_telemetry_id->setText(
+        tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper()));
+    user_verified = true;
+}
+
+void ConfigureWeb::applyConfiguration() {
+    Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked();
+    if (user_verified) {
+        Settings::values.citra_username = ui->edit_username->text().toStdString();
+        Settings::values.citra_token = ui->edit_token->text().toStdString();
+    } else {
+        QMessageBox::warning(this, tr("Username and token not verfied"),
+                             tr("Username and token were not verified. The changes to your "
+                                "username and/or token have not been saved."));
+    }
+    Settings::Apply();
+}
+
+void ConfigureWeb::RefreshTelemetryID() {
+    const u64 new_telemetry_id{Core::RegenerateTelemetryId()};
+    ui->label_telemetry_id->setText(
+        tr("Telemetry ID: 0x%1").arg(QString::number(new_telemetry_id, 16).toUpper()));
+}
+
+void ConfigureWeb::OnLoginChanged() {
+    if (ui->edit_username->text().isEmpty() && ui->edit_token->text().isEmpty()) {
+        user_verified = true;
+        ui->label_username_verified->setPixmap(QPixmap(":/icons/checked.png"));
+        ui->label_token_verified->setPixmap(QPixmap(":/icons/checked.png"));
+    } else {
+        user_verified = false;
+        ui->label_username_verified->setPixmap(QPixmap(":/icons/failed.png"));
+        ui->label_token_verified->setPixmap(QPixmap(":/icons/failed.png"));
+    }
+}
+
+void ConfigureWeb::VerifyLogin() {
+    verified =
+        Core::VerifyLogin(ui->edit_username->text().toStdString(),
+                          ui->edit_token->text().toStdString(), [&]() { emit LoginVerified(); });
+    ui->button_verify_login->setDisabled(true);
+    ui->button_verify_login->setText(tr("Verifying"));
+}
+
+void ConfigureWeb::OnLoginVerified() {
+    ui->button_verify_login->setEnabled(true);
+    ui->button_verify_login->setText(tr("Verify"));
+    if (verified.get()) {
+        user_verified = true;
+        ui->label_username_verified->setPixmap(QPixmap(":/icons/checked.png"));
+        ui->label_token_verified->setPixmap(QPixmap(":/icons/checked.png"));
+    } else {
+        ui->label_username_verified->setPixmap(QPixmap(":/icons/failed.png"));
+        ui->label_token_verified->setPixmap(QPixmap(":/icons/failed.png"));
+        QMessageBox::critical(
+            this, tr("Verification failed"),
+            tr("Verification failed. Check that you have entered your username and token "
+               "correctly, and that your internet connection is working."));
+    }
+}

+ 40 - 0
src/citra_qt/configuration/configure_web.h

@@ -0,0 +1,40 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <future>
+#include <memory>
+#include <QWidget>
+
+namespace Ui {
+class ConfigureWeb;
+}
+
+class ConfigureWeb : public QWidget {
+    Q_OBJECT
+
+public:
+    explicit ConfigureWeb(QWidget* parent = nullptr);
+    ~ConfigureWeb();
+
+    void applyConfiguration();
+
+public slots:
+    void RefreshTelemetryID();
+    void OnLoginChanged();
+    void VerifyLogin();
+    void OnLoginVerified();
+
+signals:
+    void LoginVerified();
+
+private:
+    void setConfiguration();
+
+    bool user_verified = true;
+    std::future<bool> verified;
+
+    std::unique_ptr<Ui::ConfigureWeb> ui;
+};

+ 190 - 0
src/citra_qt/configuration/configure_web.ui

@@ -0,0 +1,190 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureWeb</class>
+ <widget class="QWidget" name="ConfigureWeb">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>926</width>
+    <height>561</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <layout class="QVBoxLayout" name="verticalLayout_3">
+     <item>
+      <widget class="QGroupBox" name="groupBoxWebConfig">
+       <property name="title">
+        <string>Citra Web Service</string>
+       </property>
+       <layout class="QVBoxLayout" name="verticalLayoutCitraWebService">
+        <item>
+         <widget class="QLabel" name="web_credentials_disclaimer">
+          <property name="text">
+           <string>By providing your username and token, you agree to allow Citra to collect additional usage data, which may include user identifying information.</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <layout class="QGridLayout" name="gridLayoutCitraUsername">
+          <item row="2" column="3">
+           <widget class="QPushButton" name="button_verify_login">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="layoutDirection">
+             <enum>Qt::RightToLeft</enum>
+            </property>
+            <property name="text">
+             <string>Verify</string>
+            </property>
+           </widget>
+          </item>
+          <item row="2" column="0">
+           <widget class="QLabel" name="web_signup_link">
+            <property name="text">
+             <string>Sign up</string>
+            </property>
+           </widget>
+          </item>
+          <item row="0" column="1" colspan="3">
+           <widget class="QLineEdit" name="edit_username">
+            <property name="maxLength">
+             <number>36</number>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="0">
+           <widget class="QLabel" name="label_token">
+            <property name="text">
+             <string>Token: </string>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="4">
+           <widget class="QLabel" name="label_token_verified">
+           </widget>
+          </item>
+          <item row="0" column="0">
+           <widget class="QLabel" name="label_username">
+            <property name="text">
+             <string>Username: </string>
+            </property>
+           </widget>
+          </item>
+          <item row="0" column="4">
+           <widget class="QLabel" name="label_username_verified">
+           </widget>
+          </item>
+          <item row="1" column="1" colspan="3">
+           <widget class="QLineEdit" name="edit_token">
+            <property name="maxLength">
+             <number>36</number>
+            </property>
+            <property name="echoMode">
+             <enum>QLineEdit::Password</enum>
+            </property>
+           </widget>
+          </item>
+          <item row="2" column="1">
+           <widget class="QLabel" name="web_token_info_link">
+            <property name="text">
+             <string>What is my token?</string>
+            </property>
+           </widget>
+          </item>
+          <item row="2" column="2">
+           <spacer name="horizontalSpacer">
+            <property name="orientation">
+             <enum>Qt::Horizontal</enum>
+            </property>
+            <property name="sizeHint" stdset="0">
+             <size>
+              <width>40</width>
+              <height>20</height>
+             </size>
+            </property>
+           </spacer>
+          </item>
+         </layout>
+        </item>
+       </layout>
+      </widget>
+     </item>
+     <item>
+      <widget class="QGroupBox" name="groupBox">
+       <property name="title">
+        <string>Telemetry</string>
+       </property>
+       <layout class="QVBoxLayout" name="verticalLayout_2">
+        <item>
+         <widget class="QCheckBox" name="toggle_telemetry">
+          <property name="text">
+           <string>Share anonymous usage data with the Citra team</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QLabel" name="telemetry_learn_more">
+          <property name="text">
+           <string>Learn more</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <layout class="QGridLayout" name="gridLayoutTelemetryId">
+          <item row="0" column="0">
+           <widget class="QLabel" name="label_telemetry_id">
+            <property name="text">
+             <string>Telemetry ID:</string>
+            </property>
+           </widget>
+          </item>
+          <item row="0" column="1">
+           <widget class="QPushButton" name="button_regenerate_telemetry_id">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="layoutDirection">
+             <enum>Qt::RightToLeft</enum>
+            </property>
+            <property name="text">
+             <string>Regenerate</string>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </item>
+       </layout>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>40</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 2 - 2
src/citra_qt/debugger/graphics/graphics_cmdlists.cpp

@@ -26,8 +26,8 @@
 namespace {
 namespace {
 QImage LoadTexture(const u8* src, const Pica::Texture::TextureInfo& info) {
 QImage LoadTexture(const u8* src, const Pica::Texture::TextureInfo& info) {
     QImage decoded_image(info.width, info.height, QImage::Format_ARGB32);
     QImage decoded_image(info.width, info.height, QImage::Format_ARGB32);
-    for (int y = 0; y < info.height; ++y) {
-        for (int x = 0; x < info.width; ++x) {
+    for (u32 y = 0; y < info.height; ++y) {
+        for (u32 x = 0; x < info.width; ++x) {
             Math::Vec4<u8> color = Pica::Texture::LookupTexture(src, x, y, info, true);
             Math::Vec4<u8> color = Pica::Texture::LookupTexture(src, x, y, info, true);
             decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a()));
             decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a()));
         }
         }

+ 2 - 1
src/citra_qt/debugger/graphics/graphics_surface.cpp

@@ -273,7 +273,8 @@ void GraphicsSurfaceWidget::Pick(int x, int y) {
     surface_picker_x_control->setValue(x);
     surface_picker_x_control->setValue(x);
     surface_picker_y_control->setValue(y);
     surface_picker_y_control->setValue(y);
 
 
-    if (x < 0 || x >= surface_width || y < 0 || y >= surface_height) {
+    if (x < 0 || x >= static_cast<int>(surface_width) || y < 0 ||
+        y >= static_cast<int>(surface_height)) {
         surface_info_label->setText(tr("Pixel out of bounds"));
         surface_info_label->setText(tr("Pixel out of bounds"));
         surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
         surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
         return;
         return;

+ 18 - 23
src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp

@@ -183,23 +183,13 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con
                     print_input(output, src1, swizzle.negate_src1,
                     print_input(output, src1, swizzle.negate_src1,
                                 SelectorToString(swizzle.src1_selector));
                                 SelectorToString(swizzle.src1_selector));
                     AlignToColumn(kInputOperandColumnWidth);
                     AlignToColumn(kInputOperandColumnWidth);
-                    if (src_is_inverted) {
-                        print_input(output, src2, swizzle.negate_src2,
-                                    SelectorToString(swizzle.src2_selector));
-                    } else {
-                        print_input(output, src2, swizzle.negate_src2,
-                                    SelectorToString(swizzle.src2_selector), true,
-                                    instr.mad.AddressRegisterName());
-                    }
+                    print_input(output, src2, swizzle.negate_src2,
+                                SelectorToString(swizzle.src2_selector), true,
+                                src_is_inverted ? "" : instr.mad.AddressRegisterName());
                     AlignToColumn(kInputOperandColumnWidth);
                     AlignToColumn(kInputOperandColumnWidth);
-                    if (src_is_inverted) {
-                        print_input(output, src3, swizzle.negate_src3,
-                                    SelectorToString(swizzle.src3_selector), true,
-                                    instr.mad.AddressRegisterName());
-                    } else {
-                        print_input(output, src3, swizzle.negate_src3,
-                                    SelectorToString(swizzle.src3_selector));
-                    }
+                    print_input(output, src3, swizzle.negate_src3,
+                                SelectorToString(swizzle.src3_selector), true,
+                                src_is_inverted ? instr.mad.AddressRegisterName() : "");
                     AlignToColumn(kInputOperandColumnWidth);
                     AlignToColumn(kInputOperandColumnWidth);
                     break;
                     break;
                 }
                 }
@@ -222,16 +212,15 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con
                         SourceRegister src1 = instr.common.GetSrc1(src_is_inverted);
                         SourceRegister src1 = instr.common.GetSrc1(src_is_inverted);
                         print_input(output, src1, swizzle.negate_src1,
                         print_input(output, src1, swizzle.negate_src1,
                                     swizzle.SelectorToString(false), true,
                                     swizzle.SelectorToString(false), true,
-                                    instr.common.AddressRegisterName());
+                                    src_is_inverted ? "" : instr.common.AddressRegisterName());
                         AlignToColumn(kInputOperandColumnWidth);
                         AlignToColumn(kInputOperandColumnWidth);
                     }
                     }
 
 
-                    // TODO: In some cases, the Address Register is used as an index for SRC2
-                    // instead of SRC1
                     if (opcode_info.subtype & OpCode::Info::Src2) {
                     if (opcode_info.subtype & OpCode::Info::Src2) {
                         SourceRegister src2 = instr.common.GetSrc2(src_is_inverted);
                         SourceRegister src2 = instr.common.GetSrc2(src_is_inverted);
                         print_input(output, src2, swizzle.negate_src2,
                         print_input(output, src2, swizzle.negate_src2,
-                                    swizzle.SelectorToString(true));
+                                    swizzle.SelectorToString(true), true,
+                                    src_is_inverted ? instr.common.AddressRegisterName() : "");
                         AlignToColumn(kInputOperandColumnWidth);
                         AlignToColumn(kInputOperandColumnWidth);
                     }
                     }
                     break;
                     break;
@@ -247,7 +236,9 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con
 
 
                 switch (opcode.EffectiveOpCode()) {
                 switch (opcode.EffectiveOpCode()) {
                 case OpCode::Id::LOOP:
                 case OpCode::Id::LOOP:
-                    output << "(unknown instruction format)";
+                    output << 'i' << instr.flow_control.int_uniform_id << " (end on 0x"
+                           << std::setw(4) << std::right << std::setfill('0') << std::hex
+                           << (4 * instr.flow_control.dest_offset) << ")";
                     break;
                     break;
 
 
                 default:
                 default:
@@ -255,7 +246,7 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con
                         output << '(';
                         output << '(';
 
 
                         if (instr.flow_control.op != instr.flow_control.JustY) {
                         if (instr.flow_control.op != instr.flow_control.JustY) {
-                            if (instr.flow_control.refx)
+                            if (!instr.flow_control.refx)
                                 output << '!';
                                 output << '!';
                             output << "cc.x";
                             output << "cc.x";
                         }
                         }
@@ -267,13 +258,17 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con
                         }
                         }
 
 
                         if (instr.flow_control.op != instr.flow_control.JustX) {
                         if (instr.flow_control.op != instr.flow_control.JustX) {
-                            if (instr.flow_control.refy)
+                            if (!instr.flow_control.refy)
                                 output << '!';
                                 output << '!';
                             output << "cc.y";
                             output << "cc.y";
                         }
                         }
 
 
                         output << ") ";
                         output << ") ";
                     } else if (opcode_info.subtype & OpCode::Info::HasUniformIndex) {
                     } else if (opcode_info.subtype & OpCode::Info::HasUniformIndex) {
+                        if (opcode.EffectiveOpCode() == OpCode::Id::JMPU &&
+                            (instr.flow_control.num_instructions & 1) == 1) {
+                            output << '!';
+                        }
                         output << 'b' << instr.flow_control.bool_uniform_id << ' ';
                         output << 'b' << instr.flow_control.bool_uniform_id << ' ';
                     }
                     }
 
 

+ 47 - 1
src/citra_qt/main.cpp

@@ -48,6 +48,47 @@
 Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
 Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
 #endif
 #endif
 
 
+/**
+ * "Callouts" are one-time instructional messages shown to the user. In the config settings, there
+ * is a bitfield "callout_flags" options, used to track if a message has already been shown to the
+ * user. This is 32-bits - if we have more than 32 callouts, we should retire and recyle old ones.
+ */
+enum class CalloutFlag : uint32_t {
+    Telemetry = 0x1,
+};
+
+static void ShowCalloutMessage(const QString& message, CalloutFlag flag) {
+    if (UISettings::values.callout_flags & static_cast<uint32_t>(flag)) {
+        return;
+    }
+
+    UISettings::values.callout_flags |= static_cast<uint32_t>(flag);
+
+    QMessageBox msg;
+    msg.setText(message);
+    msg.setStandardButtons(QMessageBox::Ok);
+    msg.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+    msg.setStyleSheet("QLabel{min-width: 900px;}");
+    msg.exec();
+}
+
+void GMainWindow::ShowCallouts() {
+    static const QString telemetry_message =
+        tr("To help improve Citra, the Citra Team collects anonymous usage data. No private or "
+           "personally identifying information is collected. This data helps us to understand how "
+           "people use Citra and prioritize our efforts. Furthermore, it helps us to more easily "
+           "identify emulation bugs and performance issues. This data includes:<ul><li>Information"
+           " about the version of Citra you are using</li><li>Performance data about the games you "
+           "play</li><li>Your configuration settings</li><li>Information about your computer "
+           "hardware</li><li>Emulation errors and crash information</li></ul>By default, this "
+           "feature is enabled. To disable this feature, click 'Emulation' from the menu and then "
+           "select 'Configure...'. Then, on the 'Web' tab, uncheck 'Share anonymous usage data with"
+           " the Citra team'. <br/><br/>By using this software, you agree to the above terms.<br/>"
+           "<br/><a href='https://citra-emu.org/entry/telemetry-and-why-thats-a-good-thing/'>Learn "
+           "more</a>");
+    ShowCalloutMessage(telemetry_message, CalloutFlag::Telemetry);
+}
+
 GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
 GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
     Pica::g_debug_context = Pica::DebugContext::Construct();
     Pica::g_debug_context = Pica::DebugContext::Construct();
     setAcceptDrops(true);
     setAcceptDrops(true);
@@ -73,6 +114,9 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
 
 
     UpdateUITheme();
     UpdateUITheme();
 
 
+    // Show one-time "callout" messages to the user
+    ShowCallouts();
+
     QStringList args = QApplication::arguments();
     QStringList args = QApplication::arguments();
     if (args.length() >= 2) {
     if (args.length() >= 2) {
         BootGame(args[1]);
         BootGame(args[1]);
@@ -311,7 +355,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
 
 
     if (!gladLoadGL()) {
     if (!gladLoadGL()) {
         QMessageBox::critical(this, tr("Error while initializing OpenGL 3.3 Core!"),
         QMessageBox::critical(this, tr("Error while initializing OpenGL 3.3 Core!"),
-                              tr("Your GPU may not support OpenGL 3.3, or you do not"
+                              tr("Your GPU may not support OpenGL 3.3, or you do not "
                                  "have the latest graphics driver."));
                                  "have the latest graphics driver."));
         return false;
         return false;
     }
     }
@@ -320,6 +364,8 @@ bool GMainWindow::LoadROM(const QString& filename) {
 
 
     const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())};
     const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())};
 
 
+    Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "Qt");
+
     if (result != Core::System::ResultStatus::Success) {
     if (result != Core::System::ResultStatus::Success) {
         switch (result) {
         switch (result) {
         case Core::System::ResultStatus::ErrorGetLoader:
         case Core::System::ResultStatus::ErrorGetLoader:

+ 2 - 0
src/citra_qt/main.h

@@ -80,6 +80,8 @@ private:
     void BootGame(const QString& filename);
     void BootGame(const QString& filename);
     void ShutdownGame();
     void ShutdownGame();
 
 
+    void ShowCallouts();
+
     /**
     /**
      * Stores the filename in the recently loaded files list.
      * Stores the filename in the recently loaded files list.
      * The new filename is stored at the beginning of the recently loaded files list.
      * The new filename is stored at the beginning of the recently loaded files list.

+ 2 - 0
src/citra_qt/ui_settings.h

@@ -48,6 +48,8 @@ struct Values {
 
 
     // Shortcut name <Shortcut, context>
     // Shortcut name <Shortcut, context>
     std::vector<Shortcut> shortcuts;
     std::vector<Shortcut> shortcuts;
+
+    uint32_t callout_flags;
 };
 };
 
 
 extern Values values;
 extern Values values;

+ 4 - 0
src/common/common_types.h

@@ -24,6 +24,7 @@
 
 
 #pragma once
 #pragma once
 
 
+#include <array>
 #include <cstdint>
 #include <cstdint>
 
 
 #ifdef _MSC_VER
 #ifdef _MSC_VER
@@ -50,6 +51,9 @@ typedef double f64; ///< 64-bit floating point
 typedef u64 VAddr; ///< Represents a pointer in the userspace virtual address space.
 typedef u64 VAddr; ///< Represents a pointer in the userspace virtual address space.
 typedef u64 PAddr; ///< Represents a pointer in the ARM11 physical address space.
 typedef u64 PAddr; ///< Represents a pointer in the ARM11 physical address space.
 
 
+using u128 = std::array<std::uint64_t, 2>;
+static_assert(sizeof(u128) == 16, "u128 must be 128 bits wide");
+
 // An inheritable class to disallow the copy constructor and operator= functions
 // An inheritable class to disallow the copy constructor and operator= functions
 class NonCopyable {
 class NonCopyable {
 protected:
 protected:

+ 5 - 0
src/common/quaternion.h

@@ -30,6 +30,11 @@ public:
         return {xyz * other.w + other.xyz * w + Cross(xyz, other.xyz),
         return {xyz * other.w + other.xyz * w + Cross(xyz, other.xyz),
                 w * other.w - Dot(xyz, other.xyz)};
                 w * other.w - Dot(xyz, other.xyz)};
     }
     }
+
+    Quaternion<T> Normalized() const {
+        T length = std::sqrt(xyz.Length2() + w * w);
+        return {xyz / length, w / length};
+    }
 };
 };
 
 
 template <typename T>
 template <typename T>

+ 2 - 0
src/common/scm_rev.cpp.in

@@ -8,6 +8,7 @@
 #define GIT_BRANCH   "@GIT_BRANCH@"
 #define GIT_BRANCH   "@GIT_BRANCH@"
 #define GIT_DESC     "@GIT_DESC@"
 #define GIT_DESC     "@GIT_DESC@"
 #define BUILD_NAME   "@REPO_NAME@"
 #define BUILD_NAME   "@REPO_NAME@"
+#define BUILD_DATE   "@BUILD_DATE@"
 
 
 namespace Common {
 namespace Common {
 
 
@@ -15,6 +16,7 @@ const char g_scm_rev[]      = GIT_REV;
 const char g_scm_branch[]   = GIT_BRANCH;
 const char g_scm_branch[]   = GIT_BRANCH;
 const char g_scm_desc[]     = GIT_DESC;
 const char g_scm_desc[]     = GIT_DESC;
 const char g_build_name[]   = BUILD_NAME;
 const char g_build_name[]   = BUILD_NAME;
+const char g_build_date[]   = BUILD_DATE;
 
 
 } // namespace
 } // namespace
 
 

+ 1 - 0
src/common/scm_rev.h

@@ -10,5 +10,6 @@ extern const char g_scm_rev[];
 extern const char g_scm_branch[];
 extern const char g_scm_branch[];
 extern const char g_scm_desc[];
 extern const char g_scm_desc[];
 extern const char g_build_name[];
 extern const char g_build_name[];
+extern const char g_build_date[];
 
 
 } // namespace
 } // namespace

+ 1 - 1
src/common/string_util.cpp

@@ -117,7 +117,7 @@ std::string StringFromFormat(const char* format, ...) {
 }
 }
 
 
 // For Debugging. Read out an u8 array.
 // For Debugging. Read out an u8 array.
-std::string ArrayToString(const u8* data, u32 size, int line_len, bool spaces) {
+std::string ArrayToString(const u8* data, size_t size, int line_len, bool spaces) {
     std::ostringstream oss;
     std::ostringstream oss;
     oss << std::setfill('0') << std::hex;
     oss << std::setfill('0') << std::hex;
 
 

+ 1 - 1
src/common/string_util.h

@@ -33,7 +33,7 @@ inline void CharArrayFromFormat(char (&out)[Count], const char* format, ...) {
 }
 }
 
 
 // Good
 // Good
-std::string ArrayToString(const u8* data, u32 size, int line_len = 20, bool spaces = true);
+std::string ArrayToString(const u8* data, size_t size, int line_len = 20, bool spaces = true);
 
 
 std::string StripSpaces(const std::string& s);
 std::string StripSpaces(const std::string& s);
 std::string StripQuotes(const std::string& s);
 std::string StripQuotes(const std::string& s);

+ 13 - 16
src/common/vector_math.h

@@ -90,8 +90,9 @@ public:
         x -= other.x;
         x -= other.x;
         y -= other.y;
         y -= other.y;
     }
     }
-    template <typename Q = T, class = typename std::enable_if<std::is_signed<Q>::value>::type>
-    Vec2<decltype(-T{})> operator-() const {
+
+    template <typename U = T>
+    Vec2<std::enable_if_t<std::is_signed<U>::value, U>> operator-() const {
         return MakeVec(-x, -y);
         return MakeVec(-x, -y);
     }
     }
     Vec2<decltype(T{} * T{})> operator*(const Vec2& other) const {
     Vec2<decltype(T{} * T{})> operator*(const Vec2& other) const {
@@ -103,8 +104,7 @@ public:
     }
     }
     template <typename V>
     template <typename V>
     void operator*=(const V& f) {
     void operator*=(const V& f) {
-        x *= f;
-        y *= f;
+        *this = *this * f;
     }
     }
     template <typename V>
     template <typename V>
     Vec2<decltype(T{} / V{})> operator/(const V& f) const {
     Vec2<decltype(T{} / V{})> operator/(const V& f) const {
@@ -247,8 +247,9 @@ public:
         y -= other.y;
         y -= other.y;
         z -= other.z;
         z -= other.z;
     }
     }
-    template <typename Q = T, class = typename std::enable_if<std::is_signed<Q>::value>::type>
-    Vec3<decltype(-T{})> operator-() const {
+
+    template <typename U = T>
+    Vec3<std::enable_if_t<std::is_signed<U>::value, U>> operator-() const {
         return MakeVec(-x, -y, -z);
         return MakeVec(-x, -y, -z);
     }
     }
     Vec3<decltype(T{} * T{})> operator*(const Vec3& other) const {
     Vec3<decltype(T{} * T{})> operator*(const Vec3& other) const {
@@ -260,9 +261,7 @@ public:
     }
     }
     template <typename V>
     template <typename V>
     void operator*=(const V& f) {
     void operator*=(const V& f) {
-        x *= f;
-        y *= f;
-        z *= f;
+        *this = *this * f;
     }
     }
     template <typename V>
     template <typename V>
     Vec3<decltype(T{} / V{})> operator/(const V& f) const {
     Vec3<decltype(T{} / V{})> operator/(const V& f) const {
@@ -462,8 +461,9 @@ public:
         z -= other.z;
         z -= other.z;
         w -= other.w;
         w -= other.w;
     }
     }
-    template <typename Q = T, class = typename std::enable_if<std::is_signed<Q>::value>::type>
-    Vec4<decltype(-T{})> operator-() const {
+
+    template <typename U = T>
+    Vec4<std::enable_if_t<std::is_signed<U>::value, U>> operator-() const {
         return MakeVec(-x, -y, -z, -w);
         return MakeVec(-x, -y, -z, -w);
     }
     }
     Vec4<decltype(T{} * T{})> operator*(const Vec4& other) const {
     Vec4<decltype(T{} * T{})> operator*(const Vec4& other) const {
@@ -475,10 +475,7 @@ public:
     }
     }
     template <typename V>
     template <typename V>
     void operator*=(const V& f) {
     void operator*=(const V& f) {
-        x *= f;
-        y *= f;
-        z *= f;
-        w *= f;
+        *this = *this * f;
     }
     }
     template <typename V>
     template <typename V>
     Vec4<decltype(T{} / V{})> operator/(const V& f) const {
     Vec4<decltype(T{} / V{})> operator/(const V& f) const {
@@ -721,4 +718,4 @@ static inline Vec4<T> MakeVec(const T& x, const Vec3<T>& yzw) {
     return MakeVec(x, yzw[0], yzw[1], yzw[2]);
     return MakeVec(x, yzw[0], yzw[1], yzw[2]);
 }
 }
 
 
-} // namespace
+} // namespace Math

+ 15 - 5
src/core/CMakeLists.txt

@@ -6,6 +6,8 @@ set(SRCS
             arm/dyncom/arm_dyncom_interpreter.cpp
             arm/dyncom/arm_dyncom_interpreter.cpp
             arm/dyncom/arm_dyncom_thumb.cpp
             arm/dyncom/arm_dyncom_thumb.cpp
             arm/dyncom/arm_dyncom_trans.cpp
             arm/dyncom/arm_dyncom_trans.cpp
+            arm/unicorn/arm_unicorn.cpp
+            arm/unicorn/unicorn_dynload.c
             arm/skyeye_common/armstate.cpp
             arm/skyeye_common/armstate.cpp
             arm/skyeye_common/armsupp.cpp
             arm/skyeye_common/armsupp.cpp
             arm/skyeye_common/vfp/vfp.cpp
             arm/skyeye_common/vfp/vfp.cpp
@@ -26,14 +28,15 @@ set(SRCS
             file_sys/archive_systemsavedata.cpp
             file_sys/archive_systemsavedata.cpp
             file_sys/disk_archive.cpp
             file_sys/disk_archive.cpp
             file_sys/ivfc_archive.cpp
             file_sys/ivfc_archive.cpp
+            file_sys/ncch_container.cpp
             file_sys/path_parser.cpp
             file_sys/path_parser.cpp
             file_sys/savedata_archive.cpp
             file_sys/savedata_archive.cpp
+            file_sys/title_metadata.cpp
             frontend/camera/blank_camera.cpp
             frontend/camera/blank_camera.cpp
             frontend/camera/factory.cpp
             frontend/camera/factory.cpp
             frontend/camera/interface.cpp
             frontend/camera/interface.cpp
             frontend/emu_window.cpp
             frontend/emu_window.cpp
             frontend/framebuffer_layout.cpp
             frontend/framebuffer_layout.cpp
-            frontend/motion_emu.cpp
             gdbstub/gdbstub.cpp
             gdbstub/gdbstub.cpp
             hle/config_mem.cpp
             hle/config_mem.cpp
             hle/applets/applet.cpp
             hle/applets/applet.cpp
@@ -60,6 +63,7 @@ set(SRCS
             hle/kernel/timer.cpp
             hle/kernel/timer.cpp
             hle/kernel/vm_manager.cpp
             hle/kernel/vm_manager.cpp
             hle/kernel/wait_object.cpp
             hle/kernel/wait_object.cpp
+            hle/lock.cpp
             hle/romfs.cpp
             hle/romfs.cpp
             hle/service/ac/ac.cpp
             hle/service/ac/ac.cpp
             hle/service/ac/ac_i.cpp
             hle/service/ac/ac_i.cpp
@@ -135,7 +139,8 @@ set(SRCS
             hle/service/nim/nim_aoc.cpp
             hle/service/nim/nim_aoc.cpp
             hle/service/nim/nim_s.cpp
             hle/service/nim/nim_s.cpp
             hle/service/nim/nim_u.cpp
             hle/service/nim/nim_u.cpp
-            hle/service/ns_s.cpp
+            hle/service/ns/ns.cpp
+            hle/service/ns/ns_s.cpp
             hle/service/nwm/nwm.cpp
             hle/service/nwm/nwm.cpp
             hle/service/nwm/nwm_cec.cpp
             hle/service/nwm/nwm_cec.cpp
             hle/service/nwm/nwm_ext.cpp
             hle/service/nwm/nwm_ext.cpp
@@ -145,6 +150,7 @@ set(SRCS
             hle/service/nwm/nwm_tst.cpp
             hle/service/nwm/nwm_tst.cpp
             hle/service/nwm/nwm_uds.cpp
             hle/service/nwm/nwm_uds.cpp
             hle/service/nwm/uds_beacon.cpp
             hle/service/nwm/uds_beacon.cpp
+            hle/service/nwm/uds_connection.cpp
             hle/service/nwm/uds_data.cpp
             hle/service/nwm/uds_data.cpp
             hle/service/pm_app.cpp
             hle/service/pm_app.cpp
             hle/service/ptm/ptm.cpp
             hle/service/ptm/ptm.cpp
@@ -198,6 +204,8 @@ set(HEADERS
             arm/dyncom/arm_dyncom_run.h
             arm/dyncom/arm_dyncom_run.h
             arm/dyncom/arm_dyncom_thumb.h
             arm/dyncom/arm_dyncom_thumb.h
             arm/dyncom/arm_dyncom_trans.h
             arm/dyncom/arm_dyncom_trans.h
+            arm/unicorn/arm_unicorn.h
+            arm/unicorn/unicorn_dynload.h
             arm/skyeye_common/arm_regformat.h
             arm/skyeye_common/arm_regformat.h
             arm/skyeye_common/armstate.h
             arm/skyeye_common/armstate.h
             arm/skyeye_common/armsupp.h
             arm/skyeye_common/armsupp.h
@@ -229,7 +237,6 @@ set(HEADERS
             frontend/emu_window.h
             frontend/emu_window.h
             frontend/framebuffer_layout.h
             frontend/framebuffer_layout.h
             frontend/input.h
             frontend/input.h
-            frontend/motion_emu.h
             gdbstub/gdbstub.h
             gdbstub/gdbstub.h
             hle/config_mem.h
             hle/config_mem.h
             hle/function_wrappers.h
             hle/function_wrappers.h
@@ -261,6 +268,7 @@ set(HEADERS
             hle/kernel/timer.h
             hle/kernel/timer.h
             hle/kernel/vm_manager.h
             hle/kernel/vm_manager.h
             hle/kernel/wait_object.h
             hle/kernel/wait_object.h
+            hle/lock.h
             hle/result.h
             hle/result.h
             hle/romfs.h
             hle/romfs.h
             hle/service/ac/ac.h
             hle/service/ac/ac.h
@@ -337,7 +345,8 @@ set(HEADERS
             hle/service/nim/nim_aoc.h
             hle/service/nim/nim_aoc.h
             hle/service/nim/nim_s.h
             hle/service/nim/nim_s.h
             hle/service/nim/nim_u.h
             hle/service/nim/nim_u.h
-            hle/service/ns_s.h
+            hle/service/ns/ns.h
+            hle/service/ns/ns_s.h
             hle/service/nwm/nwm.h
             hle/service/nwm/nwm.h
             hle/service/nwm/nwm_cec.h
             hle/service/nwm/nwm_cec.h
             hle/service/nwm/nwm_ext.h
             hle/service/nwm/nwm_ext.h
@@ -347,6 +356,7 @@ set(HEADERS
             hle/service/nwm/nwm_tst.h
             hle/service/nwm/nwm_tst.h
             hle/service/nwm/nwm_uds.h
             hle/service/nwm/nwm_uds.h
             hle/service/nwm/uds_beacon.h
             hle/service/nwm/uds_beacon.h
+            hle/service/nwm/uds_connection.h
             hle/service/nwm/uds_data.h
             hle/service/nwm/uds_data.h
             hle/service/pm_app.h
             hle/service/pm_app.h
             hle/service/ptm/ptm.h
             hle/service/ptm/ptm.h
@@ -394,7 +404,7 @@ set(HEADERS
 
 
 create_directory_groups(${SRCS} ${HEADERS})
 create_directory_groups(${SRCS} ${HEADERS})
 add_library(core STATIC ${SRCS} ${HEADERS})
 add_library(core STATIC ${SRCS} ${HEADERS})
-target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
+target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core)
 target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp dynarmic fmt lz4_static)
 target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp dynarmic fmt lz4_static)
 if (ENABLE_WEB_SERVICE)
 if (ENABLE_WEB_SERVICE)
     target_link_libraries(core PUBLIC json-headers web_service)
     target_link_libraries(core PUBLIC json-headers web_service)

+ 12 - 10
src/core/arm/arm_interface.h

@@ -5,6 +5,7 @@
 #pragma once
 #pragma once
 
 
 #include "common/common_types.h"
 #include "common/common_types.h"
+#include "core/hle/kernel/vm_manager.h"
 #include "core/arm/skyeye_common/arm_regformat.h"
 #include "core/arm/skyeye_common/arm_regformat.h"
 #include "core/arm/skyeye_common/vfp/asm_vfp.h"
 #include "core/arm/skyeye_common/vfp/asm_vfp.h"
 
 
@@ -19,10 +20,11 @@ public:
         u64 sp;
         u64 sp;
         u64 pc;
         u64 pc;
         u64 cpsr;
         u64 cpsr;
-        u64 fpu_registers[64];
+        u128 fpu_registers[32];
         u64 fpscr;
         u64 fpscr;
         u64 fpexc;
         u64 fpexc;
 
 
+
         // TODO(bunnei): Fix once we have proper support for tpidrro_el0, etc. in the JIT
         // TODO(bunnei): Fix once we have proper support for tpidrro_el0, etc. in the JIT
         VAddr tls_address;
         VAddr tls_address;
     };
     };
@@ -41,9 +43,14 @@ public:
         Run(1);
         Run(1);
     }
     }
 
 
+    virtual void MapBackingMemory(VAddr address, size_t size, u8* memory, Kernel::VMAPermission perms) {}
+
     /// Clear all instruction cache
     /// Clear all instruction cache
     virtual void ClearInstructionCache() = 0;
     virtual void ClearInstructionCache() = 0;
 
 
+    /// Notify CPU emulation that page tables have changed
+    virtual void PageTableChanged() = 0;
+
     /**
     /**
      * Set the Program Counter to an address
      * Set the Program Counter to an address
      * @param addr Address to set PC to
      * @param addr Address to set PC to
@@ -70,6 +77,10 @@ public:
      */
      */
     virtual void SetReg(int index, u64 value) = 0;
     virtual void SetReg(int index, u64 value) = 0;
 
 
+    virtual const u128& GetExtReg(int index) const = 0;
+
+    virtual void SetExtReg(int index, u128& value) = 0;
+
     /**
     /**
      * Gets the value of a VFP register
      * Gets the value of a VFP register
      * @param index Register index (0-31)
      * @param index Register index (0-31)
@@ -128,12 +139,6 @@ public:
 
 
     virtual void SetTlsAddress(VAddr address) = 0;
     virtual void SetTlsAddress(VAddr address) = 0;
 
 
-    /**
-     * Advance the CPU core by the specified number of ticks (e.g. to simulate CPU execution time)
-     * @param ticks Number of ticks to advance the CPU core
-     */
-    virtual void AddTicks(u64 ticks) = 0;
-
     /**
     /**
      * Saves the current CPU context
      * Saves the current CPU context
      * @param ctx Thread context to save
      * @param ctx Thread context to save
@@ -154,9 +159,6 @@ public:
         return num_instructions;
         return num_instructions;
     }
     }
 
 
-    s64 down_count = 0; ///< A decreasing counter of remaining cycles before the next event,
-                        /// decreased by the cpu run loop
-
 protected:
 protected:
     /**
     /**
      * Executes the given number of instructions
      * Executes the given number of instructions

+ 35 - 58
src/core/arm/dynarmic/arm_dynarmic.cpp

@@ -16,24 +16,6 @@
 
 
 static void InterpreterFallback(u64 pc, Dynarmic::Jit* jit, void* user_arg) {
 static void InterpreterFallback(u64 pc, Dynarmic::Jit* jit, void* user_arg) {
     UNIMPLEMENTED_MSG("InterpreterFallback for ARM64 JIT does not exist!");
     UNIMPLEMENTED_MSG("InterpreterFallback for ARM64 JIT does not exist!");
-    //ARMul_State* state = static_cast<ARMul_State*>(user_arg);
-
-    //state->Reg = jit->Regs();
-    //state->Cpsr = jit->Cpsr();
-    //state->Reg[15] = static_cast<u32>(pc);
-    //state->ExtReg = jit->ExtRegs();
-    //state->VFP[VFP_FPSCR] = jit->Fpscr();
-    //state->NumInstrsToExecute = 1;
-
-    //InterpreterMainLoop(state);
-
-    //bool is_thumb = (state->Cpsr & (1 << 5)) != 0;
-    //state->Reg[15] &= (is_thumb ? 0xFFFFFFFE : 0xFFFFFFFC);
-
-    //jit->Regs() = state->Reg;
-    //jit->Cpsr() = state->Cpsr;
-    //jit->ExtRegs() = state->ExtReg;
-    //jit->SetFpscr(state->VFP[VFP_FPSCR]);
 }
 }
 
 
 static bool IsReadOnlyMemory(u64 vaddr) {
 static bool IsReadOnlyMemory(u64 vaddr) {
@@ -73,11 +55,10 @@ void MemoryWrite64(const u64 addr, const u64 data) {
     Memory::Write64(static_cast<VAddr>(addr), data);
     Memory::Write64(static_cast<VAddr>(addr), data);
 }
 }
 
 
-static Dynarmic::UserCallbacks GetUserCallbacks(
-    const std::shared_ptr<ARMul_State>& interpeter_state) {
+static Dynarmic::UserCallbacks GetUserCallbacks(ARM_Dynarmic* this_) {
     Dynarmic::UserCallbacks user_callbacks{};
     Dynarmic::UserCallbacks user_callbacks{};
-    //user_callbacks.InterpreterFallback = &InterpreterFallback;
-    //user_callbacks.user_arg = static_cast<void*>(interpeter_state.get());
+    user_callbacks.InterpreterFallback = &InterpreterFallback;
+    user_callbacks.user_arg = static_cast<void*>(this_);
     user_callbacks.CallSVC = &SVC::CallSVC;
     user_callbacks.CallSVC = &SVC::CallSVC;
     user_callbacks.memory.IsReadOnlyMemory = &IsReadOnlyMemory;
     user_callbacks.memory.IsReadOnlyMemory = &IsReadOnlyMemory;
     user_callbacks.memory.ReadCode = &MemoryRead32;
     user_callbacks.memory.ReadCode = &MemoryRead32;
@@ -90,13 +71,13 @@ static Dynarmic::UserCallbacks GetUserCallbacks(
     user_callbacks.memory.Write32 = &MemoryWrite32;
     user_callbacks.memory.Write32 = &MemoryWrite32;
     user_callbacks.memory.Write64 = &MemoryWrite64;
     user_callbacks.memory.Write64 = &MemoryWrite64;
     //user_callbacks.page_table = Memory::GetCurrentPageTablePointers();
     //user_callbacks.page_table = Memory::GetCurrentPageTablePointers();
-    user_callbacks.coprocessors[15] = std::make_shared<DynarmicCP15>(interpeter_state);
     return user_callbacks;
     return user_callbacks;
 }
 }
 
 
 ARM_Dynarmic::ARM_Dynarmic(PrivilegeMode initial_mode) {
 ARM_Dynarmic::ARM_Dynarmic(PrivilegeMode initial_mode) {
-    interpreter_state = std::make_shared<ARMul_State>(initial_mode);
-    jit = std::make_unique<Dynarmic::Jit>(GetUserCallbacks(interpreter_state), Dynarmic::Arch::ARM64);
+}
+
+void ARM_Dynarmic::MapBackingMemory(VAddr address, size_t size, u8* memory, Kernel::VMAPermission perms) {
 }
 }
 
 
 void ARM_Dynarmic::SetPC(u64 pc) {
 void ARM_Dynarmic::SetPC(u64 pc) {
@@ -115,30 +96,26 @@ void ARM_Dynarmic::SetReg(int index, u64 value) {
     jit->Regs64()[index] = value;
     jit->Regs64()[index] = value;
 }
 }
 
 
+const u128& ARM_Dynarmic::GetExtReg(int index) const {
+    return jit->ExtRegs64()[index];
+}
+
+void ARM_Dynarmic::SetExtReg(int index, u128& value) {
+    jit->ExtRegs64()[index] = value;
+}
+
 u32 ARM_Dynarmic::GetVFPReg(int index) const {
 u32 ARM_Dynarmic::GetVFPReg(int index) const {
-    return jit->ExtRegs()[index];
+    return {};
 }
 }
 
 
 void ARM_Dynarmic::SetVFPReg(int index, u32 value) {
 void ARM_Dynarmic::SetVFPReg(int index, u32 value) {
-    jit->ExtRegs()[index] = value;
 }
 }
 
 
 u32 ARM_Dynarmic::GetVFPSystemReg(VFPSystemRegister reg) const {
 u32 ARM_Dynarmic::GetVFPSystemReg(VFPSystemRegister reg) const {
-    if (reg == VFP_FPSCR) {
-        return jit->Fpscr();
-    }
-
-    // Dynarmic does not implement and/or expose other VFP registers, fallback to interpreter state
-    return interpreter_state->VFP[reg];
+    return {};
 }
 }
 
 
 void ARM_Dynarmic::SetVFPSystemReg(VFPSystemRegister reg, u32 value) {
 void ARM_Dynarmic::SetVFPSystemReg(VFPSystemRegister reg, u32 value) {
-    if (reg == VFP_FPSCR) {
-        jit->SetFpscr(value);
-    }
-
-    // Dynarmic does not implement and/or expose other VFP registers, fallback to interpreter state
-    interpreter_state->VFP[reg] = value;
 }
 }
 
 
 u32 ARM_Dynarmic::GetCPSR() const {
 u32 ARM_Dynarmic::GetCPSR() const {
@@ -150,11 +127,10 @@ void ARM_Dynarmic::SetCPSR(u32 cpsr) {
 }
 }
 
 
 u32 ARM_Dynarmic::GetCP15Register(CP15Register reg) {
 u32 ARM_Dynarmic::GetCP15Register(CP15Register reg) {
-    return interpreter_state->CP15[reg];
+    return {};
 }
 }
 
 
 void ARM_Dynarmic::SetCP15Register(CP15Register reg, u32 value) {
 void ARM_Dynarmic::SetCP15Register(CP15Register reg, u32 value) {
-    interpreter_state->CP15[reg] = value;
 }
 }
 
 
 VAddr ARM_Dynarmic::GetTlsAddress() const {
 VAddr ARM_Dynarmic::GetTlsAddress() const {
@@ -165,51 +141,39 @@ void ARM_Dynarmic::SetTlsAddress(VAddr address) {
     jit->TlsAddr() = address;
     jit->TlsAddr() = address;
 }
 }
 
 
-void ARM_Dynarmic::AddTicks(u64 ticks) {
-    down_count -= ticks;
-    if (down_count < 0) {
-        CoreTiming::Advance();
-    }
-}
-
 MICROPROFILE_DEFINE(ARM_Jit, "ARM JIT", "ARM JIT", MP_RGB(255, 64, 64));
 MICROPROFILE_DEFINE(ARM_Jit, "ARM JIT", "ARM JIT", MP_RGB(255, 64, 64));
 
 
 void ARM_Dynarmic::ExecuteInstructions(int num_instructions) {
 void ARM_Dynarmic::ExecuteInstructions(int num_instructions) {
+    ASSERT(Memory::GetCurrentPageTable() == current_page_table);
     MICROPROFILE_SCOPE(ARM_Jit);
     MICROPROFILE_SCOPE(ARM_Jit);
 
 
-    unsigned ticks_executed = jit->Run(1 /*static_cast<unsigned>(num_instructions)*/);
+    std::size_t ticks_executed = jit->Run(static_cast<unsigned>(num_instructions));
 
 
-    AddTicks(ticks_executed);
+    CoreTiming::AddTicks(ticks_executed);
 }
 }
 
 
 void ARM_Dynarmic::SaveContext(ARM_Interface::ThreadContext& ctx) {
 void ARM_Dynarmic::SaveContext(ARM_Interface::ThreadContext& ctx) {
     memcpy(ctx.cpu_registers, jit->Regs64().data(), sizeof(ctx.cpu_registers));
     memcpy(ctx.cpu_registers, jit->Regs64().data(), sizeof(ctx.cpu_registers));
-    //memcpy(ctx.fpu_registers, jit->ExtRegs().data(), sizeof(ctx.fpu_registers));
+    memcpy(ctx.fpu_registers, jit->ExtRegs64().data(), sizeof(ctx.fpu_registers));
 
 
     ctx.lr = jit->Regs64()[30];
     ctx.lr = jit->Regs64()[30];
     ctx.sp = jit->Regs64()[31];
     ctx.sp = jit->Regs64()[31];
     ctx.pc = jit->Regs64()[32];
     ctx.pc = jit->Regs64()[32];
     ctx.cpsr = jit->Cpsr();
     ctx.cpsr = jit->Cpsr();
 
 
-    ctx.fpscr = jit->Fpscr();
-    ctx.fpexc = interpreter_state->VFP[VFP_FPEXC];
-
     // TODO(bunnei): Fix once we have proper support for tpidrro_el0, etc. in the JIT
     // TODO(bunnei): Fix once we have proper support for tpidrro_el0, etc. in the JIT
     ctx.tls_address = jit->TlsAddr();
     ctx.tls_address = jit->TlsAddr();
 }
 }
 
 
 void ARM_Dynarmic::LoadContext(const ARM_Interface::ThreadContext& ctx) {
 void ARM_Dynarmic::LoadContext(const ARM_Interface::ThreadContext& ctx) {
     memcpy(jit->Regs64().data(), ctx.cpu_registers, sizeof(ctx.cpu_registers));
     memcpy(jit->Regs64().data(), ctx.cpu_registers, sizeof(ctx.cpu_registers));
-    //memcpy(jit->ExtRegs().data(), ctx.fpu_registers, sizeof(ctx.fpu_registers));
+    memcpy(jit->ExtRegs64().data(), ctx.fpu_registers, sizeof(ctx.fpu_registers));
 
 
     jit->Regs64()[30] = ctx.lr;
     jit->Regs64()[30] = ctx.lr;
     jit->Regs64()[31] = ctx.sp;
     jit->Regs64()[31] = ctx.sp;
     jit->Regs64()[32] = ctx.pc;
     jit->Regs64()[32] = ctx.pc;
     jit->Cpsr() = ctx.cpsr;
     jit->Cpsr() = ctx.cpsr;
 
 
-    jit->SetFpscr(ctx.fpscr);
-    interpreter_state->VFP[VFP_FPEXC] = ctx.fpexc;
-
     // TODO(bunnei): Fix once we have proper support for tpidrro_el0, etc. in the JIT
     // TODO(bunnei): Fix once we have proper support for tpidrro_el0, etc. in the JIT
     jit->TlsAddr() = ctx.tls_address;
     jit->TlsAddr() = ctx.tls_address;
 }
 }
@@ -223,3 +187,16 @@ void ARM_Dynarmic::PrepareReschedule() {
 void ARM_Dynarmic::ClearInstructionCache() {
 void ARM_Dynarmic::ClearInstructionCache() {
     jit->ClearCache();
     jit->ClearCache();
 }
 }
+
+void ARM_Dynarmic::PageTableChanged() {
+    current_page_table = Memory::GetCurrentPageTable();
+
+    auto iter = jits.find(current_page_table);
+    if (iter != jits.end()) {
+        jit = iter->second.get();
+        return;
+    }
+
+    jit = new Dynarmic::Jit(GetUserCallbacks(this), Dynarmic::Arch::ARM64);
+    jits.emplace(current_page_table, std::unique_ptr<Dynarmic::Jit>(jit));
+}

+ 13 - 4
src/core/arm/dynarmic/arm_dynarmic.h

@@ -4,20 +4,29 @@
 
 
 #pragma once
 #pragma once
 
 
+#include <map>
 #include <memory>
 #include <memory>
 #include <dynarmic/dynarmic.h>
 #include <dynarmic/dynarmic.h>
 #include "common/common_types.h"
 #include "common/common_types.h"
 #include "core/arm/arm_interface.h"
 #include "core/arm/arm_interface.h"
 #include "core/arm/skyeye_common/armstate.h"
 #include "core/arm/skyeye_common/armstate.h"
 
 
+namespace Memory {
+struct PageTable;
+} // namespace Memory
+
 class ARM_Dynarmic final : public ARM_Interface {
 class ARM_Dynarmic final : public ARM_Interface {
 public:
 public:
     ARM_Dynarmic(PrivilegeMode initial_mode);
     ARM_Dynarmic(PrivilegeMode initial_mode);
 
 
+    void MapBackingMemory(VAddr address, size_t size, u8* memory, Kernel::VMAPermission perms) override;
+
     void SetPC(u64 pc) override;
     void SetPC(u64 pc) override;
     u64 GetPC() const override;
     u64 GetPC() const override;
     u64 GetReg(int index) const override;
     u64 GetReg(int index) const override;
     void SetReg(int index, u64 value) override;
     void SetReg(int index, u64 value) override;
+    const u128& GetExtReg(int index) const override;
+    void SetExtReg(int index, u128& value) override;
     u32 GetVFPReg(int index) const override;
     u32 GetVFPReg(int index) const override;
     void SetVFPReg(int index, u32 value) override;
     void SetVFPReg(int index, u32 value) override;
     u32 GetVFPSystemReg(VFPSystemRegister reg) const override;
     u32 GetVFPSystemReg(VFPSystemRegister reg) const override;
@@ -29,8 +38,6 @@ public:
     VAddr GetTlsAddress() const override;
     VAddr GetTlsAddress() const override;
     void SetTlsAddress(VAddr address) override;
     void SetTlsAddress(VAddr address) override;
 
 
-    void AddTicks(u64 ticks) override;
-
     void SaveContext(ThreadContext& ctx) override;
     void SaveContext(ThreadContext& ctx) override;
     void LoadContext(const ThreadContext& ctx) override;
     void LoadContext(const ThreadContext& ctx) override;
 
 
@@ -38,8 +45,10 @@ public:
     void ExecuteInstructions(int num_instructions) override;
     void ExecuteInstructions(int num_instructions) override;
 
 
     void ClearInstructionCache() override;
     void ClearInstructionCache() override;
+    void PageTableChanged() override;
 
 
 private:
 private:
-    std::unique_ptr<Dynarmic::Jit> jit;
-    std::shared_ptr<ARMul_State> interpreter_state;
+    Dynarmic::Jit* jit = nullptr;
+    Memory::PageTable* current_page_table = nullptr;
+    std::map<Memory::PageTable*, std::unique_ptr<Dynarmic::Jit>> jits;
 };
 };

+ 12 - 7
src/core/arm/dyncom/arm_dyncom.cpp

@@ -29,6 +29,10 @@ void ARM_DynCom::SetPC(u64 pc) {
     state->Reg[15] = pc;
     state->Reg[15] = pc;
 }
 }
 
 
+void ARM_DynCom::PageTableChanged() {
+    ClearInstructionCache();
+}
+
 u64 ARM_DynCom::GetPC() const {
 u64 ARM_DynCom::GetPC() const {
     return state->Reg[15];
     return state->Reg[15];
 }
 }
@@ -41,6 +45,13 @@ void ARM_DynCom::SetReg(int index, u64 value) {
     state->Reg[index] = value;
     state->Reg[index] = value;
 }
 }
 
 
+const u128& ARM_DynCom::GetExtReg(int index) const {
+    return {};
+}
+
+void ARM_DynCom::SetExtReg(int index, u128& value) {
+}
+
 u32 ARM_DynCom::GetVFPReg(int index) const {
 u32 ARM_DynCom::GetVFPReg(int index) const {
     return state->ExtReg[index];
     return state->ExtReg[index];
 }
 }
@@ -80,12 +91,6 @@ VAddr ARM_DynCom::GetTlsAddress() const {
 void ARM_DynCom::SetTlsAddress(VAddr /*address*/) {
 void ARM_DynCom::SetTlsAddress(VAddr /*address*/) {
 }
 }
 
 
-void ARM_DynCom::AddTicks(u64 ticks) {
-    down_count -= ticks;
-    if (down_count < 0)
-        CoreTiming::Advance();
-}
-
 void ARM_DynCom::ExecuteInstructions(int num_instructions) {
 void ARM_DynCom::ExecuteInstructions(int num_instructions) {
     state->NumInstrsToExecute = num_instructions;
     state->NumInstrsToExecute = num_instructions;
 
 
@@ -93,7 +98,7 @@ void ARM_DynCom::ExecuteInstructions(int num_instructions) {
     // executing one instruction at a time. Otherwise, if a block is being executed, more
     // executing one instruction at a time. Otherwise, if a block is being executed, more
     // instructions may actually be executed than specified.
     // instructions may actually be executed than specified.
     unsigned ticks_executed = InterpreterMainLoop(state.get());
     unsigned ticks_executed = InterpreterMainLoop(state.get());
-    AddTicks(ticks_executed);
+    CoreTiming::AddTicks(ticks_executed);
 }
 }
 
 
 void ARM_DynCom::SaveContext(ThreadContext& ctx) {
 void ARM_DynCom::SaveContext(ThreadContext& ctx) {

+ 3 - 2
src/core/arm/dyncom/arm_dyncom.h

@@ -16,11 +16,14 @@ public:
     ~ARM_DynCom();
     ~ARM_DynCom();
 
 
     void ClearInstructionCache() override;
     void ClearInstructionCache() override;
+    void PageTableChanged() override;
 
 
     void SetPC(u64 pc) override;
     void SetPC(u64 pc) override;
     u64 GetPC() const override;
     u64 GetPC() const override;
     u64 GetReg(int index) const override;
     u64 GetReg(int index) const override;
     void SetReg(int index, u64 value) override;
     void SetReg(int index, u64 value) override;
+    const u128& GetExtReg(int index) const override;
+    void SetExtReg(int index, u128& value) override;
     u32 GetVFPReg(int index) const override;
     u32 GetVFPReg(int index) const override;
     void SetVFPReg(int index, u32 value) override;
     void SetVFPReg(int index, u32 value) override;
     u32 GetVFPSystemReg(VFPSystemRegister reg) const override;
     u32 GetVFPSystemReg(VFPSystemRegister reg) const override;
@@ -32,8 +35,6 @@ public:
     VAddr GetTlsAddress() const override;
     VAddr GetTlsAddress() const override;
     void SetTlsAddress(VAddr address) override;
     void SetTlsAddress(VAddr address) override;
 
 
-    void AddTicks(u64 ticks) override;
-
     void SaveContext(ThreadContext& ctx) override;
     void SaveContext(ThreadContext& ctx) override;
     void LoadContext(const ThreadContext& ctx) override;
     void LoadContext(const ThreadContext& ctx) override;
 
 

+ 4 - 4
src/core/arm/dyncom/arm_dyncom_interpreter.cpp

@@ -759,7 +759,7 @@ static ThumbDecodeStatus DecodeThumbInstruction(u32 inst, u32 addr, u32* arm_ins
     ThumbDecodeStatus ret = TranslateThumbInstruction(addr, inst, arm_inst, inst_size);
     ThumbDecodeStatus ret = TranslateThumbInstruction(addr, inst, arm_inst, inst_size);
     if (ret == ThumbDecodeStatus::BRANCH) {
     if (ret == ThumbDecodeStatus::BRANCH) {
         int inst_index;
         int inst_index;
-        int table_length = arm_instruction_trans_len;
+        int table_length = static_cast<int>(arm_instruction_trans_len);
         u32 tinstr = GetThumbInstruction(inst, addr);
         u32 tinstr = GetThumbInstruction(inst, addr);
 
 
         switch ((tinstr & 0xF800) >> 11) {
         switch ((tinstr & 0xF800) >> 11) {
@@ -838,7 +838,7 @@ static unsigned int InterpreterTranslateInstruction(const ARMul_State* cpu, cons
     return inst_size;
     return inst_size;
 }
 }
 
 
-static int InterpreterTranslateBlock(ARMul_State* cpu, int& bb_start, u32 addr) {
+static int InterpreterTranslateBlock(ARMul_State* cpu, std::size_t& bb_start, u32 addr) {
     MICROPROFILE_SCOPE(DynCom_Decode);
     MICROPROFILE_SCOPE(DynCom_Decode);
 
 
     // Decode instruction, get index
     // Decode instruction, get index
@@ -871,7 +871,7 @@ static int InterpreterTranslateBlock(ARMul_State* cpu, int& bb_start, u32 addr)
     return KEEP_GOING;
     return KEEP_GOING;
 }
 }
 
 
-static int InterpreterTranslateSingle(ARMul_State* cpu, int& bb_start, u32 addr) {
+static int InterpreterTranslateSingle(ARMul_State* cpu, std::size_t& bb_start, u32 addr) {
     MICROPROFILE_SCOPE(DynCom_Decode);
     MICROPROFILE_SCOPE(DynCom_Decode);
 
 
     ARM_INST_PTR inst_base = nullptr;
     ARM_INST_PTR inst_base = nullptr;
@@ -1620,7 +1620,7 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) {
     unsigned int addr;
     unsigned int addr;
     unsigned int num_instrs = 0;
     unsigned int num_instrs = 0;
 
 
-    int ptr;
+    std::size_t ptr;
 
 
     LOAD_NZCVT;
     LOAD_NZCVT;
 DISPATCH : {
 DISPATCH : {

+ 1 - 1
src/core/arm/skyeye_common/armstate.h

@@ -230,7 +230,7 @@ public:
 
 
     // TODO(bunnei): Move this cache to a better place - it should be per codeset (likely per
     // TODO(bunnei): Move this cache to a better place - it should be per codeset (likely per
     // process for our purposes), not per ARMul_State (which tracks CPU core state).
     // process for our purposes), not per ARMul_State (which tracks CPU core state).
-    std::unordered_map<u32, int> instruction_cache;
+    std::unordered_map<u32, std::size_t> instruction_cache;
 
 
 private:
 private:
     void ResetMPCoreCP15Registers();
     void ResetMPCoreCP15Registers();

+ 9 - 3
src/core/core.cpp

@@ -9,16 +9,19 @@
 #include "core/arm/arm_interface.h"
 #include "core/arm/arm_interface.h"
 #include "core/arm/dynarmic/arm_dynarmic.h"
 #include "core/arm/dynarmic/arm_dynarmic.h"
 #include "core/arm/dyncom/arm_dyncom.h"
 #include "core/arm/dyncom/arm_dyncom.h"
+#include "core/arm/unicorn/arm_unicorn.h"
 #include "core/core.h"
 #include "core/core.h"
 #include "core/core_timing.h"
 #include "core/core_timing.h"
 #include "core/gdbstub/gdbstub.h"
 #include "core/gdbstub/gdbstub.h"
 #include "core/hle/kernel/kernel.h"
 #include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/process.h"
 #include "core/hle/kernel/thread.h"
 #include "core/hle/kernel/thread.h"
 #include "core/hle/service/service.h"
 #include "core/hle/service/service.h"
 #include "core/hw/hw.h"
 #include "core/hw/hw.h"
 #include "core/loader/loader.h"
 #include "core/loader/loader.h"
 #include "core/memory_setup.h"
 #include "core/memory_setup.h"
 #include "core/settings.h"
 #include "core/settings.h"
+#include "network/network.h"
 #include "video_core/video_core.h"
 #include "video_core/video_core.h"
 
 
 namespace Core {
 namespace Core {
@@ -99,7 +102,7 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file
         return init_result;
         return init_result;
     }
     }
 
 
-    const Loader::ResultStatus load_result{app_loader->Load()};
+    const Loader::ResultStatus load_result{app_loader->Load(Kernel::g_current_process)};
     if (Loader::ResultStatus::Success != load_result) {
     if (Loader::ResultStatus::Success != load_result) {
         LOG_CRITICAL(Core, "Failed to load ROM (Error %i)!", load_result);
         LOG_CRITICAL(Core, "Failed to load ROM (Error %i)!", load_result);
         System::Shutdown();
         System::Shutdown();
@@ -136,7 +139,6 @@ void System::Reschedule() {
 }
 }
 
 
 System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
 System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
-    Memory::InitMemoryMap();
     LOG_DEBUG(HW_Memory, "initialized OK");
     LOG_DEBUG(HW_Memory, "initialized OK");
 
 
     if (Settings::values.use_cpu_jit) {
     if (Settings::values.use_cpu_jit) {
@@ -188,8 +190,12 @@ void System::Shutdown() {
     cpu_core = nullptr;
     cpu_core = nullptr;
     app_loader = nullptr;
     app_loader = nullptr;
     telemetry_session = nullptr;
     telemetry_session = nullptr;
+    if (auto room_member = Network::GetRoomMember().lock()) {
+        Network::GameInfo game_info{};
+        room_member->SendGameInfo(game_info);
+    }
 
 
     LOG_DEBUG(Core, "Shutdown OK");
     LOG_DEBUG(Core, "Shutdown OK");
 }
 }
 
 
-} // namespace
+} // namespace Core

+ 5 - 4
src/core/core.h

@@ -7,6 +7,7 @@
 #include <memory>
 #include <memory>
 #include <string>
 #include <string>
 #include "common/common_types.h"
 #include "common/common_types.h"
+#include "core/loader/loader.h"
 #include "core/memory.h"
 #include "core/memory.h"
 #include "core/perf_stats.h"
 #include "core/perf_stats.h"
 #include "core/telemetry_session.h"
 #include "core/telemetry_session.h"
@@ -14,10 +15,6 @@
 class EmuWindow;
 class EmuWindow;
 class ARM_Interface;
 class ARM_Interface;
 
 
-namespace Loader {
-class AppLoader;
-}
-
 namespace Core {
 namespace Core {
 
 
 class System {
 class System {
@@ -119,6 +116,10 @@ public:
         return status_details;
         return status_details;
     }
     }
 
 
+    Loader::AppLoader& GetAppLoader() const {
+        return *app_loader;
+    }
+
 private:
 private:
     /**
     /**
      * Initialize the emulated system.
      * Initialize the emulated system.

+ 23 - 13
src/core/core_timing.cpp

@@ -57,6 +57,9 @@ static s64 idled_cycles;
 static s64 last_global_time_ticks;
 static s64 last_global_time_ticks;
 static s64 last_global_time_us;
 static s64 last_global_time_us;
 
 
+static s64 down_count = 0; ///< A decreasing counter of remaining cycles before the next event,
+                           /// decreased by the cpu run loop
+
 static std::recursive_mutex external_event_section;
 static std::recursive_mutex external_event_section;
 
 
 // Warning: not included in save state.
 // Warning: not included in save state.
@@ -146,7 +149,7 @@ void UnregisterAllEvents() {
 }
 }
 
 
 void Init() {
 void Init() {
-    Core::CPU().down_count = INITIAL_SLICE_LENGTH;
+    down_count = INITIAL_SLICE_LENGTH;
     g_slice_length = INITIAL_SLICE_LENGTH;
     g_slice_length = INITIAL_SLICE_LENGTH;
     global_timer = 0;
     global_timer = 0;
     idled_cycles = 0;
     idled_cycles = 0;
@@ -185,8 +188,15 @@ void Shutdown() {
     }
     }
 }
 }
 
 
+void AddTicks(u64 ticks) {
+    down_count -= ticks;
+    if (down_count < 0) {
+        Advance();
+    }
+}
+
 u64 GetTicks() {
 u64 GetTicks() {
-    return (u64)global_timer + g_slice_length - Core::CPU().down_count;
+    return (u64)global_timer + g_slice_length - down_count;
 }
 }
 
 
 u64 GetIdleTicks() {
 u64 GetIdleTicks() {
@@ -460,18 +470,18 @@ void MoveEvents() {
 }
 }
 
 
 void ForceCheck() {
 void ForceCheck() {
-    s64 cycles_executed = g_slice_length - Core::CPU().down_count;
+    s64 cycles_executed = g_slice_length - down_count;
     global_timer += cycles_executed;
     global_timer += cycles_executed;
     // This will cause us to check for new events immediately.
     // This will cause us to check for new events immediately.
-    Core::CPU().down_count = 0;
+    down_count = 0;
     // But let's not eat a bunch more time in Advance() because of this.
     // But let's not eat a bunch more time in Advance() because of this.
     g_slice_length = 0;
     g_slice_length = 0;
 }
 }
 
 
 void Advance() {
 void Advance() {
-    s64 cycles_executed = g_slice_length - Core::CPU().down_count;
+    s64 cycles_executed = g_slice_length - down_count;
     global_timer += cycles_executed;
     global_timer += cycles_executed;
-    Core::CPU().down_count = g_slice_length;
+    down_count = g_slice_length;
 
 
     if (has_ts_events)
     if (has_ts_events)
         MoveEvents();
         MoveEvents();
@@ -480,7 +490,7 @@ void Advance() {
     if (!first) {
     if (!first) {
         if (g_slice_length < 10000) {
         if (g_slice_length < 10000) {
             g_slice_length += 10000;
             g_slice_length += 10000;
-            Core::CPU().down_count += g_slice_length;
+            down_count += g_slice_length;
         }
         }
     } else {
     } else {
         // Note that events can eat cycles as well.
         // Note that events can eat cycles as well.
@@ -490,7 +500,7 @@ void Advance() {
 
 
         const int diff = target - g_slice_length;
         const int diff = target - g_slice_length;
         g_slice_length += diff;
         g_slice_length += diff;
-        Core::CPU().down_count += diff;
+        down_count += diff;
     }
     }
     if (advance_callback)
     if (advance_callback)
         advance_callback(static_cast<int>(cycles_executed));
         advance_callback(static_cast<int>(cycles_executed));
@@ -506,12 +516,12 @@ void LogPendingEvents() {
 }
 }
 
 
 void Idle(int max_idle) {
 void Idle(int max_idle) {
-    s64 cycles_down = Core::CPU().down_count;
+    s64 cycles_down = down_count;
     if (max_idle != 0 && cycles_down > max_idle)
     if (max_idle != 0 && cycles_down > max_idle)
         cycles_down = max_idle;
         cycles_down = max_idle;
 
 
     if (first && cycles_down > 0) {
     if (first && cycles_down > 0) {
-        s64 cycles_executed = g_slice_length - Core::CPU().down_count;
+        s64 cycles_executed = g_slice_length - down_count;
         s64 cycles_next_event = first->time - global_timer;
         s64 cycles_next_event = first->time - global_timer;
 
 
         if (cycles_next_event < cycles_executed + cycles_down) {
         if (cycles_next_event < cycles_executed + cycles_down) {
@@ -526,9 +536,9 @@ void Idle(int max_idle) {
               cycles_down / (float)(g_clock_rate_arm11 * 0.001f));
               cycles_down / (float)(g_clock_rate_arm11 * 0.001f));
 
 
     idled_cycles += cycles_down;
     idled_cycles += cycles_down;
-    Core::CPU().down_count -= cycles_down;
-    if (Core::CPU().down_count == 0)
-        Core::CPU().down_count = -1;
+    down_count -= cycles_down;
+    if (down_count == 0)
+        down_count = -1;
 }
 }
 
 
 std::string GetScheduledEventsSummary() {
 std::string GetScheduledEventsSummary() {

+ 6 - 0
src/core/core_timing.h

@@ -67,6 +67,12 @@ void Shutdown();
 typedef void (*MHzChangeCallback)();
 typedef void (*MHzChangeCallback)();
 typedef std::function<void(u64 userdata, int cycles_late)> TimedCallback;
 typedef std::function<void(u64 userdata, int cycles_late)> TimedCallback;
 
 
+/**
+* Advance the CPU core by the specified number of ticks (e.g. to simulate CPU execution time)
+* @param ticks Number of ticks to advance the CPU core
+*/
+void AddTicks(u64 ticks);
+
 u64 GetTicks();
 u64 GetTicks();
 u64 GetIdleTicks();
 u64 GetIdleTicks();
 u64 GetGlobalTimeUs();
 u64 GetGlobalTimeUs();

+ 2 - 0
src/core/file_sys/archive_backend.cpp

@@ -90,6 +90,8 @@ std::u16string Path::AsU16Str() const {
         LOG_ERROR(Service_FS, "LowPathType cannot be converted to u16string!");
         LOG_ERROR(Service_FS, "LowPathType cannot be converted to u16string!");
         return {};
         return {};
     }
     }
+
+    UNREACHABLE();
 }
 }
 
 
 std::vector<u8> Path::AsBinary() const {
 std::vector<u8> Path::AsBinary() const {

+ 23 - 6
src/core/file_sys/archive_ncch.cpp

@@ -13,7 +13,10 @@
 #include "core/file_sys/archive_ncch.h"
 #include "core/file_sys/archive_ncch.h"
 #include "core/file_sys/errors.h"
 #include "core/file_sys/errors.h"
 #include "core/file_sys/ivfc_archive.h"
 #include "core/file_sys/ivfc_archive.h"
+#include "core/file_sys/ncch_container.h"
+#include "core/file_sys/title_metadata.h"
 #include "core/hle/service/fs/archive.h"
 #include "core/hle/service/fs/archive.h"
+#include "core/loader/loader.h"
 
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // FileSys namespace
 // FileSys namespace
@@ -25,8 +28,18 @@ static std::string GetNCCHContainerPath(const std::string& nand_directory) {
 }
 }
 
 
 static std::string GetNCCHPath(const std::string& mount_point, u32 high, u32 low) {
 static std::string GetNCCHPath(const std::string& mount_point, u32 high, u32 low) {
-    return Common::StringFromFormat("%s%08x/%08x/content/00000000.app.romfs", mount_point.c_str(),
-                                    high, low);
+    u32 content_id = 0;
+
+    // TODO(shinyquagsire23): Title database should be doing this path lookup
+    std::string content_path =
+        Common::StringFromFormat("%s%08x/%08x/content/", mount_point.c_str(), high, low);
+    std::string tmd_path = content_path + "00000000.tmd";
+    TitleMetadata tmd(tmd_path);
+    if (tmd.Load() == Loader::ResultStatus::Success) {
+        content_id = tmd.GetBootContentID();
+    }
+
+    return Common::StringFromFormat("%s%08x.app", content_path.c_str(), content_id);
 }
 }
 
 
 ArchiveFactory_NCCH::ArchiveFactory_NCCH(const std::string& nand_directory)
 ArchiveFactory_NCCH::ArchiveFactory_NCCH(const std::string& nand_directory)
@@ -38,9 +51,14 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_NCCH::Open(const Path&
     u32 high = data[1];
     u32 high = data[1];
     u32 low = data[0];
     u32 low = data[0];
     std::string file_path = GetNCCHPath(mount_point, high, low);
     std::string file_path = GetNCCHPath(mount_point, high, low);
-    auto file = std::make_shared<FileUtil::IOFile>(file_path, "rb");
 
 
-    if (!file->IsOpen()) {
+    std::shared_ptr<FileUtil::IOFile> romfs_file;
+    u64 romfs_offset = 0;
+    u64 romfs_size = 0;
+    auto ncch_container = NCCHContainer(file_path);
+
+    if (ncch_container.ReadRomFS(romfs_file, romfs_offset, romfs_size) !=
+        Loader::ResultStatus::Success) {
         // High Title ID of the archive: The category (https://3dbrew.org/wiki/Title_list).
         // High Title ID of the archive: The category (https://3dbrew.org/wiki/Title_list).
         constexpr u32 shared_data_archive = 0x0004009B;
         constexpr u32 shared_data_archive = 0x0004009B;
         constexpr u32 system_data_archive = 0x000400DB;
         constexpr u32 system_data_archive = 0x000400DB;
@@ -74,9 +92,8 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_NCCH::Open(const Path&
         }
         }
         return ERROR_NOT_FOUND;
         return ERROR_NOT_FOUND;
     }
     }
-    auto size = file->GetSize();
 
 
-    auto archive = std::make_unique<IVFCArchive>(file, 0, size);
+    auto archive = std::make_unique<IVFCArchive>(romfs_file, romfs_offset, romfs_size);
     return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
     return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
 }
 }
 
 

+ 39 - 2
src/core/file_sys/archive_sdmc.cpp

@@ -121,7 +121,25 @@ ResultCode SDMCArchive::DeleteFile(const Path& path) const {
 }
 }
 
 
 ResultCode SDMCArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
 ResultCode SDMCArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
-    if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) {
+    const PathParser path_parser_src(src_path);
+
+    // TODO: Verify these return codes with HW
+    if (!path_parser_src.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid src path %s", src_path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    const PathParser path_parser_dest(dest_path);
+
+    if (!path_parser_dest.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid dest path %s", dest_path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    const auto src_path_full = path_parser_src.BuildHostPath(mount_point);
+    const auto dest_path_full = path_parser_dest.BuildHostPath(mount_point);
+
+    if (FileUtil::Rename(src_path_full, dest_path_full)) {
         return RESULT_SUCCESS;
         return RESULT_SUCCESS;
     }
     }
 
 
@@ -260,8 +278,27 @@ ResultCode SDMCArchive::CreateDirectory(const Path& path) const {
 }
 }
 
 
 ResultCode SDMCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
 ResultCode SDMCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
-    if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString()))
+    const PathParser path_parser_src(src_path);
+
+    // TODO: Verify these return codes with HW
+    if (!path_parser_src.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid src path %s", src_path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    const PathParser path_parser_dest(dest_path);
+
+    if (!path_parser_dest.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid dest path %s", dest_path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    const auto src_path_full = path_parser_src.BuildHostPath(mount_point);
+    const auto dest_path_full = path_parser_dest.BuildHostPath(mount_point);
+
+    if (FileUtil::Rename(src_path_full, dest_path_full)) {
         return RESULT_SUCCESS;
         return RESULT_SUCCESS;
+    }
 
 
     // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
     // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
     // exist or similar. Verify.
     // exist or similar. Verify.

+ 50 - 10
src/core/file_sys/archive_selfncch.cpp

@@ -3,12 +3,14 @@
 // Refer to the license.txt file included.
 // Refer to the license.txt file included.
 
 
 #include <array>
 #include <array>
+#include <cinttypes>
 #include "common/common_types.h"
 #include "common/common_types.h"
 #include "common/logging/log.h"
 #include "common/logging/log.h"
 #include "common/swap.h"
 #include "common/swap.h"
 #include "core/file_sys/archive_selfncch.h"
 #include "core/file_sys/archive_selfncch.h"
 #include "core/file_sys/errors.h"
 #include "core/file_sys/errors.h"
 #include "core/file_sys/ivfc_archive.h"
 #include "core/file_sys/ivfc_archive.h"
+#include "core/hle/kernel/process.h"
 
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // FileSys namespace
 // FileSys namespace
@@ -102,8 +104,7 @@ public:
 
 
         switch (static_cast<SelfNCCHFilePathType>(file_path.type)) {
         switch (static_cast<SelfNCCHFilePathType>(file_path.type)) {
         case SelfNCCHFilePathType::UpdateRomFS:
         case SelfNCCHFilePathType::UpdateRomFS:
-            LOG_WARNING(Service_FS, "(STUBBED) open update RomFS");
-            return OpenRomFS();
+            return OpenUpdateRomFS();
 
 
         case SelfNCCHFilePathType::RomFS:
         case SelfNCCHFilePathType::RomFS:
             return OpenRomFS();
             return OpenRomFS();
@@ -179,6 +180,17 @@ private:
         }
         }
     }
     }
 
 
+    ResultVal<std::unique_ptr<FileBackend>> OpenUpdateRomFS() const {
+        if (ncch_data.update_romfs_file) {
+            return MakeResult<std::unique_ptr<FileBackend>>(std::make_unique<IVFCFile>(
+                ncch_data.update_romfs_file, ncch_data.update_romfs_offset,
+                ncch_data.update_romfs_size));
+        } else {
+            LOG_INFO(Service_FS, "Unable to read update RomFS");
+            return ERROR_ROMFS_NOT_FOUND;
+        }
+    }
+
     ResultVal<std::unique_ptr<FileBackend>> OpenExeFS(const std::string& filename) const {
     ResultVal<std::unique_ptr<FileBackend>> OpenExeFS(const std::string& filename) const {
         if (filename == "icon") {
         if (filename == "icon") {
             if (ncch_data.icon) {
             if (ncch_data.icon) {
@@ -217,31 +229,59 @@ private:
     NCCHData ncch_data;
     NCCHData ncch_data;
 };
 };
 
 
-ArchiveFactory_SelfNCCH::ArchiveFactory_SelfNCCH(Loader::AppLoader& app_loader) {
+void ArchiveFactory_SelfNCCH::Register(Loader::AppLoader& app_loader) {
+    u64 program_id = 0;
+    if (app_loader.ReadProgramId(program_id) != Loader::ResultStatus::Success) {
+        LOG_WARNING(
+            Service_FS,
+            "Could not read program id when registering with SelfNCCH, this might be a 3dsx file");
+    }
+
+    LOG_DEBUG(Service_FS, "Registering program %016" PRIX64 " with the SelfNCCH archive factory",
+              program_id);
+
+    if (ncch_data.find(program_id) != ncch_data.end()) {
+        LOG_WARNING(Service_FS, "Registering program %016" PRIX64
+                                " with SelfNCCH will override existing mapping",
+                    program_id);
+    }
+
+    NCCHData& data = ncch_data[program_id];
+
     std::shared_ptr<FileUtil::IOFile> romfs_file_;
     std::shared_ptr<FileUtil::IOFile> romfs_file_;
     if (Loader::ResultStatus::Success ==
     if (Loader::ResultStatus::Success ==
-        app_loader.ReadRomFS(romfs_file_, ncch_data.romfs_offset, ncch_data.romfs_size)) {
+        app_loader.ReadRomFS(romfs_file_, data.romfs_offset, data.romfs_size)) {
+
+        data.romfs_file = std::move(romfs_file_);
+    }
+
+    std::shared_ptr<FileUtil::IOFile> update_romfs_file;
+    if (Loader::ResultStatus::Success ==
+        app_loader.ReadUpdateRomFS(update_romfs_file, data.update_romfs_offset,
+                                   data.update_romfs_size)) {
 
 
-        ncch_data.romfs_file = std::move(romfs_file_);
+        data.update_romfs_file = std::move(update_romfs_file);
     }
     }
 
 
     std::vector<u8> buffer;
     std::vector<u8> buffer;
 
 
     if (Loader::ResultStatus::Success == app_loader.ReadIcon(buffer))
     if (Loader::ResultStatus::Success == app_loader.ReadIcon(buffer))
-        ncch_data.icon = std::make_shared<std::vector<u8>>(std::move(buffer));
+        data.icon = std::make_shared<std::vector<u8>>(std::move(buffer));
 
 
     buffer.clear();
     buffer.clear();
     if (Loader::ResultStatus::Success == app_loader.ReadLogo(buffer))
     if (Loader::ResultStatus::Success == app_loader.ReadLogo(buffer))
-        ncch_data.logo = std::make_shared<std::vector<u8>>(std::move(buffer));
+        data.logo = std::make_shared<std::vector<u8>>(std::move(buffer));
 
 
     buffer.clear();
     buffer.clear();
     if (Loader::ResultStatus::Success == app_loader.ReadBanner(buffer))
     if (Loader::ResultStatus::Success == app_loader.ReadBanner(buffer))
-        ncch_data.banner = std::make_shared<std::vector<u8>>(std::move(buffer));
+        data.banner = std::make_shared<std::vector<u8>>(std::move(buffer));
 }
 }
 
 
 ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SelfNCCH::Open(const Path& path) {
 ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SelfNCCH::Open(const Path& path) {
-    auto archive = std::make_unique<SelfNCCHArchive>(ncch_data);
-    return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
+    //auto archive = std::make_unique<SelfNCCHArchive>(
+    //    ncch_data[Kernel::g_current_process->codeset->program_id]);
+    //return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
+    return {};
 }
 }
 
 
 ResultCode ArchiveFactory_SelfNCCH::Format(const Path&, const FileSys::ArchiveFormatInfo&) {
 ResultCode ArchiveFactory_SelfNCCH::Format(const Path&, const FileSys::ArchiveFormatInfo&) {

+ 11 - 2
src/core/file_sys/archive_selfncch.h

@@ -6,6 +6,7 @@
 
 
 #include <memory>
 #include <memory>
 #include <string>
 #include <string>
+#include <unordered_map>
 #include <vector>
 #include <vector>
 #include "common/common_types.h"
 #include "common/common_types.h"
 #include "core/file_sys/archive_backend.h"
 #include "core/file_sys/archive_backend.h"
@@ -24,12 +25,19 @@ struct NCCHData {
     std::shared_ptr<FileUtil::IOFile> romfs_file;
     std::shared_ptr<FileUtil::IOFile> romfs_file;
     u64 romfs_offset = 0;
     u64 romfs_offset = 0;
     u64 romfs_size = 0;
     u64 romfs_size = 0;
+
+    std::shared_ptr<FileUtil::IOFile> update_romfs_file;
+    u64 update_romfs_offset = 0;
+    u64 update_romfs_size = 0;
 };
 };
 
 
 /// File system interface to the SelfNCCH archive
 /// File system interface to the SelfNCCH archive
 class ArchiveFactory_SelfNCCH final : public ArchiveFactory {
 class ArchiveFactory_SelfNCCH final : public ArchiveFactory {
 public:
 public:
-    explicit ArchiveFactory_SelfNCCH(Loader::AppLoader& app_loader);
+    ArchiveFactory_SelfNCCH() = default;
+
+    /// Registers a loaded application so that we can open its SelfNCCH archive when requested.
+    void Register(Loader::AppLoader& app_loader);
 
 
     std::string GetName() const override {
     std::string GetName() const override {
         return "SelfNCCH";
         return "SelfNCCH";
@@ -39,7 +47,8 @@ public:
     ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override;
     ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override;
 
 
 private:
 private:
-    NCCHData ncch_data;
+    /// Mapping of ProgramId -> NCCHData
+    std::unordered_map<u64, NCCHData> ncch_data;
 };
 };
 
 
 } // namespace FileSys
 } // namespace FileSys

+ 423 - 0
src/core/file_sys/ncch_container.cpp

@@ -0,0 +1,423 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cinttypes>
+#include <cstring>
+#include <memory>
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/file_sys/ncch_container.h"
+#include "core/loader/loader.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// FileSys namespace
+
+namespace FileSys {
+
+static const int kMaxSections = 8;   ///< Maximum number of sections (files) in an ExeFs
+static const int kBlockSize = 0x200; ///< Size of ExeFS blocks (in bytes)
+
+/**
+ * Get the decompressed size of an LZSS compressed ExeFS file
+ * @param buffer Buffer of compressed file
+ * @param size Size of compressed buffer
+ * @return Size of decompressed buffer
+ */
+static u32 LZSS_GetDecompressedSize(const u8* buffer, u32 size) {
+    u32 offset_size = *(u32*)(buffer + size - 4);
+    return offset_size + size;
+}
+
+/**
+ * Decompress ExeFS file (compressed with LZSS)
+ * @param compressed Compressed buffer
+ * @param compressed_size Size of compressed buffer
+ * @param decompressed Decompressed buffer
+ * @param decompressed_size Size of decompressed buffer
+ * @return True on success, otherwise false
+ */
+static bool LZSS_Decompress(const u8* compressed, u32 compressed_size, u8* decompressed,
+                            u32 decompressed_size) {
+    const u8* footer = compressed + compressed_size - 8;
+    u32 buffer_top_and_bottom = *reinterpret_cast<const u32*>(footer);
+    u32 out = decompressed_size;
+    u32 index = compressed_size - ((buffer_top_and_bottom >> 24) & 0xFF);
+    u32 stop_index = compressed_size - (buffer_top_and_bottom & 0xFFFFFF);
+
+    memset(decompressed, 0, decompressed_size);
+    memcpy(decompressed, compressed, compressed_size);
+
+    while (index > stop_index) {
+        u8 control = compressed[--index];
+
+        for (unsigned i = 0; i < 8; i++) {
+            if (index <= stop_index)
+                break;
+            if (index <= 0)
+                break;
+            if (out <= 0)
+                break;
+
+            if (control & 0x80) {
+                // Check if compression is out of bounds
+                if (index < 2)
+                    return false;
+                index -= 2;
+
+                u32 segment_offset = compressed[index] | (compressed[index + 1] << 8);
+                u32 segment_size = ((segment_offset >> 12) & 15) + 3;
+                segment_offset &= 0x0FFF;
+                segment_offset += 2;
+
+                // Check if compression is out of bounds
+                if (out < segment_size)
+                    return false;
+
+                for (unsigned j = 0; j < segment_size; j++) {
+                    // Check if compression is out of bounds
+                    if (out + segment_offset >= decompressed_size)
+                        return false;
+
+                    u8 data = decompressed[out + segment_offset];
+                    decompressed[--out] = data;
+                }
+            } else {
+                // Check if compression is out of bounds
+                if (out < 1)
+                    return false;
+                decompressed[--out] = compressed[--index];
+            }
+            control <<= 1;
+        }
+    }
+    return true;
+}
+
+NCCHContainer::NCCHContainer(const std::string& filepath) : filepath(filepath) {
+    file = FileUtil::IOFile(filepath, "rb");
+}
+
+Loader::ResultStatus NCCHContainer::OpenFile(const std::string& filepath) {
+    this->filepath = filepath;
+    file = FileUtil::IOFile(filepath, "rb");
+
+    if (!file.IsOpen()) {
+        LOG_WARNING(Service_FS, "Failed to open %s", filepath.c_str());
+        return Loader::ResultStatus::Error;
+    }
+
+    LOG_DEBUG(Service_FS, "Opened %s", filepath.c_str());
+    return Loader::ResultStatus::Success;
+}
+
+Loader::ResultStatus NCCHContainer::Load() {
+    if (is_loaded)
+        return Loader::ResultStatus::Success;
+
+    if (file.IsOpen()) {
+        // Reset read pointer in case this file has been read before.
+        file.Seek(0, SEEK_SET);
+
+        if (file.ReadBytes(&ncch_header, sizeof(NCCH_Header)) != sizeof(NCCH_Header))
+            return Loader::ResultStatus::Error;
+
+        // Skip NCSD header and load first NCCH (NCSD is just a container of NCCH files)...
+        if (Loader::MakeMagic('N', 'C', 'S', 'D') == ncch_header.magic) {
+            LOG_DEBUG(Service_FS, "Only loading the first (bootable) NCCH within the NCSD file!");
+            ncch_offset = 0x4000;
+            file.Seek(ncch_offset, SEEK_SET);
+            file.ReadBytes(&ncch_header, sizeof(NCCH_Header));
+        }
+
+        // Verify we are loading the correct file type...
+        if (Loader::MakeMagic('N', 'C', 'C', 'H') != ncch_header.magic)
+            return Loader::ResultStatus::ErrorInvalidFormat;
+
+        has_header = true;
+
+        // System archives and DLC don't have an extended header but have RomFS
+        if (ncch_header.extended_header_size) {
+            if (file.ReadBytes(&exheader_header, sizeof(ExHeader_Header)) !=
+                sizeof(ExHeader_Header))
+                return Loader::ResultStatus::Error;
+
+            is_compressed = (exheader_header.codeset_info.flags.flag & 1) == 1;
+            u32 entry_point = exheader_header.codeset_info.text.address;
+            u32 code_size = exheader_header.codeset_info.text.code_size;
+            u32 stack_size = exheader_header.codeset_info.stack_size;
+            u32 bss_size = exheader_header.codeset_info.bss_size;
+            u32 core_version = exheader_header.arm11_system_local_caps.core_version;
+            u8 priority = exheader_header.arm11_system_local_caps.priority;
+            u8 resource_limit_category =
+                exheader_header.arm11_system_local_caps.resource_limit_category;
+
+            LOG_DEBUG(Service_FS, "Name:                        %s",
+                      exheader_header.codeset_info.name);
+            LOG_DEBUG(Service_FS, "Program ID:                  %016" PRIX64,
+                      ncch_header.program_id);
+            LOG_DEBUG(Service_FS, "Code compressed:             %s", is_compressed ? "yes" : "no");
+            LOG_DEBUG(Service_FS, "Entry point:                 0x%08X", entry_point);
+            LOG_DEBUG(Service_FS, "Code size:                   0x%08X", code_size);
+            LOG_DEBUG(Service_FS, "Stack size:                  0x%08X", stack_size);
+            LOG_DEBUG(Service_FS, "Bss size:                    0x%08X", bss_size);
+            LOG_DEBUG(Service_FS, "Core version:                %d", core_version);
+            LOG_DEBUG(Service_FS, "Thread priority:             0x%X", priority);
+            LOG_DEBUG(Service_FS, "Resource limit category:     %d", resource_limit_category);
+            LOG_DEBUG(Service_FS, "System Mode:                 %d",
+                      static_cast<int>(exheader_header.arm11_system_local_caps.system_mode));
+
+            if (exheader_header.system_info.jump_id != ncch_header.program_id) {
+                LOG_ERROR(Service_FS,
+                          "ExHeader Program ID mismatch: the ROM is probably encrypted.");
+                return Loader::ResultStatus::ErrorEncrypted;
+            }
+
+            has_exheader = true;
+        }
+
+        // DLC can have an ExeFS and a RomFS but no extended header
+        if (ncch_header.exefs_size) {
+            exefs_offset = ncch_header.exefs_offset * kBlockSize;
+            u32 exefs_size = ncch_header.exefs_size * kBlockSize;
+
+            LOG_DEBUG(Service_FS, "ExeFS offset:                0x%08X", exefs_offset);
+            LOG_DEBUG(Service_FS, "ExeFS size:                  0x%08X", exefs_size);
+
+            file.Seek(exefs_offset + ncch_offset, SEEK_SET);
+            if (file.ReadBytes(&exefs_header, sizeof(ExeFs_Header)) != sizeof(ExeFs_Header))
+                return Loader::ResultStatus::Error;
+
+            exefs_file = FileUtil::IOFile(filepath, "rb");
+            has_exefs = true;
+        }
+
+        if (ncch_header.romfs_offset != 0 && ncch_header.romfs_size != 0)
+            has_romfs = true;
+    }
+
+    LoadOverrides();
+
+    // We need at least one of these or overrides, practically
+    if (!(has_exefs || has_romfs || is_tainted))
+        return Loader::ResultStatus::Error;
+
+    is_loaded = true;
+    return Loader::ResultStatus::Success;
+}
+
+Loader::ResultStatus NCCHContainer::LoadOverrides() {
+    // Check for split-off files, mark the archive as tainted if we will use them
+    std::string romfs_override = filepath + ".romfs";
+    if (FileUtil::Exists(romfs_override)) {
+        is_tainted = true;
+    }
+
+    // If we have a split-off exefs file/folder, it takes priority
+    std::string exefs_override = filepath + ".exefs";
+    std::string exefsdir_override = filepath + ".exefsdir/";
+    if (FileUtil::Exists(exefs_override)) {
+        exefs_file = FileUtil::IOFile(exefs_override, "rb");
+
+        if (exefs_file.ReadBytes(&exefs_header, sizeof(ExeFs_Header)) == sizeof(ExeFs_Header)) {
+            LOG_DEBUG(Service_FS, "Loading ExeFS section from %s", exefs_override.c_str());
+            exefs_offset = 0;
+            is_tainted = true;
+            has_exefs = true;
+        } else {
+            exefs_file = FileUtil::IOFile(filepath, "rb");
+        }
+    } else if (FileUtil::Exists(exefsdir_override) && FileUtil::IsDirectory(exefsdir_override)) {
+        is_tainted = true;
+    }
+
+    if (is_tainted)
+        LOG_WARNING(Service_FS,
+                    "Loaded NCCH %s is tainted, application behavior may not be as expected!",
+                    filepath.c_str());
+
+    return Loader::ResultStatus::Success;
+}
+
+Loader::ResultStatus NCCHContainer::LoadSectionExeFS(const char* name, std::vector<u8>& buffer) {
+    Loader::ResultStatus result = Load();
+    if (result != Loader::ResultStatus::Success)
+        return result;
+
+    // Check if we have files that can drop-in and replace
+    result = LoadOverrideExeFSSection(name, buffer);
+    if (result == Loader::ResultStatus::Success || !has_exefs)
+        return result;
+
+    // If we don't have any separate files, we'll need a full ExeFS
+    if (!exefs_file.IsOpen())
+        return Loader::ResultStatus::Error;
+
+    LOG_DEBUG(Service_FS, "%d sections:", kMaxSections);
+    // Iterate through the ExeFs archive until we find a section with the specified name...
+    for (unsigned section_number = 0; section_number < kMaxSections; section_number++) {
+        const auto& section = exefs_header.section[section_number];
+
+        // Load the specified section...
+        if (strcmp(section.name, name) == 0) {
+            LOG_DEBUG(Service_FS, "%d - offset: 0x%08X, size: 0x%08X, name: %s", section_number,
+                      section.offset, section.size, section.name);
+
+            s64 section_offset =
+                (section.offset + exefs_offset + sizeof(ExeFs_Header) + ncch_offset);
+            exefs_file.Seek(section_offset, SEEK_SET);
+
+            if (strcmp(section.name, ".code") == 0 && is_compressed) {
+                // Section is compressed, read compressed .code section...
+                std::unique_ptr<u8[]> temp_buffer;
+                try {
+                    temp_buffer.reset(new u8[section.size]);
+                } catch (std::bad_alloc&) {
+                    return Loader::ResultStatus::ErrorMemoryAllocationFailed;
+                }
+
+                if (exefs_file.ReadBytes(&temp_buffer[0], section.size) != section.size)
+                    return Loader::ResultStatus::Error;
+
+                // Decompress .code section...
+                u32 decompressed_size = LZSS_GetDecompressedSize(&temp_buffer[0], section.size);
+                buffer.resize(decompressed_size);
+                if (!LZSS_Decompress(&temp_buffer[0], section.size, &buffer[0], decompressed_size))
+                    return Loader::ResultStatus::ErrorInvalidFormat;
+            } else {
+                // Section is uncompressed...
+                buffer.resize(section.size);
+                if (exefs_file.ReadBytes(&buffer[0], section.size) != section.size)
+                    return Loader::ResultStatus::Error;
+            }
+            return Loader::ResultStatus::Success;
+        }
+    }
+    return Loader::ResultStatus::ErrorNotUsed;
+}
+
+Loader::ResultStatus NCCHContainer::LoadOverrideExeFSSection(const char* name,
+                                                             std::vector<u8>& buffer) {
+    std::string override_name;
+
+    // Map our section name to the extracted equivalent
+    if (!strcmp(name, ".code"))
+        override_name = "code.bin";
+    else if (!strcmp(name, "icon"))
+        override_name = "code.bin";
+    else if (!strcmp(name, "banner"))
+        override_name = "banner.bnr";
+    else if (!strcmp(name, "logo"))
+        override_name = "logo.bcma.lz";
+    else
+        return Loader::ResultStatus::Error;
+
+    std::string section_override = filepath + ".exefsdir/" + override_name;
+    FileUtil::IOFile section_file(section_override, "rb");
+
+    if (section_file.IsOpen()) {
+        auto section_size = section_file.GetSize();
+        buffer.resize(section_size);
+
+        section_file.Seek(0, SEEK_SET);
+        if (section_file.ReadBytes(&buffer[0], section_size) == section_size) {
+            LOG_WARNING(Service_FS, "File %s overriding built-in ExeFS file",
+                        section_override.c_str());
+            return Loader::ResultStatus::Success;
+        }
+    }
+    return Loader::ResultStatus::ErrorNotUsed;
+}
+
+Loader::ResultStatus NCCHContainer::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file,
+                                              u64& offset, u64& size) {
+    Loader::ResultStatus result = Load();
+    if (result != Loader::ResultStatus::Success)
+        return result;
+
+    if (ReadOverrideRomFS(romfs_file, offset, size) == Loader::ResultStatus::Success)
+        return Loader::ResultStatus::Success;
+
+    if (!has_romfs) {
+        LOG_DEBUG(Service_FS, "RomFS requested from NCCH which has no RomFS");
+        return Loader::ResultStatus::ErrorNotUsed;
+    }
+
+    if (!file.IsOpen())
+        return Loader::ResultStatus::Error;
+
+    u32 romfs_offset = ncch_offset + (ncch_header.romfs_offset * kBlockSize) + 0x1000;
+    u32 romfs_size = (ncch_header.romfs_size * kBlockSize) - 0x1000;
+
+    LOG_DEBUG(Service_FS, "RomFS offset:           0x%08X", romfs_offset);
+    LOG_DEBUG(Service_FS, "RomFS size:             0x%08X", romfs_size);
+
+    if (file.GetSize() < romfs_offset + romfs_size)
+        return Loader::ResultStatus::Error;
+
+    // We reopen the file, to allow its position to be independent from file's
+    romfs_file = std::make_shared<FileUtil::IOFile>(filepath, "rb");
+    if (!romfs_file->IsOpen())
+        return Loader::ResultStatus::Error;
+
+    offset = romfs_offset;
+    size = romfs_size;
+
+    return Loader::ResultStatus::Success;
+}
+
+Loader::ResultStatus NCCHContainer::ReadOverrideRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file,
+                                                      u64& offset, u64& size) {
+    // Check for RomFS overrides
+    std::string split_filepath = filepath + ".romfs";
+    if (FileUtil::Exists(split_filepath)) {
+        romfs_file = std::make_shared<FileUtil::IOFile>(split_filepath, "rb");
+        if (romfs_file->IsOpen()) {
+            LOG_WARNING(Service_FS, "File %s overriding built-in RomFS", split_filepath.c_str());
+            offset = 0;
+            size = romfs_file->GetSize();
+            return Loader::ResultStatus::Success;
+        }
+    }
+
+    return Loader::ResultStatus::ErrorNotUsed;
+}
+
+Loader::ResultStatus NCCHContainer::ReadProgramId(u64_le& program_id) {
+    Loader::ResultStatus result = Load();
+    if (result != Loader::ResultStatus::Success)
+        return result;
+
+    if (!has_header)
+        return Loader::ResultStatus::ErrorNotUsed;
+
+    program_id = ncch_header.program_id;
+    return Loader::ResultStatus::Success;
+}
+
+bool NCCHContainer::HasExeFS() {
+    Loader::ResultStatus result = Load();
+    if (result != Loader::ResultStatus::Success)
+        return false;
+
+    return has_exefs;
+}
+
+bool NCCHContainer::HasRomFS() {
+    Loader::ResultStatus result = Load();
+    if (result != Loader::ResultStatus::Success)
+        return false;
+
+    return has_romfs;
+}
+
+bool NCCHContainer::HasExHeader() {
+    Loader::ResultStatus result = Load();
+    if (result != Loader::ResultStatus::Success)
+        return false;
+
+    return has_exheader;
+}
+
+} // namespace FileSys

+ 274 - 0
src/core/file_sys/ncch_container.h

@@ -0,0 +1,274 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <cstddef>
+#include <memory>
+#include <string>
+#include <vector>
+#include "common/bit_field.h"
+#include "common/common_types.h"
+#include "common/file_util.h"
+#include "common/swap.h"
+#include "core/core.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+/// NCCH header (Note: "NCCH" appears to be a publicly unknown acronym)
+
+struct NCCH_Header {
+    u8 signature[0x100];
+    u32_le magic;
+    u32_le content_size;
+    u8 partition_id[8];
+    u16_le maker_code;
+    u16_le version;
+    u8 reserved_0[4];
+    u64_le program_id;
+    u8 reserved_1[0x10];
+    u8 logo_region_hash[0x20];
+    u8 product_code[0x10];
+    u8 extended_header_hash[0x20];
+    u32_le extended_header_size;
+    u8 reserved_2[4];
+    u8 flags[8];
+    u32_le plain_region_offset;
+    u32_le plain_region_size;
+    u32_le logo_region_offset;
+    u32_le logo_region_size;
+    u32_le exefs_offset;
+    u32_le exefs_size;
+    u32_le exefs_hash_region_size;
+    u8 reserved_3[4];
+    u32_le romfs_offset;
+    u32_le romfs_size;
+    u32_le romfs_hash_region_size;
+    u8 reserved_4[4];
+    u8 exefs_super_block_hash[0x20];
+    u8 romfs_super_block_hash[0x20];
+};
+
+static_assert(sizeof(NCCH_Header) == 0x200, "NCCH header structure size is wrong");
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// ExeFS (executable file system) headers
+
+struct ExeFs_SectionHeader {
+    char name[8];
+    u32 offset;
+    u32 size;
+};
+
+struct ExeFs_Header {
+    ExeFs_SectionHeader section[8];
+    u8 reserved[0x80];
+    u8 hashes[8][0x20];
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// ExHeader (executable file system header) headers
+
+struct ExHeader_SystemInfoFlags {
+    u8 reserved[5];
+    u8 flag;
+    u8 remaster_version[2];
+};
+
+struct ExHeader_CodeSegmentInfo {
+    u32 address;
+    u32 num_max_pages;
+    u32 code_size;
+};
+
+struct ExHeader_CodeSetInfo {
+    u8 name[8];
+    ExHeader_SystemInfoFlags flags;
+    ExHeader_CodeSegmentInfo text;
+    u32 stack_size;
+    ExHeader_CodeSegmentInfo ro;
+    u8 reserved[4];
+    ExHeader_CodeSegmentInfo data;
+    u32 bss_size;
+};
+
+struct ExHeader_DependencyList {
+    u8 program_id[0x30][8];
+};
+
+struct ExHeader_SystemInfo {
+    u64 save_data_size;
+    u64_le jump_id;
+    u8 reserved_2[0x30];
+};
+
+struct ExHeader_StorageInfo {
+    u8 ext_save_data_id[8];
+    u8 system_save_data_id[8];
+    u8 reserved[8];
+    u8 access_info[7];
+    u8 other_attributes;
+};
+
+struct ExHeader_ARM11_SystemLocalCaps {
+    u64_le program_id;
+    u32_le core_version;
+    u8 reserved_flags[2];
+    union {
+        u8 flags0;
+        BitField<0, 2, u8> ideal_processor;
+        BitField<2, 2, u8> affinity_mask;
+        BitField<4, 4, u8> system_mode;
+    };
+    u8 priority;
+    u8 resource_limit_descriptor[0x10][2];
+    ExHeader_StorageInfo storage_info;
+    u8 service_access_control[0x20][8];
+    u8 ex_service_access_control[0x2][8];
+    u8 reserved[0xf];
+    u8 resource_limit_category;
+};
+
+struct ExHeader_ARM11_KernelCaps {
+    u32_le descriptors[28];
+    u8 reserved[0x10];
+};
+
+struct ExHeader_ARM9_AccessControl {
+    u8 descriptors[15];
+    u8 descversion;
+};
+
+struct ExHeader_Header {
+    ExHeader_CodeSetInfo codeset_info;
+    ExHeader_DependencyList dependency_list;
+    ExHeader_SystemInfo system_info;
+    ExHeader_ARM11_SystemLocalCaps arm11_system_local_caps;
+    ExHeader_ARM11_KernelCaps arm11_kernel_caps;
+    ExHeader_ARM9_AccessControl arm9_access_control;
+    struct {
+        u8 signature[0x100];
+        u8 ncch_public_key_modulus[0x100];
+        ExHeader_ARM11_SystemLocalCaps arm11_system_local_caps;
+        ExHeader_ARM11_KernelCaps arm11_kernel_caps;
+        ExHeader_ARM9_AccessControl arm9_access_control;
+    } access_desc;
+};
+
+static_assert(sizeof(ExHeader_Header) == 0x800, "ExHeader structure size is wrong");
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// FileSys namespace
+
+namespace FileSys {
+
+/**
+ * Helper which implements an interface to deal with NCCH containers which can
+ * contain ExeFS archives or RomFS archives for games or other applications.
+ */
+class NCCHContainer {
+public:
+    NCCHContainer(const std::string& filepath);
+    NCCHContainer() {}
+
+    Loader::ResultStatus OpenFile(const std::string& filepath);
+
+    /**
+     * Ensure ExeFS and exheader is loaded and ready for reading sections
+     * @return ResultStatus result of function
+     */
+    Loader::ResultStatus Load();
+
+    /**
+     * Attempt to find overridden sections for the NCCH and mark the container as tainted
+     * if any are found.
+     * @return ResultStatus result of function
+     */
+    Loader::ResultStatus LoadOverrides();
+
+    /**
+     * Reads an application ExeFS section of an NCCH file (e.g. .code, .logo, etc.)
+     * @param name Name of section to read out of NCCH file
+     * @param buffer Vector to read data into
+     * @return ResultStatus result of function
+     */
+    Loader::ResultStatus LoadSectionExeFS(const char* name, std::vector<u8>& buffer);
+
+    /**
+     * Reads an application ExeFS section from external files instead of an NCCH file,
+     * (e.g. code.bin, logo.bcma.lz, icon.icn, banner.bnr)
+     * @param name Name of section to read from external files
+     * @param buffer Vector to read data into
+     * @return ResultStatus result of function
+     */
+    Loader::ResultStatus LoadOverrideExeFSSection(const char* name, std::vector<u8>& buffer);
+
+    /**
+     * Get the RomFS of the NCCH container
+     * Since the RomFS can be huge, we return a file reference instead of copying to a buffer
+     * @param romfs_file The file containing the RomFS
+     * @param offset The offset the romfs begins on
+     * @param size The size of the romfs
+     * @return ResultStatus result of function
+     */
+    Loader::ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset,
+                                   u64& size);
+
+    /**
+    * Get the override RomFS of the NCCH container
+    * Since the RomFS can be huge, we return a file reference instead of copying to a buffer
+    * @param romfs_file The file containing the RomFS
+    * @param offset The offset the romfs begins on
+    * @param size The size of the romfs
+    * @return ResultStatus result of function
+    */
+    Loader::ResultStatus ReadOverrideRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file,
+                                           u64& offset, u64& size);
+
+    /**
+     * Get the Program ID of the NCCH container
+     * @return ResultStatus result of function
+     */
+    Loader::ResultStatus ReadProgramId(u64_le& program_id);
+
+    /**
+     * Checks whether the NCCH container contains an ExeFS
+     * @return bool check result
+     */
+    bool HasExeFS();
+
+    /**
+     * Checks whether the NCCH container contains a RomFS
+     * @return bool check result
+     */
+    bool HasRomFS();
+
+    /**
+     * Checks whether the NCCH container contains an ExHeader
+     * @return bool check result
+     */
+    bool HasExHeader();
+
+    NCCH_Header ncch_header;
+    ExeFs_Header exefs_header;
+    ExHeader_Header exheader_header;
+
+private:
+    bool has_header = false;
+    bool has_exheader = false;
+    bool has_exefs = false;
+    bool has_romfs = false;
+
+    bool is_tainted = false; // Are there parts of this container being overridden?
+    bool is_loaded = false;
+    bool is_compressed = false;
+
+    u32 ncch_offset = 0; // Offset to NCCH header, can be 0 or after NCSD header
+    u32 exefs_offset = 0;
+
+    std::string filepath;
+    FileUtil::IOFile file;
+    FileUtil::IOFile exefs_file;
+};
+
+} // namespace FileSys

+ 39 - 2
src/core/file_sys/savedata_archive.cpp

@@ -106,7 +106,25 @@ ResultCode SaveDataArchive::DeleteFile(const Path& path) const {
 }
 }
 
 
 ResultCode SaveDataArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
 ResultCode SaveDataArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
-    if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) {
+    const PathParser path_parser_src(src_path);
+
+    // TODO: Verify these return codes with HW
+    if (!path_parser_src.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid src path %s", src_path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    const PathParser path_parser_dest(dest_path);
+
+    if (!path_parser_dest.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid dest path %s", dest_path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    const auto src_path_full = path_parser_src.BuildHostPath(mount_point);
+    const auto dest_path_full = path_parser_dest.BuildHostPath(mount_point);
+
+    if (FileUtil::Rename(src_path_full, dest_path_full)) {
         return RESULT_SUCCESS;
         return RESULT_SUCCESS;
     }
     }
 
 
@@ -247,8 +265,27 @@ ResultCode SaveDataArchive::CreateDirectory(const Path& path) const {
 }
 }
 
 
 ResultCode SaveDataArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
 ResultCode SaveDataArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
-    if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString()))
+    const PathParser path_parser_src(src_path);
+
+    // TODO: Verify these return codes with HW
+    if (!path_parser_src.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid src path %s", src_path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    const PathParser path_parser_dest(dest_path);
+
+    if (!path_parser_dest.IsValid()) {
+        LOG_ERROR(Service_FS, "Invalid dest path %s", dest_path.DebugStr().c_str());
+        return ERROR_INVALID_PATH;
+    }
+
+    const auto src_path_full = path_parser_src.BuildHostPath(mount_point);
+    const auto dest_path_full = path_parser_dest.BuildHostPath(mount_point);
+
+    if (FileUtil::Rename(src_path_full, dest_path_full)) {
         return RESULT_SUCCESS;
         return RESULT_SUCCESS;
+    }
 
 
     // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
     // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
     // exist or similar. Verify.
     // exist or similar. Verify.

+ 212 - 0
src/core/file_sys/title_metadata.cpp

@@ -0,0 +1,212 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cinttypes>
+#include <cryptopp/sha.h>
+#include "common/alignment.h"
+#include "common/file_util.h"
+#include "common/logging/log.h"
+#include "core/file_sys/title_metadata.h"
+#include "core/loader/loader.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// FileSys namespace
+
+namespace FileSys {
+
+static u32 GetSignatureSize(u32 signature_type) {
+    switch (signature_type) {
+    case Rsa4096Sha1:
+    case Rsa4096Sha256:
+        return 0x200;
+
+    case Rsa2048Sha1:
+    case Rsa2048Sha256:
+        return 0x100;
+
+    case EllipticSha1:
+    case EcdsaSha256:
+        return 0x3C;
+    }
+}
+
+Loader::ResultStatus TitleMetadata::Load() {
+    FileUtil::IOFile file(filepath, "rb");
+    if (!file.IsOpen())
+        return Loader::ResultStatus::Error;
+
+    if (!file.ReadBytes(&signature_type, sizeof(u32_be)))
+        return Loader::ResultStatus::Error;
+
+    // Signature lengths are variable, and the body follows the signature
+    u32 signature_size = GetSignatureSize(signature_type);
+
+    tmd_signature.resize(signature_size);
+    if (!file.ReadBytes(&tmd_signature[0], signature_size))
+        return Loader::ResultStatus::Error;
+
+    // The TMD body start position is rounded to the nearest 0x40 after the signature
+    size_t body_start = Common::AlignUp(signature_size + sizeof(u32), 0x40);
+    file.Seek(body_start, SEEK_SET);
+
+    // Read our TMD body, then load the amount of ContentChunks specified
+    if (file.ReadBytes(&tmd_body, sizeof(TitleMetadata::Body)) != sizeof(TitleMetadata::Body))
+        return Loader::ResultStatus::Error;
+
+    for (u16 i = 0; i < tmd_body.content_count; i++) {
+        ContentChunk chunk;
+        if (file.ReadBytes(&chunk, sizeof(ContentChunk)) == sizeof(ContentChunk)) {
+            tmd_chunks.push_back(chunk);
+        } else {
+            LOG_ERROR(Service_FS, "Malformed TMD %s, failed to load content chunk index %u!",
+                      filepath.c_str(), i);
+            return Loader::ResultStatus::ErrorInvalidFormat;
+        }
+    }
+
+    return Loader::ResultStatus::Success;
+}
+
+Loader::ResultStatus TitleMetadata::Save() {
+    FileUtil::IOFile file(filepath, "wb");
+    if (!file.IsOpen())
+        return Loader::ResultStatus::Error;
+
+    if (!file.WriteBytes(&signature_type, sizeof(u32_be)))
+        return Loader::ResultStatus::Error;
+
+    // Signature lengths are variable, and the body follows the signature
+    u32 signature_size = GetSignatureSize(signature_type);
+
+    if (!file.WriteBytes(tmd_signature.data(), signature_size))
+        return Loader::ResultStatus::Error;
+
+    // The TMD body start position is rounded to the nearest 0x40 after the signature
+    size_t body_start = Common::AlignUp(signature_size + sizeof(u32), 0x40);
+    file.Seek(body_start, SEEK_SET);
+
+    // Update our TMD body values and hashes
+    tmd_body.content_count = static_cast<u16>(tmd_chunks.size());
+
+    // TODO(shinyquagsire23): Do TMDs with more than one contentinfo exist?
+    // For now we'll just adjust the first index to hold all content chunks
+    // and ensure that no further content info data exists.
+    tmd_body.contentinfo = {};
+    tmd_body.contentinfo[0].index = 0;
+    tmd_body.contentinfo[0].command_count = static_cast<u16>(tmd_chunks.size());
+
+    CryptoPP::SHA256 chunk_hash;
+    for (u16 i = 0; i < tmd_body.content_count; i++) {
+        chunk_hash.Update(reinterpret_cast<u8*>(&tmd_chunks[i]), sizeof(ContentChunk));
+    }
+    chunk_hash.Final(tmd_body.contentinfo[0].hash.data());
+
+    CryptoPP::SHA256 contentinfo_hash;
+    for (size_t i = 0; i < tmd_body.contentinfo.size(); i++) {
+        chunk_hash.Update(reinterpret_cast<u8*>(&tmd_body.contentinfo[i]), sizeof(ContentInfo));
+    }
+    chunk_hash.Final(tmd_body.contentinfo_hash.data());
+
+    // Write our TMD body, then write each of our ContentChunks
+    if (file.WriteBytes(&tmd_body, sizeof(TitleMetadata::Body)) != sizeof(TitleMetadata::Body))
+        return Loader::ResultStatus::Error;
+
+    for (u16 i = 0; i < tmd_body.content_count; i++) {
+        ContentChunk chunk = tmd_chunks[i];
+        if (file.WriteBytes(&chunk, sizeof(ContentChunk)) != sizeof(ContentChunk))
+            return Loader::ResultStatus::Error;
+    }
+
+    return Loader::ResultStatus::Success;
+}
+
+u64 TitleMetadata::GetTitleID() const {
+    return tmd_body.title_id;
+}
+
+u32 TitleMetadata::GetTitleType() const {
+    return tmd_body.title_type;
+}
+
+u16 TitleMetadata::GetTitleVersion() const {
+    return tmd_body.title_version;
+}
+
+u64 TitleMetadata::GetSystemVersion() const {
+    return tmd_body.system_version;
+}
+
+size_t TitleMetadata::GetContentCount() const {
+    return tmd_chunks.size();
+}
+
+u32 TitleMetadata::GetBootContentID() const {
+    return tmd_chunks[TMDContentIndex::Main].id;
+}
+
+u32 TitleMetadata::GetManualContentID() const {
+    return tmd_chunks[TMDContentIndex::Manual].id;
+}
+
+u32 TitleMetadata::GetDLPContentID() const {
+    return tmd_chunks[TMDContentIndex::DLP].id;
+}
+
+void TitleMetadata::SetTitleID(u64 title_id) {
+    tmd_body.title_id = title_id;
+}
+
+void TitleMetadata::SetTitleType(u32 type) {
+    tmd_body.title_type = type;
+}
+
+void TitleMetadata::SetTitleVersion(u16 version) {
+    tmd_body.title_version = version;
+}
+
+void TitleMetadata::SetSystemVersion(u64 version) {
+    tmd_body.system_version = version;
+}
+
+void TitleMetadata::AddContentChunk(const ContentChunk& chunk) {
+    tmd_chunks.push_back(chunk);
+}
+
+void TitleMetadata::Print() const {
+    LOG_DEBUG(Service_FS, "%s - %u chunks", filepath.c_str(),
+              static_cast<u32>(tmd_body.content_count));
+
+    // Content info describes ranges of content chunks
+    LOG_DEBUG(Service_FS, "Content info:");
+    for (size_t i = 0; i < tmd_body.contentinfo.size(); i++) {
+        if (tmd_body.contentinfo[i].command_count == 0)
+            break;
+
+        LOG_DEBUG(Service_FS, "    Index %04X, Command Count %04X",
+                  static_cast<u32>(tmd_body.contentinfo[i].index),
+                  static_cast<u32>(tmd_body.contentinfo[i].command_count));
+    }
+
+    // For each content info, print their content chunk range
+    for (size_t i = 0; i < tmd_body.contentinfo.size(); i++) {
+        u16 index = static_cast<u16>(tmd_body.contentinfo[i].index);
+        u16 count = static_cast<u16>(tmd_body.contentinfo[i].command_count);
+
+        if (count == 0)
+            continue;
+
+        LOG_DEBUG(Service_FS, "Content chunks for content info index %zu:", i);
+        for (u16 j = index; j < index + count; j++) {
+            // Don't attempt to print content we don't have
+            if (j > tmd_body.content_count)
+                break;
+
+            const ContentChunk& chunk = tmd_chunks[j];
+            LOG_DEBUG(Service_FS, "    ID %08X, Index %04X, Type %04x, Size %016" PRIX64,
+                      static_cast<u32>(chunk.id), static_cast<u32>(chunk.index),
+                      static_cast<u32>(chunk.type), static_cast<u64>(chunk.size));
+        }
+    }
+}
+} // namespace FileSys

+ 125 - 0
src/core/file_sys/title_metadata.h

@@ -0,0 +1,125 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include <vector>
+#include "common/common_types.h"
+#include "common/swap.h"
+
+namespace Loader {
+enum class ResultStatus;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// FileSys namespace
+
+namespace FileSys {
+
+enum TMDSignatureType : u32 {
+    Rsa4096Sha1 = 0x10000,
+    Rsa2048Sha1 = 0x10001,
+    EllipticSha1 = 0x10002,
+    Rsa4096Sha256 = 0x10003,
+    Rsa2048Sha256 = 0x10004,
+    EcdsaSha256 = 0x10005
+};
+
+enum TMDContentTypeFlag : u16 {
+    Encrypted = 1 << 1,
+    Disc = 1 << 2,
+    CFM = 1 << 3,
+    Optional = 1 << 14,
+    Shared = 1 << 15
+};
+
+/**
+ * Helper which implements an interface to read and write Title Metadata (TMD) files.
+ * If a file path is provided and the file exists, it can be parsed and used, otherwise
+ * it must be created. The TMD file can then be interpreted, modified and/or saved.
+ */
+class TitleMetadata {
+public:
+    struct ContentChunk {
+        u32_be id;
+        u16_be index;
+        u16_be type;
+        u64_be size;
+        std::array<u8, 0x20> hash;
+    };
+
+    static_assert(sizeof(ContentChunk) == 0x30, "TMD ContentChunk structure size is wrong");
+
+    struct ContentInfo {
+        u16_be index;
+        u16_be command_count;
+        std::array<u8, 0x20> hash;
+    };
+
+    static_assert(sizeof(ContentInfo) == 0x24, "TMD ContentInfo structure size is wrong");
+
+#pragma pack(push, 1)
+
+    struct Body {
+        std::array<u8, 0x40> issuer;
+        u8 version;
+        u8 ca_crl_version;
+        u8 signer_crl_version;
+        u8 reserved;
+        u64_be system_version;
+        u64_be title_id;
+        u32_be title_type;
+        u16_be group_id;
+        u32_be savedata_size;
+        u32_be srl_private_savedata_size;
+        std::array<u8, 4> reserved_2;
+        u8 srl_flag;
+        std::array<u8, 0x31> reserved_3;
+        u32_be access_rights;
+        u16_be title_version;
+        u16_be content_count;
+        u16_be boot_content;
+        std::array<u8, 2> reserved_4;
+        std::array<u8, 0x20> contentinfo_hash;
+        std::array<ContentInfo, 64> contentinfo;
+    };
+
+    static_assert(sizeof(Body) == 0x9C4, "TMD body structure size is wrong");
+
+#pragma pack(pop)
+
+    explicit TitleMetadata(std::string& path) : filepath(std::move(path)) {}
+    Loader::ResultStatus Load();
+    Loader::ResultStatus Save();
+
+    u64 GetTitleID() const;
+    u32 GetTitleType() const;
+    u16 GetTitleVersion() const;
+    u64 GetSystemVersion() const;
+    size_t GetContentCount() const;
+    u32 GetBootContentID() const;
+    u32 GetManualContentID() const;
+    u32 GetDLPContentID() const;
+
+    void SetTitleID(u64 title_id);
+    void SetTitleType(u32 type);
+    void SetTitleVersion(u16 version);
+    void SetSystemVersion(u64 version);
+    void AddContentChunk(const ContentChunk& chunk);
+
+    void Print() const;
+
+private:
+    enum TMDContentIndex { Main = 0, Manual = 1, DLP = 2 };
+
+    Body tmd_body;
+    u32_be signature_type;
+    std::vector<u8> tmd_signature;
+    std::vector<ContentChunk> tmd_chunks;
+
+    std::string filepath;
+};
+
+} // namespace FileSys

+ 61 - 36
src/core/frontend/emu_window.cpp

@@ -2,14 +2,55 @@
 // Licensed under GPLv2 or any later version
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 // Refer to the license.txt file included.
 
 
-#include <algorithm>
 #include <cmath>
 #include <cmath>
-#include "common/assert.h"
-#include "core/3ds.h"
-#include "core/core.h"
+#include <mutex>
 #include "core/frontend/emu_window.h"
 #include "core/frontend/emu_window.h"
+#include "core/frontend/input.h"
 #include "core/settings.h"
 #include "core/settings.h"
 
 
+class EmuWindow::TouchState : public Input::Factory<Input::TouchDevice>,
+                              public std::enable_shared_from_this<TouchState> {
+public:
+    std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage&) override {
+        return std::make_unique<Device>(shared_from_this());
+    }
+
+    std::mutex mutex;
+
+    bool touch_pressed = false; ///< True if touchpad area is currently pressed, otherwise false
+
+    float touch_x = 0.0f; ///< Touchpad X-position
+    float touch_y = 0.0f; ///< Touchpad Y-position
+
+private:
+    class Device : public Input::TouchDevice {
+    public:
+        explicit Device(std::weak_ptr<TouchState>&& touch_state) : touch_state(touch_state) {}
+        std::tuple<float, float, bool> GetStatus() const override {
+            if (auto state = touch_state.lock()) {
+                std::lock_guard<std::mutex> guard(state->mutex);
+                return std::make_tuple(state->touch_x, state->touch_y, state->touch_pressed);
+            }
+            return std::make_tuple(0.0f, 0.0f, false);
+        }
+
+    private:
+        std::weak_ptr<TouchState> touch_state;
+    };
+};
+
+EmuWindow::EmuWindow() {
+    // TODO: Find a better place to set this.
+    config.min_client_area_size = std::make_pair(400u, 480u);
+    active_config = config;
+    touch_state = std::make_shared<TouchState>();
+    Input::RegisterFactory<Input::TouchDevice>("emu_window", touch_state);
+}
+
+EmuWindow::~EmuWindow() {
+    Input::UnregisterFactory<Input::TouchDevice>("emu_window");
+}
+
 /**
 /**
  * Check if the given x/y coordinates are within the touchpad specified by the framebuffer layout
  * Check if the given x/y coordinates are within the touchpad specified by the framebuffer layout
  * @param layout FramebufferLayout object describing the framebuffer size and screen positions
  * @param layout FramebufferLayout object describing the framebuffer size and screen positions
@@ -38,22 +79,26 @@ void EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) {
     if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y))
     if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y))
         return;
         return;
 
 
-    touch_x = Core::kScreenBottomWidth * (framebuffer_x - framebuffer_layout.bottom_screen.left) /
-              (framebuffer_layout.bottom_screen.right - framebuffer_layout.bottom_screen.left);
-    touch_y = Core::kScreenBottomHeight * (framebuffer_y - framebuffer_layout.bottom_screen.top) /
-              (framebuffer_layout.bottom_screen.bottom - framebuffer_layout.bottom_screen.top);
+    std::lock_guard<std::mutex> guard(touch_state->mutex);
+    touch_state->touch_x =
+        static_cast<float>(framebuffer_x - framebuffer_layout.bottom_screen.left) /
+        (framebuffer_layout.bottom_screen.right - framebuffer_layout.bottom_screen.left);
+    touch_state->touch_y =
+        static_cast<float>(framebuffer_y - framebuffer_layout.bottom_screen.top) /
+        (framebuffer_layout.bottom_screen.bottom - framebuffer_layout.bottom_screen.top);
 
 
-    touch_pressed = true;
+    touch_state->touch_pressed = true;
 }
 }
 
 
 void EmuWindow::TouchReleased() {
 void EmuWindow::TouchReleased() {
-    touch_pressed = false;
-    touch_x = 0;
-    touch_y = 0;
+    std::lock_guard<std::mutex> guard(touch_state->mutex);
+    touch_state->touch_pressed = false;
+    touch_state->touch_x = 0;
+    touch_state->touch_y = 0;
 }
 }
 
 
 void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) {
 void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) {
-    if (!touch_pressed)
+    if (!touch_state->touch_pressed)
         return;
         return;
 
 
     if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y))
     if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y))
@@ -62,29 +107,6 @@ void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) {
     TouchPressed(framebuffer_x, framebuffer_y);
     TouchPressed(framebuffer_x, framebuffer_y);
 }
 }
 
 
-void EmuWindow::AccelerometerChanged(float x, float y, float z) {
-    constexpr float coef = 512;
-
-    std::lock_guard<std::mutex> lock(accel_mutex);
-
-    // TODO(wwylele): do a time stretch as it in GyroscopeChanged
-    // The time stretch formula should be like
-    // stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity
-    accel_x = static_cast<s16>(x * coef);
-    accel_y = static_cast<s16>(y * coef);
-    accel_z = static_cast<s16>(z * coef);
-}
-
-void EmuWindow::GyroscopeChanged(float x, float y, float z) {
-    constexpr float FULL_FPS = 60;
-    float coef = GetGyroscopeRawToDpsCoefficient();
-    float stretch = Core::System::GetInstance().perf_stats.GetLastFrameTimeScale();
-    std::lock_guard<std::mutex> lock(gyro_mutex);
-    gyro_x = static_cast<s16>(x * coef * stretch);
-    gyro_y = static_cast<s16>(y * coef * stretch);
-    gyro_z = static_cast<s16>(z * coef * stretch);
-}
-
 void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height) {
 void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height) {
     Layout::FramebufferLayout layout;
     Layout::FramebufferLayout layout;
     if (Settings::values.custom_layout == true) {
     if (Settings::values.custom_layout == true) {
@@ -97,6 +119,9 @@ void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height)
         case Settings::LayoutOption::LargeScreen:
         case Settings::LayoutOption::LargeScreen:
             layout = Layout::LargeFrameLayout(width, height, Settings::values.swap_screen);
             layout = Layout::LargeFrameLayout(width, height, Settings::values.swap_screen);
             break;
             break;
+        case Settings::LayoutOption::SideScreen:
+            layout = Layout::SideFrameLayout(width, height, Settings::values.swap_screen);
+            break;
         case Settings::LayoutOption::Default:
         case Settings::LayoutOption::Default:
         default:
         default:
             layout = Layout::DefaultFrameLayout(width, height, Settings::values.swap_screen);
             layout = Layout::DefaultFrameLayout(width, height, Settings::values.swap_screen);

+ 5 - 109
src/core/frontend/emu_window.h

@@ -4,11 +4,10 @@
 
 
 #pragma once
 #pragma once
 
 
-#include <mutex>
+#include <memory>
 #include <tuple>
 #include <tuple>
 #include <utility>
 #include <utility>
 #include "common/common_types.h"
 #include "common/common_types.h"
-#include "common/math_util.h"
 #include "core/frontend/framebuffer_layout.h"
 #include "core/frontend/framebuffer_layout.h"
 
 
 /**
 /**
@@ -68,84 +67,6 @@ public:
      */
      */
     void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y);
     void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y);
 
 
-    /**
-     * Signal accelerometer state has changed.
-     * @param x X-axis accelerometer value
-     * @param y Y-axis accelerometer value
-     * @param z Z-axis accelerometer value
-     * @note all values are in unit of g (gravitational acceleration).
-     *    e.g. x = 1.0 means 9.8m/s^2 in x direction.
-     * @see GetAccelerometerState for axis explanation.
-     */
-    void AccelerometerChanged(float x, float y, float z);
-
-    /**
-     * Signal gyroscope state has changed.
-     * @param x X-axis accelerometer value
-     * @param y Y-axis accelerometer value
-     * @param z Z-axis accelerometer value
-     * @note all values are in deg/sec.
-     * @see GetGyroscopeState for axis explanation.
-     */
-    void GyroscopeChanged(float x, float y, float z);
-
-    /**
-     * Gets the current touch screen state (touch X/Y coordinates and whether or not it is pressed).
-     * @note This should be called by the core emu thread to get a state set by the window thread.
-     * @todo Fix this function to be thread-safe.
-     * @return std::tuple of (x, y, pressed) where `x` and `y` are the touch coordinates and
-     *         `pressed` is true if the touch screen is currently being pressed
-     */
-    std::tuple<u16, u16, bool> GetTouchState() const {
-        return std::make_tuple(touch_x, touch_y, touch_pressed);
-    }
-
-    /**
-     * Gets the current accelerometer state (acceleration along each three axis).
-     * Axis explained:
-     *   +x is the same direction as LEFT on D-pad.
-     *   +y is normal to the touch screen, pointing outward.
-     *   +z is the same direction as UP on D-pad.
-     * Units:
-     *   1 unit of return value = 1/512 g (measured by hw test),
-     *   where g is the gravitational acceleration (9.8 m/sec2).
-     * @note This should be called by the core emu thread to get a state set by the window thread.
-     * @return std::tuple of (x, y, z)
-     */
-    std::tuple<s16, s16, s16> GetAccelerometerState() {
-        std::lock_guard<std::mutex> lock(accel_mutex);
-        return std::make_tuple(accel_x, accel_y, accel_z);
-    }
-
-    /**
-     * Gets the current gyroscope state (angular rates about each three axis).
-     * Axis explained:
-     *   +x is the same direction as LEFT on D-pad.
-     *   +y is normal to the touch screen, pointing outward.
-     *   +z is the same direction as UP on D-pad.
-     * Orientation is determined by right-hand rule.
-     * Units:
-     *   1 unit of return value = (1/coef) deg/sec,
-     *   where coef is the return value of GetGyroscopeRawToDpsCoefficient().
-     * @note This should be called by the core emu thread to get a state set by the window thread.
-     * @return std::tuple of (x, y, z)
-     */
-    std::tuple<s16, s16, s16> GetGyroscopeState() {
-        std::lock_guard<std::mutex> lock(gyro_mutex);
-        return std::make_tuple(gyro_x, gyro_y, gyro_z);
-    }
-
-    /**
-     * Gets the coefficient for units conversion of gyroscope state.
-     * The conversion formula is r = coefficient * v,
-     * where v is angular rate in deg/sec,
-     * and r is the gyroscope state.
-     * @return float-type coefficient
-     */
-    f32 GetGyroscopeRawToDpsCoefficient() const {
-        return 14.375f; // taken from hw test, and gyroscope's document
-    }
-
     /**
     /**
      * Returns currently active configuration.
      * Returns currently active configuration.
      * @note Accesses to the returned object need not be consistent because it may be modified in
      * @note Accesses to the returned object need not be consistent because it may be modified in
@@ -180,21 +101,8 @@ public:
     void UpdateCurrentFramebufferLayout(unsigned width, unsigned height);
     void UpdateCurrentFramebufferLayout(unsigned width, unsigned height);
 
 
 protected:
 protected:
-    EmuWindow() {
-        // TODO: Find a better place to set this.
-        config.min_client_area_size = std::make_pair(400u, 480u);
-        active_config = config;
-        touch_x = 0;
-        touch_y = 0;
-        touch_pressed = false;
-        accel_x = 0;
-        accel_y = -512;
-        accel_z = 0;
-        gyro_x = 0;
-        gyro_y = 0;
-        gyro_z = 0;
-    }
-    virtual ~EmuWindow() {}
+    EmuWindow();
+    virtual ~EmuWindow();
 
 
     /**
     /**
      * Processes any pending configuration changes from the last SetConfig call.
      * Processes any pending configuration changes from the last SetConfig call.
@@ -250,20 +158,8 @@ private:
                                 /// ProcessConfigurationChanges)
                                 /// ProcessConfigurationChanges)
     WindowConfig active_config; ///< Internal active configuration
     WindowConfig active_config; ///< Internal active configuration
 
 
-    bool touch_pressed; ///< True if touchpad area is currently pressed, otherwise false
-
-    u16 touch_x; ///< Touchpad X-position in native 3DS pixel coordinates (0-320)
-    u16 touch_y; ///< Touchpad Y-position in native 3DS pixel coordinates (0-240)
-
-    std::mutex accel_mutex;
-    s16 accel_x; ///< Accelerometer X-axis value in native 3DS units
-    s16 accel_y; ///< Accelerometer Y-axis value in native 3DS units
-    s16 accel_z; ///< Accelerometer Z-axis value in native 3DS units
-
-    std::mutex gyro_mutex;
-    s16 gyro_x; ///< Gyroscope X-axis value in native 3DS units
-    s16 gyro_y; ///< Gyroscope Y-axis value in native 3DS units
-    s16 gyro_z; ///< Gyroscope Z-axis value in native 3DS units
+    class TouchState;
+    std::shared_ptr<TouchState> touch_state;
 
 
     /**
     /**
      * Clip the provided coordinates to be inside the touchscreen area.
      * Clip the provided coordinates to be inside the touchscreen area.

+ 35 - 1
src/core/frontend/framebuffer_layout.cpp

@@ -141,6 +141,40 @@ FramebufferLayout LargeFrameLayout(unsigned width, unsigned height, bool swapped
     return res;
     return res;
 }
 }
 
 
+FramebufferLayout SideFrameLayout(unsigned width, unsigned height, bool swapped) {
+    ASSERT(width > 0);
+    ASSERT(height > 0);
+
+    FramebufferLayout res{width, height, true, true, {}, {}};
+    // Aspect ratio of both screens side by side
+    const float emulation_aspect_ratio = static_cast<float>(Core::kScreenTopHeight) /
+                                         (Core::kScreenTopWidth + Core::kScreenBottomWidth);
+    float window_aspect_ratio = static_cast<float>(height) / width;
+    MathUtil::Rectangle<unsigned> screen_window_area{0, 0, width, height};
+    // Find largest Rectangle that can fit in the window size with the given aspect ratio
+    MathUtil::Rectangle<unsigned> screen_rect =
+        maxRectangle(screen_window_area, emulation_aspect_ratio);
+    // Find sizes of top and bottom screen
+    MathUtil::Rectangle<unsigned> top_screen = maxRectangle(screen_rect, TOP_SCREEN_ASPECT_RATIO);
+    MathUtil::Rectangle<unsigned> bot_screen = maxRectangle(screen_rect, BOT_SCREEN_ASPECT_RATIO);
+
+    if (window_aspect_ratio < emulation_aspect_ratio) {
+        // Apply borders to the left and right sides of the window.
+        u32 shift_horizontal = (screen_window_area.GetWidth() - screen_rect.GetWidth()) / 2;
+        top_screen = top_screen.TranslateX(shift_horizontal);
+        bot_screen = bot_screen.TranslateX(shift_horizontal);
+    } else {
+        // Window is narrower than the emulation content => apply borders to the top and bottom
+        u32 shift_vertical = (screen_window_area.GetHeight() - screen_rect.GetHeight()) / 2;
+        top_screen = top_screen.TranslateY(shift_vertical);
+        bot_screen = bot_screen.TranslateY(shift_vertical);
+    }
+    // Move the top screen to the right if we are swapped.
+    res.top_screen = swapped ? top_screen.TranslateX(bot_screen.GetWidth()) : top_screen;
+    res.bottom_screen = swapped ? bot_screen : bot_screen.TranslateX(top_screen.GetWidth());
+    return res;
+}
+
 FramebufferLayout CustomFrameLayout(unsigned width, unsigned height) {
 FramebufferLayout CustomFrameLayout(unsigned width, unsigned height) {
     ASSERT(width > 0);
     ASSERT(width > 0);
     ASSERT(height > 0);
     ASSERT(height > 0);
@@ -158,4 +192,4 @@ FramebufferLayout CustomFrameLayout(unsigned width, unsigned height) {
     res.bottom_screen = bot_screen;
     res.bottom_screen = bot_screen;
     return res;
     return res;
 }
 }
-}
+} // namespace Layout

+ 11 - 0
src/core/frontend/framebuffer_layout.h

@@ -53,6 +53,17 @@ FramebufferLayout SingleFrameLayout(unsigned width, unsigned height, bool is_swa
  */
  */
 FramebufferLayout LargeFrameLayout(unsigned width, unsigned height, bool is_swapped);
 FramebufferLayout LargeFrameLayout(unsigned width, unsigned height, bool is_swapped);
 
 
+/**
+* Factory method for constructing a Frame with the Top screen and bottom
+* screen side by side
+* This is useful for devices with small screens, like the GPDWin
+* @param width Window framebuffer width in pixels
+* @param height Window framebuffer height in pixels
+* @param is_swapped if true, the bottom screen will be the left display
+* @return Newly created FramebufferLayout object with default screen regions initialized
+*/
+FramebufferLayout SideFrameLayout(unsigned width, unsigned height, bool is_swapped);
+
 /**
 /**
  * Factory method for constructing a custom FramebufferLayout
  * Factory method for constructing a custom FramebufferLayout
  * @param width Window framebuffer width in pixels
  * @param width Window framebuffer width in pixels

+ 25 - 0
src/core/frontend/input.h

@@ -11,6 +11,7 @@
 #include <utility>
 #include <utility>
 #include "common/logging/log.h"
 #include "common/logging/log.h"
 #include "common/param_package.h"
 #include "common/param_package.h"
+#include "common/vector_math.h"
 
 
 namespace Input {
 namespace Input {
 
 
@@ -107,4 +108,28 @@ using ButtonDevice = InputDevice<bool>;
  */
  */
 using AnalogDevice = InputDevice<std::tuple<float, float>>;
 using AnalogDevice = InputDevice<std::tuple<float, float>>;
 
 
+/**
+ * A motion device is an input device that returns a tuple of accelerometer state vector and
+ * gyroscope state vector.
+ *
+ * For both vectors:
+ *   x+ is the same direction as LEFT on D-pad.
+ *   y+ is normal to the touch screen, pointing outward.
+ *   z+ is the same direction as UP on D-pad.
+ *
+ * For accelerometer state vector
+ *   Units: g (gravitational acceleration)
+ *
+ * For gyroscope state vector:
+ *   Orientation is determined by right-hand rule.
+ *   Units: deg/sec
+ */
+using MotionDevice = InputDevice<std::tuple<Math::Vec3<float>, Math::Vec3<float>>>;
+
+/**
+ * A touch device is an input device that returns a tuple of two floats and a bool. The floats are
+ * x and y coordinates in the range 0.0 - 1.0, and the bool indicates whether it is pressed.
+ */
+using TouchDevice = InputDevice<std::tuple<float, float, bool>>;
+
 } // namespace Input
 } // namespace Input

+ 0 - 89
src/core/frontend/motion_emu.cpp

@@ -1,89 +0,0 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "common/math_util.h"
-#include "common/quaternion.h"
-#include "core/frontend/emu_window.h"
-#include "core/frontend/motion_emu.h"
-
-namespace Motion {
-
-static constexpr int update_millisecond = 100;
-static constexpr auto update_duration =
-    std::chrono::duration_cast<std::chrono::steady_clock::duration>(
-        std::chrono::milliseconds(update_millisecond));
-
-MotionEmu::MotionEmu(EmuWindow& emu_window)
-    : motion_emu_thread(&MotionEmu::MotionEmuThread, this, std::ref(emu_window)) {}
-
-MotionEmu::~MotionEmu() {
-    if (motion_emu_thread.joinable()) {
-        shutdown_event.Set();
-        motion_emu_thread.join();
-    }
-}
-
-void MotionEmu::MotionEmuThread(EmuWindow& emu_window) {
-    auto update_time = std::chrono::steady_clock::now();
-    Math::Quaternion<float> q = MakeQuaternion(Math::Vec3<float>(), 0);
-    Math::Quaternion<float> old_q;
-
-    while (!shutdown_event.WaitUntil(update_time)) {
-        update_time += update_duration;
-        old_q = q;
-
-        {
-            std::lock_guard<std::mutex> guard(tilt_mutex);
-
-            // Find the quaternion describing current 3DS tilting
-            q = MakeQuaternion(Math::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x),
-                               tilt_angle);
-        }
-
-        auto inv_q = q.Inverse();
-
-        // Set the gravity vector in world space
-        auto gravity = Math::MakeVec(0.0f, -1.0f, 0.0f);
-
-        // Find the angular rate vector in world space
-        auto angular_rate = ((q - old_q) * inv_q).xyz * 2;
-        angular_rate *= 1000 / update_millisecond / MathUtil::PI * 180;
-
-        // Transform the two vectors from world space to 3DS space
-        gravity = QuaternionRotate(inv_q, gravity);
-        angular_rate = QuaternionRotate(inv_q, angular_rate);
-
-        // Update the sensor state
-        emu_window.AccelerometerChanged(gravity.x, gravity.y, gravity.z);
-        emu_window.GyroscopeChanged(angular_rate.x, angular_rate.y, angular_rate.z);
-    }
-}
-
-void MotionEmu::BeginTilt(int x, int y) {
-    mouse_origin = Math::MakeVec(x, y);
-    is_tilting = true;
-}
-
-void MotionEmu::Tilt(int x, int y) {
-    constexpr float SENSITIVITY = 0.01f;
-    auto mouse_move = Math::MakeVec(x, y) - mouse_origin;
-    if (is_tilting) {
-        std::lock_guard<std::mutex> guard(tilt_mutex);
-        if (mouse_move.x == 0 && mouse_move.y == 0) {
-            tilt_angle = 0;
-        } else {
-            tilt_direction = mouse_move.Cast<float>();
-            tilt_angle = MathUtil::Clamp(tilt_direction.Normalize() * SENSITIVITY, 0.0f,
-                                         MathUtil::PI * 0.5f);
-        }
-    }
-}
-
-void MotionEmu::EndTilt() {
-    std::lock_guard<std::mutex> guard(tilt_mutex);
-    tilt_angle = 0;
-    is_tilting = false;
-}
-
-} // namespace Motion

+ 0 - 52
src/core/frontend/motion_emu.h

@@ -1,52 +0,0 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-#include "common/thread.h"
-#include "common/vector_math.h"
-
-class EmuWindow;
-
-namespace Motion {
-
-class MotionEmu final {
-public:
-    MotionEmu(EmuWindow& emu_window);
-    ~MotionEmu();
-
-    /**
-     * Signals that a motion sensor tilt has begun.
-     * @param x the x-coordinate of the cursor
-     * @param y the y-coordinate of the cursor
-     */
-    void BeginTilt(int x, int y);
-
-    /**
-     * Signals that a motion sensor tilt is occurring.
-     * @param x the x-coordinate of the cursor
-     * @param y the y-coordinate of the cursor
-     */
-    void Tilt(int x, int y);
-
-    /**
-     * Signals that a motion sensor tilt has ended.
-     */
-    void EndTilt();
-
-private:
-    Math::Vec2<int> mouse_origin;
-
-    std::mutex tilt_mutex;
-    Math::Vec2<float> tilt_direction;
-    float tilt_angle = 0;
-
-    bool is_tilting = false;
-
-    Common::Event shutdown_event;
-    std::thread motion_emu_thread;
-
-    void MotionEmuThread(EmuWindow& emu_window);
-};
-
-} // namespace Motion

+ 14 - 10
src/core/gdbstub/gdbstub.cpp

@@ -644,7 +644,7 @@ static void ReadMemory() {
 
 
     auto start_offset = command_buffer + 1;
     auto start_offset = command_buffer + 1;
     auto addr_pos = std::find(start_offset, command_buffer + command_length, ',');
     auto addr_pos = std::find(start_offset, command_buffer + command_length, ',');
-    PAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset));
+    VAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset));
 
 
     start_offset = addr_pos + 1;
     start_offset = addr_pos + 1;
     u32 len =
     u32 len =
@@ -656,12 +656,14 @@ static void ReadMemory() {
         SendReply("E01");
         SendReply("E01");
     }
     }
 
 
-    const u8* data = Memory::GetPointer(addr);
-    if (!data) {
+    if (!Memory::IsValidVirtualAddress(addr)) {
         return SendReply("E00");
         return SendReply("E00");
     }
     }
 
 
-    MemToGdbHex(reply, data, len);
+    std::vector<u8> data(len);
+    Memory::ReadBlock(addr, data.data(), len);
+
+    MemToGdbHex(reply, data.data(), len);
     reply[len * 2] = '\0';
     reply[len * 2] = '\0';
     SendReply(reinterpret_cast<char*>(reply));
     SendReply(reinterpret_cast<char*>(reply));
 }
 }
@@ -670,18 +672,20 @@ static void ReadMemory() {
 static void WriteMemory() {
 static void WriteMemory() {
     auto start_offset = command_buffer + 1;
     auto start_offset = command_buffer + 1;
     auto addr_pos = std::find(start_offset, command_buffer + command_length, ',');
     auto addr_pos = std::find(start_offset, command_buffer + command_length, ',');
-    PAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset));
+    VAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset));
 
 
     start_offset = addr_pos + 1;
     start_offset = addr_pos + 1;
     auto len_pos = std::find(start_offset, command_buffer + command_length, ':');
     auto len_pos = std::find(start_offset, command_buffer + command_length, ':');
     u32 len = HexToInt(start_offset, static_cast<u32>(len_pos - start_offset));
     u32 len = HexToInt(start_offset, static_cast<u32>(len_pos - start_offset));
 
 
-    u8* dst = Memory::GetPointer(addr);
-    if (!dst) {
+    if (!Memory::IsValidVirtualAddress(addr)) {
         return SendReply("E00");
         return SendReply("E00");
     }
     }
 
 
-    GdbHexToMem(dst, len_pos + 1, len);
+    std::vector<u8> data(len);
+
+    GdbHexToMem(data.data(), len_pos + 1, len);
+    Memory::WriteBlock(addr, data.data(), len);
     SendReply("OK");
     SendReply("OK");
 }
 }
 
 
@@ -946,7 +950,7 @@ static void Init(u16 port) {
     WSAStartup(MAKEWORD(2, 2), &InitData);
     WSAStartup(MAKEWORD(2, 2), &InitData);
 #endif
 #endif
 
 
-    int tmpsock = socket(PF_INET, SOCK_STREAM, 0);
+    int tmpsock = static_cast<int>(socket(PF_INET, SOCK_STREAM, 0));
     if (tmpsock == -1) {
     if (tmpsock == -1) {
         LOG_ERROR(Debug_GDBStub, "Failed to create gdb socket");
         LOG_ERROR(Debug_GDBStub, "Failed to create gdb socket");
     }
     }
@@ -973,7 +977,7 @@ static void Init(u16 port) {
     sockaddr_in saddr_client;
     sockaddr_in saddr_client;
     sockaddr* client_addr = reinterpret_cast<sockaddr*>(&saddr_client);
     sockaddr* client_addr = reinterpret_cast<sockaddr*>(&saddr_client);
     socklen_t client_addrlen = sizeof(saddr_client);
     socklen_t client_addrlen = sizeof(saddr_client);
-    gdbserver_socket = accept(tmpsock, client_addr, &client_addrlen);
+    gdbserver_socket = static_cast<int>(accept(tmpsock, client_addr, &client_addrlen));
     if (gdbserver_socket < 0) {
     if (gdbserver_socket < 0) {
         // In the case that we couldn't start the server for whatever reason, just start CPU
         // In the case that we couldn't start the server for whatever reason, just start CPU
         // execution like normal.
         // execution like normal.

+ 2 - 2
src/core/hle/applets/erreula.cpp

@@ -31,8 +31,8 @@ ResultCode ErrEula::ReceiveParameter(const Service::APT::MessageParameter& param
     heap_memory = std::make_shared<std::vector<u8>>(capture_info.size);
     heap_memory = std::make_shared<std::vector<u8>>(capture_info.size);
     // Create a SharedMemory that directly points to this heap block.
     // Create a SharedMemory that directly points to this heap block.
     framebuffer_memory = Kernel::SharedMemory::CreateForApplet(
     framebuffer_memory = Kernel::SharedMemory::CreateForApplet(
-        heap_memory, 0, heap_memory->size(), MemoryPermission::ReadWrite,
-        MemoryPermission::ReadWrite, "ErrEula Memory");
+        heap_memory, 0, capture_info.size, MemoryPermission::ReadWrite, MemoryPermission::ReadWrite,
+        "ErrEula Memory");
 
 
     // Send the response message with the newly created SharedMemory
     // Send the response message with the newly created SharedMemory
     Service::APT::MessageParameter result;
     Service::APT::MessageParameter result;

Неке датотеке нису приказане због велике количине промена