diff options
50 files changed, 2093 insertions, 134 deletions
diff --git a/.travis-deps.sh b/.travis-deps.sh index 10b69f5c4..aad9074bf 100755 --- a/.travis-deps.sh +++ b/.travis-deps.sh @@ -9,7 +9,7 @@ if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then      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 diff --git a/.travis-upload.sh b/.travis-upload.sh index 1ad8f5e5e..7838bf079 100755 --- a/.travis-upload.sh +++ b/.travis-upload.sh @@ -23,6 +23,97 @@ 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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a436b981..6ac3df0e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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}") @@ -63,14 +62,12 @@ if (NOT DEFINED ARCHITECTURE)  endif()  message(STATUS "Target architecture: ${ARCHITECTURE}") +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +  if (NOT MSVC) -    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -Wno-attributes") +    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-attributes")      set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") - -    if (ARCHITECTURE_x86_64) -        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.1") -        set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msse4.1") -    endif()  else()      # Silence "deprecation" warnings      add_definitions(/D_CRT_SECURE_NO_WARNINGS /D_CRT_NONSTDC_NO_DEPRECATE /D_SCL_SECURE_NO_WARNINGS) @@ -184,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() diff --git a/externals/cmake-modules/FindSDL2.cmake b/externals/cmake-modules/FindSDL2.cmake index 9b8daa0d1..22ce752c5 100644 --- a/externals/cmake-modules/FindSDL2.cmake +++ b/externals/cmake-modules/FindSDL2.cmake @@ -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} diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp index e01216734..128b9a16d 100644 --- a/src/citra/citra.cpp +++ b/src/citra/citra.cpp @@ -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); diff --git a/src/citra/config.cpp b/src/citra/config.cpp index 22cb51ea8..e832ec58d 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -68,7 +68,7 @@ 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); diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index 4e63f3206..6249ef9e2 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -44,11 +44,11 @@ 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. diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 43a766053..4402ad995 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -22,6 +22,8 @@ set(SRCS              configure_debug.cpp              configure_dialog.cpp              configure_general.cpp +            configure_system.cpp +            configure_input.cpp              game_list.cpp              hotkeys.cpp              main.cpp @@ -52,6 +54,8 @@ set(HEADERS              configure_debug.h              configure_dialog.h              configure_general.h +            configure_system.h +            configure_input.h              game_list.h              game_list_p.h              hotkeys.h @@ -69,6 +73,8 @@ set(UIS              configure_audio.ui              configure_debug.ui              configure_general.ui +            configure_system.ui +            configure_input.ui              hotkeys.ui              main.ui              ) diff --git a/src/citra_qt/Info.plist b/src/citra_qt/Info.plist index 4c89e128b..7d46b39d1 100644 --- a/src/citra_qt/Info.plist +++ b/src/citra_qt/Info.plist @@ -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> diff --git a/src/citra_qt/config.cpp b/src/citra_qt/config.cpp index ba7edaff9..93c6a6e41 100644 --- a/src/citra_qt/config.cpp +++ b/src/citra_qt/config.cpp @@ -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,7 +45,7 @@ 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(); @@ -109,7 +106,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();          } @@ -191,7 +188,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);      } diff --git a/src/citra_qt/config.h b/src/citra_qt/config.h index dd0b2ef0b..0cbdb707f 100644 --- a/src/citra_qt/config.h +++ b/src/citra_qt/config.h @@ -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;  }; diff --git a/src/citra_qt/configure.ui b/src/citra_qt/configure.ui index e1624bbef..15fe17323 100644 --- a/src/citra_qt/configure.ui +++ b/src/citra_qt/configure.ui @@ -24,7 +24,12 @@         <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> @@ -58,6 +63,12 @@     <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>     <header>configure_audio.h</header> @@ -69,6 +80,12 @@     <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>   </customwidgets>   <resources/>   <connections> diff --git a/src/citra_qt/configure_dialog.cpp b/src/citra_qt/configure_dialog.cpp index 2f0317fe0..459fac4bb 100644 --- a/src/citra_qt/configure_dialog.cpp +++ b/src/citra_qt/configure_dialog.cpp @@ -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,15 @@ 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->audioTab->applyConfiguration();      ui->debugTab->applyConfiguration();  } diff --git a/src/citra_qt/configure_dialog.h b/src/citra_qt/configure_dialog.h index 89020eeb4..305b33bdf 100644 --- a/src/citra_qt/configure_dialog.h +++ b/src/citra_qt/configure_dialog.h @@ -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;  }; diff --git a/src/citra_qt/configure_input.cpp b/src/citra_qt/configure_input.cpp new file mode 100644 index 000000000..9c7a67174 --- /dev/null +++ b/src/citra_qt/configure_input.cpp @@ -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); +    } +} diff --git a/src/citra_qt/configure_input.h b/src/citra_qt/configure_input.h new file mode 100644 index 000000000..fe8ea5580 --- /dev/null +++ b/src/citra_qt/configure_input.h @@ -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(); +}; diff --git a/src/citra_qt/configure_input.ui b/src/citra_qt/configure_input.ui new file mode 100644 index 000000000..a040d4df4 --- /dev/null +++ b/src/citra_qt/configure_input.ui @@ -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> diff --git a/src/citra_qt/configure_system.cpp b/src/citra_qt/configure_system.cpp new file mode 100644 index 000000000..4f0d4dbfe --- /dev/null +++ b/src/citra_qt/configure_system.cpp @@ -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); +} diff --git a/src/citra_qt/configure_system.h b/src/citra_qt/configure_system.h new file mode 100644 index 000000000..1f5577070 --- /dev/null +++ b/src/citra_qt/configure_system.h @@ -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; +}; diff --git a/src/citra_qt/configure_system.ui b/src/citra_qt/configure_system.ui new file mode 100644 index 000000000..6a906b61b --- /dev/null +++ b/src/citra_qt/configure_system.ui @@ -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> diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 0ed1ffa5a..68a936087 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -508,11 +508,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();      }  } diff --git a/src/common/emu_window.cpp b/src/common/emu_window.cpp index 08270dd88..fd728c109 100644 --- a/src/common/emu_window.cpp +++ b/src/common/emu_window.cpp @@ -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, {}, {} }; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 02d902bb5..0773339a9 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -27,6 +27,7 @@ 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 @@ -168,6 +169,7 @@ 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 diff --git a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp index 01d5d478e..c8d45c6db 100644 --- a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp +++ b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp @@ -2820,10 +2820,12 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) {                  operand2 = (BIT(RS, 15)) ? (BITS(RS, 0, 15) | 0xffff0000) : BITS(RS, 0, 15);              else                  operand2 = (BIT(RS, 31)) ? (BITS(RS, 16, 31) | 0xffff0000) : BITS(RS, 16, 31); -            RD = operand1 * operand2 + RN; -            if (AddOverflow(operand1 * operand2, RN, RD)) +            u32 product = operand1 * operand2; +            u32 result = product + RN; +            if (AddOverflow(product, RN, result))                  cpu->Cpsr |= (1 << 27); +            RD = result;          }          cpu->Reg[15] += cpu->GetInstructionSize();          INC_PC(sizeof(smla_inst)); @@ -3228,7 +3230,7 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) {                      addr += 4;                  }                  if (BIT(inst_cream->inst, 15)) { -                    cpu->WriteMemory32(addr, cpu->Reg_usr[1] + 8); +                    cpu->WriteMemory32(addr, cpu->Reg[15] + 8);                  }              } else {                  for (int i = 0; i < 15; i++) { @@ -3243,8 +3245,9 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) {                  }                  // Check PC reg -                if (BIT(inst_cream->inst, 15)) -                    cpu->WriteMemory32(addr, cpu->Reg_usr[1] + 8); +                if (BIT(inst_cream->inst, 15)) { +                    cpu->WriteMemory32(addr, cpu->Reg[15] + 8); +                }              }          }          cpu->Reg[15] += cpu->GetInstructionSize(); diff --git a/src/core/arm/dyncom/arm_dyncom_thumb.cpp b/src/core/arm/dyncom/arm_dyncom_thumb.cpp index 29272fd5d..3576370d1 100644 --- a/src/core/arm/dyncom/arm_dyncom_thumb.cpp +++ b/src/core/arm/dyncom/arm_dyncom_thumb.cpp @@ -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} diff --git a/src/core/core_timing.h b/src/core/core_timing.h index 64f5b06d9..3d8a7d0c0 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h @@ -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) { diff --git a/src/core/hle/applets/applet.cpp b/src/core/hle/applets/applet.cpp index 90e134437..ccf35fa07 100644 --- a/src/core/hle/applets/applet.cpp +++ b/src/core/hle/applets/applet.cpp @@ -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 diff --git a/src/core/hle/applets/erreula.cpp b/src/core/hle/applets/erreula.cpp new file mode 100644 index 000000000..92a4b2323 --- /dev/null +++ b/src/core/hle/applets/erreula.cpp @@ -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 diff --git a/src/core/hle/applets/erreula.h b/src/core/hle/applets/erreula.h new file mode 100644 index 000000000..9fe72ae07 --- /dev/null +++ b/src/core/hle/applets/erreula.h @@ -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 diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 57dedcb22..268a8dad2 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -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 diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index 1e54a53dd..c009e6c98 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp @@ -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; @@ -70,6 +72,13 @@ 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, @@ -382,23 +391,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 +502,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"); diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h index 76b3a3807..077a6a316 100644 --- a/src/core/hle/service/apt/apt.h +++ b/src/core/hle/service/apt/apt.h @@ -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 diff --git a/src/core/hle/service/apt/apt_a.cpp b/src/core/hle/service/apt/apt_a.cpp index 223c0a8bd..6c44c491c 100644 --- a/src/core/hle/service/apt/apt_a.cpp +++ b/src/core/hle/service/apt/apt_a.cpp @@ -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"}  }; diff --git a/src/core/hle/service/apt/apt_s.cpp b/src/core/hle/service/apt/apt_s.cpp index f5c52fa3d..c70f2201f 100644 --- a/src/core/hle/service/apt/apt_s.cpp +++ b/src/core/hle/service/apt/apt_s.cpp @@ -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"} diff --git a/src/core/hle/service/apt/apt_u.cpp b/src/core/hle/service/apt/apt_u.cpp index 0e60bd34f..7bb804ffa 100644 --- a/src/core/hle/service/apt/apt_u.cpp +++ b/src/core/hle/service/apt/apt_u.cpp @@ -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"} diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index e067db645..a5dc47322 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -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 diff --git a/src/core/hle/service/cfg/cfg.h b/src/core/hle/service/cfg/cfg.h index c01806836..18f60f4ca 100644 --- a/src/core/hle/service/cfg/cfg.h +++ b/src/core/hle/service/cfg/cfg.h @@ -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 @@ -178,6 +186,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:   *      0 : 0x04030000 / 0x08030000 @@ -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 diff --git a/src/core/hle/service/cfg/cfg_i.cpp b/src/core/hle/service/cfg/cfg_i.cpp index b18060f6d..8b0db785f 100644 --- a/src/core/hle/service/cfg/cfg_i.cpp +++ b/src/core/hle/service/cfg/cfg_i.cpp @@ -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"}, diff --git a/src/core/hle/service/cfg/cfg_s.cpp b/src/core/hle/service/cfg/cfg_s.cpp index e001f7687..12b458783 100644 --- a/src/core/hle/service/cfg/cfg_s.cpp +++ b/src/core/hle/service/cfg/cfg_s.cpp @@ -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"}, diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index 81b9abe4c..4c7aaa7f2 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp @@ -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 diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h index 006606740..f7a50a3a7 100644 --- a/src/core/hle/service/fs/archive.h +++ b/src/core/hle/service/fs/archive.h @@ -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 diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp index 7df7da5a4..937868747 100644 --- a/src/core/hle/service/fs/fs_user.cpp +++ b/src/core/hle/service/fs/fs_user.cpp @@ -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]; @@ -672,6 +671,38 @@ static void CreateSystemSaveData(Service::Interface* self) {  }  /** + * 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:   *      0 : 0x08610042 @@ -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"}, diff --git a/src/core/hle/shared_page.cpp b/src/core/hle/shared_page.cpp index 2a1caeaac..4d9272923 100644 --- a/src/core/hle/shared_page.cpp +++ b/src/core/hle/shared_page.cpp @@ -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 diff --git a/src/core/hle/shared_page.h b/src/core/hle/shared_page.h index 35a07c685..cd9246726 100644 --- a/src/core/hle/shared_page.h +++ b/src/core/hle/shared_page.h @@ -25,13 +25,14 @@ namespace SharedPage {  struct DateTime {      u64_le date_time;                  // 0      u64_le update_tick;                // 8 -    INSERT_PADDING_BYTES(0x20 - 0x10); // 10 +    u64_le tick_to_second_coefficient; // 10 +    u64_le tick_offset;                // 18  };  static_assert(sizeof(DateTime) == 0x20, "Datetime size is wrong");  struct SharedPageDef {      // Most of these names are taken from the 3dbrew page linked above. -    u32_le   date_time_selector;         // 0 +    u32_le   date_time_counter;          // 0      u8       running_hw;                 // 4      /// "Microcontroller hardware info"      u8       mcu_hw_info;                // 5 diff --git a/src/video_core/pica.h b/src/video_core/pica.h index 09702d46a..7099c31a0 100644 --- a/src/video_core/pica.h +++ b/src/video_core/pica.h @@ -115,7 +115,28 @@ struct Regs {          BitField<24, 5, Semantic> map_w;      } vs_output_attributes[7]; -    INSERT_PADDING_WORDS(0x11); +    INSERT_PADDING_WORDS(0xe); + +    enum class ScissorMode : u32 { +        Disabled = 0, +        Exclude = 1, // Exclude pixels inside the scissor box + +        Include = 3 // Exclude pixels outside the scissor box +    }; + +    struct { +        BitField<0, 2, ScissorMode> mode; + +        union { +            BitField< 0, 16, u32> x1; +            BitField<16, 16, u32> y1; +        }; + +        union { +            BitField< 0, 16, u32> x2; +            BitField<16, 16, u32> y2; +        }; +    } scissor_test;      union {          BitField< 0, 10, s32> x; @@ -1328,6 +1349,7 @@ ASSERT_REG_POSITION(viewport_depth_range, 0x4d);  ASSERT_REG_POSITION(viewport_depth_near_plane, 0x4e);  ASSERT_REG_POSITION(vs_output_attributes[0], 0x50);  ASSERT_REG_POSITION(vs_output_attributes[1], 0x51); +ASSERT_REG_POSITION(scissor_test, 0x65);  ASSERT_REG_POSITION(viewport_corner, 0x68);  ASSERT_REG_POSITION(depthmap_enable, 0x6D);  ASSERT_REG_POSITION(texture0_enable, 0x80); diff --git a/src/video_core/rasterizer.cpp b/src/video_core/rasterizer.cpp index a84170094..6f369a00e 100644 --- a/src/video_core/rasterizer.cpp +++ b/src/video_core/rasterizer.cpp @@ -338,12 +338,26 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0,              return;      } -    // TODO: Proper scissor rect test!      u16 min_x = std::min({vtxpos[0].x, vtxpos[1].x, vtxpos[2].x});      u16 min_y = std::min({vtxpos[0].y, vtxpos[1].y, vtxpos[2].y});      u16 max_x = std::max({vtxpos[0].x, vtxpos[1].x, vtxpos[2].x});      u16 max_y = std::max({vtxpos[0].y, vtxpos[1].y, vtxpos[2].y}); +    // Convert the scissor box coordinates to 12.4 fixed point +    u16 scissor_x1 = (u16)( regs.scissor_test.x1      << 4); +    u16 scissor_y1 = (u16)( regs.scissor_test.y1      << 4); +    // x2,y2 have +1 added to cover the entire sub-pixel area +    u16 scissor_x2 = (u16)((regs.scissor_test.x2 + 1) << 4); +    u16 scissor_y2 = (u16)((regs.scissor_test.y2 + 1) << 4); + +    if (regs.scissor_test.mode == Regs::ScissorMode::Include) { +        // Calculate the new bounds +        min_x = std::max(min_x, scissor_x1); +        min_y = std::max(min_y, scissor_y1); +        max_x = std::min(max_x, scissor_x2); +        max_y = std::min(max_y, scissor_y2); +    } +      min_x &= Fix12P4::IntMask();      min_y &= Fix12P4::IntMask();      max_x = ((max_x + Fix12P4::FracMask()) & Fix12P4::IntMask()); @@ -383,6 +397,13 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0,      for (u16 y = min_y + 8; y < max_y; y += 0x10) {          for (u16 x = min_x + 8; x < max_x; x += 0x10) { +            // Do not process the pixel if it's inside the scissor box and the scissor mode is set to Exclude +            if (regs.scissor_test.mode == Regs::ScissorMode::Exclude) { +                if (x >= scissor_x1 && x < scissor_x2 && +                    y >= scissor_y1 && y < scissor_y2) +                    continue; +            } +              // Calculate the barycentric coordinates w0, w1 and w2              int w0 = bias0 + SignedArea(vtxpos[1].xy(), vtxpos[2].xy(), {x, y});              int w1 = bias1 + SignedArea(vtxpos[2].xy(), vtxpos[0].xy(), {x, y}); diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 328a4f66b..f8393c618 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -196,6 +196,14 @@ void RasterizerOpenGL::DrawTriangles() {                 (GLint)(rect.bottom + regs.viewport_corner.y * color_surface->res_scale_height),                 (GLsizei)(viewport_width * color_surface->res_scale_width), (GLsizei)(viewport_height * color_surface->res_scale_height)); +    if (uniform_block_data.data.framebuffer_scale[0] != color_surface->res_scale_width || +        uniform_block_data.data.framebuffer_scale[1] != color_surface->res_scale_height) { + +        uniform_block_data.data.framebuffer_scale[0] = color_surface->res_scale_width; +        uniform_block_data.data.framebuffer_scale[1] = color_surface->res_scale_height; +        uniform_block_data.dirty = true; +    } +      // Sync and bind the texture surfaces      const auto pica_textures = regs.GetTextures();      for (unsigned texture_index = 0; texture_index < pica_textures.size(); ++texture_index) { @@ -353,6 +361,15 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {          SyncColorWriteMask();          break; +    // Scissor test +    case PICA_REG_INDEX(scissor_test.mode): +        shader_dirty = true; +        break; +    case PICA_REG_INDEX(scissor_test.x1): // and y1 +    case PICA_REG_INDEX(scissor_test.x2): // and y2 +        SyncScissorTest(); +        break; +      // Logic op      case PICA_REG_INDEX(output_merger.logic_op):          SyncLogicOp(); @@ -1002,6 +1019,7 @@ void RasterizerOpenGL::SetShader() {          SyncDepthOffset();          SyncAlphaTest();          SyncCombinerColor(); +        SyncScissorTest();          auto& tev_stages = Pica::g_state.regs.GetTevStages();          for (int index = 0; index < tev_stages.size(); ++index)              SyncTevConstColor(index, tev_stages[index]); @@ -1166,6 +1184,22 @@ void RasterizerOpenGL::SyncDepthTest() {                              PicaToGL::CompareFunc(regs.output_merger.depth_test_func) : GL_ALWAYS;  } +void RasterizerOpenGL::SyncScissorTest() { +    const auto& regs = Pica::g_state.regs; + +    if (uniform_block_data.data.scissor_x1 != regs.scissor_test.x1 || +        uniform_block_data.data.scissor_y1 != regs.scissor_test.y1 || +        uniform_block_data.data.scissor_x2 != regs.scissor_test.x2 || +        uniform_block_data.data.scissor_y2 != regs.scissor_test.y2) { + +        uniform_block_data.data.scissor_x1 = regs.scissor_test.x1; +        uniform_block_data.data.scissor_y1 = regs.scissor_test.y1; +        uniform_block_data.data.scissor_x2 = regs.scissor_test.x2; +        uniform_block_data.data.scissor_y2 = regs.scissor_test.y2; +        uniform_block_data.dirty = true; +    } +} +  void RasterizerOpenGL::SyncCombinerColor() {      auto combiner_color = PicaToGL::ColorRGBA8(Pica::g_state.regs.tev_combiner_buffer_color.raw);      if (combiner_color != uniform_block_data.data.tev_combiner_buffer_color) { diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 42482df4b..c5029432b 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -56,6 +56,8 @@ union PicaShaderConfig {          const auto& regs = Pica::g_state.regs; +        state.scissor_test_mode = regs.scissor_test.mode; +          state.depthmap_enable = regs.depthmap_enable;          state.alpha_test_func = regs.output_merger.alpha_test.enable ? @@ -172,6 +174,7 @@ union PicaShaderConfig {      struct State {          Pica::Regs::CompareFunc alpha_test_func; +        Pica::Regs::ScissorMode scissor_test_mode;          Pica::Regs::TextureConfig::TextureType texture0_type;          std::array<TevStageConfigRaw, 6> tev_stages;          u8 combiner_buffer_input; @@ -325,9 +328,14 @@ private:      //       the end of a uniform block is included in UNIFORM_BLOCK_DATA_SIZE or not.      //       Not following that rule will cause problems on some AMD drivers.      struct UniformData { +        alignas(8) GLvec2 framebuffer_scale;          GLint alphatest_ref;          GLfloat depth_scale;          GLfloat depth_offset; +        GLint scissor_x1; +        GLint scissor_y1; +        GLint scissor_x2; +        GLint scissor_y2;          alignas(16) GLvec3 fog_color;          alignas(16) GLvec3 lighting_global_ambient;          LightSrc light_src[8]; @@ -335,7 +343,7 @@ private:          alignas(16) GLvec4 tev_combiner_buffer_color;      }; -    static_assert(sizeof(UniformData) == 0x3A0, "The size of the UniformData structure has changed, update the structure in the shader"); +    static_assert(sizeof(UniformData) == 0x3C0, "The size of the UniformData structure has changed, update the structure in the shader");      static_assert(sizeof(UniformData) < 16384, "UniformData structure must be less than 16kb as per the OpenGL spec");      /// Sets the OpenGL shader in accordance with the current PICA register state @@ -384,6 +392,9 @@ private:      /// Syncs the depth test states to match the PICA register      void SyncDepthTest(); +    /// Syncs the scissor test state to match the PICA register +    void SyncScissorTest(); +      /// Syncs the TEV combiner color buffer to match the PICA register      void SyncCombinerColor(); diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index 3bace7f01..36513dedc 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -539,6 +539,8 @@ in float texcoord0_w;  in vec4 normquat;  in vec3 view; +in vec4 gl_FragCoord; +  out vec4 color;  struct LightSrc { @@ -552,9 +554,14 @@ struct LightSrc {  };  layout (std140) uniform shader_data { +    vec2 framebuffer_scale;      int alphatest_ref;      float depth_scale;      float depth_offset; +    int scissor_x1; +    int scissor_y1; +    int scissor_x2; +    int scissor_y2;      vec3 fog_color;      vec3 lighting_global_ambient;      LightSrc light_src[NUM_LIGHTS]; @@ -582,6 +589,19 @@ vec4 secondary_fragment_color = vec4(0.0);          return out;      } +    // Append the scissor test +    if (state.scissor_test_mode != Regs::ScissorMode::Disabled) { +        out += "if ("; +        // Negate the condition if we have to keep only the pixels outside the scissor box +        if (state.scissor_test_mode == Regs::ScissorMode::Include) +            out += "!"; +        // x2,y2 have +1 added to cover the entire pixel area +        out += "(gl_FragCoord.x >= scissor_x1 * framebuffer_scale.x && " +                "gl_FragCoord.y >= scissor_y1 * framebuffer_scale.y && " +                "gl_FragCoord.x < (scissor_x2 + 1) * framebuffer_scale.x && " +                "gl_FragCoord.y < (scissor_y2 + 1) * framebuffer_scale.y)) discard;\n"; +    } +      out += "float z_over_w = 1.0 - gl_FragCoord.z * 2.0;\n";      out += "float depth = z_over_w * depth_scale + depth_offset;\n";      if (state.depthmap_enable == Pica::Regs::DepthBuffering::WBuffering) { diff --git a/src/video_core/renderer_opengl/pica_to_gl.h b/src/video_core/renderer_opengl/pica_to_gl.h index 6dc2758c5..d9b9c9cc2 100644 --- a/src/video_core/renderer_opengl/pica_to_gl.h +++ b/src/video_core/renderer_opengl/pica_to_gl.h @@ -17,6 +17,7 @@  #include "video_core/pica.h" +using GLvec2 = std::array<GLfloat, 2>;  using GLvec3 = std::array<GLfloat, 3>;  using GLvec4 = std::array<GLfloat, 4>;  | 
