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
This commit is contained in:
bunnei 2017-10-09 23:56:20 -04:00
commit b1d5db1cf6
241 changed files with 20955 additions and 2730 deletions

4
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

@ -2,38 +2,39 @@ language: cpp
matrix:
include:
- os: linux
env: NAME="linux build"
sudo: required
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
env: NAME="macos build"
sudo: false
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:
provider: releases
api_key:
secure: Mck15DIWaJdxDiS3aYVlM9N3G6y8VKUI1rnwII7/iolfm1s94U+tgvbheZDmT7SSbFyaGaYO/E8HrV/uZR9Vvs7ev20sHsTN1u60OTWfDIIyHs9SqjhcGbtq95m9/dMFschOYqTOR+gAs5BsxjuoeAotHdhpQEwvkO2oo5oR0zhGy45gjFnVvtcxT/IfpZBIpVgcK3aLb9zT6ekcJbSiPmEB15iLq3xXd0nFUNtEZdX3D6Veye4n5jB6n72qN8JVoKvPZAwaC2K0pZxpcGJaXDchLsw1q+4eCvdz6UJfUemeQ/uMAmjfeQ3wrzYGXe3nCM3WmX5wosCsB0mw4zYatzl3si6CZ1W+0GkV4Rwlx03dfp7v3EeFhTsXYCaXqhwuLZnWOLUik8t9vaSoFUx4nUIRwfO9kAMUJQSpLuHNO2nT01s3GxvqxzczuLQ9he5nGSi0RRodUzDwek1qUp6I4uV3gRHKz4B07YIc1i2fK88NLXjyQ0uLVZ+7Oq1+kgDp6+N7vvXXZ5qZ17tdaysSbKEE0Y8zsoXw7Rk1tPN19vrCS+TSpomNMyQyne1k+I5iZ/qkxPTLAS5qI6Utc2dL3GJdxWRAEfGNO9AIX3GV/jmmKfdcvwGsCYP8hxqs5vLYfgacw3D8NLf1941lQUwavC17jm9EV9g5G3Pn1Cp516E=
file_glob: true
file: "artifacts/*.tar.*"
file: "artifacts/*"
skip_cleanup: true
on:
repo: citra-emu/citra-nightly
tags: true

37
.travis/clang-format/script.sh Executable file
View File

@ -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
.travis/common/post-upload.sh Executable file
View File

@ -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
.travis/common/pre-upload.sh Executable file
View File

@ -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
.travis/linux/build.sh Executable file
View File

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

3
.travis/linux/deps.sh Executable file
View File

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

17
.travis/linux/docker.sh Executable file
View File

@ -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
.travis/linux/upload.sh Executable file
View File

@ -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
.travis/macos/build.sh Executable file
View File

@ -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
.travis/macos/deps.sh Executable file
View File

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

110
.travis/macos/upload.sh Executable file
View File

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

View File

@ -2,6 +2,7 @@
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}/externals/cmake-modules")
include(DownloadExternals)
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(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)
message(STATUS "Copying pre-commit hook")
@ -129,8 +139,8 @@ else()
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_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()
# Set file offset size to 64 bits.
@ -151,24 +161,6 @@ set_property(DIRECTORY APPEND PROPERTY
# 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)
if (NOT PNG_FOUND)
message(STATUS "libpng not found. Some debugging features have been disabled.")
@ -295,15 +287,22 @@ function(create_directory_groups)
endforeach()
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)
get_git_head_revision(GIT_REF_SPEC GIT_REV)
git_describe(GIT_DESC --always --long --dirty)
git_branch_name(GIT_BRANCH)
get_timestamp(BUILD_DATE)
enable_testing()
add_subdirectory(externals)
add_subdirectory(src)
enable_testing()
# Installation instructions

View File

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

View File

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

View File

@ -1,15 +1,21 @@
# shallow clone
clone_depth: 10
# don't build on tag
skip_tags: true
cache:
- C:\ProgramData\chocolatey\bin -> appveyor.yml
- C:\ProgramData\chocolatey\lib -> appveyor.yml
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:
- x64
@ -18,59 +24,150 @@ configuration:
install:
- 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:
- 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 ..
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:
- ps: |
$GITDATE = $(git show -s --date=short --format='%ad') -replace "-",""
$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"
}
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
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
# 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:
- 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:
- path: $(MSVC_BUILD_NAME)
name: msvcbuild
type: zip
- path: $(MSVC_BUILD_PDB)
name: msvcdebug
- path: $(BUILD_ZIP)
name: build
type: zip
- path: $(BUILD_SYMBOLS)
name: debugsymbols
- path: $(BUILD_UPDATE)
name: update
deploy:
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:
secure: "dbpsMC/MgPKWFNJCXpQl4cR8FYhepkPLjgNp/pRMktZ8oLKTqPYErfreaIxb/4P1"
artifact: msvcbuild
artifact: update,build
draft: false
prerelease: false
on:
branch: master
appveyor_repo_name: citra-emu/citra-nightly
appveyor_repo_tag: true

