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 [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then
export CC=gcc-5
export CXX=g++-5
export CC=gcc-6
export CXX=g++-6
export PKG_CONFIG_PATH=$HOME/.local/lib/pkgconfig:$PKG_CONFIG_PATH
mkdir build && cd build
cmake -DCITRA_FORCE_QT4=ON ..
cmake ..
make -j4
ctest -VV -C Release

View File

@ -5,11 +5,11 @@ set -x
#if OS is linux or is not set
if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then
export CC=gcc-5
export CXX=g++-5
export CC=gcc-6
export CXX=g++-6
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
(
@ -21,6 +21,6 @@ if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then
elif [ "$TRAVIS_OS_NAME" = "osx" ]; then
brew update > /dev/null # silence the very verbose output
brew unlink cmake
brew install cmake31 qt5 sdl2 dylibbundler
brew install cmake qt5 sdl2 dylibbundler
gem install xcpretty
fi

View File

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

View File

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

View File

@ -1,6 +1,5 @@
# CMake 3.1 required for Qt5 settings to be applied automatically on
# dependent libraries and IMPORTED targets.
cmake_minimum_required(VERSION 3.1)
# CMake 3.2 required for cmake to know the right flags for CXX standard on OSX
cmake_minimum_required(VERSION 3.2)
function(download_bundled_external remote_path lib_name prefix_var)
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(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)
message(STATUS "Copying pre-commit hook")
@ -64,14 +62,12 @@ if (NOT DEFINED ARCHITECTURE)
endif()
message(STATUS "Target architecture: ${ARCHITECTURE}")
if (NOT MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -Wno-attributes")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if (ARCHITECTURE_x86_64)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.1")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msse4.1")
endif()
if (NOT MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-attributes")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
else()
# Silence "deprecation" 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 (CITRA_USE_BUNDLED_QT)
if (MSVC14 AND ARCHITECTURE_x86_64)
set(QT_VER qt-5.5-msvc2015_64)
set(QT_VER qt-5.7-msvc2015_64)
else()
message(FATAL_ERROR "No bundled Qt binaries for your toolchain. Disable CITRA_USE_BUNDLED_QT and provide your own.")
endif()
@ -201,16 +197,8 @@ if (ENABLE_QT)
set(QT_PREFIX_HINT)
endif()
if (NOT CITRA_FORCE_QT4)
find_package(Qt5 COMPONENTS Widgets OpenGL ${QT_PREFIX_HINT})
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()
find_package(Qt5 REQUIRED COMPONENTS Widgets OpenGL ${QT_PREFIX_HINT})
set(CITRA_QT_LIBS Qt5::Widgets Qt5::OpenGL)
endif()
# 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
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.
### Building

View File

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

View File

@ -3,6 +3,7 @@
# SDL2_LIBRARY, the name of the library to link against
# SDL2_FOUND, if false, do not try to link to SDL2
# 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:
# SDL2_BUILDING_LIBRARY
@ -149,6 +150,14 @@ FIND_LIBRARY(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
HINTS
$ENV{SDL2DIR}

View File

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

View File

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

View File

@ -85,12 +85,45 @@ static StereoFrame16 GenerateCurrentFrame() {
// Audio output
static bool perform_time_stretching = true;
static std::unique_ptr<AudioCore::Sink> sink;
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) {
time_stretcher.AddSamples(&frame[0][0], frame.size());
sink->EnqueueSamples(time_stretcher.Process(sink->SamplesInQueue()));
if (perform_time_stretching) {
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
@ -111,12 +144,8 @@ void Init() {
}
void Shutdown() {
time_stretcher.Flush();
while (true) {
std::vector<s16> residual_audio = time_stretcher.Process(sink->SamplesInQueue());
if (residual_audio.empty())
break;
sink->EnqueueSamples(residual_audio);
if (perform_time_stretching) {
FlushResidualStretcherAudio();
}
}

View File

@ -544,5 +544,13 @@ bool Tick();
*/
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 DSP

View File

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

View File

@ -71,14 +71,12 @@ unsigned int SDL2Sink::GetNativeSampleRate() const {
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)
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);
impl->queue.emplace_back(samples);
impl->queue.emplace_back(samples, samples + sample_count * 2);
SDL_UnlockAudioDevice(impl->audio_device_id);
}

View File

@ -18,7 +18,7 @@ public:
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;

View File

@ -23,9 +23,10 @@ public:
/**
* 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.
virtual std::size_t SamplesInQueue() const = 0;

View File

@ -17,11 +17,16 @@
#include <getopt.h>
#endif
#ifdef _WIN32
#include <Windows.h>
#endif
#include "common/logging/log.h"
#include "common/logging/backend.h"
#include "common/logging/filter.h"
#include "common/scm_rev.h"
#include "common/scope_exit.h"
#include "common/string_util.h"
#include "core/settings.h"
#include "core/system.h"
@ -55,6 +60,15 @@ int main(int argc, char **argv) {
bool use_gdbstub = Settings::values.use_gdbstub;
u32 gdb_port = static_cast<u32>(Settings::values.gdbstub_port);
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;
static struct option long_options[] = {
@ -86,11 +100,19 @@ int main(int argc, char **argv) {
return 0;
}
} else {
#ifdef _WIN32
boot_filename = Common::UTF16ToUTF8(argv_w[optind]);
#else
boot_filename = argv[optind];
#endif
optind++;
}
}
#ifdef _WIN32
LocalFree(argv_w);
#endif
Log::Filter log_filter(Log::Level::Debug);
Log::SetFilter(&log_filter);

View File

@ -68,9 +68,10 @@ void Config::ReadValues() {
Settings::values.frame_skip = sdl2_config->GetInteger("Core", "frame_skip", 0);
// 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_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_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 1.0);
@ -78,6 +79,7 @@ void Config::ReadValues() {
// Audio
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
Settings::values.use_virtual_sd = sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);

View File

@ -44,17 +44,21 @@ frame_skip =
[Renderer]
# Whether to use software or hardware rendering.
# 0 (default): Software, 1: Hardware
# 0: Software, 1 (default): Hardware
use_hw_renderer =
# 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 =
# Whether to use native 3DS screen resolution or to scale rendering resolution to the displayed screen size.
# 0 (default): Native, 1: Scaled
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.
# Must be in range of 0.0-1.0. Defaults to 1.0 for all.
bg_red =
@ -66,6 +70,12 @@ bg_green =
# auto (default): Auto-select, null: No audio output, sdl2: SDL2 (if available)
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]
# Whether to create a virtual SD card.
# 1 (default): Yes, 0: No

View File

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

View File

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

View File

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

View File

@ -107,36 +107,13 @@ private:
};
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);
setWindowTitle(QString::fromStdString(window_title));
keyboard_id = KeyMap::NewDeviceId();
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()
@ -281,6 +258,40 @@ void GRenderWindow::OnClientAreaResized(unsigned width, unsigned 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) {
setMinimumSize(minimal_size.first, minimal_size.second);
}

View File

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

View File

@ -3,14 +3,11 @@
// Refer to the license.txt file included.
#include <QSettings>
#include <QString>
#include <QStringList>
#include "citra_qt/config.h"
#include "citra_qt/ui_settings.h"
#include "common/file_util.h"
#include "core/settings.h"
Config::Config() {
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
@ -21,7 +18,7 @@ Config::Config() {
Reload();
}
static const std::array<QVariant, Settings::NativeInput::NUM_INPUTS> defaults = {
const std::array<QVariant, Settings::NativeInput::NUM_INPUTS> Config::defaults = {
// directly mapped keys
Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X,
Qt::Key_Q, Qt::Key_W, Qt::Key_1, Qt::Key_2,
@ -48,9 +45,10 @@ void Config::ReadValues() {
qt_config->endGroup();
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_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_green = qt_config->value("bg_green", 1.0).toFloat();
@ -59,6 +57,7 @@ void Config::ReadValues() {
qt_config->beginGroup("Audio");
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->beginGroup("Data Storage");
@ -109,7 +108,7 @@ void Config::ReadValues() {
UISettings::values.shortcuts.emplace_back(
UISettings::Shortcut(group + "/" + hotkey,
UISettings::ContextualShortcut(qt_config->value("KeySeq").toString(),
qt_config->value("Context").toInt())));
qt_config->value("Context").toInt())));
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_shader_jit", Settings::values.use_shader_jit);
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
qt_config->setValue("bg_red", (double)Settings::values.bg_red);
@ -151,6 +151,7 @@ void Config::SaveValues() {
qt_config->beginGroup("Audio");
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->beginGroup("Data Storage");
@ -191,7 +192,7 @@ void Config::SaveValues() {
qt_config->endGroup();
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 + "/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
// Refer to the license.txt file included.
#pragma once
#include <string>
#include <QVariant>
#include "core/settings.h"
class QSettings;
@ -20,4 +23,5 @@ public:
void Reload();
void Save();
static const std::array<QVariant, Settings::NativeInput::NUM_INPUTS> defaults;
};

View File

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

View File

@ -36,9 +36,12 @@ void ConfigureAudio::setConfiguration() {
}
}
ui->output_sink_combo_box->setCurrentIndex(new_sink_index);
ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching);
}
void ConfigureAudio::applyConfiguration() {
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();
}

View File

@ -25,6 +25,16 @@
</item>
</layout>
</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>
</widget>
</item>

View File

@ -19,13 +19,13 @@ ConfigureDebug::~ConfigureDebug() {
}
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->setValue(Settings::values.gdbstub_port);
}
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::Apply();
}

View File

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

View File

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

View File

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

View File

@ -20,20 +20,14 @@ ConfigureGeneral::~ConfigureGeneral() {
}
void ConfigureGeneral::setConfiguration() {
ui->toogle_deepscan->setChecked(UISettings::values.gamedir_deepscan);
ui->toogle_check_exit->setChecked(UISettings::values.confirm_before_closing);
ui->toggle_deepscan->setChecked(UISettings::values.gamedir_deepscan);
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
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() {
UISettings::values.gamedir_deepscan = ui->toogle_deepscan->isChecked();
UISettings::values.confirm_before_closing = ui->toogle_check_exit->isChecked();
UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked();
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
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();
}

View File

@ -25,14 +25,14 @@
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="toogle_deepscan">
<widget class="QCheckBox" name="toggle_deepscan">
<property name="text">
<string>Recursive scan for game folder</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="toogle_check_exit">
<widget class="QCheckBox" name="toggle_check_exit">
<property name="text">
<string>Confirm exit while emulation is running</string>
</property>
@ -106,40 +106,6 @@
</layout>
</widget>
</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>
<widget class="QGroupBox" name="groupBox_3">
<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
/// runs while the widget is visible.
QTimer update_timer;
/// Scale the coordinate system appropriately when physical DPI != logical DPI.
qreal x_scale, y_scale;
/// Scale the coordinate system appropriately when dpi != 96.
qreal x_scale = 1.0, y_scale = 1.0;
};
#endif
@ -222,15 +222,14 @@ MicroProfileWidget::MicroProfileWidget(QWidget* parent) : QWidget(parent) {
MicroProfileInitUI();
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) {
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.setBackground(Qt::black);
@ -241,7 +240,7 @@ void MicroProfileWidget::paintEvent(QPaintEvent* ev) {
painter.setFont(font);
mp_painter = &painter;
MicroProfileDraw(rect().width(), rect().height());
MicroProfileDraw(rect().width() / x_scale, rect().height() / y_scale);
mp_painter = nullptr;
}

View File

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

View File

@ -243,7 +243,9 @@ bool GMainWindow::InitializeSystem() {
if (emu_thread != nullptr)
ShutdownGame();
render_window->InitRenderTarget();
render_window->MakeCurrent();
if (!gladLoadGL()) {
QMessageBox::critical(this, tr("Error while starting Citra!"),
tr("Failed to initialize the video core!\n\n"
@ -508,11 +510,12 @@ void GMainWindow::ToggleWindowMode() {
}
void GMainWindow::OnConfigure() {
ConfigureDialog configureDialog(this);
ConfigureDialog configureDialog(this, emulation_running);
auto result = configureDialog.exec();
if (result == QDialog::Accepted)
{
configureDialog.applyConfiguration();
render_window->ReloadSetKeymaps();
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) {
new_x = std::max(new_x, framebuffer_layout.bottom_screen.left);
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) {
ASSERT(width > 0);
ASSERT(height > 0);
// When hiding the widget, the function receives a size of 0
if (width == 0) width = 1;
if (height == 0) height = 1;
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());
@ -472,7 +472,7 @@ bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directo
continue;
unsigned ret_entries = 0;
if (!callback(&ret_entries, directory, virtual_name, recursion)) {
if (!callback(&ret_entries, directory, virtual_name)) {
callback_error = true;
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)
{
const auto callback = [&parent_entry](unsigned* num_entries_out,
const std::string& directory,
const std::string& virtual_name,
unsigned int recursion) -> bool {
const auto callback = [recursion, &parent_entry](unsigned* num_entries_out,
const std::string& directory,
const std::string& virtual_name) -> bool {
FSTEntry entry;
entry.virtualName = 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;
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)
{
const static auto callback = [](unsigned* num_entries_out,
const std::string& directory,
const std::string& virtual_name,
unsigned int recursion) -> bool {
const auto callback = [recursion](unsigned* num_entries_out,
const std::string& directory,
const std::string& virtual_name) -> bool {
std::string new_path = directory + DIR_SEP_CHR + virtual_name;
if (IsDirectory(new_path)) {
@ -546,7 +544,7 @@ bool DeleteDirRecursively(const std::string &directory, unsigned int recursion)
return Delete(new_path);
};
if (!ForeachDirectoryEntry(nullptr, directory, callback, recursion))
if (!ForeachDirectoryEntry(nullptr, directory, callback))
return false;
// 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 directory the path to the enclosing directory
* @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
*/
using DirectoryEntryCallable = std::function<bool(unsigned* num_entries_out,
const std::string& directory,
const std::string& virtual_name,
unsigned int recursion)>;
const std::string& virtual_name)>;
/**
* 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 directory the directory to scan
* @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
*/
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.

View File

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

View File

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

View File

@ -32,6 +32,9 @@ public:
Run(1);
}
/// Clear all instruction cache
virtual void ClearInstructionCache() = 0;
/**
* Set the Program Counter to an address
* @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_interpreter.h"
#include "core/arm/dyncom/arm_dyncom_run.h"
#include "core/arm/dyncom/arm_dyncom_trans.h"
#include "core/core.h"
#include "core/core_timing.h"
@ -23,6 +24,11 @@ ARM_DynCom::ARM_DynCom(PrivilegeMode initial_mode) {
ARM_DynCom::~ARM_DynCom() {
}
void ARM_DynCom::ClearInstructionCache() {
state->instruction_cache.clear();
trans_cache_buf_top = 0;
}
void ARM_DynCom::SetPC(u32 pc) {
state->Reg[15] = pc;
}

View File

@ -21,6 +21,8 @@ public:
ARM_DynCom(PrivilegeMode initial_mode);
~ARM_DynCom();
void ClearInstructionCache() override;
void SetPC(u32 pc) override;
u32 GetPC() 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
// Refer to the license.txt file included.
#include <cstddef>
// We can provide simple Thumb simulation by decoding the Thumb instruction into its corresponding
// 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
}
} else if ((tinstr & 0x0F00) == 0x0a00) {
static const u32 subset[3] = {
static const u32 subset[4] = {
0xE6BF0F30, // REV
0xE6BF0FB0, // REV16
0, // undefined
0xE6FF0FB0, // REVSH
};
*ainstr = subset[BITS(tinstr, 6, 7)] // base
| (BITS(tinstr, 0, 2) << 12) // Rd
| BITS(tinstr, 3, 5); // Rm
size_t subset_index = BITS(tinstr, 6, 7);
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 {
static const u32 subset[4] = {
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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8);
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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8);
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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8);
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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8);
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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8);
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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8);
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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8);
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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8);
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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8);
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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
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);
@ -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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
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);
@ -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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8);
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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8);
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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8);
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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8);
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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8);
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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8);
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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8);
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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->dp_operation = BIT(inst, 8);
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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->to_arm = BIT(inst, 20) == 1;
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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->reg = BITS(inst, 16, 19);
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->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->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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->reg = BITS(inst, 16, 19);
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->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->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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->to_arm = BIT(inst, 20) == 1;
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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->to_arm = BIT(inst, 20) == 1;
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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->single = BIT(inst, 8) == 0;
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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
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);
@ -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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->single = BIT(inst, 8) == 0;
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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
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));
@ -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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->single = BIT(inst, 8) == 0;
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->idx = index;
inst_base->br = NON_BRANCH;
inst_base->br = TransExtData::NON_BRANCH;
inst_cream->single = BIT(inst, 8) == 0;
inst_cream->add = BIT(inst, 23);

View File

@ -26,7 +26,7 @@
extern int g_clock_rate_arm11;
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) {

View File

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

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 {
Unknown = 0,
Port = 1,
Session = 2,
Event = 3,
Mutex = 4,
@ -48,6 +48,8 @@ enum class HandleType : u32 {
Timer = 11,
ResourceLimit = 12,
CodeSet = 13,
ClientPort = 14,
ServerPort = 15,
};
enum {
@ -72,6 +74,7 @@ public:
bool IsWaitable() const {
switch (GetHandleType()) {
case HandleType::Session:
case HandleType::ServerPort:
case HandleType::Event:
case HandleType::Mutex:
case HandleType::Thread:
@ -80,13 +83,13 @@ public:
return true;
case HandleType::Unknown:
case HandleType::Port:
case HandleType::SharedMemory:
case HandleType::Redirection:
case HandleType::Process:
case HandleType::AddressArbiter:
case HandleType::ResourceLimit:
case HandleType::CodeSet:
case HandleType::ClientPort:
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 {
constexpr u32 MakeHeader(u16 command_id, unsigned int regular_params, unsigned int translate_params) {
return ((u32)command_id << 16) | (((u32)regular_params & 0x3F) << 6) | (((u32)translate_params & 0x3F) << 0);
enum DescriptorType : u32 {
// 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) {
return 0x0 | ((num_handles - 1) << 26);
union Header {
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) {
return 0x10 | ((num_handles - 1) << 26);
constexpr u32 MoveHandleDesc(u32 num_handles = 1) {
return MoveHandle | ((num_handles - 1) << 26);
}
constexpr u32 CopyHandleDesc(u32 num_handles = 1) {
return CopyHandle | ((num_handles - 1) << 26);
}
constexpr u32 CallingPidDesc() {
return 0x20;
return CallingPid;
}
constexpr u32 StaticBufferDesc(u32 size, unsigned int buffer_id) {
return 0x2 | (size << 14) | ((buffer_id & 0xF) << 10);
constexpr bool isHandleDescriptor(u32 descriptor) {
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 {
R = 2,
W = 4,
R = 1,
W = 2,
RW = R | W,
};
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 {
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
* @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);
}

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
* @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
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
@ -542,8 +591,12 @@ void Reschedule() {
HLE::DoneRescheduling();
// Don't bother switching to the same thread
if (next == cur)
// Don't bother switching to the same thread.
// 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;
if (cur && next) {

View File

@ -20,6 +20,7 @@ enum class ErrorDescription : u32 {
WrongPermission = 46,
OS_InvalidBufferDescriptor = 48,
WrongAddress = 53,
FS_ArchiveNotMounted = 101,
FS_NotFound = 120,
FS_AlreadyExists = 190,
FS_InvalidOpenFlags = 230,
@ -135,15 +136,28 @@ enum class ErrorModule : u32 {
MCU = 72,
NS = 73,
News = 74,
RO_1 = 75,
RO = 75,
GD = 76,
CardSPI = 77,
EC = 78,
RO_2 = 79,
WebBrowser = 80,
Test = 81,
ENC = 82,
PIA = 83,
WebBrowser = 79,
Test = 80,
ENC = 81,
PIA = 82,
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,
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
static u8 unknown_ns_state_field;
static ScreencapPostPermission screen_capture_post_permission;
/// Parameter data to be returned in the next call to Glance/ReceiveParameter
static MessageParameter next_parameter;
@ -51,7 +53,7 @@ void Initialize(Service::Interface* self) {
u32 app_id = cmd_buff[1];
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[4] = Kernel::g_handle_table.Create(parameter_event).MoveFrom();
@ -70,15 +72,17 @@ void Initialize(Service::Interface* self) {
void GetSharedFont(Service::Interface* self) {
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.
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) {
BCFNT::RelocateSharedFont(shared_font_mem, SHARED_FONT_VADDR, target_address);
BCFNT::RelocateSharedFont(shared_font_mem, target_address);
shared_font_relocated = true;
}
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)
// and searches for an allocation of the same size as the Shared Font.
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();
}
@ -382,23 +386,23 @@ void StartLibraryApplet(Service::Interface* self) {
cmd_buff[1] = applet->Start(parameter).raw;
}
void SetNSStateField(Service::Interface* self) {
void SetScreenCapPostPermission(Service::Interface* self) {
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[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();
cmd_buff[0] = IPC::MakeHeader(0x56, 2, 0);
cmd_buff[1] = RESULT_SUCCESS.raw;
cmd_buff[8] = unknown_ns_state_field;
LOG_WARNING(Service_APT, "(STUBBED) unknown_ns_state_field=%u", unknown_ns_state_field);
cmd_buff[2] = static_cast<u32>(screen_capture_post_permission);
LOG_WARNING(Service_APT, "(STUBBED) screen_capture_post_permission=%u", screen_capture_post_permission);
}
void GetAppletInfo(Service::Interface* self) {
@ -493,6 +497,7 @@ void Init() {
cpu_percent = 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.
notification_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "APT_U:Notification");

View File

@ -66,6 +66,8 @@ enum class AppletId : u32 {
InstructionManual = 0x115,
Notifications = 0x116,
Miiverse = 0x117,
MiiversePost = 0x118,
AmiiboSettings = 0x119,
SoftwareKeyboard1 = 0x201,
Ed1 = 0x202,
PnoteApp = 0x204,
@ -78,6 +80,12 @@ enum class AppletId : u32 {
AnyLibraryApplet = 0x400,
SoftwareKeyboard2 = 0x401,
Ed2 = 0x402,
PnoteApp2 = 0x404,
SnoteApp2 = 0x405,
Error2 = 0x406,
Mint2 = 0x407,
Extrapad2 = 0x408,
Memolib2 = 0x409,
};
enum class StartupArgumentType : u32 {
@ -86,6 +94,13 @@ enum class StartupArgumentType : u32 {
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
void SendParameter(const MessageParameter& parameter);
@ -375,25 +390,24 @@ void StartLibraryApplet(Service::Interface* self);
void GetStartupArgument(Service::Interface* self);
/**
* APT::SetNSStateField service function
* APT::SetScreenCapPostPermission service function
* Inputs:
* 1 : u8 NS state field
* 0 : Header Code[0x00550040]
* 1 : u8 The screenshot posting permission
* Outputs:
* 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:
* 1 : Result of function, 0 on success, otherwise error code
* 8 : u8 NS state field
* Note:
* This returns a u8 NS state field(which can be set by cmd 0x00550040), at cmdreply+8.
* 2 : u8 The screenshot posting permission
*/
void GetNSStateField(Service::Interface* self);
void GetScreenCapPostPermission(Service::Interface* self);
/**
* APT::CheckNew3DSApp service function

View File

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

View File

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

View File

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

View File

@ -9,60 +9,97 @@ namespace Service {
namespace APT {
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;
u8* data = shared_font->GetPointer(SharedFontStartOffset);
const u8* cfnt_ptr = shared_font->GetPointer(SharedFontStartOffset);
CFNT cfnt;
memcpy(&cfnt, data, sizeof(cfnt));
memcpy(&cfnt, cfnt_ptr, sizeof(cfnt));
// Advance past the header
data = shared_font->GetPointer(SharedFontStartOffset + cfnt.header_size);
u32 assumed_cmap_offset = 0;
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) {
const u8* data = shared_font->GetPointer(current_offset);
u32 section_size = 0;
if (memcmp(data, "FINF", 4) == 0) {
SectionHeader section_header;
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;
memcpy(&finf, data, sizeof(finf));
section_size = finf.section_size;
// Relocate the offsets in the FINF section
finf.cmap_offset += new_address - previous_address;
finf.cwdh_offset += new_address - previous_address;
finf.tglp_offset += new_address - previous_address;
finf.cmap_offset += offset;
finf.cwdh_offset += offset;
finf.tglp_offset += offset;
memcpy(data, &finf, sizeof(finf));
} else if (memcmp(data, "CMAP", 4) == 0) {
} else if (memcmp(section_header.magic, "CMAP", 4) == 0) {
BCFNT::CMAP cmap;
memcpy(&cmap, data, sizeof(cmap));
section_size = cmap.section_size;
// 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));
} else if (memcmp(data, "CWDH", 4) == 0) {
} else if (memcmp(section_header.magic, "CWDH", 4) == 0) {
BCFNT::CWDH cwdh;
memcpy(&cwdh, data, sizeof(cwdh));
section_size = cwdh.section_size;
// 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));
} else if (memcmp(data, "TGLP", 4) == 0) {
} else if (memcmp(section_header.magic, "TGLP", 4) == 0) {
BCFNT::TGLP tglp;
memcpy(&tglp, data, sizeof(tglp));
section_size = tglp.section_size;
// 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));
}
data += section_size;
current_offset += section_header.section_size;
}
}

View File

@ -22,6 +22,11 @@ struct CFNT {
u32_le num_blocks;
};
struct SectionHeader {
u8 magic[4];
u32_le section_size;
};
struct FINF {
u8 magic[4];
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 previous_address Previous address at which the offsets in the structure were based.
* @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 APT

View File

@ -51,7 +51,7 @@ void GetVsyncInterruptEvent(Service::Interface* self) {
cmd_buff[0] = IPC::MakeHeader(0x5, 1, 2);
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();
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[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();
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[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();
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");
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 {
char16_t username[10]; ///< Exactly 20 bytes long, padded with zeros at the end if necessary
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 UsernameBlock CONSOLE_USERNAME_BLOCK = { u"CITRA", 0, 0 };
static const BirthdayBlock PROFILE_BIRTHDAY = { 3, 25 }; // March 25th, 2014
/// TODO(Subv): Find out what this actually is
static const u8 SOUND_OUTPUT_MODE = 2;
static const u8 SOUND_OUTPUT_MODE = SOUND_SURROUND;
static const u8 UNITED_STATES_COUNTRY_ID = 49;
/// TODO(Subv): Find what the other bytes are
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());
}
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) {
u32* cmd_buff = Kernel::GetCommandBuffer();
cmd_buff[1] = Service::CFG::UpdateConfigNANDSavegame().raw;
@ -234,13 +263,13 @@ void FormatConfig(Service::Interface* self) {
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
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),
[&](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)) {
@ -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);
}
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) {
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);
}
void* pointer;
// The data is located in the block header itself if the size is less than 4 bytes
if (itr->size <= 4)
memcpy(output, &itr->offset_or_data, itr->size);
pointer = &itr->offset_or_data;
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;
}
@ -336,25 +386,25 @@ ResultCode FormatConfig() {
res = CreateConfigInfoBlk(0x00030001, 0x8, 0xE, zero_buffer);
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;
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;
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;
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;
res = CreateConfigInfoBlk(0x000A0001, sizeof(PROFILE_BIRTHDAY), 0xE, &PROFILE_BIRTHDAY);
res = CreateConfigInfoBlk(BirthdayBlockID, sizeof(PROFILE_BIRTHDAY), 0xE, &PROFILE_BIRTHDAY);
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;
res = CreateConfigInfoBlk(0x000B0000, sizeof(COUNTRY_INFO), 0xE, &COUNTRY_INFO);
res = CreateConfigInfoBlk(CountryInfoBlockID, sizeof(COUNTRY_INFO), 0xE, &COUNTRY_INFO);
if (!res.IsSuccess()) return res;
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]);
}
// 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;
// 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;
// 0x000B0003 - Unknown, related to country/address (zip code?)
@ -382,10 +432,10 @@ ResultCode FormatConfig() {
if (!res.IsSuccess()) return res;
// 0x000D0000 - Accepted EULA version
res = CreateConfigInfoBlk(0x000D0000, 0x4, 0xE, zero_buffer);
res = CreateConfigInfoBlk(EULAVersionBlockID, 0x4, 0xE, zero_buffer);
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;
// 0x00170000 - Unknown
@ -399,11 +449,7 @@ ResultCode FormatConfig() {
return RESULT_SUCCESS;
}
void Init() {
AddService(new CFG_I_Interface);
AddService(new CFG_S_Interface);
AddService(new CFG_U_Interface);
ResultCode LoadConfigNANDSaveFile() {
// Open the SystemSaveData archive 0x00010017
FileSys::Path archive_path(cfg_system_savedata_id);
auto archive_result = Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SystemSaveData, archive_path);
@ -431,14 +477,75 @@ void Init() {
if (config_result.Succeeded()) {
auto config = config_result.MoveFrom();
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 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 Service

View File

@ -5,6 +5,7 @@
#pragma once
#include <array>
#include <string>
#include "common/common_types.h"
@ -35,7 +36,14 @@ enum SystemLanguage {
LANGUAGE_KO = 7,
LANGUAGE_NL = 8,
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
@ -177,6 +185,22 @@ void GetConfigInfoBlk2(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
* Inputs:
@ -205,7 +229,19 @@ void FormatConfig(Service::Interface* self);
* @param output A pointer where we will write the read data
* @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.
@ -236,11 +272,70 @@ ResultCode UpdateConfigNANDSavegame();
*/
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
void Init();
/// Shutdown the config service
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 Service

View File

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

View File

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

View File

@ -51,7 +51,7 @@ void Initialize(Service::Interface* self) {
mutex = Kernel::Mutex::Create(false);
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[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,
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
enum class FileCommand : u32 {
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
* 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;
@ -292,7 +296,7 @@ ResultVal<ArchiveHandle> OpenArchive(ArchiveIdCode id_code, FileSys::Path& archi
ResultCode CloseArchive(ArchiveHandle handle) {
if (handle_map.erase(handle) == 0)
return ERR_INVALID_HANDLE;
return ERR_INVALID_ARCHIVE_HANDLE;
else
return RESULT_SUCCESS;
}
@ -314,7 +318,7 @@ ResultVal<Kernel::SharedPtr<File>> OpenFileFromArchive(ArchiveHandle archive_han
const FileSys::Path& path, const FileSys::Mode mode) {
ArchiveBackend* archive = GetArchive(archive_handle);
if (archive == nullptr)
return ERR_INVALID_HANDLE;
return ERR_INVALID_ARCHIVE_HANDLE;
auto backend = archive->OpenFile(path, mode);
if (backend.Failed())
@ -327,7 +331,7 @@ ResultVal<Kernel::SharedPtr<File>> OpenFileFromArchive(ArchiveHandle archive_han
ResultCode DeleteFileFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) {
ArchiveBackend* archive = GetArchive(archive_handle);
if (archive == nullptr)
return ERR_INVALID_HANDLE;
return ERR_INVALID_ARCHIVE_HANDLE;
return archive->DeleteFile(path);
}
@ -337,7 +341,7 @@ ResultCode RenameFileBetweenArchives(ArchiveHandle src_archive_handle, const Fil
ArchiveBackend* src_archive = GetArchive(src_archive_handle);
ArchiveBackend* dest_archive = GetArchive(dest_archive_handle);
if (src_archive == nullptr || dest_archive == nullptr)
return ERR_INVALID_HANDLE;
return ERR_INVALID_ARCHIVE_HANDLE;
if (src_archive == dest_archive) {
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) {
ArchiveBackend* archive = GetArchive(archive_handle);
if (archive == nullptr)
return ERR_INVALID_HANDLE;
return ERR_INVALID_ARCHIVE_HANDLE;
if (archive->DeleteDirectory(path))
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) {
ArchiveBackend* archive = GetArchive(archive_handle);
if (archive == nullptr)
return ERR_INVALID_HANDLE;
return ERR_INVALID_ARCHIVE_HANDLE;
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) {
ArchiveBackend* archive = GetArchive(archive_handle);
if (archive == nullptr)
return ERR_INVALID_HANDLE;
return ERR_INVALID_ARCHIVE_HANDLE;
if (archive->CreateDirectory(path))
return RESULT_SUCCESS;
@ -388,7 +392,7 @@ ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle, cons
ArchiveBackend* src_archive = GetArchive(src_archive_handle);
ArchiveBackend* dest_archive = GetArchive(dest_archive_handle);
if (src_archive == nullptr || dest_archive == nullptr)
return ERR_INVALID_HANDLE;
return ERR_INVALID_ARCHIVE_HANDLE;
if (src_archive == dest_archive) {
if (src_archive->RenameDirectory(src_path, dest_path))
@ -408,7 +412,7 @@ ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle a
const FileSys::Path& path) {
ArchiveBackend* archive = GetArchive(archive_handle);
if (archive == nullptr)
return ERR_INVALID_HANDLE;
return ERR_INVALID_ARCHIVE_HANDLE;
std::unique_ptr<FileSys::DirectoryBackend> backend = archive->OpenDirectory(path);
if (backend == nullptr) {
@ -423,7 +427,7 @@ ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle a
ResultVal<u64> GetFreeBytesInArchive(ArchiveHandle archive_handle) {
ArchiveBackend* archive = GetArchive(archive_handle);
if (archive == nullptr)
return ERR_INVALID_HANDLE;
return ERR_INVALID_ARCHIVE_HANDLE;
return MakeResult<u64>(archive->GetFreeBytes());
}
@ -516,12 +520,7 @@ ResultCode CreateSystemSaveData(u32 high, u32 low) {
return RESULT_SUCCESS;
}
/// Initialize archives
void ArchiveInit() {
next_handle = 1;
AddService(new FS::Interface);
void RegisterArchiveTypes() {
// TODO(Subv): Add the other archive types (see here for the known types:
// http://3dbrew.org/wiki/FS:OpenArchive#Archive_idcodes).
@ -558,10 +557,23 @@ void ArchiveInit() {
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
void ArchiveShutdown() {
handle_map.clear();
id_code_map.clear();
UnregisterArchiveTypes();
}
} // namespace FS

View File

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

View File

@ -645,20 +645,19 @@ static void DeleteSystemSaveData(Service::Interface* self) {
* FS_User::CreateSystemSaveData service function.
* Inputs:
* 0 : 0x08560240
* 1 : High word of the SystemSaveData id to create
* 2 : Low word of the SystemSaveData id to create
* 3 : Unknown
* 4 : Unknown
* 5 : Unknown
* 6 : Unknown
* 7 : Unknown
* 8 : Unknown
* 9 : Unknown (Memory address)
* 1 : u8 MediaType of the system save data
* 2 : SystemSaveData id to create
* 3 : Total size
* 4 : Block size
* 5 : Number of directories
* 6 : Number of files
* 7 : Directory bucket count
* 8 : File bucket count
* 9 : u8 Whether to duplicate data or not
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
static void CreateSystemSaveData(Service::Interface* self) {
// TODO(Subv): Figure out the other parameters.
u32* cmd_buff = Kernel::GetCommandBuffer();
u32 savedata_high = cmd_buff[1];
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;
}
/**
* 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.
* Inputs:
@ -820,7 +851,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x080D0144, nullptr, "ControlArchive"},
{0x080E0080, CloseArchive, "CloseArchive"},
{0x080F0180, FormatThisUserSaveData, "FormatThisUserSaveData"},
{0x08100200, nullptr, "CreateSystemSaveData"},
{0x08100200, CreateLegacySystemSaveData, "CreateLegacySystemSaveData"},
{0x08110040, nullptr, "DeleteSystemSaveData"},
{0x08120080, GetFreeBytes, "GetFreeBytes"},
{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_lcd.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/ns_s.h"
#include "core/hle/service/nwm_uds.h"
@ -36,6 +35,7 @@
#include "core/hle/service/cfg/cfg.h"
#include "core/hle/service/hid/hid.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/news/news.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
// Refer to the license.txt file included.
@ -15,23 +15,64 @@ namespace SRV {
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();
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();
// TODO(bunnei): Change to a semaphore once these have been implemented
event_handle = Kernel::Event::Create(Kernel::ResetType::OneShot, "SRV:Event");
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();
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) {
ResultCode res = RESULT_SUCCESS;
u32* cmd_buff = Kernel::GetCommandBuffer();
@ -49,16 +90,80 @@ static void GetServiceHandle(Service::Interface* self) {
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[] = {
{0x00010002, Initialize, "Initialize"},
{0x00020000, GetProcSemaphore, "GetProcSemaphore"},
{0x00030100, nullptr, "RegisterService"},
{0x000400C0, nullptr, "UnregisterService"},
{0x00050100, GetServiceHandle, "GetServiceHandle"},
{0x000600C2, nullptr, "RegisterHandle"},
{0x00090040, nullptr, "Subscribe"},
{0x000B0000, nullptr, "ReceiveNotification"},
{0x000C0080, nullptr, "PublishToSubscriber"},
{0x00010002, RegisterClient, "RegisterClient"},
{0x00020000, EnableNotification, "EnableNotification"},
{0x00030100, nullptr, "RegisterService"},
{0x000400C0, nullptr, "UnregisterService"},
{0x00050100, GetServiceHandle, "GetServiceHandle"},
{0x000600C2, nullptr, "RegisterPort"},
{0x000700C0, nullptr, "UnregisterPort"},
{0x00080100, nullptr, "GetPort"},
{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() {
Register(FunctionTable);
event_handle = nullptr;
}
Interface::~Interface() {
event_handle = nullptr;
}
} // namespace
} // namespace SRV

View File

@ -2,8 +2,11 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <chrono>
#include <cstring>
#include <ctime>
#include "core/core_timing.h"
#include "core/hle/shared_page.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
@ -12,6 +15,57 @@ namespace SharedPage {
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() {
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
shared_page.unknown_value = 0x1;
update_time_event = CoreTiming::RegisterEvent("SharedPage::UpdateTimeCallback", UpdateTimeCallback);
CoreTiming::ScheduleEvent(0, update_time_event);
}
} // namespace

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