Merge pull request #206 from citra-emu/master

update
This commit is contained in:
emmauss 2016-09-02 13:23:35 +00:00 committed by GitHub
commit 18704f4f61
120 changed files with 9072 additions and 3233 deletions

25
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,25 @@
<!---
Please read the FAQ:
https://citra-emu.org/wiki/FAQ
THIS IS NOT A SUPPORT FORUM, FOR SUPPORT GO TO:
http://discuss.citra-emu.org/
If the FAQ does not answer your question, please go to:
http://discuss.citra-emu.org/
====================================================
When submitting an issue, please check the following:
- You have read the above.
- You have provided the version (commit hash) of Citra you are using.
- You have provided sufficient detail for the issue to be reproduced.
- You have provided system specs (if relevant).
- Please also provide:
- For crashes, a backtrace.
- For graphical issues, comparison screenshots with real hardware.
- For emulation inaccuracies, a test-case (if able).
--->

View File

@ -11,12 +11,12 @@ fi
#if OS is linux or is not set #if OS is linux or is not set
if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then
export CC=gcc-5 export CC=gcc-6
export CXX=g++-5 export CXX=g++-6
export PKG_CONFIG_PATH=$HOME/.local/lib/pkgconfig:$PKG_CONFIG_PATH export PKG_CONFIG_PATH=$HOME/.local/lib/pkgconfig:$PKG_CONFIG_PATH
mkdir build && cd build mkdir build && cd build
cmake -DCITRA_FORCE_QT4=ON .. cmake ..
make -j4 make -j4
ctest -VV -C Release ctest -VV -C Release

View File

@ -5,11 +5,11 @@ set -x
#if OS is linux or is not set #if OS is linux or is not set
if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then
export CC=gcc-5 export CC=gcc-6
export CXX=g++-5 export CXX=g++-6
mkdir -p $HOME/.local mkdir -p $HOME/.local
curl -L http://www.cmake.org/files/v3.1/cmake-3.1.0-Linux-i386.tar.gz \ curl -L http://www.cmake.org/files/v3.2/cmake-3.2.0-Linux-i386.tar.gz \
| tar -xz -C $HOME/.local --strip-components=1 | tar -xz -C $HOME/.local --strip-components=1
( (
@ -21,6 +21,6 @@ if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then
elif [ "$TRAVIS_OS_NAME" = "osx" ]; then elif [ "$TRAVIS_OS_NAME" = "osx" ]; then
brew update > /dev/null # silence the very verbose output brew update > /dev/null # silence the very verbose output
brew unlink cmake brew unlink cmake
brew install cmake31 qt5 sdl2 dylibbundler brew install cmake qt5 sdl2 dylibbundler
gem install xcpretty gem install xcpretty
fi fi

View File

@ -23,8 +23,103 @@ if [ "$TRAVIS_BRANCH" = "master" ]; then
# move SDL2 libs into folder for deployment # move SDL2 libs into folder for deployment
dylibbundler -b -x "${REV_NAME}/citra" -cd -d "${REV_NAME}/libs" -p "@executable_path/libs/" 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 fi
# Copy documentation
cp license.txt "$REV_NAME"
cp README.md "$REV_NAME"
ARCHIVE_NAME="${REV_NAME}.tar.xz" ARCHIVE_NAME="${REV_NAME}.tar.xz"
tar -cJvf "$ARCHIVE_NAME" "$REV_NAME" tar -cJvf "$ARCHIVE_NAME" "$REV_NAME"
lftp -c "open -u citra-builds,$BUILD_PASSWORD sftp://builds.citra-emu.org; set sftp:auto-confirm yes; put -O '$UPLOAD_DIR' '$ARCHIVE_NAME'" lftp -c "open -u citra-builds,$BUILD_PASSWORD sftp://builds.citra-emu.org; set sftp:auto-confirm yes; put -O '$UPLOAD_DIR' '$ARCHIVE_NAME'"

View File

@ -1,22 +1,26 @@
os:
- linux
- osx
language: cpp language: cpp
matrix:
include:
- os: linux
sudo: true
dist: trusty
- os: osx
sudo: false
env: env:
global: global:
- secure: "AXHFIafTmbGDsHD3mUVj5a4I397DQjti/WoqAJGUp2PglxTcc04BwxZ9Z+xLuf5N2Hs5r9ojAJLT8OGxJCLBDXzneQTNSqXbFuYSLbqrEAiIRlA9eRIotWCg+wYcO+5e8MKX+cHVKwiIWasUB21AtCdq6msh6Y3pUshZp212VPg=" - secure: "AXHFIafTmbGDsHD3mUVj5a4I397DQjti/WoqAJGUp2PglxTcc04BwxZ9Z+xLuf5N2Hs5r9ojAJLT8OGxJCLBDXzneQTNSqXbFuYSLbqrEAiIRlA9eRIotWCg+wYcO+5e8MKX+cHVKwiIWasUB21AtCdq6msh6Y3pUshZp212VPg="
sudo: false
addons: addons:
apt: apt:
sources: sources:
- ubuntu-toolchain-r-test - ubuntu-toolchain-r-test
packages: packages:
- gcc-5 - gcc-6
- g++-5 - g++-6
- qt5-default
- libqt5opengl5-dev
- xorg-dev - xorg-dev
- lib32stdc++6 # For CMake - lib32stdc++6 # For CMake
- lftp # To upload builds - lftp # To upload builds

View File

@ -1,6 +1,5 @@
# CMake 3.1 required for Qt5 settings to be applied automatically on # CMake 3.2 required for cmake to know the right flags for CXX standard on OSX
# dependent libraries and IMPORTED targets. cmake_minimum_required(VERSION 3.2)
cmake_minimum_required(VERSION 3.1)
function(download_bundled_external remote_path lib_name prefix_var) function(download_bundled_external remote_path lib_name prefix_var)
set(prefix "${CMAKE_BINARY_DIR}/externals/${lib_name}") set(prefix "${CMAKE_BINARY_DIR}/externals/${lib_name}")
@ -40,7 +39,6 @@ option(CITRA_USE_BUNDLED_SDL2 "Download bundled SDL2 binaries" OFF)
option(ENABLE_QT "Enable the Qt frontend" ON) option(ENABLE_QT "Enable the Qt frontend" ON)
option(CITRA_USE_BUNDLED_QT "Download bundled Qt binaries" OFF) option(CITRA_USE_BUNDLED_QT "Download bundled Qt binaries" OFF)
option(CITRA_FORCE_QT4 "Use Qt4 even if Qt5 is available." OFF)
if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/.git/hooks/pre-commit) if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/.git/hooks/pre-commit)
message(STATUS "Copying pre-commit hook") message(STATUS "Copying pre-commit hook")
@ -64,14 +62,12 @@ if (NOT DEFINED ARCHITECTURE)
endif() endif()
message(STATUS "Target architecture: ${ARCHITECTURE}") message(STATUS "Target architecture: ${ARCHITECTURE}")
if (NOT MSVC) set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -Wno-attributes") set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
if (ARCHITECTURE_x86_64) if (NOT MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.1") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-attributes")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msse4.1") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
endif()
else() else()
# Silence "deprecation" warnings # Silence "deprecation" warnings
add_definitions(/D_CRT_SECURE_NO_WARNINGS /D_CRT_NONSTDC_NO_DEPRECATE /D_SCL_SECURE_NO_WARNINGS) add_definitions(/D_CRT_SECURE_NO_WARNINGS /D_CRT_NONSTDC_NO_DEPRECATE /D_SCL_SECURE_NO_WARNINGS)
@ -185,7 +181,7 @@ ENDIF (APPLE)
if (ENABLE_QT) if (ENABLE_QT)
if (CITRA_USE_BUNDLED_QT) if (CITRA_USE_BUNDLED_QT)
if (MSVC14 AND ARCHITECTURE_x86_64) if (MSVC14 AND ARCHITECTURE_x86_64)
set(QT_VER qt-5.5-msvc2015_64) set(QT_VER qt-5.7-msvc2015_64)
else() else()
message(FATAL_ERROR "No bundled Qt binaries for your toolchain. Disable CITRA_USE_BUNDLED_QT and provide your own.") message(FATAL_ERROR "No bundled Qt binaries for your toolchain. Disable CITRA_USE_BUNDLED_QT and provide your own.")
endif() endif()
@ -201,16 +197,8 @@ if (ENABLE_QT)
set(QT_PREFIX_HINT) set(QT_PREFIX_HINT)
endif() endif()
if (NOT CITRA_FORCE_QT4) find_package(Qt5 REQUIRED COMPONENTS Widgets OpenGL ${QT_PREFIX_HINT})
find_package(Qt5 COMPONENTS Widgets OpenGL ${QT_PREFIX_HINT}) set(CITRA_QT_LIBS Qt5::Widgets Qt5::OpenGL)
set(CITRA_QT_LIBS Qt5::Widgets Qt5::OpenGL)
endif()
if (CITRA_FORCE_QT4 OR NOT Qt5_FOUND)
# Try to fallback to Qt4
find_package(Qt4 REQUIRED COMPONENTS QtGui QtOpenGL ${QT_PREFIX_HINT})
set(CITRA_QT_LIBS Qt4::QtGui Qt4::QtOpenGL)
endif()
endif() endif()
# This function should be passed a list of all files in a target. It will automatically generate # This function should be passed a list of all files in a target. It will automatically generate

View File