BIN
dist/citra.icns vendored

Binary file not shown.

BIN
dist/citra.ico vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 497 KiB

After

Width:  |  Height:  |  Size: 361 KiB

24
dist/citra.manifest vendored Normal file
View File

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

80
dist/citra.svg vendored

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 17 KiB

BIN
dist/doc-icon.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

BIN
dist/icons/checked.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 B

BIN
dist/icons/failed.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

6
dist/icons/icons.qrc vendored Normal file
View File

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

View File

@ -1,5 +1,8 @@
# Definitions for all external bundled libraries
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
include(DownloadExternals)
# Catch
add_library(catch-single-include INTERFACE)
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
target_include_directories(SoundTouch INTERFACE ./soundtouch/include)
# Unicorn
add_library(unicorn-headers INTERFACE)
target_include_directories(unicorn-headers INTERFACE ./unicorn/include)
# Xbyak
if (ARCHITECTURE_x86_64)
# Defined before "dynarmic" above
@ -59,9 +66,21 @@ add_subdirectory(enet)
target_include_directories(enet INTERFACE ./enet/include)
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
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)
target_include_directories(cpr INTERFACE ./cpr/include)

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

2
externals/dynarmic vendored

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

2
externals/enet vendored

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

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

View File

@ -117,7 +117,9 @@ StereoBuffer16 DecodePCM16(const unsigned num_channels, const u8* const data,
ret[i].fill(sample);
}
} 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;

View File

@ -5,13 +5,13 @@
#pragma once
#include <array>
#include <vector>
#include <deque>
#include "common/common_types.h"
namespace Codec {
/// 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
struct ADPCMState {

View File

@ -244,17 +244,27 @@ void Source::GenerateFrame() {
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);
}
@ -305,25 +315,6 @@ bool Source::DequeueBuffer() {
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
state.current_sample_number = (!buf.has_played) ? buf.play_position : 0;
state.next_sample_number = state.current_sample_number;

View File

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

View File

@ -13,74 +13,64 @@ namespace AudioInterp {
constexpr u64 scale_factor = 1 << 24;
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.
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);
if (inputi + 2 >= input.size()) {
inputi = input.size() - 2;
break;
}
while (fposition < 1 * scale_factor) {
u64 fraction = fposition & scale_mask;
output.push_back(fn(fraction, state.xn2, state.xn1, input[0]));
output[outputi++] = fn(fraction, input[inputi], input[inputi + 1], input[inputi + 2]);
fposition += step_size;
}
while (fposition < 2 * scale_factor) {
u64 fraction = fposition & scale_mask;
state.xn2 = input[inputi];
state.xn1 = input[inputi + 1];
state.fposition = fposition - inputi * scale_factor;
output.push_back(fn(fraction, state.xn1, input[0], input[1]));
fposition += step_size;
}
while (fposition < max_fposition) {
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]));
fposition += step_size;
}
state.xn2 = input[input.size() - 2];
state.xn1 = input[input.size() - 1];
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; });
}
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.
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);
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),
};
});
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

View File

