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.hmerge-requests/60/head
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
@ -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/
|
@ -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
|
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash -ex
|
||||||
|
|
||||||
|
docker run -v $(pwd):/citra ubuntu:16.04 /bin/bash /citra/.travis/linux/docker.sh
|
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh -ex
|
||||||
|
|
||||||
|
docker pull ubuntu:16.04
|
@ -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
|
@ -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
|
@ -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
|
@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh -ex
|
||||||
|
|
||||||
|
brew update
|
||||||
|
brew install qt5 sdl2 dylibbundler p7zip
|
@ -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
|
@ -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()
|
Before Width: | Height: | Size: 497 KiB After Width: | Height: | Size: 361 KiB |
@ -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>
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 451 B |
After Width: | Height: | Size: 428 B |
@ -0,0 +1,6 @@
|
|||||||
|
<RCC>
|
||||||
|
<qresource prefix="icons">
|
||||||
|
<file>checked.png</file>
|
||||||
|
<file>failed.png</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
@ -1 +1 @@
|
|||||||
Subproject commit 841c37e34765487a2968357369ab74db8b10a62d
|
Subproject commit 24bc2b85674254fb294e717eb5b47d9f53e786b8
|
@ -1 +1 @@
|
|||||||
Subproject commit 8f15e3f70cb96e56705e5de6ba97b5d09423a56b
|
Subproject commit 69eccf826d657a6cfb1d731b00629939d230ec5f
|
@ -1 +1 @@
|
|||||||
Subproject commit a84c120eff13d2fa3eadb41ef7afe0f7819f4d6c
|
Subproject commit 9d9ba122d4818f7ae1aef2197933ac696edb2331
|
@ -1 +1 @@
|
|||||||
Subproject commit 5274ec4dec498bd88ccbcd28862a0f78a3b95eff
|
Subproject commit 019d2089bbadf70d73ba85aa8ea51490b071262c
|
@ -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."));
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
};
|
@ -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>
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
|
@ -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
|
|