@ -15,6 +15,8 @@ For development discussion, please join us @ #citra on freenode.
### Development ### Development
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), [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.
### Building ### Building

View File

@ -5,6 +5,10 @@ environment:
BUILD_PASSWORD: BUILD_PASSWORD:
secure: EXGNlWKJsCtbeImEJ5EP9qrxZ+EqUFfNy+CP61nDOMA= secure: EXGNlWKJsCtbeImEJ5EP9qrxZ+EqUFfNy+CP61nDOMA=
cache:
- C:\ProgramData\chocolatey\bin -> appveyor.yml
- C:\ProgramData\chocolatey\lib -> appveyor.yml
os: Visual Studio 2015 os: Visual Studio 2015
platform: platform:
@ -38,22 +42,30 @@ on_success:
$GITREV = $(git show -s --format='%h') $GITREV = $(git show -s --format='%h')
# Where are these spaces coming from? Regardless, let's remove them # Where are these spaces coming from? Regardless, let's remove them
$BUILD_NAME = "citra-${GITDATE}-${GITREV}-windows-amd64.7z" -replace " ","" $BUILD_NAME = "citra-${GITDATE}-${GITREV}-windows-amd64.7z" -replace " ",""
$BUILD_NAME_PDB = "citra-${GITDATE}-${GITREV}-windows-amd64-debugsymbols.7z" -replace " ",""
$BUILD_NAME_NOQT = "citra-noqt-${GITDATE}-${GITREV}-windows-amd64.7z" -replace " ","" $BUILD_NAME_NOQT = "citra-noqt-${GITDATE}-${GITREV}-windows-amd64.7z" -replace " ",""
# Zip up the build folder
7z a $BUILD_NAME .\build\bin\release\*
# Do a second archive with only the binaries
7z a $BUILD_NAME_NOQT .\build\bin\release\*.exe
# Download winscp # Remove unnecessary files
Invoke-WebRequest "http://iweb.dl.sourceforge.net/project/winscp/WinSCP/5.7.3/winscp573.zip" -OutFile "winscp573.zip" rm .\build\bin\release\*tests*
7z e -y winscp573.zip
# Upload to server # Put the pdb files in a separate archive and remove them from the main download
.\WinSCP.com /command ` 7z a $BUILD_NAME_PDB .\build\bin\release\*.pdb
rm .\build\bin\release\*.pdb
# Zip up the build folder and documentation
7z a $BUILD_NAME .\build\bin\release\* .\license.txt .\README.md
# Do a second archive with only the binaries (excludes dlls) and documentation
7z a $BUILD_NAME_NOQT .\build\bin\release\*.exe .\license.txt .\README.md
# Download WinSCP and upload to server
choco install winscp.portable
WinSCP.exe /command `
"option batch abort" ` "option batch abort" `
"option confirm off" ` "option confirm off" `
"open sftp://citra-builds:${env:BUILD_PASSWORD}@builds.citra-emu.org -hostkey=*" ` "open sftp://citra-builds:${env:BUILD_PASSWORD}@builds.citra-emu.org -hostkey=*" `
"put $BUILD_NAME /citra/nightly/windows-amd64/" ` "put $BUILD_NAME /citra/nightly/windows-amd64/" `
"put $BUILD_NAME_NOQT /citra/nightly/windows-noqt-amd64/" ` "put $BUILD_NAME_NOQT /citra/nightly/windows-noqt-amd64/" `
"put $BUILD_NAME_PDB /citra/nightly/windows-amd64-debugsymbols/" `
"exit" "exit"
} }

View File

@ -3,6 +3,7 @@
# SDL2_LIBRARY, the name of the library to link against # SDL2_LIBRARY, the name of the library to link against
# SDL2_FOUND, if false, do not try to link to SDL2 # SDL2_FOUND, if false, do not try to link to SDL2
# SDL2_INCLUDE_DIR, where to find SDL.h # SDL2_INCLUDE_DIR, where to find SDL.h
# SDL2_DLL_DIR, where to find SDL2.dll if it exists
# #
# This module responds to the the flag: # This module responds to the the flag:
# SDL2_BUILDING_LIBRARY # SDL2_BUILDING_LIBRARY
@ -149,6 +150,14 @@ FIND_LIBRARY(SDL2_LIBRARY_TEMP
) )
IF(SDL2_LIBRARY_TEMP) IF(SDL2_LIBRARY_TEMP)
if(MSVC)
get_filename_component(SDL2_DLL_DIR_TEMP ${SDL2_LIBRARY_TEMP} DIRECTORY)
if(EXISTS ${SDL2_DLL_DIR_TEMP}/SDL2.dll)
set(SDL2_DLL_DIR ${SDL2_DLL_DIR_TEMP})
unset(SDL2_DLL_DIR_TEMP)
endif()
endif()
FIND_PATH(SDL2_INCLUDE_DIR SDL.h FIND_PATH(SDL2_INCLUDE_DIR SDL.h
HINTS HINTS
$ENV{SDL2DIR} $ENV{SDL2DIR}

View File

@ -71,6 +71,10 @@ void SelectSink(std::string sink_id) {
DSP::HLE::SetSink(iter->factory()); DSP::HLE::SetSink(iter->factory());
} }
void EnableStretching(bool enable) {
DSP::HLE::EnableStretching(enable);
}
void Shutdown() { void Shutdown() {
CoreTiming::UnscheduleEvent(tick_event, 0); CoreTiming::UnscheduleEvent(tick_event, 0);
DSP::HLE::Shutdown(); DSP::HLE::Shutdown();

View File

@ -23,6 +23,9 @@ void AddAddressSpace(Kernel::VMManager& vm_manager);
/// Select the sink to use based on sink id. /// Select the sink to use based on sink id.
void SelectSink(std::string sink_id); void SelectSink(std::string sink_id);
/// Enable/Disable stretching.
void EnableStretching(bool enable);
/// Shutdown Audio Core /// Shutdown Audio Core
void Shutdown(); void Shutdown();

View File

@ -85,12 +85,45 @@ static StereoFrame16 GenerateCurrentFrame() {
// Audio output // Audio output
static bool perform_time_stretching = true;
static std::unique_ptr<AudioCore::Sink> sink; static std::unique_ptr<AudioCore::Sink> sink;
static AudioCore::TimeStretcher time_stretcher; static AudioCore::TimeStretcher time_stretcher;
static void FlushResidualStretcherAudio() {
time_stretcher.Flush();
while (true) {
std::vector<s16> residual_audio = time_stretcher.Process(sink->SamplesInQueue());
if (residual_audio.empty())
break;
sink->EnqueueSamples(residual_audio.data(), residual_audio.size() / 2);
}
}
static void OutputCurrentFrame(const StereoFrame16& frame) { static void OutputCurrentFrame(const StereoFrame16& frame) {
time_stretcher.AddSamples(&frame[0][0], frame.size()); if (perform_time_stretching) {
sink->EnqueueSamples(time_stretcher.Process(sink->SamplesInQueue())); time_stretcher.AddSamples(&frame[0][0], frame.size());
std::vector<s16> stretched_samples = time_stretcher.Process(sink->SamplesInQueue());
sink->EnqueueSamples(stretched_samples.data(), stretched_samples.size() / 2);
} else {
constexpr size_t maximum_sample_latency = 1024; // about 32 miliseconds
if (sink->SamplesInQueue() > maximum_sample_latency) {
// This can occur if we're running too fast and samples are starting to back up.
// Just drop the samples.
return;
}
sink->EnqueueSamples(&frame[0][0], frame.size());
}
}
void EnableStretching(bool enable) {
if (perform_time_stretching == enable)
return;
if (!enable) {
FlushResidualStretcherAudio();
}
perform_time_stretching = enable;
} }
// Public Interface // Public Interface
@ -111,12 +144,8 @@ void Init() {
} }
void Shutdown() { void Shutdown() {
time_stretcher.Flush(); if (perform_time_stretching) {
while (true) { FlushResidualStretcherAudio();
std::vector<s16> residual_audio = time_stretcher.Process(sink->SamplesInQueue());
if (residual_audio.empty())
break;
sink->EnqueueSamples(residual_audio);
} }
} }

View File

@ -544,5 +544,13 @@ bool Tick();
*/ */
void SetSink(std::unique_ptr<AudioCore::Sink> sink); void SetSink(std::unique_ptr<AudioCore::Sink> sink);
/**
* Enables/Disables audio-stretching.
* Audio stretching is an enhancement that stretches audio to match emulation
* speed to prevent stuttering at the cost of some audio latency.
* @param enable true to enable, false to disable.
*/
void EnableStretching(bool enable);
} // namespace HLE } // namespace HLE
} // namespace DSP } // namespace DSP

View File

@ -19,7 +19,7 @@ public:
return native_sample_rate; return native_sample_rate;
} }
void EnqueueSamples(const std::vector<s16>&) override {} void EnqueueSamples(const s16*, size_t) override {}
size_t SamplesInQueue() const override { size_t SamplesInQueue() const override {
return 0; return 0;

View File

@ -71,14 +71,12 @@ unsigned int SDL2Sink::GetNativeSampleRate() const {
return impl->sample_rate; return impl->sample_rate;
} }
void SDL2Sink::EnqueueSamples(const std::vector<s16>& samples) { void SDL2Sink::EnqueueSamples(const s16* samples, size_t sample_count) {
if (impl->audio_device_id <= 0) if (impl->audio_device_id <= 0)
return; return;
ASSERT_MSG(samples.size() % 2 == 0, "Samples must be in interleaved stereo PCM16 format (size must be a multiple of two)");
SDL_LockAudioDevice(impl->audio_device_id); SDL_LockAudioDevice(impl->audio_device_id);
impl->queue.emplace_back(samples); impl->queue.emplace_back(samples, samples + sample_count * 2);
SDL_UnlockAudioDevice(impl->audio_device_id); SDL_UnlockAudioDevice(impl->audio_device_id);
} }

View File

@ -18,7 +18,7 @@ public:
unsigned int GetNativeSampleRate() const override; unsigned int GetNativeSampleRate() const override;
void EnqueueSamples(const std::vector<s16>& samples) override; void EnqueueSamples(const s16* samples, size_t sample_count) override;
size_t SamplesInQueue() const override; size_t SamplesInQueue() const override;

View File

@ -23,9 +23,10 @@ public:
/** /**
* Feed stereo samples to sink. * Feed stereo samples to sink.
* @param samples Samples in interleaved stereo PCM16 format. Size of vector must be multiple of two. * @param samples Samples in interleaved stereo PCM16 format.
* @param sample_count Number of samples.
*/ */
virtual void EnqueueSamples(const std::vector<s16>& samples) = 0; virtual void EnqueueSamples(const s16* samples, size_t sample_count) = 0;
/// Samples enqueued that have not been played yet. /// Samples enqueued that have not been played yet.
virtual std::size_t SamplesInQueue() const = 0; virtual std::size_t SamplesInQueue() const = 0;

View File

@ -17,11 +17,16 @@
#include <getopt.h> #include <getopt.h>
#endif #endif
#ifdef _WIN32
#include <Windows.h>
#endif
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/logging/backend.h" #include "common/logging/backend.h"
#include "common/logging/filter.h" #include "common/logging/filter.h"
#include "common/scm_rev.h" #include "common/scm_rev.h"
#include "common/scope_exit.h" #include "common/scope_exit.h"
#include "common/string_util.h"
#include "core/settings.h" #include "core/settings.h"
#include "core/system.h" #include "core/system.h"
@ -55,6 +60,15 @@ int main(int argc, char **argv) {
bool use_gdbstub = Settings::values.use_gdbstub; bool use_gdbstub = Settings::values.use_gdbstub;
u32 gdb_port = static_cast<u32>(Settings::values.gdbstub_port); u32 gdb_port = static_cast<u32>(Settings::values.gdbstub_port);
char *endarg; char *endarg;
#ifdef _WIN32
int argc_w;
auto argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w);
if (argv_w == nullptr) {
LOG_CRITICAL(Frontend, "Failed to get command line arguments");
return -1;
}
#endif
std::string boot_filename; std::string boot_filename;
static struct option long_options[] = { static struct option long_options[] = {
@ -86,11 +100,19 @@ int main(int argc, char **argv) {
return 0; return 0;
} }
} else { } else {
#ifdef _WIN32
boot_filename = Common::UTF16ToUTF8(argv_w[optind]);
#else
boot_filename = argv[optind]; boot_filename = argv[optind];
#endif
optind++; optind++;
} }
} }
#ifdef _WIN32
LocalFree(argv_w);
#endif
Log::Filter log_filter(Log::Level::Debug); Log::Filter log_filter(Log::Level::Debug);
Log::SetFilter(&log_filter); Log::SetFilter(&log_filter);

View File

@ -68,9 +68,10 @@ void Config::ReadValues() {
Settings::values.frame_skip = sdl2_config->GetInteger("Core", "frame_skip", 0); Settings::values.frame_skip = sdl2_config->GetInteger("Core", "frame_skip", 0);
// Renderer // Renderer
Settings::values.use_hw_renderer = sdl2_config->GetBoolean("Renderer", "use_hw_renderer", false); Settings::values.use_hw_renderer = sdl2_config->GetBoolean("Renderer", "use_hw_renderer", true);
Settings::values.use_shader_jit = sdl2_config->GetBoolean("Renderer", "use_shader_jit", true); Settings::values.use_shader_jit = sdl2_config->GetBoolean("Renderer", "use_shader_jit", true);
Settings::values.use_scaled_resolution = sdl2_config->GetBoolean("Renderer", "use_scaled_resolution", false); Settings::values.use_scaled_resolution = sdl2_config->GetBoolean("Renderer", "use_scaled_resolution", false);
Settings::values.use_vsync = sdl2_config->GetBoolean("Renderer", "use_vsync", false);
Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 1.0); Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 1.0);
Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 1.0); Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 1.0);
@ -78,6 +79,7 @@ void Config::ReadValues() {
// Audio // Audio
Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto"); Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto");
Settings::values.enable_audio_stretching = sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true);
// Data Storage // Data Storage
Settings::values.use_virtual_sd = sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true); Settings::values.use_virtual_sd = sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);

View File

@ -44,17 +44,21 @@ frame_skip =
[Renderer] [Renderer]
# Whether to use software or hardware rendering. # Whether to use software or hardware rendering.
# 0 (default): Software, 1: Hardware # 0: Software, 1 (default): Hardware
use_hw_renderer = use_hw_renderer =
# Whether to use the Just-In-Time (JIT) compiler for shader emulation # Whether to use the Just-In-Time (JIT) compiler for shader emulation
# 0 : Interpreter (slow), 1 (default): JIT (fast) # 0: Interpreter (slow), 1 (default): JIT (fast)
use_shader_jit = use_shader_jit =
# Whether to use native 3DS screen resolution or to scale rendering resolution to the displayed screen size. # Whether to use native 3DS screen resolution or to scale rendering resolution to the displayed screen size.
# 0 (default): Native, 1: Scaled # 0 (default): Native, 1: Scaled
use_scaled_resolution = use_scaled_resolution =
# Whether to enable V-Sync (caps the framerate at 60FPS) or not.
# 0 (default): Off, 1: On
use_vsync =
# The clear color for the renderer. What shows up on the sides of the bottom screen. # The clear color for the renderer. What shows up on the sides of the bottom screen.
# Must be in range of 0.0-1.0. Defaults to 1.0 for all. # Must be in range of 0.0-1.0. Defaults to 1.0 for all.
bg_red = bg_red =
@ -66,6 +70,12 @@ bg_green =
# auto (default): Auto-select, null: No audio output, sdl2: SDL2 (if available) # auto (default): Auto-select, null: No audio output, sdl2: SDL2 (if available)
output_engine = output_engine =
# Whether or not to enable the audio-stretching post-processing effect.
# This effect adjusts audio speed to match emulation speed and helps prevent audio stutter,
# at the cost of increasing audio latency.
# 0: No, 1 (default): Yes
enable_audio_stretching =
[Data Storage] [Data Storage]
# Whether to create a virtual SD card. # Whether to create a virtual SD card.
# 1 (default): Yes, 0: No # 1 (default): Yes, 0: No

View File

@ -108,6 +108,7 @@ EmuWindow_SDL2::EmuWindow_SDL2() {
OnResize(); OnResize();
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
SDL_PumpEvents(); SDL_PumpEvents();
SDL_GL_SetSwapInterval(Settings::values.use_vsync);
DoneCurrent(); DoneCurrent();
} }

View File

@ -22,6 +22,9 @@ set(SRCS
configure_debug.cpp configure_debug.cpp
configure_dialog.cpp configure_dialog.cpp
configure_general.cpp configure_general.cpp
configure_graphics.cpp
configure_system.cpp
configure_input.cpp
game_list.cpp game_list.cpp
hotkeys.cpp hotkeys.cpp
main.cpp main.cpp
@ -52,6 +55,9 @@ set(HEADERS
configure_debug.h configure_debug.h
configure_dialog.h configure_dialog.h
configure_general.h configure_general.h
configure_graphics.h
configure_system.h
configure_input.h
game_list.h game_list.h
game_list_p.h game_list_p.h
hotkeys.h hotkeys.h
@ -69,6 +75,9 @@ set(UIS
configure_audio.ui configure_audio.ui
configure_debug.ui configure_debug.ui
configure_general.ui configure_general.ui
configure_graphics.ui
configure_system.ui
configure_input.ui
hotkeys.ui hotkeys.ui
main.ui main.ui
) )

View File

@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>English</string> <string>English</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>${EXECUTABLE_NAME}</string>
<key>CFBundleGetInfoString</key> <key>CFBundleGetInfoString</key>
<string></string> <string></string>
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>

View File

@ -107,36 +107,13 @@ private:
}; };
GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) : GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) :
QWidget(parent), keyboard_id(0), emu_thread(emu_thread) { QWidget(parent), keyboard_id(0), emu_thread(emu_thread), child(nullptr) {
std::string window_title = Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc); std::string window_title = Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc);
setWindowTitle(QString::fromStdString(window_title)); setWindowTitle(QString::fromStdString(window_title));
keyboard_id = KeyMap::NewDeviceId(); keyboard_id = KeyMap::NewDeviceId();
ReloadSetKeymaps(); ReloadSetKeymaps();
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, WA_DontShowOnScreen, WA_DeleteOnClose
QGLFormat fmt;
fmt.setVersion(3,3);
fmt.setProfile(QGLFormat::CoreProfile);
// Requests a forward-compatible context, which is required to get a 3.2+ context on OS X
fmt.setOption(QGL::NoDeprecatedFunctions);
child = new GGLWidgetInternal(fmt, this);
QBoxLayout* layout = new QHBoxLayout(this);
resize(VideoCore::kScreenTopWidth, VideoCore::kScreenTopHeight + VideoCore::kScreenBottomHeight);
layout->addWidget(child);
layout->setMargin(0);
setLayout(layout);
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
OnFramebufferSizeChanged();
NotifyClientAreaSizeChanged(std::pair<unsigned,unsigned>(child->width(), child->height()));
BackupGeometry();
} }
void GRenderWindow::moveContext() void GRenderWindow::moveContext()
@ -281,6 +258,40 @@ void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height)
NotifyClientAreaSizeChanged(std::make_pair(width, height)); NotifyClientAreaSizeChanged(std::make_pair(width, height));
} }
void GRenderWindow::InitRenderTarget() {
if (child) {
delete child;
}
if (layout()) {
delete layout();
}
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, WA_DontShowOnScreen, WA_DeleteOnClose
QGLFormat fmt;
fmt.setVersion(3, 3);
fmt.setProfile(QGLFormat::CoreProfile);
fmt.setSwapInterval(Settings::values.use_vsync);
// Requests a forward-compatible context, which is required to get a 3.2+ context on OS X
fmt.setOption(QGL::NoDeprecatedFunctions);
child = new GGLWidgetInternal(fmt, this);
QBoxLayout* layout = new QHBoxLayout(this);
resize(VideoCore::kScreenTopWidth, VideoCore::kScreenTopHeight + VideoCore::kScreenBottomHeight);
layout->addWidget(child);
layout->setMargin(0);
setLayout(layout);
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
OnFramebufferSizeChanged();
NotifyClientAreaSizeChanged(std::pair<unsigned, unsigned>(child->width(), child->height()));
BackupGeometry();
}
void GRenderWindow::OnMinimalClientAreaChangeRequest(const std::pair<unsigned,unsigned>& minimal_size) { void GRenderWindow::OnMinimalClientAreaChangeRequest(const std::pair<unsigned,unsigned>& minimal_size) {
setMinimumSize(minimal_size.first, minimal_size.second); setMinimumSize(minimal_size.first, minimal_size.second);
} }

View File

@ -126,6 +126,8 @@ public:
void OnClientAreaResized(unsigned width, unsigned height); void OnClientAreaResized(unsigned width, unsigned height);
void InitRenderTarget();
public slots: public slots:
void moveContext(); // overridden void moveContext(); // overridden

View File

@ -3,14 +3,11 @@
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <QSettings> #include <QSettings>
#include <QString>
#include <QStringList>
#include "citra_qt/config.h" #include "citra_qt/config.h"
#include "citra_qt/ui_settings.h" #include "citra_qt/ui_settings.h"
#include "common/file_util.h" #include "common/file_util.h"
#include "core/settings.h"
Config::Config() { Config::Config() {
// TODO: Don't hardcode the path; let the frontend decide where to put the config files. // TODO: Don't hardcode the path; let the frontend decide where to put the config files.
@ -21,7 +18,7 @@ Config::Config() {
Reload(); Reload();
} }
static const std::array<QVariant, Settings::NativeInput::NUM_INPUTS> defaults = { const std::array<QVariant, Settings::NativeInput::NUM_INPUTS> Config::defaults = {
// directly mapped keys // directly mapped keys
Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X,
Qt::Key_Q, Qt::Key_W, Qt::Key_1, Qt::Key_2, Qt::Key_Q, Qt::Key_W, Qt::Key_1, Qt::Key_2,
@ -48,9 +45,10 @@ void Config::ReadValues() {
qt_config->endGroup(); qt_config->endGroup();
qt_config->beginGroup("Renderer"); qt_config->beginGroup("Renderer");
Settings::values.use_hw_renderer = qt_config->value("use_hw_renderer", false).toBool(); Settings::values.use_hw_renderer = qt_config->value("use_hw_renderer", true).toBool();
Settings::values.use_shader_jit = qt_config->value("use_shader_jit", true).toBool(); Settings::values.use_shader_jit = qt_config->value("use_shader_jit", true).toBool();
Settings::values.use_scaled_resolution = qt_config->value("use_scaled_resolution", false).toBool(); Settings::values.use_scaled_resolution = qt_config->value("use_scaled_resolution", false).toBool();
Settings::values.use_vsync = qt_config->value("use_vsync", false).toBool();
Settings::values.bg_red = qt_config->value("bg_red", 1.0).toFloat(); Settings::values.bg_red = qt_config->value("bg_red", 1.0).toFloat();
Settings::values.bg_green = qt_config->value("bg_green", 1.0).toFloat(); Settings::values.bg_green = qt_config->value("bg_green", 1.0).toFloat();
@ -59,6 +57,7 @@ void Config::ReadValues() {
qt_config->beginGroup("Audio"); qt_config->beginGroup("Audio");
Settings::values.sink_id = qt_config->value("output_engine", "auto").toString().toStdString(); Settings::values.sink_id = qt_config->value("output_engine", "auto").toString().toStdString();
Settings::values.enable_audio_stretching = qt_config->value("enable_audio_stretching", true).toBool();
qt_config->endGroup(); qt_config->endGroup();
qt_config->beginGroup("Data Storage"); qt_config->beginGroup("Data Storage");
@ -109,7 +108,7 @@ void Config::ReadValues() {
UISettings::values.shortcuts.emplace_back( UISettings::values.shortcuts.emplace_back(
UISettings::Shortcut(group + "/" + hotkey, UISettings::Shortcut(group + "/" + hotkey,
UISettings::ContextualShortcut(qt_config->value("KeySeq").toString(), UISettings::ContextualShortcut(qt_config->value("KeySeq").toString(),
qt_config->value("Context").toInt()))); qt_config->value("Context").toInt())));
qt_config->endGroup(); qt_config->endGroup();
} }
@ -142,6 +141,7 @@ void Config::SaveValues() {
qt_config->setValue("use_hw_renderer", Settings::values.use_hw_renderer); qt_config->setValue("use_hw_renderer", Settings::values.use_hw_renderer);
qt_config->setValue("use_shader_jit", Settings::values.use_shader_jit); qt_config->setValue("use_shader_jit", Settings::values.use_shader_jit);
qt_config->setValue("use_scaled_resolution", Settings::values.use_scaled_resolution); qt_config->setValue("use_scaled_resolution", Settings::values.use_scaled_resolution);
qt_config->setValue("use_vsync", Settings::values.use_vsync);
// Cast to double because Qt's written float values are not human-readable // Cast to double because Qt's written float values are not human-readable
qt_config->setValue("bg_red", (double)Settings::values.bg_red); qt_config->setValue("bg_red", (double)Settings::values.bg_red);
@ -151,6 +151,7 @@ void Config::SaveValues() {
qt_config->beginGroup("Audio"); qt_config->beginGroup("Audio");
qt_config->setValue("output_engine", QString::fromStdString(Settings::values.sink_id)); qt_config->setValue("output_engine", QString::fromStdString(Settings::values.sink_id));
qt_config->setValue("enable_audio_stretching", Settings::values.enable_audio_stretching);
qt_config->endGroup(); qt_config->endGroup();
qt_config->beginGroup("Data Storage"); qt_config->beginGroup("Data Storage");
@ -191,7 +192,7 @@ void Config::SaveValues() {
qt_config->endGroup(); qt_config->endGroup();
qt_config->beginGroup("Shortcuts"); qt_config->beginGroup("Shortcuts");
for (auto shortcut : UISettings::values.shortcuts ) { for (auto shortcut : UISettings::values.shortcuts) {
qt_config->setValue(shortcut.first + "/KeySeq", shortcut.second.first); qt_config->setValue(shortcut.first + "/KeySeq", shortcut.second.first);
qt_config->setValue(shortcut.first + "/Context", shortcut.second.second); qt_config->setValue(shortcut.first + "/Context", shortcut.second.second);
} }

View File

@ -1,10 +1,13 @@
// Copyright 2014 Citra Emulator Project // Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#pragma once #pragma once
#include <string> #include <string>
#include <QVariant>
#include "core/settings.h"
class QSettings; class QSettings;
@ -20,4 +23,5 @@ public:
void Reload(); void Reload();
void Save(); void Save();
static const std::array<QVariant, Settings::NativeInput::NUM_INPUTS> defaults;
}; };

View File

@ -24,16 +24,26 @@
<string>General</string> <string>General</string>
</attribute> </attribute>
</widget> </widget>
<widget class="QWidget" name="inputTab"> <widget class="ConfigureSystem" name="systemTab">
<attribute name="title">
<string>System</string>
</attribute>
</widget>
<widget class="ConfigureInput" name="inputTab">
<attribute name="title"> <attribute name="title">
<string>Input</string> <string>Input</string>
</attribute> </attribute>
</widget> </widget>
<widget class="ConfigureAudio" name="audioTab"> <widget class="ConfigureGraphics" name="graphicsTab">
<attribute name="title"> <attribute name="title">
<string>Audio</string> <string>Graphics</string>
</attribute> </attribute>
</widget> </widget>
<widget class="ConfigureAudio" name="audioTab">
<attribute name="title">
<string>Audio</string>
</attribute>
</widget>
<widget class="ConfigureDebug" name="debugTab"> <widget class="ConfigureDebug" name="debugTab">
<attribute name="title"> <attribute name="title">
<string>Debug</string> <string>Debug</string>
@ -57,6 +67,12 @@
<header>configure_general.h</header> <header>configure_general.h</header>
<container>1</container> <container>1</container>
</customwidget> </customwidget>
<customwidget>
<class>ConfigureSystem</class>
<extends>QWidget</extends>
<header>configure_system.h</header>
<container>1</container>
</customwidget>
<customwidget> <customwidget>
<class>ConfigureAudio</class> <class>ConfigureAudio</class>
<extends>QWidget</extends> <extends>QWidget</extends>
@ -69,6 +85,18 @@
<header>configure_debug.h</header> <header>configure_debug.h</header>
<container>1</container> <container>1</container>
</customwidget> </customwidget>
<customwidget>
<class>ConfigureInput</class>
<extends>QWidget</extends>
<header>configure_input.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureGraphics</class>
<extends>QWidget</extends>
<header>configure_graphics.h</header>
<container>1</container>
</customwidget>
</customwidgets> </customwidgets>
<resources/> <resources/>
<connections> <connections>

View File

@ -36,9 +36,12 @@ void ConfigureAudio::setConfiguration() {
} }
} }
ui->output_sink_combo_box->setCurrentIndex(new_sink_index); ui->output_sink_combo_box->setCurrentIndex(new_sink_index);
ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching);
} }
void ConfigureAudio::applyConfiguration() { void ConfigureAudio::applyConfiguration() {
Settings::values.sink_id = ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()).toStdString(); Settings::values.sink_id = ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()).toStdString();
Settings::values.enable_audio_stretching = ui->toggle_audio_stretching->isChecked();
Settings::Apply(); Settings::Apply();
} }

View File

@ -25,6 +25,16 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="QCheckBox" name="toggle_audio_stretching">
<property name="text">
<string>Enable audio stretching</string>
</property>
<property name="toolTip">
<string>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View File

@ -19,13 +19,13 @@ ConfigureDebug::~ConfigureDebug() {
} }
void ConfigureDebug::setConfiguration() { void ConfigureDebug::setConfiguration() {
ui->toogle_gdbstub->setChecked(Settings::values.use_gdbstub); ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub);
ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub); ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub);
ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port); ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port);
} }
void ConfigureDebug::applyConfiguration() { void ConfigureDebug::applyConfiguration() {
Settings::values.use_gdbstub = ui->toogle_gdbstub->isChecked(); Settings::values.use_gdbstub = ui->toggle_gdbstub->isChecked();
Settings::values.gdbstub_port = ui->gdbport_spinbox->value(); Settings::values.gdbstub_port = ui->gdbport_spinbox->value();
Settings::Apply(); Settings::Apply();
} }

View File

@ -25,7 +25,7 @@
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_3"> <layout class="QHBoxLayout" name="horizontalLayout_3">
<item> <item>
<widget class="QCheckBox" name="toogle_gdbstub"> <widget class="QCheckBox" name="toggle_gdbstub">
<property name="text"> <property name="text">
<string>Enable GDB Stub</string> <string>Enable GDB Stub</string>
</property> </property>
@ -83,7 +83,7 @@
<resources/> <resources/>
<connections> <connections>
<connection> <connection>
<sender>toogle_gdbstub</sender> <sender>toggle_gdbstub</sender>
<signal>toggled(bool)</signal> <signal>toggled(bool)</signal>
<receiver>gdbport_spinbox</receiver> <receiver>gdbport_spinbox</receiver>
<slot>setEnabled(bool)</slot> <slot>setEnabled(bool)</slot>

View File

@ -9,9 +9,10 @@
#include "core/settings.h" #include "core/settings.h"
ConfigureDialog::ConfigureDialog(QWidget *parent) : ConfigureDialog::ConfigureDialog(QWidget *parent, bool running) :
QDialog(parent), QDialog(parent),
ui(new Ui::ConfigureDialog) ui(new Ui::ConfigureDialog),
emulation_running(running)
{ {
ui->setupUi(this); ui->setupUi(this);
this->setConfiguration(); this->setConfiguration();
@ -21,10 +22,16 @@ ConfigureDialog::~ConfigureDialog() {
} }
void ConfigureDialog::setConfiguration() { void ConfigureDialog::setConfiguration() {
// System tab needs set manually
// depending on whether emulation is running
ui->systemTab->setConfiguration(emulation_running);
} }
void ConfigureDialog::applyConfiguration() { void ConfigureDialog::applyConfiguration() {
ui->generalTab->applyConfiguration(); ui->generalTab->applyConfiguration();
ui->systemTab->applyConfiguration();
ui->inputTab->applyConfiguration();
ui->graphicsTab->applyConfiguration();
ui->audioTab->applyConfiguration(); ui->audioTab->applyConfiguration();
ui->debugTab->applyConfiguration(); ui->debugTab->applyConfiguration();
} }

View File

@ -16,7 +16,7 @@ class ConfigureDialog : public QDialog
Q_OBJECT Q_OBJECT
public: public:
explicit ConfigureDialog(QWidget *parent = nullptr); explicit ConfigureDialog(QWidget *parent, bool emulation_running);
~ConfigureDialog(); ~ConfigureDialog();
void applyConfiguration(); void applyConfiguration();
@ -26,4 +26,5 @@ private:
private: private:
std::unique_ptr<Ui::ConfigureDialog> ui; std::unique_ptr<Ui::ConfigureDialog> ui;
bool emulation_running;
}; };

View File

@ -20,20 +20,14 @@ ConfigureGeneral::~ConfigureGeneral() {
} }
void ConfigureGeneral::setConfiguration() { void ConfigureGeneral::setConfiguration() {
ui->toogle_deepscan->setChecked(UISettings::values.gamedir_deepscan); ui->toggle_deepscan->setChecked(UISettings::values.gamedir_deepscan);
ui->toogle_check_exit->setChecked(UISettings::values.confirm_before_closing); ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
ui->region_combobox->setCurrentIndex(Settings::values.region_value); ui->region_combobox->setCurrentIndex(Settings::values.region_value);
ui->toogle_hw_renderer->setChecked(Settings::values.use_hw_renderer);
ui->toogle_shader_jit->setChecked(Settings::values.use_shader_jit);
ui->toogle_scaled_resolution->setChecked(Settings::values.use_scaled_resolution);
} }
void ConfigureGeneral::applyConfiguration() { void ConfigureGeneral::applyConfiguration() {
UISettings::values.gamedir_deepscan = ui->toogle_deepscan->isChecked(); UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked();
UISettings::values.confirm_before_closing = ui->toogle_check_exit->isChecked(); UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
Settings::values.region_value = ui->region_combobox->currentIndex(); Settings::values.region_value = ui->region_combobox->currentIndex();
Settings::values.use_hw_renderer = ui->toogle_hw_renderer->isChecked();
Settings::values.use_shader_jit = ui->toogle_shader_jit->isChecked();
Settings::values.use_scaled_resolution = ui->toogle_scaled_resolution->isChecked();
Settings::Apply(); Settings::Apply();
} }

View File

@ -25,14 +25,14 @@
<item> <item>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_2">
<item> <item>
<widget class="QCheckBox" name="toogle_deepscan"> <widget class="QCheckBox" name="toggle_deepscan">
<property name="text"> <property name="text">
<string>Recursive scan for game folder</string> <string>Recursive scan for game folder</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="toogle_check_exit"> <widget class="QCheckBox" name="toggle_check_exit">
<property name="text"> <property name="text">
<string>Confirm exit while emulation is running</string> <string>Confirm exit while emulation is running</string>
</property> </property>
@ -106,40 +106,6 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Performance</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QCheckBox" name="toogle_hw_renderer">
<property name="text">
<string>Enable hardware renderer</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="toogle_shader_jit">
<property name="text">
<string>Enable shader JIT</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="toogle_scaled_resolution">
<property name="text">
<string>Enable scaled resolution</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="groupBox_3"> <widget class="QGroupBox" name="groupBox_3">
<property name="title"> <property name="title">

View File

@ -0,0 +1,37 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "citra_qt/configure_graphics.h"
#include "ui_configure_graphics.h"
#include "core/settings.h"
#include "core/system.h"
ConfigureGraphics::ConfigureGraphics(QWidget *parent) :
QWidget(parent),
ui(new Ui::ConfigureGraphics)
{
ui->setupUi(this);
this->setConfiguration();
ui->toggle_vsync->setEnabled(!System::IsPoweredOn());
}
ConfigureGraphics::~ConfigureGraphics() {
}
void ConfigureGraphics::setConfiguration() {
ui->toggle_hw_renderer->setChecked(Settings::values.use_hw_renderer);
ui->toggle_shader_jit->setChecked(Settings::values.use_shader_jit);
ui->toggle_scaled_resolution->setChecked(Settings::values.use_scaled_resolution);
ui->toggle_vsync->setChecked(Settings::values.use_vsync);
}
void ConfigureGraphics::applyConfiguration() {
Settings::values.use_hw_renderer = ui->toggle_hw_renderer->isChecked();
Settings::values.use_shader_jit = ui->toggle_shader_jit->isChecked();
Settings::values.use_scaled_resolution = ui->toggle_scaled_resolution->isChecked();
Settings::values.use_vsync = ui->toggle_vsync->isChecked();
Settings::Apply();
}

View File

@ -0,0 +1,29 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QWidget>
namespace Ui {
class ConfigureGraphics;
}
class ConfigureGraphics : public QWidget
{
Q_OBJECT
public:
explicit ConfigureGraphics(QWidget *parent = nullptr);
~ConfigureGraphics();
void applyConfiguration();
private:
void setConfiguration();
private:
std::unique_ptr<Ui::ConfigureGraphics> ui;
};

View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureGraphics</class>
<widget class="QWidget" name="ConfigureGraphics">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</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="groupBox">
<property name="title">
<string>Graphics</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="toggle_hw_renderer">
<property name="text">
<string>Enable hardware renderer</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="toggle_shader_jit">
<property name="text">
<string>Enable shader JIT</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="toggle_scaled_resolution">
<property name="text">
<string>Enable scaled resolution</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="toggle_vsync">
<property name="text">
<string>Enable V-Sync</string>
</property>
</widget>
</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>
<connection>
<sender>toggle_gdbstub</sender>
<signal>toggled(bool)</signal>
<receiver>gdbport_spinbox</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>84</x>
<y>157</y>
</hint>
<hint type="destinationlabel">
<x>342</x>
<y>158</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,149 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <memory>
#include <utility>
#include <QTimer>
#include "citra_qt/configure_input.h"
ConfigureInput::ConfigureInput(QWidget* parent) : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()) {
ui->setupUi(this);
// Initialize mapping of input enum to UI button.
input_mapping = {
{ std::make_pair(Settings::NativeInput::Values::A, ui->buttonA) },
{ std::make_pair(Settings::NativeInput::Values::B, ui->buttonB) },
{ std::make_pair(Settings::NativeInput::Values::X, ui->buttonX) },
{ std::make_pair(Settings::NativeInput::Values::Y, ui->buttonY) },
{ std::make_pair(Settings::NativeInput::Values::L, ui->buttonL) },
{ std::make_pair(Settings::NativeInput::Values::R, ui->buttonR) },
{ std::make_pair(Settings::NativeInput::Values::ZL, ui->buttonZL) },
{ std::make_pair(Settings::NativeInput::Values::ZR, ui->buttonZR) },
{ std::make_pair(Settings::NativeInput::Values::START, ui->buttonStart) },
{ std::make_pair(Settings::NativeInput::Values::SELECT, ui->buttonSelect) },
{ std::make_pair(Settings::NativeInput::Values::HOME, ui->buttonHome) },
{ std::make_pair(Settings::NativeInput::Values::DUP, ui->buttonDpadUp) },
{ std::make_pair(Settings::NativeInput::Values::DDOWN, ui->buttonDpadDown) },
{ std::make_pair(Settings::NativeInput::Values::DLEFT, ui->buttonDpadLeft) },
{ std::make_pair(Settings::NativeInput::Values::DRIGHT, ui->buttonDpadRight) },
{ std::make_pair(Settings::NativeInput::Values::CUP, ui->buttonCStickUp) },
{ std::make_pair(Settings::NativeInput::Values::CDOWN, ui->buttonCStickDown) },
{ std::make_pair(Settings::NativeInput::Values::CLEFT, ui->buttonCStickLeft) },
{ std::make_pair(Settings::NativeInput::Values::CRIGHT, ui->buttonCStickRight) },
{ std::make_pair(Settings::NativeInput::Values::CIRCLE_UP, ui->buttonCircleUp) },
{ std::make_pair(Settings::NativeInput::Values::CIRCLE_DOWN, ui->buttonCircleDown) },
{ std::make_pair(Settings::NativeInput::Values::CIRCLE_LEFT, ui->buttonCircleLeft) },
{ std::make_pair(Settings::NativeInput::Values::CIRCLE_RIGHT, ui->buttonCircleRight) },
{ std::make_pair(Settings::NativeInput::Values::CIRCLE_MODIFIER, ui->buttonCircleMod) },
};
// Attach handle click method to each button click.
for (const auto& entry : input_mapping) {
connect(entry.second, SIGNAL(released()), this, SLOT(handleClick()));
}
connect(ui->buttonRestoreDefaults, SIGNAL(released()), this, SLOT(restoreDefaults()));
setFocusPolicy(Qt::ClickFocus);
timer = new QTimer(this);
timer->setSingleShot(true);
connect(timer, &QTimer::timeout, this, [&]() { key_pressed = Qt::Key_Escape; setKey(); });
this->setConfiguration();
}
void ConfigureInput::handleClick() {
QPushButton* sender = qobject_cast<QPushButton*>(QObject::sender());
previous_mapping = sender->text();
sender->setText(tr("[waiting]"));
sender->setFocus();
grabKeyboard();
grabMouse();
changing_button = sender;
timer->start(5000); //Cancel after 5 seconds
}
void ConfigureInput::applyConfiguration() {
for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
int value = getKeyValue(input_mapping[Settings::NativeInput::Values(i)]->text());
Settings::values.input_mappings[Settings::NativeInput::All[i]] = value;
}
Settings::Apply();
}
void ConfigureInput::setConfiguration() {
for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
QString keyValue = getKeyName(Settings::values.input_mappings[i]);
input_mapping[Settings::NativeInput::Values(i)]->setText(keyValue);
}
}
void ConfigureInput::keyPressEvent(QKeyEvent* event) {
if (!changing_button)
return;
if (!event || event->key() == Qt::Key_unknown)
return;
key_pressed = event->key();
timer->stop();
setKey();
}
void ConfigureInput::setKey() {
const QString key_value = getKeyName(key_pressed);
if (key_pressed == Qt::Key_Escape)
changing_button->setText(previous_mapping);
else
changing_button->setText(key_value);
removeDuplicates(key_value);
key_pressed = Qt::Key_unknown;
releaseKeyboard();
releaseMouse();
changing_button = nullptr;
previous_mapping = nullptr;
}
QString ConfigureInput::getKeyName(int key_code) const {
if (key_code == Qt::Key_Shift)
return tr("Shift");
if (key_code == Qt::Key_Control)
return tr("Ctrl");
if (key_code == Qt::Key_Alt)
return tr("Alt");
if (key_code == Qt::Key_Meta)
return "";
if (key_code == -1)
return "";
return QKeySequence(key_code).toString();
}
Qt::Key ConfigureInput::getKeyValue(const QString& text) const {
if (text == "Shift")
return Qt::Key_Shift;
if (text == "Ctrl")
return Qt::Key_Control;
if (text == "Alt")
return Qt::Key_Alt;
if (text == "Meta")
return Qt::Key_unknown;
if (text == "")
return Qt::Key_unknown;
return Qt::Key(QKeySequence(text)[0]);
}
void ConfigureInput::removeDuplicates(const QString& newValue) {
for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
if (changing_button != input_mapping[Settings::NativeInput::Values(i)]) {
const QString oldValue = input_mapping[Settings::NativeInput::Values(i)]->text();
if (newValue == oldValue)
input_mapping[Settings::NativeInput::Values(i)]->setText("");
}
}
}
void ConfigureInput::restoreDefaults() {
for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
const QString keyValue = getKeyName(Config::defaults[i].toInt());
input_mapping[Settings::NativeInput::Values(i)]->setText(keyValue);
}
}

View File

@ -0,0 +1,63 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <QWidget>
#include <QKeyEvent>
#include "citra_qt/config.h"
#include "core/settings.h"
#include "ui_configure_input.h"
class QPushButton;
class QString;
class QTimer;
namespace Ui {
class ConfigureInput;
}
class ConfigureInput : public QWidget {
Q_OBJECT
public:
explicit ConfigureInput(QWidget* parent = nullptr);
/// Save all button configurations to settings file
void applyConfiguration();
private:
std::unique_ptr<Ui::ConfigureInput> ui;
std::map<Settings::NativeInput::Values, QPushButton*> input_mapping;
int key_pressed;
QPushButton* changing_button = nullptr; ///< button currently waiting for key press.
QString previous_mapping;
QTimer* timer;
/// Load configuration settings into button text
void setConfiguration();
/// Check all inputs for duplicate keys. Clears out any other button with the same value as this button's new value.
void removeDuplicates(const QString& newValue);
/// Handle key press event for input tab when a button is 'waiting'.
void keyPressEvent(QKeyEvent* event) override;
/// Convert key ASCII value to its' letter/name
QString getKeyName(int key_code) const;
/// Convert letter/name of key to its ASCII value.
Qt::Key getKeyValue(const QString& text) const;
/// Set button text to name of key pressed.
void setKey();
private slots:
/// Event handler for all button released() event.
void handleClick();
/// Restore all buttons to their default values.
void restoreDefaults();
};

View File

@ -0,0 +1,593 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureInput</class>
<widget class="QWidget" name="ConfigureInput">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>370</width>
<height>534</height>
</rect>
</property>
<property name="windowTitle">
<string>ConfigureInput</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QGridLayout" name="gridLayout_7">
<item row="0" column="0">
<widget class="QGroupBox" name="faceButtons">
<property name="title">
<string>Face Buttons</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>A:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonA">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>B:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonB">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>X:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonX">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="1">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Y:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonY">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item row="0" column="1">
<widget class="QGroupBox" name="faceButtons_2">
<property name="title">
<string>Directional Pad</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0">
<layout class="QVBoxLayout" name="verticalLayout_12">
<item>
<widget class="QLabel" name="label_34">
<property name="text">
<string>Up:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonDpadUp">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="1">
<layout class="QVBoxLayout" name="verticalLayout_9">
<item>
<widget class="QLabel" name="label_35">
<property name="text">
<string>Down:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonDpadDown">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_10">
<item>
<widget class="QLabel" name="label_32">
<property name="text">
<string>Left:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonDpadLeft">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout_11">
<item>
<widget class="QLabel" name="label_33">
<property name="text">
<string>Right:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonDpadRight">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="faceButtons_3">
<property name="title">
<string>Shoulder Buttons</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_13">
<item>
<widget class="QLabel" name="label_17">
<property name="text">
<string>L:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonL">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout_14">
<item>
<widget class="QLabel" name="label_19">
<property name="text">
<string>R:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonR">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QVBoxLayout" name="verticalLayout_15">
<item>
<widget class="QLabel" name="label_20">
<property name="text">
<string>ZL:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonZL">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="1">
<layout class="QVBoxLayout" name="verticalLayout_16">
<item>
<widget class="QLabel" name="label_18">
<property name="text">
<string>ZR:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonZR">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
<zorder></zorder>
</widget>
</item>
<item row="1" column="1">
<widget class="QGroupBox" name="faceButtons_4">
<property name="title">
<string>Circle Pad</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_17">
<item>
<widget class="QLabel" name="label_21">
<property name="text">
<string>Left:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonCircleLeft">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout_18">
<item>
<widget class="QLabel" name="label_23">
<property name="text">
<string>Right:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonCircleRight">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QVBoxLayout" name="verticalLayout_19">
<item>
<widget class="QLabel" name="label_24">
<property name="text">
<string>Up:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonCircleUp">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="1">
<layout class="QVBoxLayout" name="verticalLayout_20">
<item>
<widget class="QLabel" name="label_22">
<property name="text">
<string>Down:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonCircleDown">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<widget class="QGroupBox" name="faceButtons_5">
<property name="title">
<string>C-Stick</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_21">
<item>
<widget class="QLabel" name="label_25">
<property name="text">
<string>Left:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonCStickLeft">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout_22">
<item>
<widget class="QLabel" name="label_27">
<property name="text">
<string>Right:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonCStickRight">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QVBoxLayout" name="verticalLayout_23">
<item>
<widget class="QLabel" name="label_28">
<property name="text">
<string>Up:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonCStickUp">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="1">
<layout class="QVBoxLayout" name="verticalLayout_24">
<item>
<widget class="QLabel" name="label_26">
<property name="text">
<string>Down:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonCStickDown">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item row="2" column="1">
<widget class="QGroupBox" name="faceButtons_6">
<property name="title">
<string>Misc.</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_6">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_25">
<item>
<widget class="QLabel" name="label_29">
<property name="text">
<string>Start:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonStart">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout_26">
<item>
<widget class="QLabel" name="label_30">
<property name="text">
<string>Select:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonSelect">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QVBoxLayout" name="verticalLayout_27">
<item>
<widget class="QLabel" name="label_31">
<property name="text">
<string>Home:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonHome">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="1">
<layout class="QVBoxLayout" name="verticalLayout_28">
<item>
<widget class="QLabel" name="label_34">
<property name="text">
<string>Circle Mod:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonCircleMod">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<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>
<item>
<widget class="QPushButton" name="buttonRestoreDefaults">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="sizeIncrement">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string>Restore Defaults</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,136 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "citra_qt/configure_system.h"
#include "citra_qt/ui_settings.h"
#include "ui_configure_system.h"
#include "core/hle/service/fs/archive.h"
#include "core/hle/service/cfg/cfg.h"
static const std::array<int, 12> days_in_month = {{
31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
}};
ConfigureSystem::ConfigureSystem(QWidget *parent) :
QWidget(parent),
ui(new Ui::ConfigureSystem) {
ui->setupUi(this);
connect(ui->combo_birthmonth, SIGNAL(currentIndexChanged(int)), SLOT(updateBirthdayComboBox(int)));
}
ConfigureSystem::~ConfigureSystem() {
}
void ConfigureSystem::setConfiguration(bool emulation_running) {
enabled = !emulation_running;
if (!enabled) {
ReadSystemSettings();
ui->group_system_settings->setEnabled(false);
} else {
// This tab is enabled only when game is not running (i.e. all service are not initialized).
// Temporarily register archive types and load the config savegame file to memory.
Service::FS::RegisterArchiveTypes();
ResultCode result = Service::CFG::LoadConfigNANDSaveFile();
Service::FS::UnregisterArchiveTypes();
if (result.IsError()) {
ui->label_disable_info->setText(tr("Failed to load system settings data."));
ui->group_system_settings->setEnabled(false);
enabled = false;
return;
}
ReadSystemSettings();
ui->label_disable_info->hide();
}
}
void ConfigureSystem::ReadSystemSettings() {
// set username
username = Service::CFG::GetUsername();
// ui->edit_username->setText(QString::fromStdU16String(username)); // TODO(wwylele): Use this when we move to Qt 5.5
ui->edit_username->setText(QString::fromUtf16(reinterpret_cast<const ushort*>(username.data())));
// set birthday
std::tie(birthmonth, birthday) = Service::CFG::GetBirthday();
ui->combo_birthmonth->setCurrentIndex(birthmonth - 1);
ui->combo_birthday->setCurrentIndex(birthday - 1);
// set system language
language_index = Service::CFG::GetSystemLanguage();
ui->combo_language->setCurrentIndex(language_index);
// set sound output mode
sound_index = Service::CFG::GetSoundOutputMode();
ui->combo_sound->setCurrentIndex(sound_index);
}
void ConfigureSystem::applyConfiguration() {
if (!enabled)
return;
bool modified = false;
// apply username
// std::u16string new_username = ui->edit_username->text().toStdU16String(); // TODO(wwylele): Use this when we move to Qt 5.5
std::u16string new_username(reinterpret_cast<const char16_t*>(ui->edit_username->text().utf16()));
if (new_username != username) {
Service::CFG::SetUsername(new_username);
modified = true;
}
// apply birthday
int new_birthmonth = ui->combo_birthmonth->currentIndex() + 1;
int new_birthday = ui->combo_birthday->currentIndex() + 1;
if (birthmonth != new_birthmonth || birthday != new_birthday) {
Service::CFG::SetBirthday(new_birthmonth, new_birthday);
modified = true;
}
// apply language
int new_language = ui->combo_language->currentIndex();
if (language_index != new_language) {
Service::CFG::SetSystemLanguage(static_cast<Service::CFG::SystemLanguage>(new_language));
modified = true;
}
// apply sound
int new_sound = ui->combo_sound->currentIndex();
if (sound_index != new_sound) {
Service::CFG::SetSoundOutputMode(static_cast<Service::CFG::SoundOutputMode>(new_sound));
modified = true;
}
// update the config savegame if any item is modified.
if (modified)
Service::CFG::UpdateConfigNANDSavegame();
}
void ConfigureSystem::updateBirthdayComboBox(int birthmonth_index) {
if (birthmonth_index < 0 || birthmonth_index >= 12)
return;
// store current day selection
int birthday_index = ui->combo_birthday->currentIndex();
// get number of days in the new selected month
int days = days_in_month[birthmonth_index];
// if the selected day is out of range,
// reset it to 1st
if (birthday_index < 0 || birthday_index >= days)
birthday_index = 0;
// update the day combo box
ui->combo_birthday->clear();
for (int i = 1; i <= days; ++i) {
ui->combo_birthday->addItem(QString::number(i));
}
// restore the day selection
ui->combo_birthday->setCurrentIndex(birthday_index);
}

View File

@ -0,0 +1,38 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QWidget>
namespace Ui {
class ConfigureSystem;
}
class ConfigureSystem : public QWidget
{
Q_OBJECT
public:
explicit ConfigureSystem(QWidget *parent = nullptr);
~ConfigureSystem();
void applyConfiguration();
void setConfiguration(bool emulation_running);
public slots:
void updateBirthdayComboBox(int birthmonth_index);
private:
void ReadSystemSettings();
std::unique_ptr<Ui::ConfigureSystem> ui;
bool enabled;
std::u16string username;
int birthmonth, birthday;
int language_index;
int sound_index;
};

View File

@ -0,0 +1,252 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureSystem</class>
<widget class="QWidget" name="ConfigureSystem">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>360</width>
<height>377</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="group_system_settings">
<property name="title">
<string>System Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_username">
<property name="text">
<string>Username</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="edit_username">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maxLength">
<number>10</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_birthday">
<property name="text">
<string>Birthday</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_birthday2">
<item>
<widget class="QComboBox" name="combo_birthmonth">
<item>
<property name="text">
<string>January</string>
</property>
</item>
<item>
<property name="text">
<string>February</string>
</property>
</item>
<item>
<property name="text">
<string>March</string>
</property>
</item>
<item>
<property name="text">
<string>April</string>
</property>
</item>
<item>
<property name="text">
<string>May</string>
</property>
</item>
<item>
<property name="text">
<string>June</string>
</property>
</item>
<item>
<property name="text">
<string>July</string>
</property>
</item>
<item>
<property name="text">
<string>August</string>
</property>
</item>
<item>
<property name="text">
<string>September</string>
</property>
</item>
<item>
<property name="text">
<string>October</string>
</property>
</item>
<item>
<property name="text">
<string>November</string>
</property>
</item>
<item>
<property name="text">
<string>December</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QComboBox" name="combo_birthday"/>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_language">
<property name="text">
<string>Language</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="combo_language">
<item>
<property name="text">
<string>Japanese (日本語)</string>
</property>
</item>
<item>
<property name="text">
<string>English</string>
</property>
</item>
<item>
<property name="text">
<string>French (français)</string>
</property>
</item>
<item>
<property name="text">
<string>German (Deutsch)</string>
</property>
</item>
<item>
<property name="text">
<string>Italian (italiano)</string>
</property>
</item>
<item>
<property name="text">
<string>Spanish (español)</string>
</property>
</item>
<item>
<property name="text">
<string>Simplified Chinese (简体中文)</string>
</property>
</item>
<item>
<property name="text">
<string>Korean (한국어)</string>
</property>
</item>
<item>
<property name="text">
<string>Dutch (Nederlands)</string>
</property>
</item>
<item>
<property name="text">
<string>Portuguese (português)</string>
</property>
</item>
<item>
<property name="text">
<string>Russian (Русский)</string>
</property>
</item>
<item>
<property name="text">
<string>Traditional Chinese (正體中文)</string>
</property>
</item>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_sound">
<property name="text">
<string>Sound output mode</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="combo_sound">
<item>
<property name="text">
<string>Mono</string>
</property>
</item>
<item>
<property name="text">
<string>Stereo</string>
</property>
</item>
<item>
<property name="text">
<string>Surround</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="label_disable_info">
<property name="text">
<string>System settings are available only when game is not running.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</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>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -151,8 +151,8 @@ private:
/// This timer is used to redraw the widget's contents continuously. To save resources, it only /// This timer is used to redraw the widget's contents continuously. To save resources, it only
/// runs while the widget is visible. /// runs while the widget is visible.
QTimer update_timer; QTimer update_timer;
/// Scale the coordinate system appropriately when physical DPI != logical DPI. /// Scale the coordinate system appropriately when dpi != 96.
qreal x_scale, y_scale; qreal x_scale = 1.0, y_scale = 1.0;
}; };
#endif #endif
@ -222,15 +222,14 @@ MicroProfileWidget::MicroProfileWidget(QWidget* parent) : QWidget(parent) {
MicroProfileInitUI(); MicroProfileInitUI();
connect(&update_timer, SIGNAL(timeout()), SLOT(update())); connect(&update_timer, SIGNAL(timeout()), SLOT(update()));
QPainter painter(this);
x_scale = qreal(painter.device()->physicalDpiX()) / qreal(painter.device()->logicalDpiX());
y_scale = qreal(painter.device()->physicalDpiY()) / qreal(painter.device()->logicalDpiY());
} }
void MicroProfileWidget::paintEvent(QPaintEvent* ev) { void MicroProfileWidget::paintEvent(QPaintEvent* ev) {
QPainter painter(this); QPainter painter(this);
// The units used by Microprofile for drawing are based in pixels on a 96 dpi display.
x_scale = qreal(painter.device()->logicalDpiX()) / 96.0;
y_scale = qreal(painter.device()->logicalDpiY()) / 96.0;
painter.scale(x_scale, y_scale); painter.scale(x_scale, y_scale);
painter.setBackground(Qt::black); painter.setBackground(Qt::black);
@ -241,7 +240,7 @@ void MicroProfileWidget::paintEvent(QPaintEvent* ev) {
painter.setFont(font); painter.setFont(font);
mp_painter = &painter; mp_painter = &painter;
MicroProfileDraw(rect().width(), rect().height()); MicroProfileDraw(rect().width() / x_scale, rect().height() / y_scale);
mp_painter = nullptr; mp_painter = nullptr;
} }

View File

@ -120,11 +120,9 @@ void GameList::LoadInterfaceLayout()
void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion)
{ {
const auto callback = [&](unsigned* num_entries_out, const auto callback = [this, recursion](unsigned* num_entries_out,
const std::string& directory, const std::string& directory,
const std::string& virtual_name, const std::string& virtual_name) -> bool {
unsigned int recursion) -> bool {
std::string physical_name = directory + DIR_SEP + virtual_name; std::string physical_name = directory + DIR_SEP + virtual_name;
if (stop_processing) if (stop_processing)

View File

@ -243,7 +243,9 @@ bool GMainWindow::InitializeSystem() {
if (emu_thread != nullptr) if (emu_thread != nullptr)
ShutdownGame(); ShutdownGame();
render_window->InitRenderTarget();
render_window->MakeCurrent(); render_window->MakeCurrent();
if (!gladLoadGL()) { if (!gladLoadGL()) {
QMessageBox::critical(this, tr("Error while starting Citra!"), QMessageBox::critical(this, tr("Error while starting Citra!"),
tr("Failed to initialize the video core!\n\n" tr("Failed to initialize the video core!\n\n"
@ -508,11 +510,12 @@ void GMainWindow::ToggleWindowMode() {
} }
void GMainWindow::OnConfigure() { void GMainWindow::OnConfigure() {
ConfigureDialog configureDialog(this); ConfigureDialog configureDialog(this, emulation_running);
auto result = configureDialog.exec(); auto result = configureDialog.exec();
if (result == QDialog::Accepted) if (result == QDialog::Accepted)
{ {
configureDialog.applyConfiguration(); configureDialog.applyConfiguration();
render_window->ReloadSetKeymaps();
config->Save(); config->Save();
} }
} }

View File

@ -51,7 +51,6 @@ static bool IsWithinTouchscreen(const EmuWindow::FramebufferLayout& layout, unsi
} }
std::tuple<unsigned,unsigned> EmuWindow::ClipToTouchScreen(unsigned new_x, unsigned new_y) { std::tuple<unsigned,unsigned> EmuWindow::ClipToTouchScreen(unsigned new_x, unsigned new_y) {
new_x = std::max(new_x, framebuffer_layout.bottom_screen.left); new_x = std::max(new_x, framebuffer_layout.bottom_screen.left);
new_x = std::min(new_x, framebuffer_layout.bottom_screen.right-1); new_x = std::min(new_x, framebuffer_layout.bottom_screen.right-1);
@ -92,9 +91,9 @@ void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) {
} }
EmuWindow::FramebufferLayout EmuWindow::FramebufferLayout::DefaultScreenLayout(unsigned width, unsigned height) { EmuWindow::FramebufferLayout EmuWindow::FramebufferLayout::DefaultScreenLayout(unsigned width, unsigned height) {
// When hiding the widget, the function receives a size of 0
ASSERT(width > 0); if (width == 0) width = 1;
ASSERT(height > 0); if (height == 0) height = 1;
EmuWindow::FramebufferLayout res = { width, height, {}, {} }; EmuWindow::FramebufferLayout res = { width, height, {}, {} };

View File

@ -434,7 +434,7 @@ bool CreateEmptyFile(const std::string &filename)
} }
bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directory, DirectoryEntryCallable callback, unsigned int recursion) bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directory, DirectoryEntryCallable callback)
{ {
LOG_TRACE(Common_Filesystem, "directory %s", directory.c_str()); LOG_TRACE(Common_Filesystem, "directory %s", directory.c_str());
@ -472,7 +472,7 @@ bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directo
continue; continue;
unsigned ret_entries = 0; unsigned ret_entries = 0;
if (!callback(&ret_entries, directory, virtual_name, recursion)) { if (!callback(&ret_entries, directory, virtual_name)) {
callback_error = true; callback_error = true;
break; break;
} }
@ -497,10 +497,9 @@ bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directo
unsigned ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry, unsigned int recursion) unsigned ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry, unsigned int recursion)
{ {
const auto callback = [&parent_entry](unsigned* num_entries_out, const auto callback = [recursion, &parent_entry](unsigned* num_entries_out,
const std::string& directory, const std::string& directory,
const std::string& virtual_name, const std::string& virtual_name) -> bool {
unsigned int recursion) -> bool {
FSTEntry entry; FSTEntry entry;
entry.virtualName = virtual_name; entry.virtualName = virtual_name;
entry.physicalName = directory + DIR_SEP + virtual_name; entry.physicalName = directory + DIR_SEP + virtual_name;
@ -526,16 +525,15 @@ unsigned ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry,
}; };
unsigned num_entries; unsigned num_entries;
return ForeachDirectoryEntry(&num_entries, directory, callback, recursion) ? num_entries : 0; return ForeachDirectoryEntry(&num_entries, directory, callback) ? num_entries : 0;
} }
bool DeleteDirRecursively(const std::string &directory, unsigned int recursion) bool DeleteDirRecursively(const std::string &directory, unsigned int recursion)
{ {
const static auto callback = [](unsigned* num_entries_out, const auto callback = [recursion](unsigned* num_entries_out,
const std::string& directory, const std::string& directory,
const std::string& virtual_name, const std::string& virtual_name) -> bool {
unsigned int recursion) -> bool {
std::string new_path = directory + DIR_SEP_CHR + virtual_name; std::string new_path = directory + DIR_SEP_CHR + virtual_name;
if (IsDirectory(new_path)) { if (IsDirectory(new_path)) {
@ -546,7 +544,7 @@ bool DeleteDirRecursively(const std::string &directory, unsigned int recursion)
return Delete(new_path); return Delete(new_path);
}; };
if (!ForeachDirectoryEntry(nullptr, directory, callback, recursion)) if (!ForeachDirectoryEntry(nullptr, directory, callback))
return false; return false;
// Delete the outermost directory // Delete the outermost directory

View File

@ -105,13 +105,11 @@ bool CreateEmptyFile(const std::string &filename);
* @param num_entries_out to be assigned by the callable with the number of iterated directory entries, never null * @param num_entries_out to be assigned by the callable with the number of iterated directory entries, never null
* @param directory the path to the enclosing directory * @param directory the path to the enclosing directory
* @param virtual_name the entry name, without any preceding directory info * @param virtual_name the entry name, without any preceding directory info
* @param recursion Number of children directory to read before giving up
* @return whether handling the entry succeeded * @return whether handling the entry succeeded
*/ */
using DirectoryEntryCallable = std::function<bool(unsigned* num_entries_out, using DirectoryEntryCallable = std::function<bool(unsigned* num_entries_out,
const std::string& directory, const std::string& directory,
const std::string& virtual_name, const std::string& virtual_name)>;
unsigned int recursion)>;
/** /**
* Scans a directory, calling the callback for each file/directory contained within. * Scans a directory, calling the callback for each file/directory contained within.
@ -119,10 +117,9 @@ using DirectoryEntryCallable = std::function<bool(unsigned* num_entries_out,
* @param num_entries_out assigned by the function with the number of iterated directory entries, can be null * @param num_entries_out assigned by the function with the number of iterated directory entries, can be null
* @param directory the directory to scan * @param directory the directory to scan
* @param callback The callback which will be called for each entry * @param callback The callback which will be called for each entry
* @param recursion Number of children directories to read before giving up
* @return whether scanning the directory succeeded * @return whether scanning the directory succeeded
*/ */
bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directory, DirectoryEntryCallable callback, unsigned int recursion = 0); bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directory, DirectoryEntryCallable callback);
/** /**
* Scans the directory tree, storing the results. * Scans the directory tree, storing the results.

View File

@ -117,7 +117,7 @@ Entry CreateEntry(Class log_class, Level log_level,
vsnprintf(formatting_buffer.data(), formatting_buffer.size(), format, args); vsnprintf(formatting_buffer.data(), formatting_buffer.size(), format, args);
entry.message = std::string(formatting_buffer.data()); entry.message = std::string(formatting_buffer.data());
return std::move(entry); return entry;
} }
static Filter* filter = nullptr; static Filter* filter = nullptr;

View File

@ -5,6 +5,7 @@ set(SRCS
arm/dyncom/arm_dyncom_dec.cpp arm/dyncom/arm_dyncom_dec.cpp
arm/dyncom/arm_dyncom_interpreter.cpp arm/dyncom/arm_dyncom_interpreter.cpp
arm/dyncom/arm_dyncom_thumb.cpp arm/dyncom/arm_dyncom_thumb.cpp
arm/dyncom/arm_dyncom_trans.cpp
arm/skyeye_common/armstate.cpp arm/skyeye_common/armstate.cpp
arm/skyeye_common/armsupp.cpp arm/skyeye_common/armsupp.cpp
arm/skyeye_common/vfp/vfp.cpp arm/skyeye_common/vfp/vfp.cpp
@ -26,9 +27,11 @@ set(SRCS
hle/config_mem.cpp hle/config_mem.cpp
hle/hle.cpp hle/hle.cpp
hle/applets/applet.cpp hle/applets/applet.cpp
hle/applets/erreula.cpp
hle/applets/mii_selector.cpp hle/applets/mii_selector.cpp
hle/applets/swkbd.cpp hle/applets/swkbd.cpp
hle/kernel/address_arbiter.cpp hle/kernel/address_arbiter.cpp
hle/kernel/client_port.cpp
hle/kernel/event.cpp hle/kernel/event.cpp
hle/kernel/kernel.cpp hle/kernel/kernel.cpp
hle/kernel/memory.cpp hle/kernel/memory.cpp
@ -36,6 +39,7 @@ set(SRCS
hle/kernel/process.cpp hle/kernel/process.cpp
hle/kernel/resource_limit.cpp hle/kernel/resource_limit.cpp
hle/kernel/semaphore.cpp hle/kernel/semaphore.cpp
hle/kernel/server_port.cpp
hle/kernel/session.cpp hle/kernel/session.cpp
hle/kernel/shared_memory.cpp hle/kernel/shared_memory.cpp
hle/kernel/thread.cpp hle/kernel/thread.cpp
@ -91,7 +95,9 @@ set(SRCS
hle/service/ir/ir_rst.cpp hle/service/ir/ir_rst.cpp
hle/service/ir/ir_u.cpp hle/service/ir/ir_u.cpp
hle/service/ir/ir_user.cpp hle/service/ir/ir_user.cpp
hle/service/ldr_ro.cpp hle/service/ldr_ro/cro_helper.cpp
hle/service/ldr_ro/ldr_ro.cpp
hle/service/ldr_ro/memory_synchronizer.cpp
hle/service/mic_u.cpp hle/service/mic_u.cpp
hle/service/ndm/ndm.cpp hle/service/ndm/ndm.cpp
hle/service/ndm/ndm_u.cpp hle/service/ndm/ndm_u.cpp
@ -140,6 +146,7 @@ set(HEADERS
arm/dyncom/arm_dyncom_interpreter.h arm/dyncom/arm_dyncom_interpreter.h
arm/dyncom/arm_dyncom_run.h arm/dyncom/arm_dyncom_run.h
arm/dyncom/arm_dyncom_thumb.h arm/dyncom/arm_dyncom_thumb.h
arm/dyncom/arm_dyncom_trans.h
arm/skyeye_common/arm_regformat.h arm/skyeye_common/arm_regformat.h
arm/skyeye_common/armstate.h arm/skyeye_common/armstate.h
arm/skyeye_common/armsupp.h arm/skyeye_common/armsupp.h
@ -164,9 +171,11 @@ set(HEADERS
hle/function_wrappers.h hle/function_wrappers.h
hle/hle.h hle/hle.h
hle/applets/applet.h hle/applets/applet.h
hle/applets/erreula.h
hle/applets/mii_selector.h hle/applets/mii_selector.h
hle/applets/swkbd.h hle/applets/swkbd.h
hle/kernel/address_arbiter.h hle/kernel/address_arbiter.h
hle/kernel/client_port.h
hle/kernel/event.h hle/kernel/event.h
hle/kernel/kernel.h hle/kernel/kernel.h
hle/kernel/memory.h hle/kernel/memory.h
@ -174,6 +183,7 @@ set(HEADERS
hle/kernel/process.h hle/kernel/process.h
hle/kernel/resource_limit.h hle/kernel/resource_limit.h
hle/kernel/semaphore.h hle/kernel/semaphore.h
hle/kernel/server_port.h
hle/kernel/session.h hle/kernel/session.h
hle/kernel/shared_memory.h hle/kernel/shared_memory.h
hle/kernel/thread.h hle/kernel/thread.h
@ -230,7 +240,9 @@ set(HEADERS
hle/service/ir/ir_rst.h hle/service/ir/ir_rst.h
hle/service/ir/ir_u.h hle/service/ir/ir_u.h
hle/service/ir/ir_user.h hle/service/ir/ir_user.h
hle/service/ldr_ro.h hle/service/ldr_ro/cro_helper.h
hle/service/ldr_ro/ldr_ro.h
hle/service/ldr_ro/memory_synchronizer.h
hle/service/mic_u.h hle/service/mic_u.h
hle/service/ndm/ndm.h hle/service/ndm/ndm.h
hle/service/ndm/ndm_u.h hle/service/ndm/ndm_u.h

View File

@ -32,6 +32,9 @@ public:
Run(1); Run(1);
} }
/// Clear all instruction cache
virtual void ClearInstructionCache() = 0;
/** /**
* Set the Program Counter to an address * Set the Program Counter to an address
* @param addr Address to set PC to * @param addr Address to set PC to

View File

@ -12,6 +12,7 @@
#include "core/arm/dyncom/arm_dyncom.h" #include "core/arm/dyncom/arm_dyncom.h"
#include "core/arm/dyncom/arm_dyncom_interpreter.h" #include "core/arm/dyncom/arm_dyncom_interpreter.h"
#include "core/arm/dyncom/arm_dyncom_run.h" #include "core/arm/dyncom/arm_dyncom_run.h"
#include "core/arm/dyncom/arm_dyncom_trans.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_timing.h" #include "core/core_timing.h"
@ -23,6 +24,11 @@ ARM_DynCom::ARM_DynCom(PrivilegeMode initial_mode) {
ARM_DynCom::~ARM_DynCom() { ARM_DynCom::~ARM_DynCom() {
} }
void ARM_DynCom::ClearInstructionCache() {
state->instruction_cache.clear();
trans_cache_buf_top = 0;
}
void ARM_DynCom::SetPC(u32 pc) { void ARM_DynCom::SetPC(u32 pc) {
state->Reg[15] = pc; state->Reg[15] = pc;
} }

View File

@ -21,6 +21,8 @@ public:
ARM_DynCom(PrivilegeMode initial_mode); ARM_DynCom(PrivilegeMode initial_mode);
~ARM_DynCom(); ~ARM_DynCom();
void ClearInstructionCache() override;
void SetPC(u32 pc) override; void SetPC(u32 pc) override;
u32 GetPC() const override; u32 GetPC() const override;
u32 GetReg(int index) const override; u32 GetReg(int index) const override;

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <cstddef>
// We can provide simple Thumb simulation by decoding the Thumb instruction into its corresponding // We can provide simple Thumb simulation by decoding the Thumb instruction into its corresponding
// ARM instruction, and using the existing ARM simulator. // ARM instruction, and using the existing ARM simulator.
@ -293,15 +295,22 @@ ThumbDecodeStatus TranslateThumbInstruction(u32 addr, u32 instr, u32* ainstr, u3
| (BIT(tinstr, 4) << 18); // enable bit | (BIT(tinstr, 4) << 18); // enable bit
} }
} else if ((tinstr & 0x0F00) == 0x0a00) { } else if ((tinstr & 0x0F00) == 0x0a00) {
static const u32 subset[3] = { static const u32 subset[4] = {
0xE6BF0F30, // REV 0xE6BF0F30, // REV
0xE6BF0FB0, // REV16 0xE6BF0FB0, // REV16
0, // undefined
0xE6FF0FB0, // REVSH 0xE6FF0FB0, // REVSH
}; };
*ainstr = subset[BITS(tinstr, 6, 7)] // base size_t subset_index = BITS(tinstr, 6, 7);
| (BITS(tinstr, 0, 2) << 12) // Rd
| BITS(tinstr, 3, 5); // Rm if (subset_index == 2) {
valid = ThumbDecodeStatus::UNDEFINED;
} else {
*ainstr = subset[subset_index] // base
| (BITS(tinstr, 0, 2) << 12) // Rd
| BITS(tinstr, 3, 5); // Rm
}
} else { } else {
static const u32 subset[4] = { static const u32 subset[4] = {
0xE92D0000, // STMDB sp!,{rlist} 0xE92D0000, // STMDB sp!,{rlist}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,493 @@
struct ARMul_State;
typedef unsigned int (*shtop_fp_t)(ARMul_State* cpu, unsigned int sht_oper);
enum class TransExtData {
COND = (1 << 0),
NON_BRANCH = (1 << 1),
DIRECT_BRANCH = (1 << 2),
INDIRECT_BRANCH = (1 << 3),
CALL = (1 << 4),
RET = (1 << 5),
END_OF_PAGE = (1 << 6),
THUMB = (1 << 7),
SINGLE_STEP = (1 << 8)
};
struct arm_inst {
unsigned int idx;
unsigned int cond;
TransExtData br;
char component[0];
};
struct generic_arm_inst {
u32 Ra;
u32 Rm;
u32 Rn;
u32 Rd;
u8 op1;
u8 op2;
};
struct adc_inst {
unsigned int I;
unsigned int S;
unsigned int Rn;
unsigned int Rd;
unsigned int shifter_operand;
shtop_fp_t shtop_func;
};
struct add_inst {
unsigned int I;
unsigned int S;
unsigned int Rn;
unsigned int Rd;
unsigned int shifter_operand;
shtop_fp_t shtop_func;
};
struct orr_inst {
unsigned int I;
unsigned int S;
unsigned int Rn;
unsigned int Rd;
unsigned int shifter_operand;
shtop_fp_t shtop_func;
};
struct and_inst {
unsigned int I;
unsigned int S;
unsigned int Rn;
unsigned int Rd;
unsigned int shifter_operand;
shtop_fp_t shtop_func;
};
struct eor_inst {
unsigned int I;
unsigned int S;
unsigned int Rn;
unsigned int Rd;
unsigned int shifter_operand;
shtop_fp_t shtop_func;
};
struct bbl_inst {
unsigned int L;
int signed_immed_24;
unsigned int next_addr;
unsigned int jmp_addr;
};
struct bx_inst {
unsigned int Rm;
};
struct blx_inst {
union {
s32 signed_immed_24;
u32 Rm;
} val;
unsigned int inst;
};
struct clz_inst {
unsigned int Rm;
unsigned int Rd;
};
struct cps_inst {
unsigned int imod0;
unsigned int imod1;
unsigned int mmod;
unsigned int A, I, F;
unsigned int mode;
};
struct clrex_inst {
};
struct cpy_inst {
unsigned int Rm;
unsigned int Rd;
};
struct bic_inst {
unsigned int I;
unsigned int S;
unsigned int Rn;
unsigned int Rd;
unsigned int shifter_operand;
shtop_fp_t shtop_func;
};
struct sub_inst {
unsigned int I;
unsigned int S;
unsigned int Rn;
unsigned int Rd;
unsigned int shifter_operand;
shtop_fp_t shtop_func;
};
struct tst_inst {
unsigned int I;
unsigned int S;
unsigned int Rn;
unsigned int Rd;
unsigned int shifter_operand;
shtop_fp_t shtop_func;
};
struct cmn_inst {
unsigned int I;
unsigned int Rn;
unsigned int shifter_operand;
shtop_fp_t shtop_func;
};
struct teq_inst {
unsigned int I;
unsigned int Rn;
unsigned int shifter_operand;
shtop_fp_t shtop_func;
};
struct stm_inst {
unsigned int inst;
};
struct bkpt_inst {
u32 imm;
};
struct stc_inst {
};
struct ldc_inst {
};
struct swi_inst {
unsigned int num;
};
struct cmp_inst {
unsigned int I;
unsigned int Rn;
unsigned int shifter_operand;
shtop_fp_t shtop_func;
};
struct mov_inst {
unsigned int I;
unsigned int S;
unsigned int Rd;
unsigned int shifter_operand;
shtop_fp_t shtop_func;
};
struct mvn_inst {
unsigned int I;
unsigned int S;
unsigned int Rd;
unsigned int shifter_operand;
shtop_fp_t shtop_func;
};
struct rev_inst {
unsigned int Rd;
unsigned int Rm;
unsigned int op1;
unsigned int op2;
};
struct rsb_inst {
unsigned int I;
unsigned int S;
unsigned int Rn;
unsigned int Rd;
unsigned int shifter_operand;
shtop_fp_t shtop_func;
};
struct rsc_inst {
unsigned int I;
unsigned int S;
unsigned int Rn;
unsigned int Rd;
unsigned int shifter_operand;
shtop_fp_t shtop_func;
};
struct sbc_inst {
unsigned int I;
unsigned int S;
unsigned int Rn;
unsigned int Rd;
unsigned int shifter_operand;
shtop_fp_t shtop_func;
};
struct mul_inst {
unsigned int S;
unsigned int Rd;
unsigned int Rs;
unsigned int Rm;
};
struct smul_inst {
unsigned int Rd;
unsigned int Rs;
unsigned int Rm;
unsigned int x;
unsigned int y;
};
struct umull_inst {
unsigned int S;
unsigned int RdHi;
unsigned int RdLo;
unsigned int Rs;
unsigned int Rm;
};
struct smlad_inst {
unsigned int m;
unsigned int Rm;
unsigned int Rd;
unsigned int Ra;
unsigned int Rn;
unsigned int op1;
unsigned int op2;
};
struct smla_inst {
unsigned int x;
unsigned int y;
unsigned int Rm;
unsigned int Rd;
unsigned int Rs;
unsigned int Rn;
};
struct smlalxy_inst {
unsigned int x;
unsigned int y;
unsigned int RdLo;
unsigned int RdHi;
unsigned int Rm;
unsigned int Rn;
};
struct ssat_inst {
unsigned int Rn;
unsigned int Rd;
unsigned int imm5;
unsigned int sat_imm;
unsigned int shift_type;
};
struct umaal_inst {
unsigned int Rn;
unsigned int Rm;
unsigned int RdHi;
unsigned int RdLo;
};
struct umlal_inst {
unsigned int S;
unsigned int Rm;
unsigned int Rs;
unsigned int RdHi;
unsigned int RdLo;
};
struct smlal_inst {
unsigned int S;
unsigned int Rm;
unsigned int Rs;
unsigned int RdHi;
unsigned int RdLo;
};
struct smlald_inst {
unsigned int RdLo;
unsigned int RdHi;
unsigned int Rm;
unsigned int Rn;
unsigned int swap;
unsigned int op1;
unsigned int op2;
};
struct mla_inst {
unsigned int S;
unsigned int Rn;
unsigned int Rd;
unsigned int Rs;
unsigned int Rm;
};
struct mrc_inst {
unsigned int opcode_1;
unsigned int opcode_2;
unsigned int cp_num;
unsigned int crn;
unsigned int crm;
unsigned int Rd;
unsigned int inst;
};
struct mcr_inst {
unsigned int opcode_1;
unsigned int opcode_2;
unsigned int cp_num;
unsigned int crn;
unsigned int crm;
unsigned int Rd;
unsigned int inst;
};
struct mcrr_inst {
unsigned int opcode_1;
unsigned int cp_num;
unsigned int crm;
unsigned int rt;
unsigned int rt2;
};
struct mrs_inst {
unsigned int R;
unsigned int Rd;
};
struct msr_inst {
unsigned int field_mask;
unsigned int R;
unsigned int inst;
};
struct pld_inst {
};
struct sxtb_inst {
unsigned int Rd;
unsigned int Rm;
unsigned int rotate;
};
struct sxtab_inst {
unsigned int Rd;
unsigned int Rn;
unsigned int Rm;
unsigned rotate;
};
struct sxtah_inst {
unsigned int Rd;
unsigned int Rn;
unsigned int Rm;
unsigned int rotate;
};
struct sxth_inst {
unsigned int Rd;
unsigned int Rm;
unsigned int rotate;
};
struct uxtab_inst {
unsigned int Rn;
unsigned int Rd;
unsigned int rotate;
unsigned int Rm;
};
struct uxtah_inst {
unsigned int Rn;
unsigned int Rd;
unsigned int rotate;
unsigned int Rm;
};
struct uxth_inst {
unsigned int Rd;
unsigned int Rm;
unsigned int rotate;
};
struct cdp_inst {
unsigned int opcode_1;
unsigned int CRn;
unsigned int CRd;
unsigned int cp_num;
unsigned int opcode_2;
unsigned int CRm;
unsigned int inst;
};
struct uxtb_inst {
unsigned int Rd;
unsigned int Rm;
unsigned int rotate;
};
struct swp_inst {
unsigned int Rn;
unsigned int Rd;
unsigned int Rm;
};
struct setend_inst {
unsigned int set_bigend;
};
struct b_2_thumb {
unsigned int imm;
};
struct b_cond_thumb {
unsigned int imm;
unsigned int cond;
};
struct bl_1_thumb {
unsigned int imm;
};
struct bl_2_thumb {
unsigned int imm;
};
struct blx_1_thumb {
unsigned int imm;
unsigned int instr;
};
struct pkh_inst {
unsigned int Rm;
unsigned int Rn;
unsigned int Rd;
unsigned char imm;
};
// Floating point VFPv3 structures
#define VFP_INTERPRETER_STRUCT
#include "core/arm/skyeye_common/vfp/vfpinstr.cpp"
#undef VFP_INTERPRETER_STRUCT
typedef void (*get_addr_fp_t)(ARMul_State *cpu, unsigned int inst, unsigned int &virt_addr);
struct ldst_inst {
unsigned int inst;
get_addr_fp_t get_addr;
};
typedef arm_inst* ARM_INST_PTR;
typedef ARM_INST_PTR (*transop_fp_t)(unsigned int, int);
extern const transop_fp_t arm_instruction_trans[];
extern const size_t arm_instruction_trans_len;
#define TRANS_CACHE_SIZE (64 * 1024 * 2000)
extern char trans_cache_buf[TRANS_CACHE_SIZE];
extern size_t trans_cache_buf_top;

View File

@ -26,7 +26,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vmla)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8); inst_cream->dp_operation = BIT(inst, 8);
inst_cream->instr = inst; inst_cream->instr = inst;
@ -75,7 +75,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vmls)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8); inst_cream->dp_operation = BIT(inst, 8);
inst_cream->instr = inst; inst_cream->instr = inst;
@ -124,7 +124,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vnmla)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8); inst_cream->dp_operation = BIT(inst, 8);
inst_cream->instr = inst; inst_cream->instr = inst;
@ -174,7 +174,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vnmls)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8); inst_cream->dp_operation = BIT(inst, 8);
inst_cream->instr = inst; inst_cream->instr = inst;
@ -223,7 +223,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vnmul)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8); inst_cream->dp_operation = BIT(inst, 8);
inst_cream->instr = inst; inst_cream->instr = inst;
@ -272,7 +272,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vmul)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8); inst_cream->dp_operation = BIT(inst, 8);
inst_cream->instr = inst; inst_cream->instr = inst;
@ -321,7 +321,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vadd)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8); inst_cream->dp_operation = BIT(inst, 8);
inst_cream->instr = inst; inst_cream->instr = inst;
@ -370,7 +370,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vsub)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8); inst_cream->dp_operation = BIT(inst, 8);
inst_cream->instr = inst; inst_cream->instr = inst;
@ -419,7 +419,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vdiv)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8); inst_cream->dp_operation = BIT(inst, 8);
inst_cream->instr = inst; inst_cream->instr = inst;
@ -470,7 +470,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vmovi)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->single = BIT(inst, 8) == 0; inst_cream->single = BIT(inst, 8) == 0;
inst_cream->d = (inst_cream->single ? BITS(inst,12,15)<<1 | BIT(inst,22) : BITS(inst,12,15) | BIT(inst,22)<<4); inst_cream->d = (inst_cream->single ? BITS(inst,12,15)<<1 | BIT(inst,22) : BITS(inst,12,15) | BIT(inst,22)<<4);
@ -518,7 +518,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vmovr)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->single = BIT(inst, 8) == 0; inst_cream->single = BIT(inst, 8) == 0;
inst_cream->d = (inst_cream->single ? BITS(inst,12,15)<<1 | BIT(inst,22) : BITS(inst,12,15) | BIT(inst,22)<<4); inst_cream->d = (inst_cream->single ? BITS(inst,12,15)<<1 | BIT(inst,22) : BITS(inst,12,15) | BIT(inst,22)<<4);
@ -560,7 +560,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vabs)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8); inst_cream->dp_operation = BIT(inst, 8);
inst_cream->instr = inst; inst_cream->instr = inst;
@ -610,7 +610,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vneg)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8); inst_cream->dp_operation = BIT(inst, 8);
inst_cream->instr = inst; inst_cream->instr = inst;
@ -659,7 +659,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vsqrt)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8); inst_cream->dp_operation = BIT(inst, 8);
inst_cream->instr = inst; inst_cream->instr = inst;
@ -708,7 +708,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vcmp)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8); inst_cream->dp_operation = BIT(inst, 8);
inst_cream->instr = inst; inst_cream->instr = inst;
@ -757,7 +757,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vcmp2)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8); inst_cream->dp_operation = BIT(inst, 8);
inst_cream->instr = inst; inst_cream->instr = inst;
@ -806,7 +806,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vcvtbds)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8); inst_cream->dp_operation = BIT(inst, 8);
inst_cream->instr = inst; inst_cream->instr = inst;
@ -857,7 +857,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vcvtbff)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8); inst_cream->dp_operation = BIT(inst, 8);
inst_cream->instr = inst; inst_cream->instr = inst;
@ -906,7 +906,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vcvtbfi)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8); inst_cream->dp_operation = BIT(inst, 8);
inst_cream->instr = inst; inst_cream->instr = inst;
@ -962,7 +962,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vmovbrs)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->to_arm = BIT(inst, 20) == 1; inst_cream->to_arm = BIT(inst, 20) == 1;
inst_cream->t = BITS(inst, 12, 15); inst_cream->t = BITS(inst, 12, 15);
@ -1006,7 +1006,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vmsr)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->reg = BITS(inst, 16, 19); inst_cream->reg = BITS(inst, 16, 19);
inst_cream->Rt = BITS(inst, 12, 15); inst_cream->Rt = BITS(inst, 12, 15);
@ -1069,7 +1069,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vmovbrc)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->d = BITS(inst, 16, 19)|BIT(inst, 7)<<4; inst_cream->d = BITS(inst, 16, 19)|BIT(inst, 7)<<4;
inst_cream->t = BITS(inst, 12, 15); inst_cream->t = BITS(inst, 12, 15);
@ -1115,7 +1115,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vmrs)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->reg = BITS(inst, 16, 19); inst_cream->reg = BITS(inst, 16, 19);
inst_cream->Rt = BITS(inst, 12, 15); inst_cream->Rt = BITS(inst, 12, 15);
@ -1200,7 +1200,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vmovbcr)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->d = BITS(inst, 16, 19)|BIT(inst, 7)<<4; inst_cream->d = BITS(inst, 16, 19)|BIT(inst, 7)<<4;
inst_cream->t = BITS(inst, 12, 15); inst_cream->t = BITS(inst, 12, 15);
@ -1253,7 +1253,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vmovbrrss)(unsigned int inst, int inde
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->to_arm = BIT(inst, 20) == 1; inst_cream->to_arm = BIT(inst, 20) == 1;
inst_cream->t = BITS(inst, 12, 15); inst_cream->t = BITS(inst, 12, 15);
@ -1301,7 +1301,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vmovbrrd)(unsigned int inst, int index
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->to_arm = BIT(inst, 20) == 1; inst_cream->to_arm = BIT(inst, 20) == 1;
inst_cream->t = BITS(inst, 12, 15); inst_cream->t = BITS(inst, 12, 15);
@ -1354,7 +1354,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vstr)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->single = BIT(inst, 8) == 0; inst_cream->single = BIT(inst, 8) == 0;
inst_cream->add = BIT(inst, 23); inst_cream->add = BIT(inst, 23);
@ -1420,7 +1420,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vpush)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->single = BIT(inst, 8) == 0; inst_cream->single = BIT(inst, 8) == 0;
inst_cream->d = (inst_cream->single ? BITS(inst, 12, 15)<<1|BIT(inst, 22) : BITS(inst, 12, 15)|BIT(inst, 22)<<4); inst_cream->d = (inst_cream->single ? BITS(inst, 12, 15)<<1|BIT(inst, 22) : BITS(inst, 12, 15)|BIT(inst, 22)<<4);
@ -1495,7 +1495,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vstm)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->single = BIT(inst, 8) == 0; inst_cream->single = BIT(inst, 8) == 0;
inst_cream->add = BIT(inst, 23); inst_cream->add = BIT(inst, 23);
@ -1580,7 +1580,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vpop)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->single = BIT(inst, 8) == 0; inst_cream->single = BIT(inst, 8) == 0;
inst_cream->d = (inst_cream->single ? (BITS(inst, 12, 15)<<1)|BIT(inst, 22) : BITS(inst, 12, 15)|(BIT(inst, 22)<<4)); inst_cream->d = (inst_cream->single ? (BITS(inst, 12, 15)<<1)|BIT(inst, 22) : BITS(inst, 12, 15)|(BIT(inst, 22)<<4));
@ -1653,7 +1653,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vldr)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->single = BIT(inst, 8) == 0; inst_cream->single = BIT(inst, 8) == 0;
inst_cream->add = BIT(inst, 23); inst_cream->add = BIT(inst, 23);
@ -1722,7 +1722,7 @@ static ARM_INST_PTR INTERPRETER_TRANSLATE(vldm)(unsigned int inst, int index)
inst_base->cond = BITS(inst, 28, 31); inst_base->cond = BITS(inst, 28, 31);
inst_base->idx = index; inst_base->idx = index;
inst_base->br = NON_BRANCH; inst_base->br = TransExtData::NON_BRANCH;
inst_cream->single = BIT(inst, 8) == 0; inst_cream->single = BIT(inst, 8) == 0;
inst_cream->add = BIT(inst, 23); inst_cream->add = BIT(inst, 23);

View File

@ -26,7 +26,7 @@
extern int g_clock_rate_arm11; extern int g_clock_rate_arm11;
inline s64 msToCycles(int ms) { inline s64 msToCycles(int ms) {
return g_clock_rate_arm11 / 1000 * ms; return (s64)g_clock_rate_arm11 / 1000 * ms;
} }
inline s64 msToCycles(float ms) { inline s64 msToCycles(float ms) {

View File

@ -12,6 +12,7 @@
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/hle/applets/applet.h" #include "core/hle/applets/applet.h"
#include "core/hle/applets/erreula.h"
#include "core/hle/applets/mii_selector.h" #include "core/hle/applets/mii_selector.h"
#include "core/hle/applets/swkbd.h" #include "core/hle/applets/swkbd.h"
#include "core/hle/result.h" #include "core/hle/result.h"
@ -52,6 +53,10 @@ ResultCode Applet::Create(Service::APT::AppletId id) {
case Service::APT::AppletId::Ed2: case Service::APT::AppletId::Ed2:
applets[id] = std::make_shared<MiiSelector>(id); applets[id] = std::make_shared<MiiSelector>(id);
break; break;
case Service::APT::AppletId::Error:
case Service::APT::AppletId::Error2:
applets[id] = std::make_shared<ErrEula>(id);
break;
default: default:
LOG_ERROR(Service_APT, "Could not create applet %u", id); LOG_ERROR(Service_APT, "Could not create applet %u", id);
// TODO(Subv): Find the right error code // TODO(Subv): Find the right error code

View File

@ -0,0 +1,72 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/string_util.h"
#include "core/hle/applets/erreula.h"
#include "core/hle/service/apt/apt.h"
namespace HLE {
namespace Applets {
ResultCode ErrEula::ReceiveParameter(const Service::APT::MessageParameter& parameter) {
if (parameter.signal != static_cast<u32>(Service::APT::SignalType::LibAppJustStarted)) {
LOG_ERROR(Service_APT, "unsupported signal %u", parameter.signal);
UNIMPLEMENTED();
// TODO(Subv): Find the right error code
return ResultCode(-1);
}
// The LibAppJustStarted message contains a buffer with the size of the framebuffer shared memory.
// Create the SharedMemory that will hold the framebuffer data
Service::APT::CaptureBufferInfo capture_info;
ASSERT(sizeof(capture_info) == parameter.buffer.size());
memcpy(&capture_info, parameter.buffer.data(), sizeof(capture_info));
// TODO: allocated memory never released
using Kernel::MemoryPermission;
// Allocate a heap block of the required size for this applet.
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");
// Send the response message with the newly created SharedMemory
Service::APT::MessageParameter result;
result.signal = static_cast<u32>(Service::APT::SignalType::LibAppFinished);
result.buffer.clear();
result.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
result.sender_id = static_cast<u32>(id);
result.object = framebuffer_memory;
Service::APT::SendParameter(result);
return RESULT_SUCCESS;
}
ResultCode ErrEula::StartImpl(const Service::APT::AppletStartupParameter& parameter) {
started = true;
// TODO(Subv): Set the expected fields in the response buffer before resending it to the application.
// TODO(Subv): Reverse the parameter format for the ErrEula applet
// Let the application know that we're closing
Service::APT::MessageParameter message;
message.buffer.resize(parameter.buffer.size());
std::fill(message.buffer.begin(), message.buffer.end(), 0);
message.signal = static_cast<u32>(Service::APT::SignalType::LibAppClosed);
message.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
message.sender_id = static_cast<u32>(id);
Service::APT::SendParameter(message);
started = false;
return RESULT_SUCCESS;
}
void ErrEula::Update() {
}
} // namespace Applets
} // namespace HLE

View File

@ -0,0 +1,31 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/applets/applet.h"
#include "core/hle/kernel/shared_memory.h"
namespace HLE {
namespace Applets {
class ErrEula final : public Applet {
public:
explicit ErrEula(Service::APT::AppletId id): Applet(id) { }
ResultCode ReceiveParameter(const Service::APT::MessageParameter& parameter) override;
ResultCode StartImpl(const Service::APT::AppletStartupParameter& parameter) override;
void Update() override;
bool IsRunning() const override { return started; }
/// This SharedMemory will be created when we receive the LibAppJustStarted message.
/// It holds the framebuffer info retrieved by the application with GSPGPU::ImportDisplayCaptureInfo
Kernel::SharedPtr<Kernel::SharedMemory> framebuffer_memory;
private:
/// Whether this applet is currently running instead of the host application or not.
bool started = false;
};
} // namespace Applets
} // namespace HLE

View File

@ -194,6 +194,16 @@ template<ResultCode func(Handle, u32)> void Wrap() {
FuncReturn(func(PARAM(0), PARAM(1)).raw); FuncReturn(func(PARAM(0), PARAM(1)).raw);
} }
template<ResultCode func(Handle*, Handle*, const char*, u32)> void Wrap() {
Handle param_1 = 0;
Handle param_2 = 0;
u32 retval = func(&param_1, &param_2, reinterpret_cast<const char*>(Memory::GetPointer(PARAM(2))), PARAM(3)).raw;
// The first out parameter is moved into R2 and the second is moved into R1.
Core::g_app_core->SetReg(1, param_2);
Core::g_app_core->SetReg(2, param_1);
FuncReturn(retval);
}
//////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Function wrappers that return type u32 // Function wrappers that return type u32

View File

@ -0,0 +1,16 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/assert.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/server_port.h"
namespace Kernel {
ClientPort::ClientPort() {}
ClientPort::~ClientPort() {}
} // namespace

View File

@ -0,0 +1,36 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <string>
#include "common/common_types.h"
#include "core/hle/kernel/kernel.h"
namespace Kernel {
class ServerPort;
class ClientPort : public Object {
public:
friend class ServerPort;
std::string GetTypeName() const override { return "ClientPort"; }
std::string GetName() const override { return name; }
static const HandleType HANDLE_TYPE = HandleType::ClientPort;
HandleType GetHandleType() const override { return HANDLE_TYPE; }
SharedPtr<ServerPort> server_port; ///< ServerPort associated with this client port.
u32 max_sessions; ///< Maximum number of simultaneous sessions the port can have
u32 active_sessions; ///< Number of currently open sessions to this port
std::string name; ///< Name of client port (optional)
protected:
ClientPort();
~ClientPort() override;
};
} // namespace

View File

@ -35,7 +35,7 @@ enum KernelHandle : Handle {
enum class HandleType : u32 { enum class HandleType : u32 {
Unknown = 0, Unknown = 0,
Port = 1,
Session = 2, Session = 2,
Event = 3, Event = 3,
Mutex = 4, Mutex = 4,
@ -48,6 +48,8 @@ enum class HandleType : u32 {
Timer = 11, Timer = 11,
ResourceLimit = 12, ResourceLimit = 12,
CodeSet = 13, CodeSet = 13,
ClientPort = 14,
ServerPort = 15,
}; };
enum { enum {
@ -72,6 +74,7 @@ public:
bool IsWaitable() const { bool IsWaitable() const {
switch (GetHandleType()) { switch (GetHandleType()) {
case HandleType::Session: case HandleType::Session:
case HandleType::ServerPort:
case HandleType::Event: case HandleType::Event:
case HandleType::Mutex: case HandleType::Mutex:
case HandleType::Thread: case HandleType::Thread:
@ -80,13 +83,13 @@ public:
return true; return true;
case HandleType::Unknown: case HandleType::Unknown:
case HandleType::Port:
case HandleType::SharedMemory: case HandleType::SharedMemory:
case HandleType::Redirection: case HandleType::Redirection:
case HandleType::Process: case HandleType::Process:
case HandleType::AddressArbiter: case HandleType::AddressArbiter:
case HandleType::ResourceLimit: case HandleType::ResourceLimit:
case HandleType::CodeSet: case HandleType::CodeSet:
case HandleType::ClientPort:
return false; return false;
} }
} }

View File

@ -0,0 +1,41 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <tuple>
#include "common/assert.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/server_port.h"
#include "core/hle/kernel/thread.h"
namespace Kernel {
ServerPort::ServerPort() {}
ServerPort::~ServerPort() {}
bool ServerPort::ShouldWait() {
// If there are no pending sessions, we wait until a new one is added.
return pending_sessions.size() == 0;
}
void ServerPort::Acquire() {
ASSERT_MSG(!ShouldWait(), "object unavailable!");
}
std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> ServerPort::CreatePortPair(u32 max_sessions, std::string name) {
SharedPtr<ServerPort> server_port(new ServerPort);
SharedPtr<ClientPort> client_port(new ClientPort);
server_port->name = name + "_Server";
client_port->name = name + "_Client";
client_port->server_port = server_port;
client_port->max_sessions = max_sessions;
client_port->active_sessions = 0;
return std::make_tuple(std::move(server_port), std::move(client_port));
}
} // namespace

View File

@ -0,0 +1,46 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <string>
#include <tuple>
#include "common/common_types.h"
#include "core/hle/kernel/kernel.h"
namespace Kernel {
class ClientPort;
class ServerPort final : public WaitObject {
public:
/**
* Creates a pair of ServerPort and an associated ClientPort.
* @param max_sessions Maximum number of sessions to the port
* @param name Optional name of the ports
* @return The created port tuple
*/
static std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> CreatePortPair(u32 max_sessions, std::string name = "UnknownPort");
std::string GetTypeName() const override { return "ServerPort"; }
std::string GetName() const override { return name; }
static const HandleType HANDLE_TYPE = HandleType::ServerPort;
HandleType GetHandleType() const override { return HANDLE_TYPE; }
std::string name; ///< Name of port (optional)
std::vector<SharedPtr<WaitObject>> pending_sessions; ///< ServerSessions waiting to be accepted by the port
bool ShouldWait() override;
void Acquire() override;
private:
ServerPort();
~ServerPort() override;
};
} // namespace

View File

@ -16,38 +16,130 @@
namespace IPC { namespace IPC {
constexpr u32 MakeHeader(u16 command_id, unsigned int regular_params, unsigned int translate_params) { enum DescriptorType : u32 {
return ((u32)command_id << 16) | (((u32)regular_params & 0x3F) << 6) | (((u32)translate_params & 0x3F) << 0); // Buffer related desciptors types (mask : 0x0F)
StaticBuffer = 0x02,
PXIBuffer = 0x04,
MappedBuffer = 0x08,
// Handle related descriptors types (mask : 0x30, but need to check for buffer related descriptors first )
CopyHandle = 0x00,
MoveHandle = 0x10,
CallingPid = 0x20,
};
/**
* @brief Creates a command header to be used for IPC
* @param command_id ID of the command to create a header for.
* @param normal_params Size of the normal parameters in words. Up to 63.
* @param translate_params_size Size of the translate parameters in words. Up to 63.
* @return The created IPC header.
*
* Normal parameters are sent directly to the process while the translate parameters might go through modifications and checks by the kernel.
* The translate parameters are described by headers generated with the IPC::*Desc functions.
*
* @note While #normal_params is equivalent to the number of normal parameters, #translate_params_size includes the size occupied by the translate parameters headers.
*/
constexpr u32 MakeHeader(u16 command_id, unsigned int normal_params, unsigned int translate_params_size) {
return (u32(command_id) << 16) | ((u32(normal_params) & 0x3F) << 6) | (u32(translate_params_size) & 0x3F);
} }
constexpr u32 MoveHandleDesc(unsigned int num_handles = 1) { union Header {
return 0x0 | ((num_handles - 1) << 26); u32 raw;
BitField< 0, 6, u32> translate_params_size;
BitField< 6, 6, u32> normal_params;
BitField<16, 16, u32> command_id;
};
inline Header ParseHeader(u32 header) {
return{ header };
} }
constexpr u32 CopyHandleDesc(unsigned int num_handles = 1) { constexpr u32 MoveHandleDesc(u32 num_handles = 1) {
return 0x10 | ((num_handles - 1) << 26); return MoveHandle | ((num_handles - 1) << 26);
}
constexpr u32 CopyHandleDesc(u32 num_handles = 1) {
return CopyHandle | ((num_handles - 1) << 26);
} }
constexpr u32 CallingPidDesc() { constexpr u32 CallingPidDesc() {
return 0x20; return CallingPid;
} }
constexpr u32 StaticBufferDesc(u32 size, unsigned int buffer_id) { constexpr bool isHandleDescriptor(u32 descriptor) {
return 0x2 | (size << 14) | ((buffer_id & 0xF) << 10); return (descriptor & 0xF) == 0x0;
}
constexpr u32 HandleNumberFromDesc(u32 handle_descriptor) {
return (handle_descriptor >> 26) + 1;
}
constexpr u32 StaticBufferDesc(u32 size, u8 buffer_id) {
return StaticBuffer | (size << 14) | ((buffer_id & 0xF) << 10);
}
union StaticBufferDescInfo {
u32 raw;
BitField< 10, 4, u32> buffer_id;
BitField< 14, 18, u32> size;
};
inline StaticBufferDescInfo ParseStaticBufferDesc(const u32 desc) {
return{ desc };
}
/**
* @brief Creates a header describing a buffer to be sent over PXI.
* @param size Size of the buffer. Max 0x00FFFFFF.
* @param buffer_id The Id of the buffer. Max 0xF.
* @param is_read_only true if the buffer is read-only. If false, the buffer is considered to have read-write access.
* @return The created PXI buffer header.
*
* The next value is a phys-address of a table located in the BASE memregion.
*/
inline u32 PXIBufferDesc(u32 size, unsigned buffer_id, bool is_read_only) {
u32 type = PXIBuffer;
if (is_read_only) type |= 0x2;
return type | (size << 8) | ((buffer_id & 0xF) << 4);
} }
enum MappedBufferPermissions { enum MappedBufferPermissions {
R = 2, R = 1,
W = 4, W = 2,
RW = R | W, RW = R | W,
}; };
constexpr u32 MappedBufferDesc(u32 size, MappedBufferPermissions perms) { constexpr u32 MappedBufferDesc(u32 size, MappedBufferPermissions perms) {
return 0x8 | (size << 4) | (u32)perms; return MappedBuffer | (size << 4) | (u32(perms) << 1);
} }
union MappedBufferDescInfo {
u32 raw;
BitField< 4, 28, u32> size;
BitField< 1, 2, MappedBufferPermissions> perms;
};
inline MappedBufferDescInfo ParseMappedBufferDesc(const u32 desc) {
return{ desc };
} }
inline DescriptorType GetDescriptorType(u32 descriptor) {
// Note: Those checks must be done in this order
if (isHandleDescriptor(descriptor))
return (DescriptorType)(descriptor & 0x30);
// handle the fact that the following descriptors can have rights
if (descriptor & MappedBuffer)
return MappedBuffer;
if (descriptor & PXIBuffer)
return PXIBuffer;
return StaticBuffer;
}
} // namespace IPC
namespace Kernel { namespace Kernel {
static const int kCommandHeaderOffset = 0x80; ///< Offset into command buffer of header static const int kCommandHeaderOffset = 0x80; ///< Offset into command buffer of header
@ -60,7 +152,7 @@ static const int kCommandHeaderOffset = 0x80; ///< Offset into command buffer of
* @param offset Optional offset into command buffer * @param offset Optional offset into command buffer
* @return Pointer to command buffer * @return Pointer to command buffer
*/ */
inline static u32* GetCommandBuffer(const int offset = 0) { inline u32* GetCommandBuffer(const int offset = 0) {
return (u32*)Memory::GetPointer(GetCurrentThread()->GetTLSAddress() + kCommandHeaderOffset + offset); return (u32*)Memory::GetPointer(GetCurrentThread()->GetTLSAddress() + kCommandHeaderOffset + offset);
} }

View File

@ -181,6 +181,48 @@ static void PriorityBoostStarvedThreads() {
} }
} }
/**
* Gets the registers for timeout parameter of the next WaitSynchronization call.
* @param thread a pointer to the thread that is ready to call WaitSynchronization
* @returns a tuple of two register pointers to low and high part of the timeout parameter
*/
static std::tuple<u32*, u32*> GetWaitSynchTimeoutParameterRegister(Thread* thread) {
bool thumb_mode = (thread->context.cpsr & TBIT) != 0;
u16 thumb_inst = Memory::Read16(thread->context.pc & 0xFFFFFFFE);
u32 inst = Memory::Read32(thread->context.pc & 0xFFFFFFFC) & 0x0FFFFFFF;
if ((thumb_mode && thumb_inst == 0xDF24) || (!thumb_mode && inst == 0x0F000024)) {
// svc #0x24 (WaitSynchronization1)
return std::make_tuple(&thread->context.cpu_registers[2], &thread->context.cpu_registers[3]);
} else if ((thumb_mode && thumb_inst == 0xDF25) || (!thumb_mode && inst == 0x0F000025)) {
// svc #0x25 (WaitSynchronizationN)
return std::make_tuple(&thread->context.cpu_registers[0], &thread->context.cpu_registers[4]);
}
UNREACHABLE();
}
/**
* Updates the WaitSynchronization timeout paramter according to the difference
* between ticks of the last WaitSynchronization call and the incoming one.
* @param timeout_low a pointer to the register for the low part of the timeout parameter
* @param timeout_high a pointer to the register for the high part of the timeout parameter
* @param last_tick tick of the last WaitSynchronization call
*/
static void UpdateTimeoutParameter(u32* timeout_low, u32* timeout_high, u64 last_tick) {
s64 timeout = ((s64)*timeout_high << 32) | *timeout_low;
if (timeout != -1) {
timeout -= cyclesToUs(CoreTiming::GetTicks() - last_tick) * 1000; // in nanoseconds
if (timeout < 0)
timeout = 0;
*timeout_low = timeout & 0xFFFFFFFF;
*timeout_high = timeout >> 32;
}
}
/** /**
* Switches the CPU's active thread context to that of the specified thread * Switches the CPU's active thread context to that of the specified thread
* @param new_thread The thread to switch to * @param new_thread The thread to switch to
@ -219,6 +261,13 @@ static void SwitchContext(Thread* new_thread) {
// SVC instruction is 2 bytes for THUMB, 4 bytes for ARM // SVC instruction is 2 bytes for THUMB, 4 bytes for ARM
new_thread->context.pc -= thumb_mode ? 2 : 4; new_thread->context.pc -= thumb_mode ? 2 : 4;
// Get the register for timeout parameter
u32* timeout_low, *timeout_high;
std::tie(timeout_low, timeout_high) = GetWaitSynchTimeoutParameterRegister(new_thread);
// Update the timeout parameter
UpdateTimeoutParameter(timeout_low, timeout_high, new_thread->last_running_ticks);
} }
// Clean up the thread's wait_objects, they'll be restored if needed during // Clean up the thread's wait_objects, they'll be restored if needed during
@ -542,8 +591,12 @@ void Reschedule() {
HLE::DoneRescheduling(); HLE::DoneRescheduling();
// Don't bother switching to the same thread // Don't bother switching to the same thread.
if (next == cur) // But if the thread was waiting on objects, we still need to switch it
// to perform PC modification, change state to RUNNING, etc.
// This occurs in the case when an object the thread is waiting on immediately wakes up
// the current thread before Reschedule() is called.
if (next == cur && (next == nullptr || next->waitsynch_waited == false))
return; return;
if (cur && next) { if (cur && next) {

View File

@ -20,6 +20,7 @@ enum class ErrorDescription : u32 {
WrongPermission = 46, WrongPermission = 46,
OS_InvalidBufferDescriptor = 48, OS_InvalidBufferDescriptor = 48,
WrongAddress = 53, WrongAddress = 53,
FS_ArchiveNotMounted = 101,
FS_NotFound = 120, FS_NotFound = 120,
FS_AlreadyExists = 190, FS_AlreadyExists = 190,
FS_InvalidOpenFlags = 230, FS_InvalidOpenFlags = 230,
@ -135,15 +136,28 @@ enum class ErrorModule : u32 {
MCU = 72, MCU = 72,
NS = 73, NS = 73,
News = 74, News = 74,
RO_1 = 75, RO = 75,
GD = 76, GD = 76,
CardSPI = 77, CardSPI = 77,
EC = 78, EC = 78,
RO_2 = 79, WebBrowser = 79,
WebBrowser = 80, Test = 80,
Test = 81, ENC = 81,
ENC = 82, PIA = 82,
PIA = 83, ACT = 83,
VCTL = 84,
OLV = 85,
NEIA = 86,
NPNS = 87,
AVD = 90,
L2B = 91,
MVD = 92,
NFC = 93,
UART = 94,
SPM = 95,
QTM = 96,
NFP = 97,
Application = 254, Application = 254,
InvalidResult = 255 InvalidResult = 255

View File

@ -37,6 +37,8 @@ static u32 cpu_percent; ///< CPU time available to the running application
// APT::CheckNew3DSApp will check this unknown_ns_state_field to determine processing mode // APT::CheckNew3DSApp will check this unknown_ns_state_field to determine processing mode
static u8 unknown_ns_state_field; static u8 unknown_ns_state_field;
static ScreencapPostPermission screen_capture_post_permission;
/// Parameter data to be returned in the next call to Glance/ReceiveParameter /// Parameter data to be returned in the next call to Glance/ReceiveParameter
static MessageParameter next_parameter; static MessageParameter next_parameter;
@ -51,7 +53,7 @@ void Initialize(Service::Interface* self) {
u32 app_id = cmd_buff[1]; u32 app_id = cmd_buff[1];
u32 flags = cmd_buff[2]; u32 flags = cmd_buff[2];
cmd_buff[2] = IPC::MoveHandleDesc(2); cmd_buff[2] = IPC::CopyHandleDesc(2);
cmd_buff[3] = Kernel::g_handle_table.Create(notification_event).MoveFrom(); cmd_buff[3] = Kernel::g_handle_table.Create(notification_event).MoveFrom();
cmd_buff[4] = Kernel::g_handle_table.Create(parameter_event).MoveFrom(); cmd_buff[4] = Kernel::g_handle_table.Create(parameter_event).MoveFrom();
@ -70,15 +72,17 @@ void Initialize(Service::Interface* self) {
void GetSharedFont(Service::Interface* self) { void GetSharedFont(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); u32* cmd_buff = Kernel::GetCommandBuffer();
if (!shared_font_mem) {
LOG_ERROR(Service_APT, "shared font file missing - go dump it from your 3ds");
cmd_buff[0] = IPC::MakeHeader(0x44, 2, 2);
cmd_buff[1] = -1; // TODO: Find the right error code
return;
}
// The shared font has to be relocated to the new address before being passed to the application. // The shared font has to be relocated to the new address before being passed to the application.
VAddr target_address = Memory::PhysicalToVirtualAddress(shared_font_mem->linear_heap_phys_address); VAddr target_address = Memory::PhysicalToVirtualAddress(shared_font_mem->linear_heap_phys_address);
// The shared font dumped by 3dsutils (https://github.com/citra-emu/3dsutils) uses this address as base,
// so we relocate it from there to our real address.
// TODO(Subv): This address is wrong if the shared font is dumped from a n3DS,
// we need a way to automatically calculate the original address of the font from the file.
static const VAddr SHARED_FONT_VADDR = 0x18000000;
if (!shared_font_relocated) { if (!shared_font_relocated) {
BCFNT::RelocateSharedFont(shared_font_mem, SHARED_FONT_VADDR, target_address); BCFNT::RelocateSharedFont(shared_font_mem, target_address);
shared_font_relocated = true; shared_font_relocated = true;
} }
cmd_buff[0] = IPC::MakeHeader(0x44, 2, 2); cmd_buff[0] = IPC::MakeHeader(0x44, 2, 2);
@ -87,7 +91,7 @@ void GetSharedFont(Service::Interface* self) {
// the real APT service calculates this address by scanning the entire address space (using svcQueryMemory) // the real APT service calculates this address by scanning the entire address space (using svcQueryMemory)
// and searches for an allocation of the same size as the Shared Font. // and searches for an allocation of the same size as the Shared Font.
cmd_buff[2] = target_address; cmd_buff[2] = target_address;
cmd_buff[3] = IPC::MoveHandleDesc(); cmd_buff[3] = IPC::CopyHandleDesc();
cmd_buff[4] = Kernel::g_handle_table.Create(shared_font_mem).MoveFrom(); cmd_buff[4] = Kernel::g_handle_table.Create(shared_font_mem).MoveFrom();
} }
@ -382,23 +386,23 @@ void StartLibraryApplet(Service::Interface* self) {
cmd_buff[1] = applet->Start(parameter).raw; cmd_buff[1] = applet->Start(parameter).raw;
} }
void SetNSStateField(Service::Interface* self) { void SetScreenCapPostPermission(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); u32* cmd_buff = Kernel::GetCommandBuffer();
unknown_ns_state_field = cmd_buff[1]; screen_capture_post_permission = static_cast<ScreencapPostPermission>(cmd_buff[1] & 0xF);
cmd_buff[0] = IPC::MakeHeader(0x55, 1, 0); cmd_buff[0] = IPC::MakeHeader(0x55, 1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[1] = RESULT_SUCCESS.raw;
LOG_WARNING(Service_APT, "(STUBBED) unknown_ns_state_field=%u", unknown_ns_state_field); LOG_WARNING(Service_APT, "(STUBBED) screen_capture_post_permission=%u", screen_capture_post_permission);
} }
void GetNSStateField(Service::Interface* self) { void GetScreenCapPostPermission(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); u32* cmd_buff = Kernel::GetCommandBuffer();
cmd_buff[0] = IPC::MakeHeader(0x56, 2, 0); cmd_buff[0] = IPC::MakeHeader(0x56, 2, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[1] = RESULT_SUCCESS.raw;
cmd_buff[8] = unknown_ns_state_field; cmd_buff[2] = static_cast<u32>(screen_capture_post_permission);
LOG_WARNING(Service_APT, "(STUBBED) unknown_ns_state_field=%u", unknown_ns_state_field); LOG_WARNING(Service_APT, "(STUBBED) screen_capture_post_permission=%u", screen_capture_post_permission);
} }
void GetAppletInfo(Service::Interface* self) { void GetAppletInfo(Service::Interface* self) {
@ -493,6 +497,7 @@ void Init() {
cpu_percent = 0; cpu_percent = 0;
unknown_ns_state_field = 0; unknown_ns_state_field = 0;
screen_capture_post_permission = ScreencapPostPermission::CleanThePermission; // TODO(JamePeng): verify the initial value
// TODO(bunnei): Check if these are created in Initialize or on APT process startup. // TODO(bunnei): Check if these are created in Initialize or on APT process startup.
notification_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "APT_U:Notification"); notification_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "APT_U:Notification");

View File

@ -66,6 +66,8 @@ enum class AppletId : u32 {
InstructionManual = 0x115, InstructionManual = 0x115,
Notifications = 0x116, Notifications = 0x116,
Miiverse = 0x117, Miiverse = 0x117,
MiiversePost = 0x118,
AmiiboSettings = 0x119,
SoftwareKeyboard1 = 0x201, SoftwareKeyboard1 = 0x201,
Ed1 = 0x202, Ed1 = 0x202,
PnoteApp = 0x204, PnoteApp = 0x204,
@ -78,6 +80,12 @@ enum class AppletId : u32 {
AnyLibraryApplet = 0x400, AnyLibraryApplet = 0x400,
SoftwareKeyboard2 = 0x401, SoftwareKeyboard2 = 0x401,
Ed2 = 0x402, Ed2 = 0x402,
PnoteApp2 = 0x404,
SnoteApp2 = 0x405,
Error2 = 0x406,
Mint2 = 0x407,
Extrapad2 = 0x408,
Memolib2 = 0x409,
}; };
enum class StartupArgumentType : u32 { enum class StartupArgumentType : u32 {
@ -86,6 +94,13 @@ enum class StartupArgumentType : u32 {
OtherMedia = 2, OtherMedia = 2,
}; };
enum class ScreencapPostPermission : u32 {
CleanThePermission = 0, //TODO(JamePeng): verify what "zero" means
NoExplicitSetting = 1,
EnableScreenshotPostingToMiiverse = 2,
DisableScreenshotPostingToMiiverse = 3
};
/// Send a parameter to the currently-running application, which will read it via ReceiveParameter /// Send a parameter to the currently-running application, which will read it via ReceiveParameter
void SendParameter(const MessageParameter& parameter); void SendParameter(const MessageParameter& parameter);
@ -375,25 +390,24 @@ void StartLibraryApplet(Service::Interface* self);
void GetStartupArgument(Service::Interface* self); void GetStartupArgument(Service::Interface* self);
/** /**
* APT::SetNSStateField service function * APT::SetScreenCapPostPermission service function
* Inputs: * Inputs:
* 1 : u8 NS state field * 0 : Header Code[0x00550040]
* 1 : u8 The screenshot posting permission
* Outputs: * Outputs:
* 1 : Result of function, 0 on success, otherwise error code * 1 : Result of function, 0 on success, otherwise error code
* Note:
* This writes the input u8 to a NS state field.
*/ */
void SetNSStateField(Service::Interface* self); void SetScreenCapPostPermission(Service::Interface* self);
/** /**
* APT::GetNSStateField service function * APT::GetScreenCapPostPermission service function
* Inputs:
* 0 : Header Code[0x00560000]
* Outputs: * Outputs:
* 1 : Result of function, 0 on success, otherwise error code * 1 : Result of function, 0 on success, otherwise error code
* 8 : u8 NS state field * 2 : u8 The screenshot posting permission
* Note:
* This returns a u8 NS state field(which can be set by cmd 0x00550040), at cmdreply+8.
*/ */
void GetNSStateField(Service::Interface* self); void GetScreenCapPostPermission(Service::Interface* self);
/** /**
* APT::CheckNew3DSApp service function * APT::CheckNew3DSApp service function

View File

@ -33,8 +33,8 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x004F0080, SetAppCpuTimeLimit, "SetAppCpuTimeLimit"}, {0x004F0080, SetAppCpuTimeLimit, "SetAppCpuTimeLimit"},
{0x00500040, GetAppCpuTimeLimit, "GetAppCpuTimeLimit"}, {0x00500040, GetAppCpuTimeLimit, "GetAppCpuTimeLimit"},
{0x00510080, GetStartupArgument, "GetStartupArgument"}, {0x00510080, GetStartupArgument, "GetStartupArgument"},
{0x00550040, SetNSStateField, "SetNSStateField?"}, {0x00550040, SetScreenCapPostPermission, "SetScreenCapPostPermission"},
{0x00560000, GetNSStateField, "GetNSStateField?"}, {0x00560000, GetScreenCapPostPermission, "GetScreenCapPostPermission"},
{0x01010000, CheckNew3DSApp, "CheckNew3DSApp"}, {0x01010000, CheckNew3DSApp, "CheckNew3DSApp"},
{0x01020000, CheckNew3DS, "CheckNew3DS"} {0x01020000, CheckNew3DS, "CheckNew3DS"}
}; };

View File

@ -92,8 +92,8 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00510080, GetStartupArgument, "GetStartupArgument"}, {0x00510080, GetStartupArgument, "GetStartupArgument"},
{0x00520104, nullptr, "Wrap1"}, {0x00520104, nullptr, "Wrap1"},
{0x00530104, nullptr, "Unwrap1"}, {0x00530104, nullptr, "Unwrap1"},
{0x00550040, SetNSStateField, "SetNSStateField?" }, {0x00550040, SetScreenCapPostPermission, "SetScreenCapPostPermission"},
{0x00560000, GetNSStateField, "GetNSStateField?" }, {0x00560000, GetScreenCapPostPermission, "GetScreenCapPostPermission"},
{0x00580002, nullptr, "GetProgramID"}, {0x00580002, nullptr, "GetProgramID"},
{0x01010000, CheckNew3DSApp, "CheckNew3DSApp"}, {0x01010000, CheckNew3DSApp, "CheckNew3DSApp"},
{0x01020000, CheckNew3DS, "CheckNew3DS"} {0x01020000, CheckNew3DS, "CheckNew3DS"}

View File

@ -92,8 +92,8 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00510080, GetStartupArgument, "GetStartupArgument"}, {0x00510080, GetStartupArgument, "GetStartupArgument"},
{0x00520104, nullptr, "Wrap1"}, {0x00520104, nullptr, "Wrap1"},
{0x00530104, nullptr, "Unwrap1"}, {0x00530104, nullptr, "Unwrap1"},
{0x00550040, SetNSStateField, "SetNSStateField?"}, {0x00550040, SetScreenCapPostPermission, "SetScreenCapPostPermission"},
{0x00560000, GetNSStateField, "GetNSStateField?"}, {0x00560000, GetScreenCapPostPermission, "GetScreenCapPostPermission"},
{0x00580002, nullptr, "GetProgramID"}, {0x00580002, nullptr, "GetProgramID"},
{0x01010000, CheckNew3DSApp, "CheckNew3DSApp"}, {0x01010000, CheckNew3DSApp, "CheckNew3DSApp"},
{0x01020000, CheckNew3DS, "CheckNew3DS"} {0x01020000, CheckNew3DS, "CheckNew3DS"}

View File

@ -9,60 +9,97 @@ namespace Service {
namespace APT { namespace APT {
namespace BCFNT { namespace BCFNT {
void RelocateSharedFont(Kernel::SharedPtr<Kernel::SharedMemory> shared_font, VAddr previous_address, VAddr new_address) { void RelocateSharedFont(Kernel::SharedPtr<Kernel::SharedMemory> shared_font, VAddr new_address) {
static const u32 SharedFontStartOffset = 0x80; static const u32 SharedFontStartOffset = 0x80;
u8* data = shared_font->GetPointer(SharedFontStartOffset); const u8* cfnt_ptr = shared_font->GetPointer(SharedFontStartOffset);
CFNT cfnt; CFNT cfnt;
memcpy(&cfnt, data, sizeof(cfnt)); memcpy(&cfnt, cfnt_ptr, sizeof(cfnt));
// Advance past the header u32 assumed_cmap_offset = 0;
data = shared_font->GetPointer(SharedFontStartOffset + cfnt.header_size); u32 assumed_cwdh_offset = 0;
u32 assumed_tglp_offset = 0;
u32 first_cmap_offset = 0;
u32 first_cwdh_offset = 0;
u32 first_tglp_offset = 0;
// First discover the location of sections so that the rebase offset can be auto-detected
u32 current_offset = SharedFontStartOffset + cfnt.header_size;
for (unsigned block = 0; block < cfnt.num_blocks; ++block) { for (unsigned block = 0; block < cfnt.num_blocks; ++block) {
const u8* data = shared_font->GetPointer(current_offset);
u32 section_size = 0; SectionHeader section_header;
if (memcmp(data, "FINF", 4) == 0) { memcpy(&section_header, data, sizeof(section_header));
if (first_cmap_offset == 0 && memcmp(section_header.magic, "CMAP", 4) == 0) {
first_cmap_offset = current_offset;
} else if (first_cwdh_offset == 0 && memcmp(section_header.magic, "CWDH", 4) == 0) {
first_cwdh_offset = current_offset;
} else if (first_tglp_offset == 0 && memcmp(section_header.magic, "TGLP", 4) == 0) {
first_tglp_offset = current_offset;
} else if (memcmp(section_header.magic, "FINF", 4) == 0) {
BCFNT::FINF finf;
memcpy(&finf, data, sizeof(finf));
assumed_cmap_offset = finf.cmap_offset - sizeof(SectionHeader);
assumed_cwdh_offset = finf.cwdh_offset - sizeof(SectionHeader);
assumed_tglp_offset = finf.tglp_offset - sizeof(SectionHeader);
}
current_offset += section_header.section_size;
}
u32 previous_base = assumed_cmap_offset - first_cmap_offset;
ASSERT(previous_base == assumed_cwdh_offset - first_cwdh_offset);
ASSERT(previous_base == assumed_tglp_offset - first_tglp_offset);
u32 offset = new_address - previous_base;
// Reset pointer back to start of sections and do the actual rebase
current_offset = SharedFontStartOffset + cfnt.header_size;
for (unsigned block = 0; block < cfnt.num_blocks; ++block) {
u8* data = shared_font->GetPointer(current_offset);
SectionHeader section_header;
memcpy(&section_header, data, sizeof(section_header));
if (memcmp(section_header.magic, "FINF", 4) == 0) {
BCFNT::FINF finf; BCFNT::FINF finf;
memcpy(&finf, data, sizeof(finf)); memcpy(&finf, data, sizeof(finf));
section_size = finf.section_size;
// Relocate the offsets in the FINF section // Relocate the offsets in the FINF section
finf.cmap_offset += new_address - previous_address; finf.cmap_offset += offset;
finf.cwdh_offset += new_address - previous_address; finf.cwdh_offset += offset;
finf.tglp_offset += new_address - previous_address; finf.tglp_offset += offset;
memcpy(data, &finf, sizeof(finf)); memcpy(data, &finf, sizeof(finf));
} else if (memcmp(data, "CMAP", 4) == 0) { } else if (memcmp(section_header.magic, "CMAP", 4) == 0) {
BCFNT::CMAP cmap; BCFNT::CMAP cmap;
memcpy(&cmap, data, sizeof(cmap)); memcpy(&cmap, data, sizeof(cmap));
section_size = cmap.section_size;
// Relocate the offsets in the CMAP section // Relocate the offsets in the CMAP section
cmap.next_cmap_offset += new_address - previous_address; cmap.next_cmap_offset += offset;
memcpy(data, &cmap, sizeof(cmap)); memcpy(data, &cmap, sizeof(cmap));
} else if (memcmp(data, "CWDH", 4) == 0) { } else if (memcmp(section_header.magic, "CWDH", 4) == 0) {
BCFNT::CWDH cwdh; BCFNT::CWDH cwdh;
memcpy(&cwdh, data, sizeof(cwdh)); memcpy(&cwdh, data, sizeof(cwdh));
section_size = cwdh.section_size;
// Relocate the offsets in the CWDH section // Relocate the offsets in the CWDH section
cwdh.next_cwdh_offset += new_address - previous_address; cwdh.next_cwdh_offset += offset;
memcpy(data, &cwdh, sizeof(cwdh)); memcpy(data, &cwdh, sizeof(cwdh));
} else if (memcmp(data, "TGLP", 4) == 0) { } else if (memcmp(section_header.magic, "TGLP", 4) == 0) {
BCFNT::TGLP tglp; BCFNT::TGLP tglp;
memcpy(&tglp, data, sizeof(tglp)); memcpy(&tglp, data, sizeof(tglp));
section_size = tglp.section_size;
// Relocate the offsets in the TGLP section // Relocate the offsets in the TGLP section
tglp.sheet_data_offset += new_address - previous_address; tglp.sheet_data_offset += offset;
memcpy(data, &tglp, sizeof(tglp)); memcpy(data, &tglp, sizeof(tglp));
} }
data += section_size; current_offset += section_header.section_size;
} }
} }

View File

@ -22,6 +22,11 @@ struct CFNT {
u32_le num_blocks; u32_le num_blocks;
}; };
struct SectionHeader {
u8 magic[4];
u32_le section_size;
};
struct FINF { struct FINF {
u8 magic[4]; u8 magic[4];
u32_le section_size; u32_le section_size;
@ -75,12 +80,13 @@ struct CWDH {
}; };
/** /**
* Relocates the internal addresses of the BCFNT Shared Font to the new base. * Relocates the internal addresses of the BCFNT Shared Font to the new base. The current base will
* be auto-detected based on the file headers.
*
* @param shared_font SharedMemory object that contains the Shared Font * @param shared_font SharedMemory object that contains the Shared Font
* @param previous_address Previous address at which the offsets in the structure were based.
* @param new_address New base for the offsets in the structure. * @param new_address New base for the offsets in the structure.
*/ */
void RelocateSharedFont(Kernel::SharedPtr<Kernel::SharedMemory> shared_font, VAddr previous_address, VAddr new_address); void RelocateSharedFont(Kernel::SharedPtr<Kernel::SharedMemory> shared_font, VAddr new_address);
} // namespace BCFNT } // namespace BCFNT
} // namespace APT } // namespace APT

View File

@ -51,7 +51,7 @@ void GetVsyncInterruptEvent(Service::Interface* self) {
cmd_buff[0] = IPC::MakeHeader(0x5, 1, 2); cmd_buff[0] = IPC::MakeHeader(0x5, 1, 2);
cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[1] = RESULT_SUCCESS.raw;
cmd_buff[2] = IPC::MoveHandleDesc(); cmd_buff[2] = IPC::CopyHandleDesc();
cmd_buff[3] = Kernel::g_handle_table.Create(vsync_interrupt_error_event).MoveFrom(); cmd_buff[3] = Kernel::g_handle_table.Create(vsync_interrupt_error_event).MoveFrom();
LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d", port); LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d", port);
@ -64,7 +64,7 @@ void GetBufferErrorInterruptEvent(Service::Interface* self) {
cmd_buff[0] = IPC::MakeHeader(0x6, 1, 2); cmd_buff[0] = IPC::MakeHeader(0x6, 1, 2);
cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[1] = RESULT_SUCCESS.raw;
cmd_buff[2] = IPC::MoveHandleDesc(); cmd_buff[2] = IPC::CopyHandleDesc();
cmd_buff[3] = Kernel::g_handle_table.Create(interrupt_error_event).MoveFrom(); cmd_buff[3] = Kernel::g_handle_table.Create(interrupt_error_event).MoveFrom();
LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d", port); LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d", port);
@ -85,7 +85,7 @@ void SetReceiving(Service::Interface* self) {
cmd_buff[0] = IPC::MakeHeader(0x7, 1, 2); cmd_buff[0] = IPC::MakeHeader(0x7, 1, 2);
cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[1] = RESULT_SUCCESS.raw;
cmd_buff[2] = IPC::MoveHandleDesc(); cmd_buff[2] = IPC::CopyHandleDesc();
cmd_buff[3] = Kernel::g_handle_table.Create(completion_event).MoveFrom(); cmd_buff[3] = Kernel::g_handle_table.Create(completion_event).MoveFrom();
LOG_WARNING(Service_CAM, "(STUBBED) called, addr=0x%X, port=%d, image_size=%d, trans_unit=%d", LOG_WARNING(Service_CAM, "(STUBBED) called, addr=0x%X, port=%d, image_size=%d, trans_unit=%d",

View File

@ -40,6 +40,20 @@ struct SaveFileConfig {
}; };
static_assert(sizeof(SaveFileConfig) == 0x455C, "SaveFileConfig header must be exactly 0x455C bytes"); static_assert(sizeof(SaveFileConfig) == 0x455C, "SaveFileConfig header must be exactly 0x455C bytes");
enum ConfigBlockID {
StereoCameraSettingsBlockID = 0x00050005,
SoundOutputModeBlockID = 0x00070001,
ConsoleUniqueIDBlockID = 0x00090001,
UsernameBlockID = 0x000A0000,
BirthdayBlockID = 0x000A0001,
LanguageBlockID = 0x000A0002,
CountryInfoBlockID = 0x000B0000,
CountryNameBlockID = 0x000B0001,
StateNameBlockID = 0x000B0002,
EULAVersionBlockID = 0x000D0000,
ConsoleModelBlockID = 0x000F0004,
};
struct UsernameBlock { struct UsernameBlock {
char16_t username[10]; ///< Exactly 20 bytes long, padded with zeros at the end if necessary char16_t username[10]; ///< Exactly 20 bytes long, padded with zeros at the end if necessary
u32 zero; u32 zero;
@ -73,8 +87,7 @@ static const ConsoleModelInfo CONSOLE_MODEL = { NINTENDO_3DS_XL, { 0, 0, 0 } };
static const u8 CONSOLE_LANGUAGE = LANGUAGE_EN; static const u8 CONSOLE_LANGUAGE = LANGUAGE_EN;
static const UsernameBlock CONSOLE_USERNAME_BLOCK = { u"CITRA", 0, 0 }; static const UsernameBlock CONSOLE_USERNAME_BLOCK = { u"CITRA", 0, 0 };
static const BirthdayBlock PROFILE_BIRTHDAY = { 3, 25 }; // March 25th, 2014 static const BirthdayBlock PROFILE_BIRTHDAY = { 3, 25 }; // March 25th, 2014
/// TODO(Subv): Find out what this actually is static const u8 SOUND_OUTPUT_MODE = SOUND_SURROUND;
static const u8 SOUND_OUTPUT_MODE = 2;
static const u8 UNITED_STATES_COUNTRY_ID = 49; static const u8 UNITED_STATES_COUNTRY_ID = 49;
/// TODO(Subv): Find what the other bytes are /// TODO(Subv): Find what the other bytes are
static const ConsoleCountryInfo COUNTRY_INFO = { { 0, 0, 0 }, UNITED_STATES_COUNTRY_ID }; static const ConsoleCountryInfo COUNTRY_INFO = { { 0, 0, 0 }, UNITED_STATES_COUNTRY_ID };
@ -224,6 +237,22 @@ void GetConfigInfoBlk8(Service::Interface* self) {
Memory::WriteBlock(data_pointer, data.data(), data.size()); Memory::WriteBlock(data_pointer, data.data(), data.size());
} }
void SetConfigInfoBlk4(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
u32 block_id = cmd_buff[1];
u32 size = cmd_buff[2];
VAddr data_pointer = cmd_buff[4];
if (!Memory::IsValidVirtualAddress(data_pointer)) {
cmd_buff[1] = -1; // TODO(Subv): Find the right error code
return;
}
std::vector<u8> data(size);
Memory::ReadBlock(data_pointer, data.data(), data.size());
cmd_buff[1] = Service::CFG::SetConfigInfoBlock(block_id, size, 0x4, data.data()).raw;
}
void UpdateConfigNANDSavegame(Service::Interface* self) { void UpdateConfigNANDSavegame(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); u32* cmd_buff = Kernel::GetCommandBuffer();
cmd_buff[1] = Service::CFG::UpdateConfigNANDSavegame().raw; cmd_buff[1] = Service::CFG::UpdateConfigNANDSavegame().raw;
@ -234,13 +263,13 @@ void FormatConfig(Service::Interface* self) {
cmd_buff[1] = Service::CFG::FormatConfig().raw; cmd_buff[1] = Service::CFG::FormatConfig().raw;
} }
ResultCode GetConfigInfoBlock(u32 block_id, u32 size, u32 flag, u8* output) { static ResultVal<void*> GetConfigInfoBlockPointer(u32 block_id, u32 size, u32 flag) {
// Read the header // Read the header
SaveFileConfig* config = reinterpret_cast<SaveFileConfig*>(cfg_config_file_buffer.data()); SaveFileConfig* config = reinterpret_cast<SaveFileConfig*>(cfg_config_file_buffer.data());
auto itr = std::find_if(std::begin(config->block_entries), std::end(config->block_entries), auto itr = std::find_if(std::begin(config->block_entries), std::end(config->block_entries),
[&](const SaveConfigBlockEntry& entry) { [&](const SaveConfigBlockEntry& entry) {
return entry.block_id == block_id && (entry.flags & flag); return entry.block_id == block_id;
}); });
if (itr == std::end(config->block_entries)) { if (itr == std::end(config->block_entries)) {
@ -248,17 +277,38 @@ ResultCode GetConfigInfoBlock(u32 block_id, u32 size, u32 flag, u8* output) {
return ResultCode(ErrorDescription::NotFound, ErrorModule::Config, ErrorSummary::WrongArgument, ErrorLevel::Permanent); return ResultCode(ErrorDescription::NotFound, ErrorModule::Config, ErrorSummary::WrongArgument, ErrorLevel::Permanent);
} }
if ((itr->flags & flag) == 0) {
LOG_ERROR(Service_CFG, "Invalid flag %u for config block 0x%X with size %u", flag, block_id, size);
return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::Config, ErrorSummary::WrongArgument, ErrorLevel::Permanent);
}
if (itr->size != size) { if (itr->size != size) {
LOG_ERROR(Service_CFG, "Invalid size %u for config block 0x%X with flags %u", size, block_id, flag); LOG_ERROR(Service_CFG, "Invalid size %u for config block 0x%X with flags %u", size, block_id, flag);
return ResultCode(ErrorDescription::InvalidSize, ErrorModule::Config, ErrorSummary::WrongArgument, ErrorLevel::Permanent); return ResultCode(ErrorDescription::InvalidSize, ErrorModule::Config, ErrorSummary::WrongArgument, ErrorLevel::Permanent);
} }
void* pointer;
// The data is located in the block header itself if the size is less than 4 bytes // The data is located in the block header itself if the size is less than 4 bytes
if (itr->size <= 4) if (itr->size <= 4)
memcpy(output, &itr->offset_or_data, itr->size); pointer = &itr->offset_or_data;
else else
memcpy(output, &cfg_config_file_buffer[itr->offset_or_data], itr->size); pointer = &cfg_config_file_buffer[itr->offset_or_data];
return MakeResult<void*>(pointer);
}
ResultCode GetConfigInfoBlock(u32 block_id, u32 size, u32 flag, void* output) {
void* pointer;
CASCADE_RESULT(pointer, GetConfigInfoBlockPointer(block_id, size, flag));
memcpy(output, pointer, size);
return RESULT_SUCCESS;
}
ResultCode SetConfigInfoBlock(u32 block_id, u32 size, u32 flag, const void* input) {
void* pointer;
CASCADE_RESULT(pointer, GetConfigInfoBlockPointer(block_id, size, flag));
memcpy(pointer, input, size);
return RESULT_SUCCESS; return RESULT_SUCCESS;
} }
@ -336,25 +386,25 @@ ResultCode FormatConfig() {
res = CreateConfigInfoBlk(0x00030001, 0x8, 0xE, zero_buffer); res = CreateConfigInfoBlk(0x00030001, 0x8, 0xE, zero_buffer);
if (!res.IsSuccess()) return res; if (!res.IsSuccess()) return res;
res = CreateConfigInfoBlk(0x00050005, sizeof(STEREO_CAMERA_SETTINGS), 0xE, STEREO_CAMERA_SETTINGS.data()); res = CreateConfigInfoBlk(StereoCameraSettingsBlockID, sizeof(STEREO_CAMERA_SETTINGS), 0xE, STEREO_CAMERA_SETTINGS.data());
if (!res.IsSuccess()) return res; if (!res.IsSuccess()) return res;
res = CreateConfigInfoBlk(0x00070001, sizeof(SOUND_OUTPUT_MODE), 0xE, &SOUND_OUTPUT_MODE); res = CreateConfigInfoBlk(SoundOutputModeBlockID, sizeof(SOUND_OUTPUT_MODE), 0xE, &SOUND_OUTPUT_MODE);
if (!res.IsSuccess()) return res; if (!res.IsSuccess()) return res;
res = CreateConfigInfoBlk(0x00090001, sizeof(CONSOLE_UNIQUE_ID), 0xE, &CONSOLE_UNIQUE_ID); res = CreateConfigInfoBlk(ConsoleUniqueIDBlockID, sizeof(CONSOLE_UNIQUE_ID), 0xE, &CONSOLE_UNIQUE_ID);
if (!res.IsSuccess()) return res; if (!res.IsSuccess()) return res;
res = CreateConfigInfoBlk(0x000A0000, sizeof(CONSOLE_USERNAME_BLOCK), 0xE, &CONSOLE_USERNAME_BLOCK); res = CreateConfigInfoBlk(UsernameBlockID, sizeof(CONSOLE_USERNAME_BLOCK), 0xE, &CONSOLE_USERNAME_BLOCK);
if (!res.IsSuccess()) return res; if (!res.IsSuccess()) return res;
res = CreateConfigInfoBlk(0x000A0001, sizeof(PROFILE_BIRTHDAY), 0xE, &PROFILE_BIRTHDAY); res = CreateConfigInfoBlk(BirthdayBlockID, sizeof(PROFILE_BIRTHDAY), 0xE, &PROFILE_BIRTHDAY);
if (!res.IsSuccess()) return res; if (!res.IsSuccess()) return res;
res = CreateConfigInfoBlk(0x000A0002, sizeof(CONSOLE_LANGUAGE), 0xE, &CONSOLE_LANGUAGE); res = CreateConfigInfoBlk(LanguageBlockID, sizeof(CONSOLE_LANGUAGE), 0xE, &CONSOLE_LANGUAGE);
if (!res.IsSuccess()) return res; if (!res.IsSuccess()) return res;
res = CreateConfigInfoBlk(0x000B0000, sizeof(COUNTRY_INFO), 0xE, &COUNTRY_INFO); res = CreateConfigInfoBlk(CountryInfoBlockID, sizeof(COUNTRY_INFO), 0xE, &COUNTRY_INFO);
if (!res.IsSuccess()) return res; if (!res.IsSuccess()) return res;
u16_le country_name_buffer[16][0x40] = {}; u16_le country_name_buffer[16][0x40] = {};
@ -363,10 +413,10 @@ ResultCode FormatConfig() {
std::copy(region_name.cbegin(), region_name.cend(), country_name_buffer[i]); std::copy(region_name.cbegin(), region_name.cend(), country_name_buffer[i]);
} }
// 0x000B0001 - Localized names for the profile Country // 0x000B0001 - Localized names for the profile Country
res = CreateConfigInfoBlk(0x000B0001, sizeof(country_name_buffer), 0xE, country_name_buffer); res = CreateConfigInfoBlk(CountryNameBlockID, sizeof(country_name_buffer), 0xE, country_name_buffer);
if (!res.IsSuccess()) return res; if (!res.IsSuccess()) return res;
// 0x000B0002 - Localized names for the profile State/Province // 0x000B0002 - Localized names for the profile State/Province
res = CreateConfigInfoBlk(0x000B0002, sizeof(country_name_buffer), 0xE, country_name_buffer); res = CreateConfigInfoBlk(StateNameBlockID, sizeof(country_name_buffer), 0xE, country_name_buffer);
if (!res.IsSuccess()) return res; if (!res.IsSuccess()) return res;
// 0x000B0003 - Unknown, related to country/address (zip code?) // 0x000B0003 - Unknown, related to country/address (zip code?)
@ -382,10 +432,10 @@ ResultCode FormatConfig() {
if (!res.IsSuccess()) return res; if (!res.IsSuccess()) return res;
// 0x000D0000 - Accepted EULA version // 0x000D0000 - Accepted EULA version
res = CreateConfigInfoBlk(0x000D0000, 0x4, 0xE, zero_buffer); res = CreateConfigInfoBlk(EULAVersionBlockID, 0x4, 0xE, zero_buffer);
if (!res.IsSuccess()) return res; if (!res.IsSuccess()) return res;
res = CreateConfigInfoBlk(0x000F0004, sizeof(CONSOLE_MODEL), 0xC, &CONSOLE_MODEL); res = CreateConfigInfoBlk(ConsoleModelBlockID, sizeof(CONSOLE_MODEL), 0xC, &CONSOLE_MODEL);
if (!res.IsSuccess()) return res; if (!res.IsSuccess()) return res;
// 0x00170000 - Unknown // 0x00170000 - Unknown
@ -399,11 +449,7 @@ ResultCode FormatConfig() {
return RESULT_SUCCESS; return RESULT_SUCCESS;
} }
void Init() { ResultCode LoadConfigNANDSaveFile() {
AddService(new CFG_I_Interface);
AddService(new CFG_S_Interface);
AddService(new CFG_U_Interface);
// Open the SystemSaveData archive 0x00010017 // Open the SystemSaveData archive 0x00010017
FileSys::Path archive_path(cfg_system_savedata_id); FileSys::Path archive_path(cfg_system_savedata_id);
auto archive_result = Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SystemSaveData, archive_path); auto archive_result = Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SystemSaveData, archive_path);
@ -431,14 +477,75 @@ void Init() {
if (config_result.Succeeded()) { if (config_result.Succeeded()) {
auto config = config_result.MoveFrom(); auto config = config_result.MoveFrom();
config->backend->Read(0, CONFIG_SAVEFILE_SIZE, cfg_config_file_buffer.data()); config->backend->Read(0, CONFIG_SAVEFILE_SIZE, cfg_config_file_buffer.data());
return; return RESULT_SUCCESS;
} }
FormatConfig(); return FormatConfig();
}
void Init() {
AddService(new CFG_I_Interface);
AddService(new CFG_S_Interface);
AddService(new CFG_U_Interface);
LoadConfigNANDSaveFile();
} }
void Shutdown() { void Shutdown() {
} }
void SetUsername(const std::u16string& name) {
ASSERT(name.size() <= 10);
UsernameBlock block{};
name.copy(block.username, name.size());
SetConfigInfoBlock(UsernameBlockID, sizeof(block), 4, &block);
}
std::u16string GetUsername() {
UsernameBlock block;
GetConfigInfoBlock(UsernameBlockID, sizeof(block), 8, &block);
// the username string in the block isn't null-terminated,
// so we need to find the end manually.
std::u16string username(block.username, ARRAY_SIZE(block.username));
const size_t pos = username.find(u'\0');
if (pos != std::u16string::npos)
username.erase(pos);
return username;
}
void SetBirthday(u8 month, u8 day) {
BirthdayBlock block = { month, day };
SetConfigInfoBlock(BirthdayBlockID, sizeof(block), 4, &block);
}
std::tuple<u8, u8> GetBirthday() {
BirthdayBlock block;
GetConfigInfoBlock(BirthdayBlockID, sizeof(block), 8, &block);
return std::make_tuple(block.month, block.day);
}
void SetSystemLanguage(SystemLanguage language) {
u8 block = language;
SetConfigInfoBlock(LanguageBlockID, sizeof(block), 4, &block);
}
SystemLanguage GetSystemLanguage() {
u8 block;
GetConfigInfoBlock(LanguageBlockID, sizeof(block), 8, &block);
return static_cast<SystemLanguage>(block);
}
void SetSoundOutputMode(SoundOutputMode mode) {
u8 block = mode;
SetConfigInfoBlock(SoundOutputModeBlockID, sizeof(block), 4, &block);
}
SoundOutputMode GetSoundOutputMode() {
u8 block;
GetConfigInfoBlock(SoundOutputModeBlockID, sizeof(block), 8, &block);
return static_cast<SoundOutputMode>(block);
}
} // namespace CFG } // namespace CFG
} // namespace Service } // namespace Service

View File

@ -5,6 +5,7 @@
#pragma once #pragma once
#include <array> #include <array>
#include <string>
#include "common/common_types.h" #include "common/common_types.h"
@ -35,7 +36,14 @@ enum SystemLanguage {
LANGUAGE_KO = 7, LANGUAGE_KO = 7,
LANGUAGE_NL = 8, LANGUAGE_NL = 8,
LANGUAGE_PT = 9, LANGUAGE_PT = 9,
LANGUAGE_RU = 10 LANGUAGE_RU = 10,
LANGUAGE_TW = 11
};
enum SoundOutputMode {
SOUND_MONO = 0,
SOUND_STEREO = 1,
SOUND_SURROUND = 2
}; };
/// Block header in the config savedata file /// Block header in the config savedata file
@ -177,6 +185,22 @@ void GetConfigInfoBlk2(Service::Interface* self);
*/ */
void GetConfigInfoBlk8(Service::Interface* self); void GetConfigInfoBlk8(Service::Interface* self);
/**
* CFG::SetConfigInfoBlk4 service function
* Inputs:
* 0 : 0x04020082 / 0x08020082
* 1 : Block ID
* 2 : Size
* 3 : Descriptor for the output buffer
* 4 : Output buffer pointer
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* Note:
* The parameters order is different from GetConfigInfoBlk2/8's,
* where Block ID and Size are switched.
*/
void SetConfigInfoBlk4(Service::Interface* self);
/** /**
* CFG::UpdateConfigNANDSavegame service function * CFG::UpdateConfigNANDSavegame service function
* Inputs: * Inputs:
@ -205,7 +229,19 @@ void FormatConfig(Service::Interface* self);
* @param output A pointer where we will write the read data * @param output A pointer where we will write the read data
* @returns ResultCode indicating the result of the operation, 0 on success * @returns ResultCode indicating the result of the operation, 0 on success
*/ */
ResultCode GetConfigInfoBlock(u32 block_id, u32 size, u32 flag, u8* output); ResultCode GetConfigInfoBlock(u32 block_id, u32 size, u32 flag, void* output);
/**
* Reads data from input and writes to a block with the specified id and flag
* in the Config savegame buffer.
* The input size must match exactly the size of the target block
* @param block_id The id of the block we want to write
* @param size The size of the block we want to write
* @param flag The target block must have this flag set
* @param input A pointer where we will read data and write to Config savegame buffer
* @returns ResultCode indicating the result of the operation, 0 on success
*/
ResultCode SetConfigInfoBlock(u32 block_id, u32 size, u32 flag, const void* input);
/** /**
* Creates a block with the specified id and writes the input data to the cfg savegame buffer in memory. * Creates a block with the specified id and writes the input data to the cfg savegame buffer in memory.
@ -236,11 +272,70 @@ ResultCode UpdateConfigNANDSavegame();
*/ */
ResultCode FormatConfig(); ResultCode FormatConfig();
/**
* Open the config savegame file and load it to the memory buffer
* @returns ResultCode indicating the result of the operation, 0 on success
*/
ResultCode LoadConfigNANDSaveFile();
/// Initialize the config service /// Initialize the config service
void Init(); void Init();
/// Shutdown the config service /// Shutdown the config service
void Shutdown(); void Shutdown();
// Utilities for frontend to set config data.
// Note: before calling these functions, LoadConfigNANDSaveFile should be called,
// and UpdateConfigNANDSavegame should be called after making changes to config data.
/**
* Sets the username in config savegame.
* @param name the username to set. The maximum size is 10 in char16_t.
*/
void SetUsername(const std::u16string& name);
/**
* Gets the username from config savegame.
* @returns the username
*/
std::u16string GetUsername();
/**
* Sets the profile birthday in config savegame.
* @param month the month of birthday.
* @param day the day of the birthday.
*/
void SetBirthday(u8 month, u8 day);
/**
* Gets the profile birthday from the config savegame.
* @returns a tuple of (month, day) of birthday
*/
std::tuple<u8, u8> GetBirthday();
/**
* Sets the system language in config savegame.
* @param language the system language to set.
*/
void SetSystemLanguage(SystemLanguage language);
/**
* Gets the system language from config savegame.
* @returns the system language
*/
SystemLanguage GetSystemLanguage();
/**
* Sets the sound output mode in config savegame.
* @param mode the sound output mode to set
*/
void SetSoundOutputMode(SoundOutputMode mode);
/**
* Gets the sound output mode from config savegame.
* @returns the sound output mode
*/
SoundOutputMode GetSoundOutputMode();
} // namespace CFG } // namespace CFG
} // namespace Service } // namespace Service

View File

@ -22,7 +22,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x000A0040, GetCountryCodeID, "GetCountryCodeID"}, {0x000A0040, GetCountryCodeID, "GetCountryCodeID"},
// cfg:i // cfg:i
{0x04010082, GetConfigInfoBlk8, "GetConfigInfoBlk8"}, {0x04010082, GetConfigInfoBlk8, "GetConfigInfoBlk8"},
{0x04020082, nullptr, "SetConfigInfoBlk4"}, {0x04020082, SetConfigInfoBlk4, "SetConfigInfoBlk4"},
{0x04030000, UpdateConfigNANDSavegame, "UpdateConfigNANDSavegame"}, {0x04030000, UpdateConfigNANDSavegame, "UpdateConfigNANDSavegame"},
{0x04040042, nullptr, "GetLocalFriendCodeSeedData"}, {0x04040042, nullptr, "GetLocalFriendCodeSeedData"},
{0x04050000, nullptr, "GetLocalFriendCodeSeed"}, {0x04050000, nullptr, "GetLocalFriendCodeSeed"},
@ -31,7 +31,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x04080042, nullptr, "SecureInfoGetSerialNo"}, {0x04080042, nullptr, "SecureInfoGetSerialNo"},
{0x04090000, nullptr, "UpdateConfigBlk00040003"}, {0x04090000, nullptr, "UpdateConfigBlk00040003"},
{0x08010082, GetConfigInfoBlk8, "GetConfigInfoBlk8"}, {0x08010082, GetConfigInfoBlk8, "GetConfigInfoBlk8"},
{0x08020082, nullptr, "SetConfigInfoBlk4"}, {0x08020082, SetConfigInfoBlk4, "SetConfigInfoBlk4"},
{0x08030000, UpdateConfigNANDSavegame, "UpdateConfigNANDSavegame"}, {0x08030000, UpdateConfigNANDSavegame, "UpdateConfigNANDSavegame"},
{0x080400C2, nullptr, "CreateConfigInfoBlk"}, {0x080400C2, nullptr, "CreateConfigInfoBlk"},
{0x08050000, nullptr, "DeleteConfigNANDSavefile"}, {0x08050000, nullptr, "DeleteConfigNANDSavefile"},

View File

@ -22,7 +22,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x000A0040, GetCountryCodeID, "GetCountryCodeID"}, {0x000A0040, GetCountryCodeID, "GetCountryCodeID"},
// cfg:s // cfg:s
{0x04010082, GetConfigInfoBlk8, "GetConfigInfoBlk8"}, {0x04010082, GetConfigInfoBlk8, "GetConfigInfoBlk8"},
{0x04020082, nullptr, "SetConfigInfoBlk4"}, {0x04020082, SetConfigInfoBlk4, "SetConfigInfoBlk4"},
{0x04030000, UpdateConfigNANDSavegame, "UpdateConfigNANDSavegame"}, {0x04030000, UpdateConfigNANDSavegame, "UpdateConfigNANDSavegame"},
{0x04040042, nullptr, "GetLocalFriendCodeSeedData"}, {0x04040042, nullptr, "GetLocalFriendCodeSeedData"},
{0x04050000, nullptr, "GetLocalFriendCodeSeed"}, {0x04050000, nullptr, "GetLocalFriendCodeSeed"},

View File

@ -51,7 +51,7 @@ void Initialize(Service::Interface* self) {
mutex = Kernel::Mutex::Create(false); mutex = Kernel::Mutex::Create(false);
cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[1] = RESULT_SUCCESS.raw;
cmd_buff[2] = IPC::MoveHandleDesc(2); cmd_buff[2] = IPC::CopyHandleDesc(2);
cmd_buff[3] = Kernel::g_handle_table.Create(mutex).MoveFrom(); cmd_buff[3] = Kernel::g_handle_table.Create(mutex).MoveFrom();
cmd_buff[4] = Kernel::g_handle_table.Create(shared_memory).MoveFrom(); cmd_buff[4] = Kernel::g_handle_table.Create(shared_memory).MoveFrom();
} }

View File

@ -58,6 +58,10 @@ namespace FS {
const ResultCode ERR_INVALID_HANDLE(ErrorDescription::InvalidHandle, ErrorModule::FS, const ResultCode ERR_INVALID_HANDLE(ErrorDescription::InvalidHandle, ErrorModule::FS,
ErrorSummary::InvalidArgument, ErrorLevel::Permanent); ErrorSummary::InvalidArgument, ErrorLevel::Permanent);
/// Returned when a function is passed an invalid archive handle.
const ResultCode ERR_INVALID_ARCHIVE_HANDLE(ErrorDescription::FS_ArchiveNotMounted, ErrorModule::FS,
ErrorSummary::NotFound, ErrorLevel::Status); // 0xC8804465
// Command to access archive file // Command to access archive file
enum class FileCommand : u32 { enum class FileCommand : u32 {
Dummy1 = 0x000100C6, Dummy1 = 0x000100C6,
@ -255,7 +259,7 @@ using FileSys::ArchiveFactory;
/** /**
* Map of registered archives, identified by id code. Once an archive is registered here, it is * Map of registered archives, identified by id code. Once an archive is registered here, it is
* never removed until the FS service is shut down. * never removed until UnregisterArchiveTypes is called.
*/ */
static boost::container::flat_map<ArchiveIdCode, std::unique_ptr<ArchiveFactory>> id_code_map; static boost::container::flat_map<ArchiveIdCode, std::unique_ptr<ArchiveFactory>> id_code_map;
@ -292,7 +296,7 @@ ResultVal<ArchiveHandle> OpenArchive(ArchiveIdCode id_code, FileSys::Path& archi
ResultCode CloseArchive(ArchiveHandle handle) { ResultCode CloseArchive(ArchiveHandle handle) {
if (handle_map.erase(handle) == 0) if (handle_map.erase(handle) == 0)
return ERR_INVALID_HANDLE; return ERR_INVALID_ARCHIVE_HANDLE;
else else
return RESULT_SUCCESS; return RESULT_SUCCESS;
} }
@ -314,7 +318,7 @@ ResultVal<Kernel::SharedPtr<File>> OpenFileFromArchive(ArchiveHandle archive_han
const FileSys::Path& path, const FileSys::Mode mode) { const FileSys::Path& path, const FileSys::Mode mode) {
ArchiveBackend* archive = GetArchive(archive_handle); ArchiveBackend* archive = GetArchive(archive_handle);
if (archive == nullptr) if (archive == nullptr)
return ERR_INVALID_HANDLE; return ERR_INVALID_ARCHIVE_HANDLE;
auto backend = archive->OpenFile(path, mode); auto backend = archive->OpenFile(path, mode);
if (backend.Failed()) if (backend.Failed())
@ -327,7 +331,7 @@ ResultVal<Kernel::SharedPtr<File>> OpenFileFromArchive(ArchiveHandle archive_han
ResultCode DeleteFileFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) { ResultCode DeleteFileFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) {
ArchiveBackend* archive = GetArchive(archive_handle); ArchiveBackend* archive = GetArchive(archive_handle);
if (archive == nullptr) if (archive == nullptr)
return ERR_INVALID_HANDLE; return ERR_INVALID_ARCHIVE_HANDLE;
return archive->DeleteFile(path); return archive->DeleteFile(path);
} }
@ -337,7 +341,7 @@ ResultCode RenameFileBetweenArchives(ArchiveHandle src_archive_handle, const Fil
ArchiveBackend* src_archive = GetArchive(src_archive_handle); ArchiveBackend* src_archive = GetArchive(src_archive_handle);
ArchiveBackend* dest_archive = GetArchive(dest_archive_handle); ArchiveBackend* dest_archive = GetArchive(dest_archive_handle);
if (src_archive == nullptr || dest_archive == nullptr) if (src_archive == nullptr || dest_archive == nullptr)
return ERR_INVALID_HANDLE; return ERR_INVALID_ARCHIVE_HANDLE;
if (src_archive == dest_archive) { if (src_archive == dest_archive) {
if (src_archive->RenameFile(src_path, dest_path)) if (src_archive->RenameFile(src_path, dest_path))
@ -356,7 +360,7 @@ ResultCode RenameFileBetweenArchives(ArchiveHandle src_archive_handle, const Fil
ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) { ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) {
ArchiveBackend* archive = GetArchive(archive_handle); ArchiveBackend* archive = GetArchive(archive_handle);
if (archive == nullptr) if (archive == nullptr)
return ERR_INVALID_HANDLE; return ERR_INVALID_ARCHIVE_HANDLE;
if (archive->DeleteDirectory(path)) if (archive->DeleteDirectory(path))
return RESULT_SUCCESS; return RESULT_SUCCESS;
@ -367,7 +371,7 @@ ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSy
ResultCode CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path& path, u64 file_size) { ResultCode CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path& path, u64 file_size) {
ArchiveBackend* archive = GetArchive(archive_handle); ArchiveBackend* archive = GetArchive(archive_handle);
if (archive == nullptr) if (archive == nullptr)
return ERR_INVALID_HANDLE; return ERR_INVALID_ARCHIVE_HANDLE;
return archive->CreateFile(path, file_size); return archive->CreateFile(path, file_size);
} }
@ -375,7 +379,7 @@ ResultCode CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path
ResultCode CreateDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) { ResultCode CreateDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) {
ArchiveBackend* archive = GetArchive(archive_handle); ArchiveBackend* archive = GetArchive(archive_handle);
if (archive == nullptr) if (archive == nullptr)
return ERR_INVALID_HANDLE; return ERR_INVALID_ARCHIVE_HANDLE;
if (archive->CreateDirectory(path)) if (archive->CreateDirectory(path))
return RESULT_SUCCESS; return RESULT_SUCCESS;
@ -388,7 +392,7 @@ ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle, cons
ArchiveBackend* src_archive = GetArchive(src_archive_handle); ArchiveBackend* src_archive = GetArchive(src_archive_handle);
ArchiveBackend* dest_archive = GetArchive(dest_archive_handle); ArchiveBackend* dest_archive = GetArchive(dest_archive_handle);
if (src_archive == nullptr || dest_archive == nullptr) if (src_archive == nullptr || dest_archive == nullptr)
return ERR_INVALID_HANDLE; return ERR_INVALID_ARCHIVE_HANDLE;
if (src_archive == dest_archive) { if (src_archive == dest_archive) {
if (src_archive->RenameDirectory(src_path, dest_path)) if (src_archive->RenameDirectory(src_path, dest_path))
@ -408,7 +412,7 @@ ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle a
const FileSys::Path& path) { const FileSys::Path& path) {
ArchiveBackend* archive = GetArchive(archive_handle); ArchiveBackend* archive = GetArchive(archive_handle);
if (archive == nullptr) if (archive == nullptr)
return ERR_INVALID_HANDLE; return ERR_INVALID_ARCHIVE_HANDLE;
std::unique_ptr<FileSys::DirectoryBackend> backend = archive->OpenDirectory(path); std::unique_ptr<FileSys::DirectoryBackend> backend = archive->OpenDirectory(path);
if (backend == nullptr) { if (backend == nullptr) {
@ -423,7 +427,7 @@ ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle a
ResultVal<u64> GetFreeBytesInArchive(ArchiveHandle archive_handle) { ResultVal<u64> GetFreeBytesInArchive(ArchiveHandle archive_handle) {
ArchiveBackend* archive = GetArchive(archive_handle); ArchiveBackend* archive = GetArchive(archive_handle);
if (archive == nullptr) if (archive == nullptr)
return ERR_INVALID_HANDLE; return ERR_INVALID_ARCHIVE_HANDLE;
return MakeResult<u64>(archive->GetFreeBytes()); return MakeResult<u64>(archive->GetFreeBytes());
} }
@ -516,12 +520,7 @@ ResultCode CreateSystemSaveData(u32 high, u32 low) {
return RESULT_SUCCESS; return RESULT_SUCCESS;
} }
/// Initialize archives void RegisterArchiveTypes() {
void ArchiveInit() {
next_handle = 1;
AddService(new FS::Interface);
// TODO(Subv): Add the other archive types (see here for the known types: // TODO(Subv): Add the other archive types (see here for the known types:
// http://3dbrew.org/wiki/FS:OpenArchive#Archive_idcodes). // http://3dbrew.org/wiki/FS:OpenArchive#Archive_idcodes).
@ -558,10 +557,23 @@ void ArchiveInit() {
RegisterArchiveType(std::move(systemsavedata_factory), ArchiveIdCode::SystemSaveData); RegisterArchiveType(std::move(systemsavedata_factory), ArchiveIdCode::SystemSaveData);
} }
void UnregisterArchiveTypes() {
id_code_map.clear();
}
/// Initialize archives
void ArchiveInit() {
next_handle = 1;
AddService(new FS::Interface);
RegisterArchiveTypes();
}
/// Shutdown archives /// Shutdown archives
void ArchiveShutdown() { void ArchiveShutdown() {
handle_map.clear(); handle_map.clear();
id_code_map.clear(); UnregisterArchiveTypes();
} }
} // namespace FS } // namespace FS

View File

@ -235,5 +235,11 @@ void ArchiveInit();
/// Shutdown archives /// Shutdown archives
void ArchiveShutdown(); void ArchiveShutdown();
/// Register all archive types
void RegisterArchiveTypes();
/// Unregister all archive types
void UnregisterArchiveTypes();
} // namespace FS } // namespace FS
} // namespace Service } // namespace Service

View File

@ -645,20 +645,19 @@ static void DeleteSystemSaveData(Service::Interface* self) {
* FS_User::CreateSystemSaveData service function. * FS_User::CreateSystemSaveData service function.
* Inputs: * Inputs:
* 0 : 0x08560240 * 0 : 0x08560240
* 1 : High word of the SystemSaveData id to create * 1 : u8 MediaType of the system save data
* 2 : Low word of the SystemSaveData id to create * 2 : SystemSaveData id to create
* 3 : Unknown * 3 : Total size
* 4 : Unknown * 4 : Block size
* 5 : Unknown * 5 : Number of directories
* 6 : Unknown * 6 : Number of files
* 7 : Unknown * 7 : Directory bucket count
* 8 : Unknown * 8 : File bucket count
* 9 : Unknown (Memory address) * 9 : u8 Whether to duplicate data or not
* Outputs: * Outputs:
* 1 : Result of function, 0 on success, otherwise error code * 1 : Result of function, 0 on success, otherwise error code
*/ */
static void CreateSystemSaveData(Service::Interface* self) { static void CreateSystemSaveData(Service::Interface* self) {
// TODO(Subv): Figure out the other parameters.
u32* cmd_buff = Kernel::GetCommandBuffer(); u32* cmd_buff = Kernel::GetCommandBuffer();
u32 savedata_high = cmd_buff[1]; u32 savedata_high = cmd_buff[1];
u32 savedata_low = cmd_buff[2]; u32 savedata_low = cmd_buff[2];
@ -671,6 +670,38 @@ static void CreateSystemSaveData(Service::Interface* self) {
cmd_buff[1] = CreateSystemSaveData(savedata_high, savedata_low).raw; cmd_buff[1] = CreateSystemSaveData(savedata_high, savedata_low).raw;
} }
/**
* FS_User::CreateLegacySystemSaveData service function.
* This function appears to be obsolete and seems to have been replaced by
* command 0x08560240 (CreateSystemSaveData).
*
* Inputs:
* 0 : 0x08100200
* 1 : SystemSaveData id to create
* 2 : Total size
* 3 : Block size
* 4 : Number of directories
* 5 : Number of files
* 6 : Directory bucket count
* 7 : File bucket count
* 8 : u8 Duplicate data
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
static void CreateLegacySystemSaveData(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
u32 savedata_id = cmd_buff[1];
LOG_WARNING(Service_FS, "(STUBBED) savedata_id=%08X cmd_buff[3]=%08X "
"cmd_buff[4]=%08X cmd_buff[5]=%08X cmd_buff[6]=%08X cmd_buff[7]=%08X cmd_buff[8]=%08X "
"cmd_buff[9]=%08X", savedata_id, cmd_buff[3], cmd_buff[4], cmd_buff[5],
cmd_buff[6], cmd_buff[7], cmd_buff[8], cmd_buff[9]);
cmd_buff[0] = IPC::MakeHeader(0x810, 0x1, 0);
// With this command, the SystemSaveData always has save_high = 0 (Always created in the NAND)
cmd_buff[1] = CreateSystemSaveData(0, savedata_id).raw;
}
/** /**
* FS_User::InitializeWithSdkVersion service function. * FS_User::InitializeWithSdkVersion service function.
* Inputs: * Inputs:
@ -820,7 +851,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x080D0144, nullptr, "ControlArchive"}, {0x080D0144, nullptr, "ControlArchive"},
{0x080E0080, CloseArchive, "CloseArchive"}, {0x080E0080, CloseArchive, "CloseArchive"},
{0x080F0180, FormatThisUserSaveData, "FormatThisUserSaveData"}, {0x080F0180, FormatThisUserSaveData, "FormatThisUserSaveData"},
{0x08100200, nullptr, "CreateSystemSaveData"}, {0x08100200, CreateLegacySystemSaveData, "CreateLegacySystemSaveData"},
{0x08110040, nullptr, "DeleteSystemSaveData"}, {0x08110040, nullptr, "DeleteSystemSaveData"},
{0x08120080, GetFreeBytes, "GetFreeBytes"}, {0x08120080, GetFreeBytes, "GetFreeBytes"},
{0x08130000, nullptr, "GetCardType"}, {0x08130000, nullptr, "GetCardType"},

View File

@ -1,96 +0,0 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/hle/service/ldr_ro.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
// Namespace LDR_RO
namespace LDR_RO {
/**
* LDR_RO::Initialize service function
* Inputs:
* 1 : CRS buffer pointer
* 2 : CRS Size
* 3 : Process memory address where the CRS will be mapped
* 4 : Value, must be zero
* 5 : KProcess handle
* Outputs:
* 0 : Return header
* 1 : Result of function, 0 on success, otherwise error code
*/
static void Initialize(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
u32 crs_buffer_ptr = cmd_buff[1];
u32 crs_size = cmd_buff[2];
u32 address = cmd_buff[3];
u32 value = cmd_buff[4];
u32 process = cmd_buff[5];
if (value != 0) {
LOG_ERROR(Service_LDR, "This value should be zero, but is actually %u!", value);
}
// TODO(purpasmart96): Verify return header on HW
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
LOG_WARNING(Service_LDR, "(STUBBED) called. crs_buffer_ptr=0x%08X, crs_size=0x%08X, address=0x%08X, value=0x%08X, process=0x%08X",
crs_buffer_ptr, crs_size, address, value, process);
}
/**
* LDR_RO::LoadCRR service function
* Inputs:
* 1 : CRS buffer pointer
* 2 : CRS Size
* 3 : Value, must be zero
* 4 : KProcess handle
* Outputs:
* 0 : Return header
* 1 : Result of function, 0 on success, otherwise error code
*/
static void LoadCRR(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
u32 crs_buffer_ptr = cmd_buff[1];
u32 crs_size = cmd_buff[2];
u32 value = cmd_buff[3];
u32 process = cmd_buff[4];
if (value != 0) {
LOG_ERROR(Service_LDR, "This value should be zero, but is actually %u!", value);
}
// TODO(purpasmart96): Verify return header on HW
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
LOG_WARNING(Service_LDR, "(STUBBED) called. crs_buffer_ptr=0x%08X, crs_size=0x%08X, value=0x%08X, process=0x%08X",
crs_buffer_ptr, crs_size, value, process);
}
const Interface::FunctionInfo FunctionTable[] = {
{0x000100C2, Initialize, "Initialize"},
{0x00020082, LoadCRR, "LoadCRR"},
{0x00030042, nullptr, "UnloadCCR"},
{0x000402C2, nullptr, "LoadExeCRO"},
{0x000500C2, nullptr, "LoadCROSymbols"},
{0x00060042, nullptr, "CRO_Load?"},
{0x00070042, nullptr, "LoadCROSymbols"},
{0x00080042, nullptr, "Shutdown"},
{0x000902C2, nullptr, "LoadExeCRO_New?"},
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// Interface class
Interface::Interface() {
Register(FunctionTable);
}
} // namespace

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,691 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <tuple>
#include "common/common_types.h"
#include "common/swap.h"
#include "core/memory.h"
#include "core/hle/result.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
// Namespace LDR_RO
namespace LDR_RO {
// GCC versions < 5.0 do not implement std::is_trivially_copyable.
// Excluding MSVC because it has weird behaviour for std::is_trivially_copyable.
#if (__GNUC__ >= 5) || defined(__clang__)
#define ASSERT_CRO_STRUCT(name, size) \
static_assert(std::is_standard_layout<name>::value, "CRO structure " #name " doesn't use standard layout"); \
static_assert(std::is_trivially_copyable<name>::value, "CRO structure " #name " isn't trivially copyable"); \
static_assert(sizeof(name) == (size), "Unexpected struct size for CRO structure " #name)
#else
#define ASSERT_CRO_STRUCT(name, size) \
static_assert(std::is_standard_layout<name>::value, "CRO structure " #name " doesn't use standard layout"); \
static_assert(sizeof(name) == (size), "Unexpected struct size for CRO structure " #name)
#endif
static constexpr u32 CRO_HEADER_SIZE = 0x138;
static constexpr u32 CRO_HASH_SIZE = 0x80;
/// Represents a loaded module (CRO) with interfaces manipulating it.
class CROHelper final {
public:
explicit CROHelper(VAddr cro_address) : module_address(cro_address) {
}
std::string ModuleName() const {
return Memory::ReadCString(GetField(ModuleNameOffset), GetField(ModuleNameSize));
}
u32 GetFileSize() const {
return GetField(FileSize);
}
/**
* Rebases the module according to its address.
* @param crs_address the virtual address of the static module
* @param cro_size the size of the CRO file
* @param data_segment_address buffer address for .data segment
* @param data_segment_size the buffer size for .data segment
* @param bss_segment_address the buffer address for .bss segment
* @param bss_segment_size the buffer size for .bss segment
* @param is_crs true if the module itself is the static module
* @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
*/
ResultCode Rebase(VAddr crs_address, u32 cro_size,
VAddr data_segment_addresss, u32 data_segment_size,
VAddr bss_segment_address, u32 bss_segment_size, bool is_crs);
/**
* Unrebases the module.
* @param is_crs true if the module itself is the static module
*/
void Unrebase(bool is_crs);
/**
* Verifies module hash by CRR.
* @param cro_size the size of the CRO
* @param crr the virtual address of the CRR
* @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
*/
ResultCode VerifyHash(u32 cro_size, VAddr crr) const;
/**
* Links this module with all registered auto-link module.
* @param crs_address the virtual address of the static module
* @param link_on_load_bug_fix true if links when loading and fixes the bug
* @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
*/
ResultCode Link(VAddr crs_address, bool link_on_load_bug_fix);
/**
* Unlinks this module with other modules.
* @param crs_address the virtual address of the static module
* @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
*/
ResultCode Unlink(VAddr crs_address);
/**
* Clears all relocations to zero.
* @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
*/
ResultCode ClearRelocations();
/// Initialize this module as the static module (CRS)
void InitCRS();
/**
* Registers this module and adds it to the module list.
* @param crs_address the virtual address of the static module
* @auto_link whether to register as an auto link module
*/
void Register(VAddr crs_address, bool auto_link);
/**
* Unregisters this module and removes from the module list.
* @param crs_address the virtual address of the static module
*/
void Unregister(VAddr crs_address);
/**
* Gets the end of reserved data according to the fix level.
* @param fix_level fix level from 0 to 3
* @returns the end of reserved data.
*/
u32 GetFixEnd(u32 fix_level) const;
/**
* Zeros offsets to cropped data according to the fix level and marks as fixed.
* @param fix_level fix level from 0 to 3
* @returns page-aligned size of the module after fixing.
*/
u32 Fix(u32 fix_level);
bool IsFixed() const {
return GetField(Magic) == MAGIC_FIXD;
}
u32 GetFixedSize() const {
return GetField(FixedSize);
}
bool IsLoaded() const;
/**
* Gets the page address and size of the code segment.
* @returns a tuple of (address, size); (0, 0) if the code segment doesn't exist.
*/
std::tuple<VAddr, u32> GetExecutablePages() const;
private:
const VAddr module_address; ///< the virtual address of this module
/**
* Each item in this enum represents a u32 field in the header begin from address+0x80, successively.
* We don't directly use a struct here, to avoid GetPointer, reinterpret_cast, or Read/WriteBlock repeatedly.
*/
enum HeaderField {
Magic = 0,
NameOffset,
NextCRO,
PreviousCRO,
FileSize,
BssSize,
FixedSize,
UnknownZero,
UnkSegmentTag,
OnLoadSegmentTag,
OnExitSegmentTag,
OnUnresolvedSegmentTag,
CodeOffset,
CodeSize,
DataOffset,
DataSize,
ModuleNameOffset,
ModuleNameSize,
SegmentTableOffset,
SegmentNum,
ExportNamedSymbolTableOffset,
ExportNamedSymbolNum,
ExportIndexedSymbolTableOffset,
ExportIndexedSymbolNum,
ExportStringsOffset,
ExportStringsSize,
ExportTreeTableOffset,
ExportTreeNum,
ImportModuleTableOffset,
ImportModuleNum,
ExternalRelocationTableOffset,
ExternalRelocationNum,
ImportNamedSymbolTableOffset,
ImportNamedSymbolNum,
ImportIndexedSymbolTableOffset,
ImportIndexedSymbolNum,
ImportAnonymousSymbolTableOffset,
ImportAnonymousSymbolNum,
ImportStringsOffset,
ImportStringsSize,
StaticAnonymousSymbolTableOffset,
StaticAnonymousSymbolNum,
InternalRelocationTableOffset,
InternalRelocationNum,
StaticRelocationTableOffset,
StaticRelocationNum,
Fix0Barrier,
Fix3Barrier = ExportNamedSymbolTableOffset,
Fix2Barrier = ImportModuleTableOffset,
Fix1Barrier = StaticAnonymousSymbolTableOffset,
};
static_assert(Fix0Barrier == (CRO_HEADER_SIZE - CRO_HASH_SIZE) / 4, "CRO Header fields are wrong!");
enum class SegmentType : u32 {
Code = 0,
ROData = 1,
Data = 2,
BSS = 3,
};
/**
* Identifies a program location inside of a segment.
* Required to refer to program locations because individual segments may be relocated independently of each other.
*/
union SegmentTag {
u32_le raw;
BitField<0, 4, u32_le> segment_index;
BitField<4, 28, u32_le> offset_into_segment;
SegmentTag() = default;
explicit SegmentTag(u32 raw_) : raw(raw_) {}
};
/// Information of a segment in this module.
struct SegmentEntry {
u32_le offset;
u32_le size;
SegmentType type;
static constexpr HeaderField TABLE_OFFSET_FIELD = SegmentTableOffset;
};
ASSERT_CRO_STRUCT(SegmentEntry, 12);
/// Identifies a named symbol exported from this module.
struct ExportNamedSymbolEntry {
u32_le name_offset; // pointing to a substring in ExportStrings
SegmentTag symbol_position; // to self's segment
static constexpr HeaderField TABLE_OFFSET_FIELD = ExportNamedSymbolTableOffset;
};
ASSERT_CRO_STRUCT(ExportNamedSymbolEntry, 8);
/// Identifies an indexed symbol exported from this module.
struct ExportIndexedSymbolEntry {
SegmentTag symbol_position; // to self's segment
static constexpr HeaderField TABLE_OFFSET_FIELD = ExportIndexedSymbolTableOffset;
};
ASSERT_CRO_STRUCT(ExportIndexedSymbolEntry, 4);
/// A tree node in the symbol lookup tree.
struct ExportTreeEntry {
u16_le test_bit; // bit address into the name to test
union Child {
u16_le raw;
BitField<0, 15, u16_le> next_index;
BitField<15, 1, u16_le> is_end;
} left, right;
u16_le export_table_index; // index of an ExportNamedSymbolEntry
static constexpr HeaderField TABLE_OFFSET_FIELD = ExportTreeTableOffset;
};
ASSERT_CRO_STRUCT(ExportTreeEntry, 8);
/// Identifies a named symbol imported from another module.
struct ImportNamedSymbolEntry {
u32_le name_offset; // pointing to a substring in ImportStrings
u32_le relocation_batch_offset; // pointing to a relocation batch in ExternalRelocationTable
static constexpr HeaderField TABLE_OFFSET_FIELD = ImportNamedSymbolTableOffset;
};
ASSERT_CRO_STRUCT(ImportNamedSymbolEntry, 8);
/// Identifies an indexed symbol imported from another module.
struct ImportIndexedSymbolEntry {
u32_le index; // index of an ExportIndexedSymbolEntry in the exporting module
u32_le relocation_batch_offset; // pointing to a relocation batch in ExternalRelocationTable
static constexpr HeaderField TABLE_OFFSET_FIELD = ImportIndexedSymbolTableOffset;
};
ASSERT_CRO_STRUCT(ImportIndexedSymbolEntry, 8);
/// Identifies an anonymous symbol imported from another module.
struct ImportAnonymousSymbolEntry {
SegmentTag symbol_position; // in the exporting segment
u32_le relocation_batch_offset; // pointing to a relocation batch in ExternalRelocationTable
static constexpr HeaderField TABLE_OFFSET_FIELD = ImportAnonymousSymbolTableOffset;
};
ASSERT_CRO_STRUCT(ImportAnonymousSymbolEntry, 8);
/// Information of a imported module and symbols imported from it.
struct ImportModuleEntry {
u32_le name_offset; // pointing to a substring in ImportStrings
u32_le import_indexed_symbol_table_offset; // pointing to a subtable in ImportIndexedSymbolTable
u32_le import_indexed_symbol_num;
u32_le import_anonymous_symbol_table_offset; // pointing to a subtable in ImportAnonymousSymbolTable
u32_le import_anonymous_symbol_num;
static constexpr HeaderField TABLE_OFFSET_FIELD = ImportModuleTableOffset;
void GetImportIndexedSymbolEntry(u32 index, ImportIndexedSymbolEntry& entry) {
Memory::ReadBlock(import_indexed_symbol_table_offset + index * sizeof(ImportIndexedSymbolEntry),
&entry, sizeof(ImportIndexedSymbolEntry));
}
void GetImportAnonymousSymbolEntry(u32 index, ImportAnonymousSymbolEntry& entry) {
Memory::ReadBlock(import_anonymous_symbol_table_offset + index * sizeof(ImportAnonymousSymbolEntry),
&entry, sizeof(ImportAnonymousSymbolEntry));
}
};
ASSERT_CRO_STRUCT(ImportModuleEntry, 20);
enum class RelocationType : u8 {
Nothing = 0,
AbsoluteAddress = 2,
RelativeAddress = 3,
ThumbBranch = 10,
ArmBranch = 28,
ModifyArmBranch = 29,
AbsoluteAddress2 = 38,
AlignedRelativeAddress = 42,
};
struct RelocationEntry {
SegmentTag target_position; // to self's segment as an ExternalRelocationEntry; to static module segment as a StaticRelocationEntry
RelocationType type;
u8 is_batch_end;
u8 is_batch_resolved; // set at a batch beginning if the batch is resolved
INSERT_PADDING_BYTES(1);
u32_le addend;
};
/// Identifies a normal cross-module relocation.
struct ExternalRelocationEntry : RelocationEntry {
static constexpr HeaderField TABLE_OFFSET_FIELD = ExternalRelocationTableOffset;
};
ASSERT_CRO_STRUCT(ExternalRelocationEntry, 12);
/// Identifies a special static relocation (no game is known using this).
struct StaticRelocationEntry : RelocationEntry {
static constexpr HeaderField TABLE_OFFSET_FIELD = StaticRelocationTableOffset;
};
ASSERT_CRO_STRUCT(StaticRelocationEntry, 12);
/// Identifies a in-module relocation.
struct InternalRelocationEntry {
SegmentTag target_position; // to self's segment
RelocationType type;
u8 symbol_segment;
INSERT_PADDING_BYTES(2);
u32_le addend;
static constexpr HeaderField TABLE_OFFSET_FIELD = InternalRelocationTableOffset;
};
ASSERT_CRO_STRUCT(InternalRelocationEntry, 12);
/// Identifies a special static anonymous symbol (no game is known using this).
struct StaticAnonymousSymbolEntry {
SegmentTag symbol_position; // to self's segment
u32_le relocation_batch_offset; // pointing to a relocation batch in StaticRelocationTable
static constexpr HeaderField TABLE_OFFSET_FIELD = StaticAnonymousSymbolTableOffset;
};
ASSERT_CRO_STRUCT(StaticAnonymousSymbolEntry, 8);
/**
* Entry size of each table, from Code to StaticRelocationTable.
* Byte string contents (such as Code) are treated with entries of size 1.
* This is used for verifying the size of each table and calculating the fix end.
*/
static const std::array<int, 17> ENTRY_SIZE;
/// The offset field of the table where to crop for each fix level
static const std::array<HeaderField, 4> FIX_BARRIERS;
static constexpr u32 MAGIC_CRO0 = 0x304F5243;
static constexpr u32 MAGIC_FIXD = 0x44584946;
VAddr Field(HeaderField field) const {
return module_address + CRO_HASH_SIZE + field * 4;
}
u32 GetField(HeaderField field) const {
return Memory::Read32(Field(field));
}
void SetField(HeaderField field, u32 value) {
Memory::Write32(Field(field), value);
}
/**
* Reads an entry in one of module tables.
* @param index index of the entry
* @param data where to put the read entry
* @note the entry type must have the static member TABLE_OFFSET_FIELD
* indicating which table the entry is in.
*/
template <typename T>
void GetEntry(std::size_t index, T& data) const {
Memory::ReadBlock(GetField(T::TABLE_OFFSET_FIELD) + index * sizeof(T), &data, sizeof(T));
}
/**
* Writes an entry to one of module tables.
* @param index index of the entry
* @param data the entry data to write
* @note the entry type must have the static member TABLE_OFFSET_FIELD
* indicating which table the entry is in.
*/
template <typename T>
void SetEntry(std::size_t index, const T& data) {
Memory::WriteBlock(GetField(T::TABLE_OFFSET_FIELD) + index * sizeof(T), &data, sizeof(T));
}
/**
* Converts a segment tag to virtual address in this module.
* @param segment_tag the segment tag to convert
* @returns VAddr the virtual address the segment tag points to; 0 if invalid.
*/
VAddr SegmentTagToAddress(SegmentTag segment_tag) const;
VAddr NextModule() const {
return GetField(NextCRO);
}
VAddr PreviousModule() const {
return GetField(PreviousCRO);
}
void SetNextModule(VAddr next) {
SetField(NextCRO, next);
}
void SetPreviousModule(VAddr previous) {
SetField(PreviousCRO, previous);
}
/**
* A helper function iterating over all registered auto-link modules, including the static module.
* @param crs_address the virtual address of the static module
* @param func a function object to operate on a module. It accepts one parameter
* CROHelper and returns ResultVal<bool>. It should return true to continue the iteration,
* false to stop the iteration, or an error code (which will also stop the iteration).
* @returns ResultCode indicating the result of the operation, RESULT_SUCCESS if all iteration success,
* otherwise error code of the last iteration.
*/
template <typename FunctionObject>
static ResultCode ForEachAutoLinkCRO(VAddr crs_address, FunctionObject func) {
VAddr current = crs_address;
while (current != 0) {
CROHelper cro(current);
CASCADE_RESULT(bool next, func(cro));
if (!next)
break;
current = cro.NextModule();
}
return RESULT_SUCCESS;
}
/**
* Applies a relocation
* @param target_address where to apply the relocation
* @param relocation_type the type of the relocation
* @param addend address addend applied to the relocated symbol
* @param symbol_address the symbol address to be relocated with
* @param target_future_address the future address of the target.
* Usually equals to target_address, but will be different for a target in .data segment
* @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
*/
ResultCode ApplyRelocation(VAddr target_address, RelocationType relocation_type,
u32 addend, u32 symbol_address, u32 target_future_address);
/**
* Clears a relocation to zero
* @param target_address where to apply the relocation
* @param relocation_type the type of the relocation
* @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
*/
ResultCode ClearRelocation(VAddr target_address, RelocationType relocation_type);
/**
* Applies or resets a batch of relocations
* @param batch the virtual address of the first relocation in the batch
* @param symbol_address the symbol address to be relocated with
* @param reset false to set the batch to resolved state, true to reset the batch to unresolved state
* @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
*/
ResultCode ApplyRelocationBatch(VAddr batch, u32 symbol_address, bool reset = false);
/**
* Finds an exported named symbol in this module.
* @param name the name of the symbol to find
* @return VAddr the virtual address of the symbol; 0 if not found.
*/
VAddr FindExportNamedSymbol(const std::string& name) const;
/**
* Rebases offsets in module header according to module address.
* @param cro_size the size of the CRO file
* @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code.
*/
ResultCode RebaseHeader(u32 cro_size);
/**
* Rebases offsets in segment table according to module address.
* @param cro_size the size of the CRO file
* @param data_segment_address the buffer address for .data segment
* @param data_segment_size the buffer size for .data segment
* @param bss_segment_address the buffer address for .bss segment
* @param bss_segment_size the buffer size for .bss segment
* @returns ResultVal<VAddr> with the virtual address of .data segment in CRO.
*/
ResultVal<VAddr> RebaseSegmentTable(u32 cro_size,
VAddr data_segment_address, u32 data_segment_size,
VAddr bss_segment_address, u32 bss_segment_size);
/**
* Rebases offsets in exported named symbol table according to module address.
* @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code.
*/
ResultCode RebaseExportNamedSymbolTable();
/**
* Verifies indices in export tree table.
* @returns ResultCode RESULT_SUCCESS if all indices are verified as valid, otherwise error code.
*/
ResultCode VerifyExportTreeTable() const;
/**
* Rebases offsets in exported module table according to module address.
* @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code.
*/
ResultCode RebaseImportModuleTable();
/**
* Rebases offsets in imported named symbol table according to module address.
* @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code.
*/
ResultCode RebaseImportNamedSymbolTable();
/**
* Rebases offsets in imported indexed symbol table according to module address.
* @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code.
*/
ResultCode RebaseImportIndexedSymbolTable();
/**
* Rebases offsets in imported anonymous symbol table according to module address.
* @returns ResultCode RESULT_SUCCESS if all offsets are verified as valid, otherwise error code.
*/
ResultCode RebaseImportAnonymousSymbolTable();
/**
* Gets the address of OnUnresolved function in this module.
* Used as the applied symbol for reset relocation.
* @returns the virtual address of OnUnresolved. 0 if not provided.
*/
VAddr GetOnUnresolvedAddress();
/**
* Resets all external relocations to unresolved state.
* @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
*/
ResultCode ResetExternalRelocations();
/**
* Clears all external relocations to zero.
* @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
*/
ResultCode ClearExternalRelocations();
/**
* Applies all static anonymous symbol to the static module.
* @param crs_address the virtual address of the static module
* @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
*/
ResultCode ApplyStaticAnonymousSymbolToCRS(VAddr crs_address);
/**
* Applies all internal relocations to the module itself.
* @param old_data_segment_address the virtual address of data segment in CRO buffer
* @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
*/
ResultCode ApplyInternalRelocations(u32 old_data_segment_address);
/**
* Clears all internal relocations to zero.
* @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
*/
ResultCode ClearInternalRelocations();
/// Unrebases offsets in imported anonymous symbol table
void UnrebaseImportAnonymousSymbolTable();
/// Unrebases offsets in imported indexed symbol table
void UnrebaseImportIndexedSymbolTable();
/// Unrebases offsets in imported named symbol table
void UnrebaseImportNamedSymbolTable();
/// Unrebases offsets in imported module table
void UnrebaseImportModuleTable();
/// Unrebases offsets in exported named symbol table
void UnrebaseExportNamedSymbolTable();
/// Unrebases offsets in segment table
void UnrebaseSegmentTable();
/// Unrebases offsets in module header
void UnrebaseHeader();
/**
* Looks up all imported named symbols of this module in all registered auto-link modules, and resolves them if found.
* @param crs_address the virtual address of the static module
* @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
*/
ResultCode ApplyImportNamedSymbol(VAddr crs_address);
/**
* Resets all imported named symbols of this module to unresolved state.
* @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
*/
ResultCode ResetImportNamedSymbol();
/**
* Resets all imported indexed symbols of this module to unresolved state.
* @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
*/
ResultCode ResetImportIndexedSymbol();
/**
* Resets all imported anonymous symbols of this module to unresolved state.
* @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
*/
ResultCode ResetImportAnonymousSymbol();
/**
* Finds registered auto-link modules that this module imports, and resolves indexed and anonymous symbols exported by them.
* @param crs_address the virtual address of the static module
* @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
*/
ResultCode ApplyModuleImport(VAddr crs_address);
/**
* Resolves target module's imported named symbols that exported by this module.
* @param target the module to resolve.
* @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
*/
ResultCode ApplyExportNamedSymbol(CROHelper target);
/**
* Resets target's named symbols imported from this module to unresolved state.
* @param target the module to reset.
* @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
*/
ResultCode ResetExportNamedSymbol(CROHelper target);
/**
* Resolves imported indexed and anonymous symbols in the target module which imports this module.
* @param target the module to resolve.
* @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
*/
ResultCode ApplyModuleExport(CROHelper target);
/**
* Resets target's indexed and anonymous symbol imported from this module to unresolved state.
* @param target the module to reset.
* @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
*/
ResultCode ResetModuleExport(CROHelper target);
/**
* Resolves the exit function in this module
* @param crs_address the virtual address of the static module.
* @returns ResultCode RESULT_SUCCESS on success, otherwise error code.
*/
ResultCode ApplyExitRelocations(VAddr crs_address);
};
} // namespace

View File

@ -0,0 +1,748 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/alignment.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/arm/arm_interface.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/hle/service/ldr_ro/cro_helper.h"
#include "core/hle/service/ldr_ro/ldr_ro.h"
#include "core/hle/service/ldr_ro/memory_synchronizer.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
// Namespace LDR_RO
namespace LDR_RO {
static const ResultCode ERROR_ALREADY_INITIALIZED = // 0xD9612FF9
ResultCode(ErrorDescription::AlreadyInitialized, ErrorModule::RO, ErrorSummary::Internal, ErrorLevel::Permanent);
static const ResultCode ERROR_NOT_INITIALIZED = // 0xD9612FF8
ResultCode(ErrorDescription::NotInitialized, ErrorModule::RO, ErrorSummary::Internal, ErrorLevel::Permanent);
static const ResultCode ERROR_BUFFER_TOO_SMALL = // 0xE0E12C1F
ResultCode(static_cast<ErrorDescription>(31), ErrorModule::RO, ErrorSummary::InvalidArgument, ErrorLevel::Usage);
static const ResultCode ERROR_MISALIGNED_ADDRESS = // 0xD9012FF1
ResultCode(ErrorDescription::MisalignedAddress, ErrorModule::RO, ErrorSummary::WrongArgument, ErrorLevel::Permanent);
static const ResultCode ERROR_MISALIGNED_SIZE = // 0xD9012FF2
ResultCode(ErrorDescription::MisalignedSize, ErrorModule::RO, ErrorSummary::WrongArgument, ErrorLevel::Permanent);
static const ResultCode ERROR_ILLEGAL_ADDRESS = // 0xE1612C0F
ResultCode(static_cast<ErrorDescription>(15), ErrorModule::RO, ErrorSummary::Internal, ErrorLevel::Usage);
static const ResultCode ERROR_INVALID_MEMORY_STATE = // 0xD8A12C08
ResultCode(static_cast<ErrorDescription>(8), ErrorModule::RO, ErrorSummary::InvalidState, ErrorLevel::Permanent);
static const ResultCode ERROR_NOT_LOADED = // 0xD8A12C0D
ResultCode(static_cast<ErrorDescription>(13), ErrorModule::RO, ErrorSummary::InvalidState, ErrorLevel::Permanent);
static const ResultCode ERROR_INVALID_DESCRIPTOR = // 0xD9001830
ResultCode(ErrorDescription::OS_InvalidBufferDescriptor, ErrorModule::OS, ErrorSummary::WrongArgument, ErrorLevel::Permanent);
static MemorySynchronizer memory_synchronizer;
// TODO(wwylele): this should be in the per-client storage when we implement multi-process
static VAddr loaded_crs; ///< the virtual address of the static module
static bool VerifyBufferState(VAddr buffer_ptr, u32 size) {
auto vma = Kernel::g_current_process->vm_manager.FindVMA(buffer_ptr);
return vma != Kernel::g_current_process->vm_manager.vma_map.end()
&& vma->second.base + vma->second.size >= buffer_ptr + size
&& vma->second.permissions == Kernel::VMAPermission::ReadWrite
&& vma->second.meminfo_state == Kernel::MemoryState::Private;
}
/**
* LDR_RO::Initialize service function
* Inputs:
* 0 : 0x000100C2
* 1 : CRS buffer pointer
* 2 : CRS Size
* 3 : Process memory address where the CRS will be mapped
* 4 : handle translation descriptor (zero)
* 5 : KProcess handle
* Outputs:
* 0 : Return header
* 1 : Result of function, 0 on success, otherwise error code
*/
static void Initialize(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
VAddr crs_buffer_ptr = cmd_buff[1];
u32 crs_size = cmd_buff[2];
VAddr crs_address = cmd_buff[3];
u32 descriptor = cmd_buff[4];
u32 process = cmd_buff[5];
LOG_DEBUG(Service_LDR, "called, crs_buffer_ptr=0x%08X, crs_address=0x%08X, crs_size=0x%X, descriptor=0x%08X, process=0x%08X",
crs_buffer_ptr, crs_address, crs_size, descriptor, process);
if (descriptor != 0) {
LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor);
cmd_buff[0] = IPC::MakeHeader(0, 1, 0);
cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw;
return;
}
cmd_buff[0] = IPC::MakeHeader(1, 1, 0);
if (loaded_crs != 0) {
LOG_ERROR(Service_LDR, "Already initialized");
cmd_buff[1] = ERROR_ALREADY_INITIALIZED.raw;
return;
}
if (crs_size < CRO_HEADER_SIZE) {
LOG_ERROR(Service_LDR, "CRS is too small");
cmd_buff[1] = ERROR_BUFFER_TOO_SMALL.raw;
return;
}
if (crs_buffer_ptr & Memory::PAGE_MASK) {
LOG_ERROR(Service_LDR, "CRS original address is not aligned");
cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw;
return;
}
if (crs_address & Memory::PAGE_MASK) {
LOG_ERROR(Service_LDR, "CRS mapping address is not aligned");
cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw;
return;
}
if (crs_size & Memory::PAGE_MASK) {
LOG_ERROR(Service_LDR, "CRS size is not aligned");
cmd_buff[1] = ERROR_MISALIGNED_SIZE.raw;
return;
}
if (!VerifyBufferState(crs_buffer_ptr, crs_size)) {
LOG_ERROR(Service_LDR, "CRS original buffer is in invalid state");
cmd_buff[1] = ERROR_INVALID_MEMORY_STATE.raw;
return;
}
if (crs_address < Memory::PROCESS_IMAGE_VADDR || crs_address + crs_size > Memory::PROCESS_IMAGE_VADDR_END) {
LOG_ERROR(Service_LDR, "CRS mapping address is not in the process image region");
cmd_buff[1] = ERROR_ILLEGAL_ADDRESS.raw;
return;
}
ResultCode result = RESULT_SUCCESS;
if (crs_buffer_ptr != crs_address) {
// TODO(wwylele): should be memory aliasing
std::shared_ptr<std::vector<u8>> crs_mem = std::make_shared<std::vector<u8>>(crs_size);
Memory::ReadBlock(crs_buffer_ptr, crs_mem->data(), crs_size);
result = Kernel::g_current_process->vm_manager.MapMemoryBlock(crs_address, crs_mem, 0, crs_size, Kernel::MemoryState::Code).Code();
if (result.IsError()) {
LOG_ERROR(Service_LDR, "Error mapping memory block %08X", result.raw);
cmd_buff[1] = result.raw;
return;
}
result = Kernel::g_current_process->vm_manager.ReprotectRange(crs_address, crs_size, Kernel::VMAPermission::Read);
if (result.IsError()) {
LOG_ERROR(Service_LDR, "Error reprotecting memory block %08X", result.raw);
cmd_buff[1] = result.raw;
return;
}
memory_synchronizer.AddMemoryBlock(crs_address, crs_buffer_ptr, crs_size);
} else {
// Do nothing if buffer_ptr == address
// TODO(wwylele): verify this behaviour. This is only seen in the web browser app,
// and the actual behaviour is unclear. "Do nothing" is probably an incorrect implement.
// There is also a chance that another issue causes the app passing wrong arguments.
LOG_WARNING(Service_LDR, "crs_buffer_ptr == crs_address (0x%08X)", crs_address);
}
CROHelper crs(crs_address);
crs.InitCRS();
result = crs.Rebase(0, crs_size, 0, 0, 0, 0, true);
if (result.IsError()) {
LOG_ERROR(Service_LDR, "Error rebasing CRS 0x%08X", result.raw);
cmd_buff[1] = result.raw;
return;
}
memory_synchronizer.SynchronizeOriginalMemory();
loaded_crs = crs_address;
cmd_buff[1] = RESULT_SUCCESS.raw;
}
/**
* LDR_RO::LoadCRR service function
* Inputs:
* 0 : 0x00020082
* 1 : CRR buffer pointer
* 2 : CRR Size
* 3 : handle translation descriptor (zero)
* 4 : KProcess handle
* Outputs:
* 0 : Return header
* 1 : Result of function, 0 on success, otherwise error code
*/
static void LoadCRR(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
u32 crr_buffer_ptr = cmd_buff[1];
u32 crr_size = cmd_buff[2];
u32 descriptor = cmd_buff[3];
u32 process = cmd_buff[4];
if (descriptor != 0) {
LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor);
cmd_buff[0] = IPC::MakeHeader(0, 1, 0);
cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw;
return;
}
cmd_buff[0] = IPC::MakeHeader(2, 1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
LOG_WARNING(Service_LDR, "(STUBBED) called, crr_buffer_ptr=0x%08X, crr_size=0x%08X, descriptor=0x%08X, process=0x%08X",
crr_buffer_ptr, crr_size, descriptor, process);
}
/**
* LDR_RO::UnloadCRR service function
* Inputs:
* 0 : 0x00030042
* 1 : CRR buffer pointer
* 2 : handle translation descriptor (zero)
* 3 : KProcess handle
* Outputs:
* 0 : Return header
* 1 : Result of function, 0 on success, otherwise error code
*/
static void UnloadCRR(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
u32 crr_buffer_ptr = cmd_buff[1];
u32 descriptor = cmd_buff[2];
u32 process = cmd_buff[3];
if (descriptor != 0) {
LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor);
cmd_buff[0] = IPC::MakeHeader(0, 1, 0);
cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw;
return;
}
cmd_buff[0] = IPC::MakeHeader(3, 1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
LOG_WARNING(Service_LDR, "(STUBBED) called, crr_buffer_ptr=0x%08X, descriptor=0x%08X, process=0x%08X",
crr_buffer_ptr, descriptor, process);
}
/**
* LDR_RO::LoadCRO service function
* Inputs:
* 0 : 0x000402C2 (old) / 0x000902C2 (new)
* 1 : CRO buffer pointer
* 2 : memory address where the CRO will be mapped
* 3 : CRO Size
* 4 : .data segment buffer pointer
* 5 : must be zero
* 6 : .data segment buffer size
* 7 : .bss segment buffer pointer
* 8 : .bss segment buffer size
* 9 : (bool) register CRO as auto-link module
* 10 : fix level
* 11 : CRR address (zero if use loaded CRR)
* 12 : handle translation descriptor (zero)
* 13 : KProcess handle
* Outputs:
* 0 : Return header
* 1 : Result of function, 0 on success, otherwise error code
* 2 : CRO fixed size
* Note:
* This service function has two versions. The function defined here is a
* unified one of two, with an additional parameter link_on_load_bug_fix.
* There is a dispatcher template below.
*/
static void LoadCRO(Service::Interface* self, bool link_on_load_bug_fix) {
u32* cmd_buff = Kernel::GetCommandBuffer();
VAddr cro_buffer_ptr = cmd_buff[1];
VAddr cro_address = cmd_buff[2];
u32 cro_size = cmd_buff[3];
VAddr data_segment_address = cmd_buff[4];
u32 zero = cmd_buff[5];
u32 data_segment_size = cmd_buff[6];
u32 bss_segment_address = cmd_buff[7];
u32 bss_segment_size = cmd_buff[8];
bool auto_link = (cmd_buff[9] & 0xFF) != 0;
u32 fix_level = cmd_buff[10];
VAddr crr_address = cmd_buff[11];
u32 descriptor = cmd_buff[12];
u32 process = cmd_buff[13];
LOG_DEBUG(Service_LDR, "called (%s), cro_buffer_ptr=0x%08X, cro_address=0x%08X, cro_size=0x%X, "
"data_segment_address=0x%08X, zero=%d, data_segment_size=0x%X, bss_segment_address=0x%08X, bss_segment_size=0x%X, "
"auto_link=%s, fix_level=%d, crr_address=0x%08X, descriptor=0x%08X, process=0x%08X",
link_on_load_bug_fix ? "new" : "old", cro_buffer_ptr, cro_address, cro_size,
data_segment_address, zero, data_segment_size, bss_segment_address, bss_segment_size,
auto_link ? "true" : "false", fix_level, crr_address, descriptor, process
);
if (descriptor != 0) {
LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor);
cmd_buff[0] = IPC::MakeHeader(0, 1, 0);
cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw;
return;
}
cmd_buff[0] = IPC::MakeHeader(link_on_load_bug_fix ? 9 : 4, 2, 0);
if (loaded_crs == 0) {
LOG_ERROR(Service_LDR, "Not initialized");
cmd_buff[1] = ERROR_NOT_INITIALIZED.raw;
return;
}
if (cro_size < CRO_HEADER_SIZE) {
LOG_ERROR(Service_LDR, "CRO too small");
cmd_buff[1] = ERROR_BUFFER_TOO_SMALL.raw;
return;
}
if (cro_buffer_ptr & Memory::PAGE_MASK) {
LOG_ERROR(Service_LDR, "CRO original address is not aligned");
cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw;
return;
}
if (cro_address & Memory::PAGE_MASK) {
LOG_ERROR(Service_LDR, "CRO mapping address is not aligned");
cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw;
return;
}
if (cro_size & Memory::PAGE_MASK) {
LOG_ERROR(Service_LDR, "CRO size is not aligned");
cmd_buff[1] = ERROR_MISALIGNED_SIZE.raw;
return;
}
if (!VerifyBufferState(cro_buffer_ptr, cro_size)) {
LOG_ERROR(Service_LDR, "CRO original buffer is in invalid state");
cmd_buff[1] = ERROR_INVALID_MEMORY_STATE.raw;
return;
}
if (cro_address < Memory::PROCESS_IMAGE_VADDR
|| cro_address + cro_size > Memory::PROCESS_IMAGE_VADDR_END) {
LOG_ERROR(Service_LDR, "CRO mapping address is not in the process image region");
cmd_buff[1] = ERROR_ILLEGAL_ADDRESS.raw;
return;
}
if (zero) {
LOG_ERROR(Service_LDR, "Zero is not zero %d", zero);
cmd_buff[1] = ResultCode(static_cast<ErrorDescription>(29), ErrorModule::RO, ErrorSummary::Internal, ErrorLevel::Usage).raw;
return;
}
ResultCode result = RESULT_SUCCESS;
if (cro_buffer_ptr != cro_address) {
// TODO(wwylele): should be memory aliasing
std::shared_ptr<std::vector<u8>> cro_mem = std::make_shared<std::vector<u8>>(cro_size);
Memory::ReadBlock(cro_buffer_ptr, cro_mem->data(), cro_size);
result = Kernel::g_current_process->vm_manager.MapMemoryBlock(cro_address, cro_mem, 0, cro_size, Kernel::MemoryState::Code).Code();
if (result.IsError()) {
LOG_ERROR(Service_LDR, "Error mapping memory block %08X", result.raw);
cmd_buff[1] = result.raw;
return;
}
result = Kernel::g_current_process->vm_manager.ReprotectRange(cro_address, cro_size, Kernel::VMAPermission::Read);
if (result.IsError()) {
LOG_ERROR(Service_LDR, "Error reprotecting memory block %08X", result.raw);
Kernel::g_current_process->vm_manager.UnmapRange(cro_address, cro_size);
cmd_buff[1] = result.raw;
return;
}
memory_synchronizer.AddMemoryBlock(cro_address, cro_buffer_ptr, cro_size);
} else {
// Do nothing if buffer_ptr == address
// TODO(wwylele): verify this behaviour.
// This is derived from the case of LoadCRS with buffer_ptr==address,
// and is never seen in any game. "Do nothing" is probably an incorrect implement.
// There is also a chance that this case is just prohibited.
LOG_WARNING(Service_LDR, "cro_buffer_ptr == cro_address (0x%08X)", cro_address);
}
CROHelper cro(cro_address);
result = cro.VerifyHash(cro_size, crr_address);
if (result.IsError()) {
LOG_ERROR(Service_LDR, "Error verifying CRO in CRR %08X", result.raw);
Kernel::g_current_process->vm_manager.UnmapRange(cro_address, cro_size);
cmd_buff[1] = result.raw;
return;
}
result = cro.Rebase(loaded_crs, cro_size, data_segment_address, data_segment_size, bss_segment_address, bss_segment_size, false);
if (result.IsError()) {
LOG_ERROR(Service_LDR, "Error rebasing CRO %08X", result.raw);
Kernel::g_current_process->vm_manager.UnmapRange(cro_address, cro_size);
cmd_buff[1] = result.raw;
return;
}
result = cro.Link(loaded_crs, link_on_load_bug_fix);
if (result.IsError()) {
LOG_ERROR(Service_LDR, "Error linking CRO %08X", result.raw);
Kernel::g_current_process->vm_manager.UnmapRange(cro_address, cro_size);
cmd_buff[1] = result.raw;
return;
}
cro.Register(loaded_crs, auto_link);
u32 fix_size = cro.Fix(fix_level);
memory_synchronizer.SynchronizeOriginalMemory();
// TODO(wwylele): verify the behaviour when buffer_ptr == address
if (cro_buffer_ptr != cro_address) {
if (fix_size != cro_size) {
result = Kernel::g_current_process->vm_manager.UnmapRange(cro_address + fix_size, cro_size - fix_size);
if (result.IsError()) {
LOG_ERROR(Service_LDR, "Error unmapping memory block %08X", result.raw);
Kernel::g_current_process->vm_manager.UnmapRange(cro_address, cro_size);
cmd_buff[1] = result.raw;
return;
}
}
// Changes the block size
memory_synchronizer.ResizeMemoryBlock(cro_address, cro_buffer_ptr, fix_size);
}
VAddr exe_begin;
u32 exe_size;
std::tie(exe_begin, exe_size) = cro.GetExecutablePages();
if (exe_begin) {
result = Kernel::g_current_process->vm_manager.ReprotectRange(exe_begin, exe_size, Kernel::VMAPermission::ReadExecute);
if (result.IsError()) {
LOG_ERROR(Service_LDR, "Error reprotecting memory block %08X", result.raw);
Kernel::g_current_process->vm_manager.UnmapRange(cro_address, fix_size);
cmd_buff[1] = result.raw;
return;
}
}
Core::g_app_core->ClearInstructionCache();
LOG_INFO(Service_LDR, "CRO \"%s\" loaded at 0x%08X, fixed_end=0x%08X",
cro.ModuleName().data(), cro_address, cro_address+fix_size);
cmd_buff[1] = RESULT_SUCCESS.raw;
cmd_buff[2] = fix_size;
}
template <bool link_on_load_bug_fix>
static void LoadCRO(Service::Interface* self) {
LoadCRO(self, link_on_load_bug_fix);
}
/**
* LDR_RO::UnloadCRO service function
* Inputs:
* 0 : 0x000500C2
* 1 : mapped CRO pointer
* 2 : zero? (RO service doesn't care)
* 3 : original CRO pointer
* 4 : handle translation descriptor (zero)
* 5 : KProcess handle
* Outputs:
* 0 : Return header
* 1 : Result of function, 0 on success, otherwise error code
*/
static void UnloadCRO(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
VAddr cro_address = cmd_buff[1];
u32 zero = cmd_buff[2];
VAddr cro_buffer_ptr = cmd_buff[3];
u32 descriptor = cmd_buff[4];
u32 process = cmd_buff[5];
LOG_DEBUG(Service_LDR, "called, cro_address=0x%08X, zero=%d, cro_buffer_ptr=0x%08X, descriptor=0x%08X, process=0x%08X",
cro_address, zero, cro_buffer_ptr, descriptor, process);
if (descriptor != 0) {
LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor);
cmd_buff[0] = IPC::MakeHeader(0, 1, 0);
cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw;
return;
}
CROHelper cro(cro_address);
cmd_buff[0] = IPC::MakeHeader(5, 1, 0);
if (loaded_crs == 0) {
LOG_ERROR(Service_LDR, "Not initialized");
cmd_buff[1] = ERROR_NOT_INITIALIZED.raw;
return;
}
if (cro_address & Memory::PAGE_MASK) {
LOG_ERROR(Service_LDR, "CRO address is not aligned");
cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw;
return;
}
if (!cro.IsLoaded()) {
LOG_ERROR(Service_LDR, "Invalid or not loaded CRO");
cmd_buff[1] = ERROR_NOT_LOADED.raw;
return;
}
LOG_INFO(Service_LDR, "Unloading CRO \"%s\"", cro.ModuleName().data());
u32 fixed_size = cro.GetFixedSize();
cro.Unregister(loaded_crs);
ResultCode result = cro.Unlink(loaded_crs);
if (result.IsError()) {
LOG_ERROR(Service_LDR, "Error unlinking CRO %08X", result.raw);
cmd_buff[1] = result.raw;
return;
}
// If the module is not fixed, clears all external/internal relocations
// to restore the state before loading, so that it can be loaded again(?)
if (!cro.IsFixed()) {
result = cro.ClearRelocations();
if (result.IsError()) {
LOG_ERROR(Service_LDR, "Error clearing relocations %08X", result.raw);
cmd_buff[1] = result.raw;
return;
}
}
cro.Unrebase(false);
memory_synchronizer.SynchronizeOriginalMemory();
// TODO(wwylele): verify the behaviour when buffer_ptr == address
if (cro_address != cro_buffer_ptr) {
result = Kernel::g_current_process->vm_manager.UnmapRange(cro_address, fixed_size);
if (result.IsError()) {
LOG_ERROR(Service_LDR, "Error unmapping CRO %08X", result.raw);
}
memory_synchronizer.RemoveMemoryBlock(cro_address, cro_buffer_ptr);
}
Core::g_app_core->ClearInstructionCache();
cmd_buff[1] = result.raw;
}
/**
* LDR_RO::LinkCRO service function
* Inputs:
* 0 : 0x00060042
* 1 : mapped CRO pointer
* 2 : handle translation descriptor (zero)
* 3 : KProcess handle
* Outputs:
* 0 : Return header
* 1 : Result of function, 0 on success, otherwise error code
*/
static void LinkCRO(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
VAddr cro_address = cmd_buff[1];
u32 descriptor = cmd_buff[2];
u32 process = cmd_buff[3];
LOG_DEBUG(Service_LDR, "called, cro_address=0x%08X, descriptor=0x%08X, process=0x%08X",
cro_address, descriptor, process);
if (descriptor != 0) {
LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor);
cmd_buff[0] = IPC::MakeHeader(0, 1, 0);
cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw;
return;
}
CROHelper cro(cro_address);
cmd_buff[0] = IPC::MakeHeader(6, 1, 0);
if (loaded_crs == 0) {
LOG_ERROR(Service_LDR, "Not initialized");
cmd_buff[1] = ERROR_NOT_INITIALIZED.raw;
return;
}
if (cro_address & Memory::PAGE_MASK) {
LOG_ERROR(Service_LDR, "CRO address is not aligned");
cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw;
return;
}
if (!cro.IsLoaded()) {
LOG_ERROR(Service_LDR, "Invalid or not loaded CRO");
cmd_buff[1] = ERROR_NOT_LOADED.raw;
return;
}
LOG_INFO(Service_LDR, "Linking CRO \"%s\"", cro.ModuleName().data());
ResultCode result = cro.Link(loaded_crs, false);
if (result.IsError()) {
LOG_ERROR(Service_LDR, "Error linking CRO %08X", result.raw);
}
memory_synchronizer.SynchronizeOriginalMemory();
Core::g_app_core->ClearInstructionCache();
cmd_buff[1] = result.raw;
}
/**
* LDR_RO::UnlinkCRO service function
* Inputs:
* 0 : 0x00070042
* 1 : mapped CRO pointer
* 2 : handle translation descriptor (zero)
* 3 : KProcess handle
* Outputs:
* 0 : Return header
* 1 : Result of function, 0 on success, otherwise error code
*/
static void UnlinkCRO(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
VAddr cro_address = cmd_buff[1];
u32 descriptor = cmd_buff[2];
u32 process = cmd_buff[3];
LOG_DEBUG(Service_LDR, "called, cro_address=0x%08X, descriptor=0x%08X, process=0x%08X",
cro_address, descriptor, process);
if (descriptor != 0) {
LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor);
cmd_buff[0] = IPC::MakeHeader(0, 1, 0);
cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw;
return;
}
CROHelper cro(cro_address);
cmd_buff[0] = IPC::MakeHeader(7, 1, 0);
if (loaded_crs == 0) {
LOG_ERROR(Service_LDR, "Not initialized");
cmd_buff[1] = ERROR_NOT_INITIALIZED.raw;
return;
}
if (cro_address & Memory::PAGE_MASK) {
LOG_ERROR(Service_LDR, "CRO address is not aligned");
cmd_buff[1] = ERROR_MISALIGNED_ADDRESS.raw;
return;
}
if (!cro.IsLoaded()) {
LOG_ERROR(Service_LDR, "Invalid or not loaded CRO");
cmd_buff[1] = ERROR_NOT_LOADED.raw;
return;
}
LOG_INFO(Service_LDR, "Unlinking CRO \"%s\"", cro.ModuleName().data());
ResultCode result = cro.Unlink(loaded_crs);
if (result.IsError()) {
LOG_ERROR(Service_LDR, "Error unlinking CRO %08X", result.raw);
}
memory_synchronizer.SynchronizeOriginalMemory();
Core::g_app_core->ClearInstructionCache();
cmd_buff[1] = result.raw;
}
/**
* LDR_RO::Shutdown service function
* Inputs:
* 0 : 0x00080042
* 1 : original CRS buffer pointer
* 2 : handle translation descriptor (zero)
* 3 : KProcess handle
* Outputs:
* 0 : Return header
* 1 : Result of function, 0 on success, otherwise error code
*/
static void Shutdown(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
VAddr crs_buffer_ptr = cmd_buff[1];
u32 descriptor = cmd_buff[2];
u32 process = cmd_buff[3];
LOG_DEBUG(Service_LDR, "called, crs_buffer_ptr=0x%08X, descriptor=0x%08X, process=0x%08X",
crs_buffer_ptr, descriptor, process);
if (descriptor != 0) {
LOG_ERROR(Service_LDR, "IPC handle descriptor failed validation (0x%X)", descriptor);
cmd_buff[0] = IPC::MakeHeader(0, 1, 0);
cmd_buff[1] = ERROR_INVALID_DESCRIPTOR.raw;
return;
}
if (loaded_crs == 0) {
LOG_ERROR(Service_LDR, "Not initialized");
cmd_buff[1] = ERROR_NOT_INITIALIZED.raw;
return;
}
cmd_buff[0] = IPC::MakeHeader(8, 1, 0);
CROHelper crs(loaded_crs);
crs.Unrebase(true);
memory_synchronizer.SynchronizeOriginalMemory();
ResultCode result = RESULT_SUCCESS;
// TODO(wwylele): verify the behaviour when buffer_ptr == address
if (loaded_crs != crs_buffer_ptr) {
result = Kernel::g_current_process->vm_manager.UnmapRange(loaded_crs, crs.GetFileSize());
if (result.IsError()) {
LOG_ERROR(Service_LDR, "Error unmapping CRS %08X", result.raw);
}
memory_synchronizer.RemoveMemoryBlock(loaded_crs, crs_buffer_ptr);
}
loaded_crs = 0;
cmd_buff[1] = result.raw;
}
const Interface::FunctionInfo FunctionTable[] = {
{0x000100C2, Initialize, "Initialize"},
{0x00020082, LoadCRR, "LoadCRR"},
{0x00030042, UnloadCRR, "UnloadCRR"},
{0x000402C2, LoadCRO<false>, "LoadCRO"},
{0x000500C2, UnloadCRO, "UnloadCRO"},
{0x00060042, LinkCRO, "LinkCRO"},
{0x00070042, UnlinkCRO, "UnlinkCRO"},
{0x00080042, Shutdown, "Shutdown"},
{0x000902C2, LoadCRO<true>, "LoadCRO_New"},
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// Interface class
Interface::Interface() {
Register(FunctionTable);
loaded_crs = 0;
memory_synchronizer.Clear();
}
} // namespace

View File

@ -0,0 +1,46 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include "common/assert.h"
#include "core/hle/service/ldr_ro/memory_synchronizer.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
// Namespace LDR_RO
namespace LDR_RO {
auto MemorySynchronizer::FindMemoryBlock(VAddr mapping, VAddr original) {
auto block = std::find_if(memory_blocks.begin(), memory_blocks.end(), [=](MemoryBlock& b){
return b.original == original;
});
ASSERT(block->mapping == mapping);
return block;
}
void MemorySynchronizer::Clear() {
memory_blocks.clear();
}
void MemorySynchronizer::AddMemoryBlock(VAddr mapping, VAddr original, u32 size) {
memory_blocks.push_back(MemoryBlock{mapping, original, size});
}
void MemorySynchronizer::ResizeMemoryBlock(VAddr mapping, VAddr original, u32 size) {
FindMemoryBlock(mapping, original)->size = size;
}
void MemorySynchronizer::RemoveMemoryBlock(VAddr mapping, VAddr original) {
memory_blocks.erase(FindMemoryBlock(mapping, original));
}
void MemorySynchronizer::SynchronizeOriginalMemory() {
for (auto& block : memory_blocks) {
Memory::CopyBlock(block.original, block.mapping, block.size);
}
}
} // namespace

View File

@ -0,0 +1,44 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <vector>
#include "core/memory.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
// Namespace LDR_RO
namespace LDR_RO {
/**
* This is a work-around before we implement memory aliasing.
* CRS and CRO are mapped (aliased) to another memory when loading. Games can read
* from both the original buffer and the mapping memory. So we use this to synchronize
* all original buffers with mapping memory after modifying the content.
*/
class MemorySynchronizer {
public:
void Clear();
void AddMemoryBlock(VAddr mapping, VAddr original, u32 size);
void ResizeMemoryBlock(VAddr mapping, VAddr original, u32 size);
void RemoveMemoryBlock(VAddr mapping, VAddr original);
void SynchronizeOriginalMemory();
private:
struct MemoryBlock {
VAddr mapping;
VAddr original;
u32 size;
};
std::vector<MemoryBlock> memory_blocks;
auto FindMemoryBlock(VAddr mapping, VAddr original);
};
} // namespace

View File

@ -15,7 +15,6 @@
#include "core/hle/service/gsp_gpu.h" #include "core/hle/service/gsp_gpu.h"
#include "core/hle/service/gsp_lcd.h" #include "core/hle/service/gsp_lcd.h"
#include "core/hle/service/http_c.h" #include "core/hle/service/http_c.h"
#include "core/hle/service/ldr_ro.h"
#include "core/hle/service/mic_u.h" #include "core/hle/service/mic_u.h"
#include "core/hle/service/ns_s.h" #include "core/hle/service/ns_s.h"
#include "core/hle/service/nwm_uds.h" #include "core/hle/service/nwm_uds.h"
@ -36,6 +35,7 @@
#include "core/hle/service/cfg/cfg.h" #include "core/hle/service/cfg/cfg.h"
#include "core/hle/service/hid/hid.h" #include "core/hle/service/hid/hid.h"
#include "core/hle/service/ir/ir.h" #include "core/hle/service/ir/ir.h"
#include "core/hle/service/ldr_ro/ldr_ro.h"
#include "core/hle/service/ndm/ndm.h" #include "core/hle/service/ndm/ndm.h"
#include "core/hle/service/news/news.h" #include "core/hle/service/news/news.h"
#include "core/hle/service/nim/nim.h" #include "core/hle/service/nim/nim.h"

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project // Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -15,23 +15,64 @@ namespace SRV {
static Kernel::SharedPtr<Kernel::Event> event_handle; static Kernel::SharedPtr<Kernel::Event> event_handle;
static void Initialize(Service::Interface* self) { /**
* SRV::RegisterClient service function
* Inputs:
* 0: 0x00010002
* 1: ProcessId Header (must be 0x20)
* Outputs:
* 0: 0x00010040
* 1: ResultCode
*/
static void RegisterClient(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); u32* cmd_buff = Kernel::GetCommandBuffer();
cmd_buff[1] = 0; // No error if (cmd_buff[1] != IPC::CallingPidDesc()) {
cmd_buff[0] = IPC::MakeHeader(0x0, 0x1, 0); //0x40
cmd_buff[1] = ResultCode(ErrorDescription::OS_InvalidBufferDescriptor, ErrorModule::OS,
ErrorSummary::WrongArgument, ErrorLevel::Permanent).raw;
return;
}
cmd_buff[0] = IPC::MakeHeader(0x1, 0x1, 0); //0x10040
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
LOG_WARNING(Service_SRV, "(STUBBED) called");
} }
static void GetProcSemaphore(Service::Interface* self) { /**
* SRV::EnableNotification service function
* Inputs:
* 0: 0x00020000
* Outputs:
* 0: 0x00020042
* 1: ResultCode
* 2: Translation descriptor: 0x20
* 3: Handle to semaphore signaled on process notification
*/
static void EnableNotification(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); u32* cmd_buff = Kernel::GetCommandBuffer();
// TODO(bunnei): Change to a semaphore once these have been implemented // TODO(bunnei): Change to a semaphore once these have been implemented
event_handle = Kernel::Event::Create(Kernel::ResetType::OneShot, "SRV:Event"); event_handle = Kernel::Event::Create(Kernel::ResetType::OneShot, "SRV:Event");
event_handle->Clear(); event_handle->Clear();
cmd_buff[1] = 0; // No error cmd_buff[0] = IPC::MakeHeader(0x2, 0x1, 0x2); // 0x20042
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
cmd_buff[2] = IPC::CopyHandleDesc(1);
cmd_buff[3] = Kernel::g_handle_table.Create(event_handle).MoveFrom(); cmd_buff[3] = Kernel::g_handle_table.Create(event_handle).MoveFrom();
LOG_WARNING(Service_SRV, "(STUBBED) called");
} }
/**
* SRV::GetServiceHandle service function
* Inputs:
* 0: 0x00050100
* 1-2: 8-byte UTF-8 service name
* 3: Name length
* 4: Flags (bit0: if not set, return port-handle if session-handle unavailable)
* Outputs:
* 1: ResultCode
* 3: Service handle
*/
static void GetServiceHandle(Service::Interface* self) { static void GetServiceHandle(Service::Interface* self) {
ResultCode res = RESULT_SUCCESS; ResultCode res = RESULT_SUCCESS;
u32* cmd_buff = Kernel::GetCommandBuffer(); u32* cmd_buff = Kernel::GetCommandBuffer();
@ -49,16 +90,80 @@ static void GetServiceHandle(Service::Interface* self) {
cmd_buff[1] = res.raw; cmd_buff[1] = res.raw;
} }
/**
* SRV::Subscribe service function
* Inputs:
* 0: 0x00090040
* 1: Notification ID
* Outputs:
* 0: 0x00090040
* 1: ResultCode
*/
static void Subscribe(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
u32 notification_id = cmd_buff[1];
cmd_buff[0] = IPC::MakeHeader(0x9, 0x1, 0); // 0x90040
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
LOG_WARNING(Service_SRV, "(STUBBED) called, notification_id=0x%X", notification_id);
}
/**
* SRV::Unsubscribe service function
* Inputs:
* 0: 0x000A0040
* 1: Notification ID
* Outputs:
* 0: 0x000A0040
* 1: ResultCode
*/
static void Unsubscribe(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
u32 notification_id = cmd_buff[1];
cmd_buff[0] = IPC::MakeHeader(0xA, 0x1, 0); // 0xA0040
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
LOG_WARNING(Service_SRV, "(STUBBED) called, notification_id=0x%X", notification_id);
}
/**
* SRV::PublishToSubscriber service function
* Inputs:
* 0: 0x000C0080
* 1: Notification ID
* 2: Flags (bit0: only fire if not fired, bit1: report errors)
* Outputs:
* 0: 0x000C0040
* 1: ResultCode
*/
static void PublishToSubscriber(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
u32 notification_id = cmd_buff[1];
u8 flags = cmd_buff[2] & 0xFF;
cmd_buff[0] = IPC::MakeHeader(0xC, 0x1, 0); // 0xC0040
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
LOG_WARNING(Service_SRV, "(STUBBED) called, notification_id=0x%X, flags=%u", notification_id, flags);
}
const Interface::FunctionInfo FunctionTable[] = { const Interface::FunctionInfo FunctionTable[] = {
{0x00010002, Initialize, "Initialize"}, {0x00010002, RegisterClient, "RegisterClient"},
{0x00020000, GetProcSemaphore, "GetProcSemaphore"}, {0x00020000, EnableNotification, "EnableNotification"},
{0x00030100, nullptr, "RegisterService"}, {0x00030100, nullptr, "RegisterService"},
{0x000400C0, nullptr, "UnregisterService"}, {0x000400C0, nullptr, "UnregisterService"},
{0x00050100, GetServiceHandle, "GetServiceHandle"}, {0x00050100, GetServiceHandle, "GetServiceHandle"},
{0x000600C2, nullptr, "RegisterHandle"}, {0x000600C2, nullptr, "RegisterPort"},
{0x00090040, nullptr, "Subscribe"}, {0x000700C0, nullptr, "UnregisterPort"},
{0x000B0000, nullptr, "ReceiveNotification"}, {0x00080100, nullptr, "GetPort"},
{0x000C0080, nullptr, "PublishToSubscriber"}, {0x00090040, Subscribe, "Subscribe"},
{0x000A0040, Unsubscribe, "Unsubscribe"},
{0x000B0000, nullptr, "ReceiveNotification"},
{0x000C0080, PublishToSubscriber, "PublishToSubscriber"},
{0x000D0040, nullptr, "PublishAndGetSubscriber"},
{0x000E00C0, nullptr, "IsServiceRegistered"},
}; };
//////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////
@ -66,10 +171,11 @@ const Interface::FunctionInfo FunctionTable[] = {
Interface::Interface() { Interface::Interface() {
Register(FunctionTable); Register(FunctionTable);
event_handle = nullptr;
} }
Interface::~Interface() { Interface::~Interface() {
event_handle = nullptr; event_handle = nullptr;
} }
} // namespace } // namespace SRV

View File

@ -2,8 +2,11 @@
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <chrono>
#include <cstring> #include <cstring>
#include <ctime>
#include "core/core_timing.h"
#include "core/hle/shared_page.h" #include "core/hle/shared_page.h"
//////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////
@ -12,6 +15,57 @@ namespace SharedPage {
SharedPageDef shared_page; SharedPageDef shared_page;
static int update_time_event;
/// Gets system time in 3DS format. The epoch is Jan 1900, and the unit is millisecond.
static u64 GetSystemTime() {
auto now = std::chrono::system_clock::now();
// 3DS system does't allow user to set a time before Jan 1 2000,
// so we use it as an auxiliary epoch to calculate the console time.
std::tm epoch_tm;
epoch_tm.tm_sec = 0;
epoch_tm.tm_min = 0;
epoch_tm.tm_hour = 0;
epoch_tm.tm_mday = 1;
epoch_tm.tm_mon = 0;
epoch_tm.tm_year = 100;
epoch_tm.tm_isdst = 0;
auto epoch = std::chrono::system_clock::from_time_t(std::mktime(&epoch_tm));
// 3DS console time uses Jan 1 1900 as internal epoch,
// so we use the milliseconds between 1900 and 2000 as base console time
u64 console_time = 3155673600000ULL;
// Only when system time is after 2000, we set it as 3DS system time
if (now > epoch) {
console_time += std::chrono::duration_cast<std::chrono::milliseconds>(now - epoch).count();
}
// If the system time is in daylight saving, we give an additional hour to console time
std::time_t now_time_t = std::chrono::system_clock::to_time_t(now);
std::tm* now_tm = std::localtime(&now_time_t);
if (now_tm && now_tm->tm_isdst > 0)
console_time += 60 * 60 * 1000;
return console_time;
}
static void UpdateTimeCallback(u64 userdata, int cycles_late) {
DateTime& date_time = shared_page.date_time_counter % 2 ?
shared_page.date_time_0 : shared_page.date_time_1;
date_time.date_time = GetSystemTime();
date_time.update_tick = CoreTiming::GetTicks();
date_time.tick_to_second_coefficient = g_clock_rate_arm11;
date_time.tick_offset = 0;
++shared_page.date_time_counter;
// system time is updated hourly
CoreTiming::ScheduleEvent(msToCycles(60 * 60 * 1000) - cycles_late, update_time_event);
}
void Init() { void Init() {
std::memset(&shared_page, 0, sizeof(shared_page)); std::memset(&shared_page, 0, sizeof(shared_page));
@ -19,6 +73,9 @@ void Init() {
// Some games wait until this value becomes 0x1, before asking running_hw // Some games wait until this value becomes 0x1, before asking running_hw
shared_page.unknown_value = 0x1; shared_page.unknown_value = 0x1;
update_time_event = CoreTiming::RegisterEvent("SharedPage::UpdateTimeCallback", UpdateTimeCallback);
CoreTiming::ScheduleEvent(0, update_time_event);
} }
} // namespace } // namespace

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