@ -5,40 +5,45 @@
#pragma once
#include <array>
#include <vector>
#include <deque>
#include "audio_core/hle/common.h"
#include "common/common_types.h"
namespace AudioInterp {
/// 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 {
// Two historical samples.
/// Two historical samples.
std::array<s16, 2> xn1 = {}; ///< x[n-1]
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.
* @param state Interpolation state.
* @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.
* @param state Interpolation state.
* @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

View File

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

View File

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

View File

@ -76,6 +76,11 @@ void Config::ReadValues() {
Settings::values.analogs[i] = default_param;
}
Settings::values.motion_device = sdl2_config->Get(
"Controls", "motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01");
Settings::values.touch_device =
sdl2_config->Get("Controls", "touch_device", "engine:emu_window");
// Core
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));
// Web Service
Settings::values.enable_telemetry =
sdl2_config->GetBoolean("WebService", "enable_telemetry", true);
Settings::values.telemetry_endpoint_url = sdl2_config->Get(
"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() {

View File

@ -12,7 +12,7 @@ const char* sdl2_config_file = R"(
# 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
# for button input, the following devices are avaible:
# for button input, the following devices are available:
# - "keyboard" (default) for keyboard input. Required parameters:
# - "code": the code of the key to bind
# - "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
# - "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"
# - "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
# - "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
@ -42,8 +42,8 @@ button_zl=
button_zr=
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.
# 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.
@ -56,6 +56,16 @@ button_home=
circle_pad=
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]
# Whether to use the Just-In-Time (JIT) compiler for CPU emulation
# 0: Interpreter (slow), 1 (default): JIT (fast)
@ -170,7 +180,16 @@ use_gdbstub=false
gdbstub_port=24689
[WebService]
# Whether or not to enable telemetry
# 0: No, 1 (default): Yes
enable_telemetry =
# 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 =
)";
}

View File

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

View File

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

View File

@ -12,6 +12,7 @@ set(SRCS
configuration/configure_graphics.cpp
configuration/configure_input.cpp
configuration/configure_system.cpp
configuration/configure_web.cpp
debugger/graphics/graphics.cpp
debugger/graphics/graphics_breakpoint_observer.cpp
debugger/graphics/graphics_breakpoints.cpp
@ -42,6 +43,7 @@ set(HEADERS
configuration/configure_graphics.h
configuration/configure_input.h
configuration/configure_system.h
configuration/configure_web.h
debugger/graphics/graphics.h
debugger/graphics/graphics_breakpoint_observer.h
debugger/graphics/graphics_breakpoints.h
@ -71,11 +73,13 @@ set(UIS
configuration/configure_graphics.ui
configuration/configure_input.ui
configuration/configure_system.ui
configuration/configure_web.ui
debugger/registers.ui
hotkeys.ui
main.ui
)
file(GLOB_RECURSE ICONS ${CMAKE_SOURCE_DIR}/dist/icons/*)
file(GLOB_RECURSE THEMES ${CMAKE_SOURCE_DIR}/dist/qt_themes/*)
create_directory_groups(${SRCS} ${HEADERS} ${UIS})
@ -89,10 +93,10 @@ endif()
if (APPLE)
set(MACOSX_ICON "../../dist/citra.icns")
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)
else()
add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS} ${THEMES})
add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS} ${ICONS} ${THEMES})
endif()
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)

View File

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

View File

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

View File

@ -1,3 +1,4 @@
#include "winresrc.h"
/////////////////////////////////////////////////////////////////////////////
//
// Icon
@ -5,5 +6,14 @@
// Icon with lowest ID value placed first to ensure application icon
// 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"

View File

@ -57,6 +57,13 @@ void Config::ReadValues() {
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->beginGroup("Core");
@ -134,10 +141,17 @@ void Config::ReadValues() {
qt_config->endGroup();
qt_config->beginGroup("WebService");
Settings::values.enable_telemetry = qt_config->value("enable_telemetry", true).toBool();
Settings::values.telemetry_endpoint_url =
qt_config->value("telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry")
.toString()
.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->beginGroup("UI");
@ -189,6 +203,7 @@ void Config::ReadValues() {
UISettings::values.show_status_bar = qt_config->value("showStatusBar", 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.callout_flags = qt_config->value("calloutFlags", 0).toUInt();
qt_config->endGroup();
}
@ -203,6 +218,8 @@ void Config::SaveValues() {
qt_config->setValue(QString::fromStdString(Settings::NativeAnalog::mapping[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->beginGroup("Core");
@ -277,8 +294,13 @@ void Config::SaveValues() {
qt_config->endGroup();
qt_config->beginGroup("WebService");
qt_config->setValue("enable_telemetry", Settings::values.enable_telemetry);
qt_config->setValue("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->beginGroup("UI");
@ -314,6 +336,7 @@ void Config::SaveValues() {
qt_config->setValue("showStatusBar", UISettings::values.show_status_bar);
qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing);
qt_config->setValue("firstStart", UISettings::values.first_start);
qt_config->setValue("calloutFlags", UISettings::values.callout_flags);
qt_config->endGroup();
}

View File

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

View File

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

View File

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

View File

@ -78,7 +78,8 @@ void ConfigureSystem::ReadSystemSettings() {
// set the console id
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() {

View File

@ -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."));
}
}

View File

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

View File

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

View File

@ -26,8 +26,8 @@
namespace {
QImage LoadTexture(const u8* src, const Pica::Texture::TextureInfo& info) {
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);
decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a()));
}

View File

@ -273,7 +273,8 @@ void GraphicsSurfaceWidget::Pick(int x, int y) {
surface_picker_x_control->setValue(x);
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->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
return;

View File

@ -183,23 +183,13 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con
print_input(output, src1, swizzle.negate_src1,
SelectorToString(swizzle.src1_selector));
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);
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);
break;
}
@ -222,16 +212,15 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con
SourceRegister src1 = instr.common.GetSrc1(src_is_inverted);
print_input(output, src1, swizzle.negate_src1,
swizzle.SelectorToString(false), true,
instr.common.AddressRegisterName());
src_is_inverted ? "" : instr.common.AddressRegisterName());
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) {
SourceRegister src2 = instr.common.GetSrc2(src_is_inverted);
print_input(output, src2, swizzle.negate_src2,
swizzle.SelectorToString(true));
swizzle.SelectorToString(true), true,
src_is_inverted ? instr.common.AddressRegisterName() : "");
AlignToColumn(kInputOperandColumnWidth);
}
break;
@ -247,7 +236,9 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con
switch (opcode.EffectiveOpCode()) {
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;
default:
@ -255,7 +246,7 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con
output << '(';
if (instr.flow_control.op != instr.flow_control.JustY) {
if (instr.flow_control.refx)
if (!instr.flow_control.refx)
output << '!';
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.refy)
if (!instr.flow_control.refy)
output << '!';
output << "cc.y";
}
output << ") ";
} 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 << ' ';
}

View File

@ -48,6 +48,47 @@
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
#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) {
Pica::g_debug_context = Pica::DebugContext::Construct();
setAcceptDrops(true);
@ -73,6 +114,9 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
UpdateUITheme();
// Show one-time "callout" messages to the user
ShowCallouts();
QStringList args = QApplication::arguments();
if (args.length() >= 2) {
BootGame(args[1]);
@ -311,7 +355,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
if (!gladLoadGL()) {
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."));
return false;
}
@ -320,6 +364,8 @@ bool GMainWindow::LoadROM(const QString& filename) {
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) {
switch (result) {
case Core::System::ResultStatus::ErrorGetLoader:

View File

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

View File

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

View File

@ -24,6 +24,7 @@
#pragma once
#include <array>
#include <cstdint>
#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 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
class NonCopyable {
protected:

View File

@ -30,6 +30,11 @@ public:
return {xyz * other.w + other.xyz * w + Cross(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>

View File

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

View File

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

View File

@ -117,7 +117,7 @@ std::string StringFromFormat(const char* format, ...) {
}
// 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;
oss << std::setfill('0') << std::hex;

View File

@ -33,7 +33,7 @@ inline void CharArrayFromFormat(char (&out)[Count], const char* format, ...) {
}
// 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 StripQuotes(const std::string& s);

View File

@ -90,8 +90,9 @@ public:
x -= other.x;
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);
}
Vec2<decltype(T{} * T{})> operator*(const Vec2& other) const {
@ -103,8 +104,7 @@ public:
}
template <typename V>
void operator*=(const V& f) {
x *= f;
y *= f;
*this = *this * f;
}
template <typename V>
Vec2<decltype(T{} / V{})> operator/(const V& f) const {
@ -247,8 +247,9 @@ public:
y -= other.y;
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);
}
Vec3<decltype(T{} * T{})> operator*(const Vec3& other) const {
@ -260,9 +261,7 @@ public:
}
template <typename V>
void operator*=(const V& f) {
x *= f;
y *= f;
z *= f;
*this = *this * f;
}
template <typename V>
Vec3<decltype(T{} / V{})> operator/(const V& f) const {
@ -462,8 +461,9 @@ public:
z -= other.z;
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);
}
Vec4<decltype(T{} * T{})> operator*(const Vec4& other) const {
@ -475,10 +475,7 @@ public:
}
template <typename V>
void operator*=(const V& f) {
x *= f;
y *= f;
z *= f;
w *= f;
*this = *this * f;
}
template <typename V>
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]);
}
} // namespace
} // namespace Math

View File

@ -6,6 +6,8 @@ set(SRCS
arm/dyncom/arm_dyncom_interpreter.cpp
arm/dyncom/arm_dyncom_thumb.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/armsupp.cpp
arm/skyeye_common/vfp/vfp.cpp
@ -26,14 +28,15 @@ set(SRCS
file_sys/archive_systemsavedata.cpp
file_sys/disk_archive.cpp
file_sys/ivfc_archive.cpp
file_sys/ncch_container.cpp
file_sys/path_parser.cpp
file_sys/savedata_archive.cpp
file_sys/title_metadata.cpp
frontend/camera/blank_camera.cpp
frontend/camera/factory.cpp
frontend/camera/interface.cpp
frontend/emu_window.cpp
frontend/framebuffer_layout.cpp
frontend/motion_emu.cpp
gdbstub/gdbstub.cpp
hle/config_mem.cpp
hle/applets/applet.cpp
@ -60,6 +63,7 @@ set(SRCS
hle/kernel/timer.cpp
hle/kernel/vm_manager.cpp
hle/kernel/wait_object.cpp
hle/lock.cpp
hle/romfs.cpp
hle/service/ac/ac.cpp
hle/service/ac/ac_i.cpp
@ -135,7 +139,8 @@ set(SRCS
hle/service/nim/nim_aoc.cpp
hle/service/nim/nim_s.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_cec.cpp
hle/service/nwm/nwm_ext.cpp
@ -145,6 +150,7 @@ set(SRCS
hle/service/nwm/nwm_tst.cpp
hle/service/nwm/nwm_uds.cpp
hle/service/nwm/uds_beacon.cpp
hle/service/nwm/uds_connection.cpp
hle/service/nwm/uds_data.cpp
hle/service/pm_app.cpp
hle/service/ptm/ptm.cpp
@ -198,6 +204,8 @@ set(HEADERS
arm/dyncom/arm_dyncom_run.h
arm/dyncom/arm_dyncom_thumb.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/armstate.h
arm/skyeye_common/armsupp.h
@ -229,7 +237,6 @@ set(HEADERS
frontend/emu_window.h
frontend/framebuffer_layout.h
frontend/input.h
frontend/motion_emu.h
gdbstub/gdbstub.h
hle/config_mem.h
hle/function_wrappers.h
@ -261,6 +268,7 @@ set(HEADERS
hle/kernel/timer.h
hle/kernel/vm_manager.h
hle/kernel/wait_object.h
hle/lock.h
hle/result.h
hle/romfs.h
hle/service/ac/ac.h
@ -337,7 +345,8 @@ set(HEADERS
hle/service/nim/nim_aoc.h
hle/service/nim/nim_s.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_cec.h
hle/service/nwm/nwm_ext.h
@ -347,6 +356,7 @@ set(HEADERS
hle/service/nwm/nwm_tst.h
hle/service/nwm/nwm_uds.h
hle/service/nwm/uds_beacon.h
hle/service/nwm/uds_connection.h
hle/service/nwm/uds_data.h
hle/service/pm_app.h
hle/service/ptm/ptm.h
@ -394,7 +404,7 @@ set(HEADERS
create_directory_groups(${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)
if (ENABLE_WEB_SERVICE)
target_link_libraries(core PUBLIC json-headers web_service)

View File

@ -5,6 +5,7 @@
#pragma once
#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/vfp/asm_vfp.h"
@ -19,10 +20,11 @@ public:
u64 sp;
u64 pc;
u64 cpsr;
u64 fpu_registers[64];
u128 fpu_registers[32];
u64 fpscr;
u64 fpexc;
// TODO(bunnei): Fix once we have proper support for tpidrro_el0, etc. in the JIT
VAddr tls_address;
};
@ -41,9 +43,14 @@ public:
Run(1);
}
virtual void MapBackingMemory(VAddr address, size_t size, u8* memory, Kernel::VMAPermission perms) {}
/// Clear all instruction cache
virtual void ClearInstructionCache() = 0;
/// Notify CPU emulation that page tables have changed
virtual void PageTableChanged() = 0;
/**
* Set the Program Counter to an address
* @param addr Address to set PC to
@ -70,6 +77,10 @@ public:
*/
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
* @param index Register index (0-31)
@ -128,12 +139,6 @@ public:
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
* @param ctx Thread context to save
@ -154,9 +159,6 @@ public:
return num_instructions;
}
s64 down_count = 0; ///< A decreasing counter of remaining cycles before the next event,
/// decreased by the cpu run loop
protected:
/**
* Executes the given number of instructions

View File

@ -16,24 +16,6 @@
static void InterpreterFallback(u64 pc, Dynarmic::Jit* jit, void* user_arg) {
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) {
@ -73,11 +55,10 @@ void MemoryWrite64(const u64 addr, const u64 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{};
//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.memory.IsReadOnlyMemory = &IsReadOnlyMemory;
user_callbacks.memory.ReadCode = &MemoryRead32;
@ -90,13 +71,13 @@ static Dynarmic::UserCallbacks GetUserCallbacks(
user_callbacks.memory.Write32 = &MemoryWrite32;
user_callbacks.memory.Write64 = &MemoryWrite64;
//user_callbacks.page_table = Memory::GetCurrentPageTablePointers();
user_callbacks.coprocessors[15] = std::make_shared<DynarmicCP15>(interpeter_state);
return user_callbacks;
}
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) {
@ -115,30 +96,26 @@ void ARM_Dynarmic::SetReg(int index, u64 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 {
return jit->ExtRegs()[index];
return {};
}
void ARM_Dynarmic::SetVFPReg(int index, u32 value) {
jit->ExtRegs()[index] = value;
}
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) {
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 {
@ -150,11 +127,10 @@ void ARM_Dynarmic::SetCPSR(u32 cpsr) {
}
u32 ARM_Dynarmic::GetCP15Register(CP15Register reg) {
return interpreter_state->CP15[reg];
return {};
}
void ARM_Dynarmic::SetCP15Register(CP15Register reg, u32 value) {
interpreter_state->CP15[reg] = value;
}
VAddr ARM_Dynarmic::GetTlsAddress() const {
@ -165,51 +141,39 @@ void ARM_Dynarmic::SetTlsAddress(VAddr 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));
void ARM_Dynarmic::ExecuteInstructions(int num_instructions) {
ASSERT(Memory::GetCurrentPageTable() == current_page_table);
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) {
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.sp = jit->Regs64()[31];
ctx.pc = jit->Regs64()[32];
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
ctx.tls_address = jit->TlsAddr();
}
void ARM_Dynarmic::LoadContext(const ARM_Interface::ThreadContext& ctx) {
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()[31] = ctx.sp;
jit->Regs64()[32] = ctx.pc;
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
jit->TlsAddr() = ctx.tls_address;
}
@ -223,3 +187,16 @@ void ARM_Dynarmic::PrepareReschedule() {
void ARM_Dynarmic::ClearInstructionCache() {
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));
}

View File

@ -4,20 +4,29 @@
#pragma once
#include <map>
#include <memory>
#include <dynarmic/dynarmic.h>
#include "common/common_types.h"
#include "core/arm/arm_interface.h"
#include "core/arm/skyeye_common/armstate.h"
namespace Memory {
struct PageTable;
} // namespace Memory
class ARM_Dynarmic final : public ARM_Interface {
public:
ARM_Dynarmic(PrivilegeMode initial_mode);
void MapBackingMemory(VAddr address, size_t size, u8* memory, Kernel::VMAPermission perms) override;
void SetPC(u64 pc) override;
u64 GetPC() const override;
u64 GetReg(int index) const 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;
void SetVFPReg(int index, u32 value) override;
u32 GetVFPSystemReg(VFPSystemRegister reg) const override;
@ -29,8 +38,6 @@ public:
VAddr GetTlsAddress() const override;
void SetTlsAddress(VAddr address) override;
void AddTicks(u64 ticks) override;
void SaveContext(ThreadContext& ctx) override;
void LoadContext(const ThreadContext& ctx) override;
@ -38,8 +45,10 @@ public:
void ExecuteInstructions(int num_instructions) override;
void ClearInstructionCache() override;
void PageTableChanged() override;
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;
};

View File

@ -29,6 +29,10 @@ void ARM_DynCom::SetPC(u64 pc) {
state->Reg[15] = pc;
}
void ARM_DynCom::PageTableChanged() {
ClearInstructionCache();
}
u64 ARM_DynCom::GetPC() const {
return state->Reg[15];
}
@ -41,6 +45,13 @@ void ARM_DynCom::SetReg(int index, u64 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 {
return state->ExtReg[index];
}
@ -80,12 +91,6 @@ VAddr ARM_DynCom::GetTlsAddress() const {
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) {
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
// instructions may actually be executed than specified.
unsigned ticks_executed = InterpreterMainLoop(state.get());
AddTicks(ticks_executed);
CoreTiming::AddTicks(ticks_executed);
}
void ARM_DynCom::SaveContext(ThreadContext& ctx) {

View File

@ -16,11 +16,14 @@ public:
~ARM_DynCom();
void ClearInstructionCache() override;
void PageTableChanged() override;
void SetPC(u64 pc) override;
u64 GetPC() const override;
u64 GetReg(int index) const 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;
void SetVFPReg(int index, u32 value) override;
u32 GetVFPSystemReg(VFPSystemRegister reg) const override;
@ -32,8 +35,6 @@ public:
VAddr GetTlsAddress() const override;
void SetTlsAddress(VAddr address) override;
void AddTicks(u64 ticks) override;
void SaveContext(ThreadContext& ctx) override;
void LoadContext(const ThreadContext& ctx) override;

View File

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

View File

@ -230,7 +230,7 @@ public:
// 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).
std::unordered_map<u32, int> instruction_cache;
std::unordered_map<u32, std::size_t> instruction_cache;
private:
void ResetMPCoreCP15Registers();

View File

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

View File

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

View File

@ -57,6 +57,9 @@ static s64 idled_cycles;
static s64 last_global_time_ticks;
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;
// Warning: not included in save state.
@ -146,7 +149,7 @@ void UnregisterAllEvents() {
}
void Init() {
Core::CPU().down_count = INITIAL_SLICE_LENGTH;
down_count = INITIAL_SLICE_LENGTH;
g_slice_length = INITIAL_SLICE_LENGTH;
global_timer = 0;
idled_cycles = 0;
@ -185,8 +188,15 @@ void Shutdown() {
}
}
void AddTicks(u64 ticks) {
down_count -= ticks;
if (down_count < 0) {
Advance();
}
}
u64 GetTicks() {
return (u64)global_timer + g_slice_length - Core::CPU().down_count;
return (u64)global_timer + g_slice_length - down_count;
}
u64 GetIdleTicks() {
@ -460,18 +470,18 @@ void MoveEvents() {
}
void ForceCheck() {
s64 cycles_executed = g_slice_length - Core::CPU().down_count;
s64 cycles_executed = g_slice_length - down_count;
global_timer += cycles_executed;
// 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.
g_slice_length = 0;
}
void Advance() {
s64 cycles_executed = g_slice_length - Core::CPU().down_count;
s64 cycles_executed = g_slice_length - down_count;
global_timer += cycles_executed;
Core::CPU().down_count = g_slice_length;
down_count = g_slice_length;
if (has_ts_events)
MoveEvents();
@ -480,7 +490,7 @@ void Advance() {
if (!first) {
if (g_slice_length < 10000) {
g_slice_length += 10000;
Core::CPU().down_count += g_slice_length;
down_count += g_slice_length;
}
} else {
// Note that events can eat cycles as well.
@ -490,7 +500,7 @@ void Advance() {
const int diff = target - g_slice_length;
g_slice_length += diff;
Core::CPU().down_count += diff;
down_count += diff;
}
if (advance_callback)
advance_callback(static_cast<int>(cycles_executed));
@ -506,12 +516,12 @@ void LogPendingEvents() {
}
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)
cycles_down = max_idle;
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;
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));
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() {

View File

@ -67,6 +67,12 @@ void Shutdown();
typedef void (*MHzChangeCallback)();
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 GetIdleTicks();
u64 GetGlobalTimeUs();

View File

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

View File

@ -13,7 +13,10 @@
#include "core/file_sys/archive_ncch.h"
#include "core/file_sys/errors.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/loader/loader.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
// 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) {
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)
@ -38,9 +51,14 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_NCCH::Open(const Path&
u32 high = data[1];
u32 low = data[0];
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).
constexpr u32 shared_data_archive = 0x0004009B;
constexpr u32 system_data_archive = 0x000400DB;
@ -74,9 +92,8 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_NCCH::Open(const Path&
}
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));
}

View File

@ -121,7 +121,25 @@ ResultCode SDMCArchive::DeleteFile(const Path& 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;
}
@ -260,8 +278,27 @@ ResultCode SDMCArchive::CreateDirectory(const Path& 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;
}
// TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
// exist or similar. Verify.

View File

@ -3,12 +3,14 @@
// Refer to the license.txt file included.
#include <array>
#include <cinttypes>
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/swap.h"
#include "core/file_sys/archive_selfncch.h"
#include "core/file_sys/errors.h"
#include "core/file_sys/ivfc_archive.h"
#include "core/hle/kernel/process.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
// FileSys namespace
@ -102,8 +104,7 @@ public:
switch (static_cast<SelfNCCHFilePathType>(file_path.type)) {
case SelfNCCHFilePathType::UpdateRomFS:
LOG_WARNING(Service_FS, "(STUBBED) open update RomFS");
return OpenRomFS();
return OpenUpdateRomFS();
case SelfNCCHFilePathType::RomFS:
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 {
if (filename == "icon") {
if (ncch_data.icon) {
@ -217,31 +229,59 @@ private:
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_;
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)) {
ncch_data.romfs_file = std::move(romfs_file_);
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)) {
data.update_romfs_file = std::move(update_romfs_file);
}
std::vector<u8> 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();
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();
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) {
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&) {

View File

@ -6,6 +6,7 @@
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include "common/common_types.h"
#include "core/file_sys/archive_backend.h"
@ -24,12 +25,19 @@ struct NCCHData {
std::shared_ptr<FileUtil::IOFile> romfs_file;
u64 romfs_offset = 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
class ArchiveFactory_SelfNCCH final : public ArchiveFactory {
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 {
return "SelfNCCH";
@ -39,7 +47,8 @@ public:
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override;
private:
NCCHData ncch_data;
/// Mapping of ProgramId -> NCCHData
std::unordered_map<u64, NCCHData> ncch_data;
};
} // namespace FileSys

View File

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

View File

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

View File

@ -106,7 +106,25 @@ ResultCode SaveDataArchive::DeleteFile(const Path& 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;
}
@ -247,8 +265,27 @@ ResultCode SaveDataArchive::CreateDirectory(const Path& 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;
}
// TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
// exist or similar. Verify.

View File

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

View File

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

View File

@ -2,14 +2,55 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#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/input.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
* @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))
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() {
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) {
if (!touch_pressed)
if (!touch_state->touch_pressed)
return;
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);
}
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) {
Layout::FramebufferLayout layout;
if (Settings::values.custom_layout == true) {
@ -97,6 +119,9 @@ void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height)
case Settings::LayoutOption::LargeScreen:
layout = Layout::LargeFrameLayout(width, height, Settings::values.swap_screen);
break;
case Settings::LayoutOption::SideScreen:
layout = Layout::SideFrameLayout(width, height, Settings::values.swap_screen);
break;
case Settings::LayoutOption::Default:
default:
layout = Layout::DefaultFrameLayout(width, height, Settings::values.swap_screen);

View File

@ -4,11 +4,10 @@
#pragma once
#include <mutex>
#include <memory>
#include <tuple>
#include <utility>
#include "common/common_types.h"
#include "common/math_util.h"
#include "core/frontend/framebuffer_layout.h"
/**
@ -68,84 +67,6 @@ public:
*/
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.
* @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);
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.
@ -250,20 +158,8 @@ private:
/// ProcessConfigurationChanges)
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.

View File

@ -141,6 +141,40 @@ FramebufferLayout LargeFrameLayout(unsigned width, unsigned height, bool swapped
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) {
ASSERT(width > 0);
ASSERT(height > 0);
@ -158,4 +192,4 @@ FramebufferLayout CustomFrameLayout(unsigned width, unsigned height) {
res.bottom_screen = bot_screen;
return res;
}
}
} // namespace Layout

View File

@ -53,6 +53,17 @@ FramebufferLayout SingleFrameLayout(unsigned width, unsigned height, bool is_swa
*/
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
* @param width Window framebuffer width in pixels

View File

@ -11,6 +11,7 @@
#include <utility>
#include "common/logging/log.h"
#include "common/param_package.h"
#include "common/vector_math.h"
namespace Input {
@ -107,4 +108,28 @@ using ButtonDevice = InputDevice<bool>;
*/
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

View File

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

View File

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

View File

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

View File

@ -31,8 +31,8 @@ ResultCode ErrEula::ReceiveParameter(const Service::APT::MessageParameter& param
heap_memory = std::make_shared<std::vector<u8>>(capture_info.size);
// Create a SharedMemory that directly points to this heap block.
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
Service::APT::MessageParameter result;

Some files were not shown because too many files have changed in this diff Show More