diff --git a/.github/ISSUE_TEMPLATE/blank_issue_template.yml b/.github/ISSUE_TEMPLATE/blank_issue_template.yml new file mode 100644 index 000000000..49b7f3822 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/blank_issue_template.yml @@ -0,0 +1,10 @@ +name: New Issue (Developers Only) +description: A blank issue template for developers only. If you are not a developer, do not use this issue template. Your issue WILL BE CLOSED if you do not use the appropriate issue template. +body: + - type: markdown + attributes: + value: | + **If you are not a developer, do not use this issue template. Your issue WILL BE CLOSED if you do not use the appropriate issue template.** + - type: textarea + attributes: + label: "Issue" diff --git a/.github/ISSUE_TEMPLATE/bug-report-feature-request.md b/.github/ISSUE_TEMPLATE/bug-report-feature-request.md deleted file mode 100644 index 156c63858..000000000 --- a/.github/ISSUE_TEMPLATE/bug-report-feature-request.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -name: Bug Report / Feature Request -about: Tech support does not belong here. You should only file an issue here if you think you have experienced an actual bug with Citra or you are requesting a feature you believe would make Citra better. -title: '' -labels: '' -assignees: '' - ---- - - diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..944218654 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,64 @@ +name: Bug Report +description: File a bug report +body: + - type: markdown + attributes: + value: Tech support does not belong here. You should only file an issue here if you think you have experienced an actual bug with Citra. + - type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue already exists for the bug you encountered. + options: + - label: I have searched the existing issues + required: true + - type: input + attributes: + label: Affected Build(s) + description: List the affected build(s) that this issue applies to. + placeholder: Nightly 1234 / Canary 1234 + validations: + required: true + - type: textarea + id: issue-desc + attributes: + label: Description of Issue + description: A brief description of the issue encountered along with any images and/or videos. + validations: + required: true + - type: textarea + id: expected-behavior + attributes: + label: Expected Behavior + description: A brief description of how it is expected to work along with any images and/or videos. + validations: + required: true + - type: textarea + id: reproduction-steps + attributes: + label: Reproduction Steps + description: A brief explanation of how to reproduce this issue. If possible, provide a save file to aid in reproducing the issue. + validations: + required: true + - type: textarea + id: log + attributes: + label: Log File + description: A log file will help our developers to better diagnose and fix the issue. + validations: + required: true + - type: textarea + id: system-config + attributes: + label: System Configuration + placeholder: | + CPU: Intel i5-10400 / AMD Ryzen 5 3600 + GPU/Driver: NVIDIA GeForce GTX 1060 (Driver 512.95) + RAM: 16GB DDR4-3200 + OS: Windows 11 22H2 (Build 22621.819) + value: | + CPU: + GPU/Driver: + RAM: + OS: + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index e58da5502..88575a335 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -6,6 +6,3 @@ contact_links: - name: Community forums url: https://community.citra-emu.org about: This is an alternative place for tech support, however helpers there are not as active. - - name: Citra Android - url: https://github.com/citra-emu/citra-android - about: If you need tech support on Citra Android, you should use either of the above two options. If you want to file an issue, you should go to the Android repo linked here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..7ab8172e3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,28 @@ +name: Feature Request +description: File a feature request +labels: "request" +body: + - type: markdown + attributes: + value: Tech support does not belong here. You should only file an issue here if you are requesting a feature you believe would make Citra better. + - type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue already exists for the feature you are requesting. + options: + - label: I have searched the existing issues + required: true + - type: textarea + id: what-feature + attributes: + label: What feature are you suggesting? + description: A brief description of the requested feature. + validations: + required: true + - type: textarea + id: why-feature + attributes: + label: Why would this feature be useful? + description: A brief description of why this feature would make Citra better. + validations: + required: true diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d84f74f6..65e06b69c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -210,7 +210,7 @@ if (ENABLE_QT) set(QT_PREFIX_HINT) endif() - find_package(Qt5 REQUIRED COMPONENTS Widgets Multimedia ${QT_PREFIX_HINT}) + find_package(Qt5 REQUIRED COMPONENTS Widgets Multimedia Concurrent ${QT_PREFIX_HINT}) if (ENABLE_QT_TRANSLATION) find_package(Qt5 REQUIRED COMPONENTS LinguistTools ${QT_PREFIX_HINT}) diff --git a/CMakeModules/CopyCitraQt5Deps.cmake b/CMakeModules/CopyCitraQt5Deps.cmake index 48bb94055..c2fdfb13c 100644 --- a/CMakeModules/CopyCitraQt5Deps.cmake +++ b/CMakeModules/CopyCitraQt5Deps.cmake @@ -17,6 +17,7 @@ function(copy_citra_Qt5_deps target_dir) Qt5Core$<$:d>.* Qt5Gui$<$:d>.* Qt5Widgets$<$:d>.* + Qt5Concurrent$<$:d>.* Qt5Multimedia$<$:d>.* Qt5Network$<$:d>.* ) diff --git a/README.md b/README.md index 81d0f884f..402699093 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,49 @@ -**BEFORE FILING AN ISSUE, READ THE RELEVANT SECTION IN THE [CONTRIBUTING](https://github.com/citra-emu/citra/wiki/Contributing#reporting-issues) FILE!!!** +

+
+ Citra +
+ Citra +
+

-# Citra +

Citra is the world's most popular, open-source, Nintendo 3DS emulator. +
+It is written in C++ with portability in mind and builds are actively maintained for Windows, Linux, Android and macOS. +

-[![GitHub Actions Build Status](https://github.com/citra-emu/citra/workflows/citra-ci/badge.svg)](https://github.com/citra-emu/citra/actions) -[![Bitrise CI Build Status](https://app.bitrise.io/app/4ccd8e5720f0d13b/status.svg?token=H32TmbCwxb3OQ-M66KbAyw&branch=master)](https://app.bitrise.io/app/4ccd8e5720f0d13b) -[![Discord](https://img.shields.io/discord/220740965957107713?color=%237289DA&label=Citra&logo=discord&logoColor=white)](https://discord.gg/FAXfZV9) +

+ + GitHub Actions Build Status + + + Discord + +

-Citra is an experimental open-source Nintendo 3DS emulator/debugger written in C++. It is written with portability in mind, with builds actively maintained for Windows, Linux and macOS. +

+ Compatibility | + Releases | + Development | + Building | + Support | + License +

-Citra emulates a subset of 3DS hardware and therefore is useful for running/debugging homebrew applications, and it is also able to run many commercial games! Some of these do not run at a playable state, but we are working every day to advance the project forward. (Playable here means compatibility of at least "Okay" on our [game compatibility list](https://citra-emu.org/game).) -Citra is licensed under the GPLv2 (or any later version). Refer to the license.txt file included. Please read the [FAQ](https://citra-emu.org/wiki/faq/) before getting started with the project. +## Compatibility -Check out our [website](https://citra-emu.org/)! +The emulator is capable of running most commercial games at full speed, provided you meet the necessary hardware requirements. + +For a full list of games Citra supports, please visit our [Compatibility page](https://citra-emu.org/game/) + +Check out our [website](https://citra-emu.org/) for the latest news on exciting features, progress reports, and more! +Please read the [FAQ](https://citra-emu.org/wiki/faq/) before getting started with the project. Need help? Check out our [asking for help](https://citra-emu.org/help/reference/asking/) guide. -For development discussion, please join us on our [Discord server](https://citra-emu.org/discord/) or at #citra-dev on libera. - -### Releases +## Releases Citra has two main release channels: Nightly and Canary. @@ -28,30 +53,46 @@ The [Canary](https://github.com/citra-emu/citra-canary) build is based on the ma Both builds can be installed with the installer provided on the [website](https://citra-emu.org/download/), but those looking for specific versions or standalone releases can find them in the release tabs of the [Nightly](https://github.com/citra-emu/citra-nightly/releases) and [Canary](https://github.com/citra-emu/citra-canary/releases) repositories. -Currently, development and releases of the Android version are in [a separate repository](https://github.com/citra-emu/citra-android). +Android builds can be downloaded from the Google Play Store. A Flatpak for Citra is available on [Flathub](https://flathub.org/apps/details/org.citra_emu.citra). Details on the build process can be found in [our Flathub repository](https://github.com/flathub/org.citra_emu.citra). -### Development +## Development Most of the development happens on GitHub. It's also where [our central repository](https://github.com/citra-emu/citra) is hosted. +For development discussion, please join us on our [Discord server](https://citra-emu.org/discord/) or at #citra-dev on libera. -If you want to contribute please take a look at the [Contributor's Guide](https://github.com/citra-emu/citra/wiki/Contributing) and [Developer Information](https://github.com/citra-emu/citra/wiki/Developer-Information). You should also contact any of the developers in the forum in order to know about the current state of the emulator because the [TODO list](https://docs.google.com/document/d/1SWIop0uBI9IW8VGg97TAtoT_CHNoP42FzYmvG1F4QDA) isn't maintained anymore. +If you want to contribute please take a look at the [Contributor's Guide](https://github.com/citra-emu/citra/wiki/Contributing) and [Developer Information](https://github.com/citra-emu/citra/wiki/Developer-Information). You can also contact any of the developers on Discord in order to know about the current state of the emulator. -If you want to contribute to the user interface translation, please check out the [citra project on transifex](https://www.transifex.com/citra/citra). We centralize the translation work there, and periodically upstream translations. +If you want to contribute to the user interface translation, please check out the [Citra project on transifex](https://www.transifex.com/citra/citra). We centralize the translation work there, and periodically upstream translations. -### Building +## Building * __Windows__: [Windows Build](https://github.com/citra-emu/citra/wiki/Building-For-Windows) * __Linux__: [Linux Build](https://github.com/citra-emu/citra/wiki/Building-For-Linux) * __macOS__: [macOS Build](https://github.com/citra-emu/citra/wiki/Building-for-macOS) +* __Android__: [Android Build](https://github.com/citra-emu/citra/wiki/Building-for-Android) -### Support -We happily accept monetary donations or donated games and hardware. Please see our [donations page](https://citra-emu.org/donate/) for more information on how you can contribute to Citra. Any donations received will go towards things like: +## Support + +If you enjoy the project and want to support us financially, check out our Patreon! + + + + + +We also happily accept donated games and hardware. +Please see our [donations page](https://citra-emu.org/donate/) for more information on how you can contribute to Citra. +Any donations received will go towards things like: * 3DS consoles for developers to explore the hardware * 3DS games for testing * Any equipment required for homebrew * Infrastructure setup We also more than gladly accept used 3DS consoles! If you would like to give yours away, don't hesitate to join our [Discord server](https://citra-emu.org/discord/) and talk to bunnei. + + +## License + +Citra is licensed under the GPLv2 (or any later version). Refer to the [LICENSE.txt](https://github.com/citra-emu/citra/blob/master/license.txt) file. diff --git a/dist/qt_themes/default/style.qss b/dist/qt_themes/default/style.qss index 9ed47a3a6..8e7a56f97 100644 --- a/dist/qt_themes/default/style.qss +++ b/dist/qt_themes/default/style.qss @@ -10,4 +10,19 @@ QPushButton#GraphicsAPIStatusBarButton { QPushButton#GraphicsAPIStatusBarButton:hover { border: 1px solid #76797C; -} \ No newline at end of file +} + +QPushButton#3DOptionStatusBarButton { + color: #A5A5A5; + font-weight: bold; + border: 1px solid transparent; + background-color: transparent; + padding: 0px 3px 0px 3px; + text-align: center; + min-width: 60px; + min-height: 20px; +} + +QPushButton#3DOptionStatusBarButton:hover { + border: 1px solid #76797C; +} diff --git a/dist/qt_themes/qdarkstyle/style.qss b/dist/qt_themes/qdarkstyle/style.qss index ea6ea95d6..2194a3caf 100644 --- a/dist/qt_themes/qdarkstyle/style.qss +++ b/dist/qt_themes/qdarkstyle/style.qss @@ -1249,4 +1249,19 @@ QPushButton#GraphicsAPIStatusBarButton { QPushButton#GraphicsAPIStatusBarButton:hover { border: 1px solid #76797C; -} \ No newline at end of file +} + +QPushButton#3DOptionStatusBarButton { + color: #A5A5A5; + font-weight: bold; + border: 1px solid transparent; + background-color: transparent; + padding: 0px 3px 0px 3px; + text-align: center; + min-width: 60px; + min-height: 20px; +} + +QPushButton#3DOptionStatusBarButton:hover { + border: 1px solid #76797C; +} diff --git a/src/citra/config.cpp b/src/citra/config.cpp index 7907d82cb..2be45f28d 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -178,6 +178,8 @@ void Config::ReadValues() { static_cast(sdl2_config->GetInteger("Layout", "custom_bottom_right", 360)); Settings::values.custom_bottom_bottom = static_cast(sdl2_config->GetInteger("Layout", "custom_bottom_bottom", 480)); + Settings::values.custom_second_layer_opacity = + static_cast(sdl2_config->GetInteger("Layout", "custom_second_layer_opacity", 100)); // Utility Settings::values.dump_textures = sdl2_config->GetBoolean("Utility", "dump_textures", false); diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index e84532a3c..9cc72ba09 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -204,6 +204,9 @@ custom_bottom_top = custom_bottom_right = custom_bottom_bottom = +# Opacity of second layer when using custom layout option (bottom screen unless swapped) +custom_second_layer_opacity = + # Swaps the prominent screen with the other screen. # For example, if Single Screen is chosen, setting this to 1 will display the bottom screen instead of the top screen. # 0 (default): Top Screen is prominent, 1: Bottom Screen is prominent diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index d139594f0..b6e880692 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -266,7 +266,7 @@ endif() create_target_directory_groups(citra-qt) target_link_libraries(citra-qt PRIVATE audio_core common core input_common network video_core) -target_link_libraries(citra-qt PRIVATE Boost::boost glad vma vulkan-headers nihstro-headers Qt5::Widgets Qt5::Multimedia) +target_link_libraries(citra-qt PRIVATE Boost::boost glad vma vulkan-headers nihstro-headers Qt5::Widgets Qt5::Multimedia Qt5::Concurrent) target_link_libraries(citra-qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) if (NOT WIN32) diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 379ba37ac..340a0b7ca 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -9,6 +9,7 @@ #include #include "citra_qt/configuration/config.h" #include "common/file_util.h" +#include "common/settings.h" #include "core/frontend/mic.h" #include "core/hle/service/service.h" #include "input_common/main.h" @@ -55,14 +56,16 @@ const std::array, Settings::NativeAnalog::NumAnalogs> Config: // This must be in alphabetical order according to action name as it must have the same order as // UISetting::values.shortcuts, which is alphabetically ordered. // clang-format off -const std::array Config::default_hotkeys {{ +const std::array Config::default_hotkeys {{ {QStringLiteral("Advance Frame"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}}, {QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}}, {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}}, + {QStringLiteral("Decrease 3D Factor"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+-"), Qt::ApplicationShortcut}}, {QStringLiteral("Decrease Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("-"), Qt::ApplicationShortcut}}, {QStringLiteral("Exit Citra"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), Qt::WindowShortcut}}, {QStringLiteral("Exit Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("Esc"), Qt::WindowShortcut}}, {QStringLiteral("Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("F11"), Qt::WindowShortcut}}, + {QStringLiteral("Increase 3D Factor"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl++"), Qt::ApplicationShortcut}}, {QStringLiteral("Increase Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("+"), Qt::ApplicationShortcut}}, {QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::WidgetWithChildrenShortcut}}, {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WidgetWithChildrenShortcut}}, @@ -74,6 +77,7 @@ const std::array Config::default_hotkeys {{ {QStringLiteral("Save to Oldest Slot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+C"), Qt::WindowShortcut}}, {QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}}, {QStringLiteral("Swap Screens"), QStringLiteral("Main Window"), {QStringLiteral("F9"), Qt::WindowShortcut}}, + {QStringLiteral("Toggle 3D"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+3"), Qt::ApplicationShortcut}}, {QStringLiteral("Toggle Per-Game Speed"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), Qt::ApplicationShortcut}}, {QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}}, {QStringLiteral("Toggle Frame Advancing"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+A"), Qt::ApplicationShortcut}}, @@ -495,6 +499,7 @@ void Config::ReadLayoutValues() { ReadBasicSetting(Settings::values.custom_bottom_top); ReadBasicSetting(Settings::values.custom_bottom_right); ReadBasicSetting(Settings::values.custom_bottom_bottom); + ReadBasicSetting(Settings::values.custom_second_layer_opacity); } qt_config->endGroup(); @@ -1011,6 +1016,7 @@ void Config::SaveLayoutValues() { WriteBasicSetting(Settings::values.custom_bottom_top); WriteBasicSetting(Settings::values.custom_bottom_right); WriteBasicSetting(Settings::values.custom_bottom_bottom); + WriteBasicSetting(Settings::values.custom_second_layer_opacity); } qt_config->endGroup(); diff --git a/src/citra_qt/configuration/config.h b/src/citra_qt/configuration/config.h index 7f1855d72..d28983a7a 100644 --- a/src/citra_qt/configuration/config.h +++ b/src/citra_qt/configuration/config.h @@ -26,7 +26,7 @@ public: static const std::array default_buttons; static const std::array, Settings::NativeAnalog::NumAnalogs> default_analogs; - static const std::array default_hotkeys; + static const std::array default_hotkeys; private: void Initialize(const std::string& config_name); diff --git a/src/citra_qt/configuration/configure_general.cpp b/src/citra_qt/configuration/configure_general.cpp index 4bb9627e3..3a223398f 100644 --- a/src/citra_qt/configuration/configure_general.cpp +++ b/src/citra_qt/configuration/configure_general.cpp @@ -34,11 +34,11 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent) ui->emulation_speed_display_label->setMinimumWidth(tr("unthrottled").size() * 6); ui->emulation_speed_combo->setVisible(!Settings::IsConfiguringGlobal()); ui->screenshot_combo->setVisible(!Settings::IsConfiguringGlobal()); + ui->updateBox->setVisible(UISettings::values.updater_found); SetupPerGameUI(); SetConfiguration(); - ui->updateBox->setVisible(UISettings::values.updater_found); connect(ui->button_reset_defaults, &QPushButton::clicked, this, &ConfigureGeneral::ResetDefaults); diff --git a/src/citra_qt/configuration/configure_system.cpp b/src/citra_qt/configuration/configure_system.cpp index e47b0db96..8c4fbe5c5 100644 --- a/src/citra_qt/configuration/configure_system.cpp +++ b/src/citra_qt/configuration/configure_system.cpp @@ -3,14 +3,22 @@ // Refer to the license.txt file included. #include +#include #include +#include +#include #include "citra_qt/configuration/configuration_shared.h" #include "citra_qt/configuration/configure_system.h" #include "common/settings.h" #include "core/core.h" +#include "core/hle/service/am/am.h" #include "core/hle/service/cfg/cfg.h" #include "core/hle/service/ptm/ptm.h" +#include "core/hw/aes/key.h" #include "ui_configure_system.h" +#ifdef ENABLE_WEB_SERVICE +#include "web_service/nus_titles.h" +#endif static const std::array days_in_month = {{ 31, @@ -239,6 +247,8 @@ ConfigureSystem::ConfigureSystem(QWidget* parent) &ConfigureSystem::UpdateInitTime); connect(ui->button_regenerate_console_id, &QPushButton::clicked, this, &ConfigureSystem::RefreshConsoleID); + connect(ui->button_start_download, &QPushButton::clicked, this, + &ConfigureSystem::DownloadFromNUS); for (u8 i = 0; i < country_names.size(); i++) { if (std::strcmp(country_names.at(i), "") != 0) { ui->combo_country->addItem(tr(country_names.at(i)), i); @@ -257,6 +267,30 @@ ConfigureSystem::ConfigureSystem(QWidget* parent) ui->clock_speed_combo->setVisible(!Settings::IsConfiguringGlobal()); SetupPerGameUI(); + + ui->combo_download_mode->setCurrentIndex(1); // set to Recommended + bool keys_available = true; + HW::AES::InitKeys(true); + for (u8 i = 0; i < HW::AES::MaxCommonKeySlot; i++) { + HW::AES::SelectCommonKeyIndex(i); + if (!HW::AES::IsNormalKeyAvailable(HW::AES::KeySlotID::TicketCommonKey)) { + keys_available = false; + break; + } + } + if (keys_available) { + ui->button_start_download->setEnabled(true); + ui->combo_download_mode->setEnabled(true); + ui->label_nus_download->setText(tr("Download System Files from Nintendo servers")); + } else { + ui->button_start_download->setEnabled(false); + ui->combo_download_mode->setEnabled(false); + ui->label_nus_download->setText( + tr("Citra is missing keys to download system files.
How to get keys?")); + } + ConfigureTime(); } @@ -542,3 +576,44 @@ void ConfigureSystem::SetupPerGameUI() { ConfigurationShared::SetColoredTristate(ui->toggle_new_3ds, Settings::values.is_new_3ds, is_new_3ds); } + +void ConfigureSystem::DownloadFromNUS() { +#ifdef ENABLE_WEB_SERVICE + ui->button_start_download->setEnabled(false); + + const auto mode = static_cast(ui->combo_download_mode->currentIndex()); + const std::vector titles = BuildFirmwareTitleList(mode, cfg->GetRegionValue()); + + QProgressDialog progress(tr("Downloading files..."), tr("Cancel"), 0, + static_cast(titles.size()), this); + progress.setWindowModality(Qt::WindowModal); + + QFutureWatcher future_watcher; + QObject::connect(&future_watcher, &QFutureWatcher::finished, &progress, + &QProgressDialog::reset); + QObject::connect(&progress, &QProgressDialog::canceled, &future_watcher, + &QFutureWatcher::cancel); + QObject::connect(&future_watcher, &QFutureWatcher::progressValueChanged, &progress, + &QProgressDialog::setValue); + + auto failed = false; + const auto download_title = [&future_watcher, &failed](const u64& title_id) { + if (Service::AM::InstallFromNus(title_id) != Service::AM::InstallStatus::Success) { + failed = true; + future_watcher.cancel(); + } + }; + + future_watcher.setFuture(QtConcurrent::map(titles, download_title)); + progress.exec(); + future_watcher.waitForFinished(); + + if (failed) { + QMessageBox::critical(this, tr("Citra"), tr("Downloading system files failed.")); + } else if (!future_watcher.isCanceled()) { + QMessageBox::information(this, tr("Citra"), tr("Successfully downloaded system files.")); + } + + ui->button_start_download->setEnabled(true); +#endif +} diff --git a/src/citra_qt/configuration/configure_system.h b/src/citra_qt/configuration/configure_system.h index 007bed199..d8b0e25fb 100644 --- a/src/citra_qt/configuration/configure_system.h +++ b/src/citra_qt/configuration/configure_system.h @@ -43,6 +43,8 @@ private: void SetupPerGameUI(); + void DownloadFromNUS(); + ConfigurationShared::CheckState is_new_3ds; std::unique_ptr ui; bool enabled = false; diff --git a/src/citra_qt/configuration/configure_system.ui b/src/citra_qt/configuration/configure_system.ui index 0dae06b70..02acf35c5 100644 --- a/src/citra_qt/configuration/configure_system.ui +++ b/src/citra_qt/configuration/configure_system.ui @@ -361,6 +361,52 @@ + + + + Download System Files from Nitendo servers + + + + + + + + + + All + + + + + Recommended + + + + + Minimal + + + + + + + + + 0 + 0 + + + + Qt::RightToLeft + + + Download + + + + + diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index 4a3771bf8..f9dbcdfee 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp @@ -501,8 +501,8 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, u64 progra QAction* delete_opengl_disk_shader_cache = shader_menu->addAction(tr("Delete OpenGL Shader Cache")); - const bool is_application = - 0x0004000000000000 <= program_id && program_id <= 0x00040000FFFFFFFF; + const u32 program_id_high = (program_id >> 32) & 0xFFFFFFFF; + const bool is_application = program_id_high == 0x00040000 || program_id_high == 0x00040010; bool opengl_cache_exists = false; ForEachOpenGLCacheFile( diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index cddbeaeb8..fa72a9b70 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -113,6 +113,7 @@ __declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001; #endif constexpr int default_mouse_timeout = 2500; +constexpr int num_options_3d = 5; /** * "Callouts" are one-time instructional messages shown to the user. In the config settings, there @@ -156,6 +157,28 @@ static void InitializeLogging() { #endif } +static QString PrettyProductName() { +#ifdef _WIN32 + // After Windows 10 Version 2004, Microsoft decided to switch to a different notation: 20H2 + // With that notation change they changed the registry key used to denote the current version + QSettings windows_registry( + QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), + QSettings::NativeFormat); + const QString release_id = windows_registry.value(QStringLiteral("ReleaseId")).toString(); + if (release_id == QStringLiteral("2009")) { + const u32 current_build = windows_registry.value(QStringLiteral("CurrentBuild")).toUInt(); + const QString display_version = + windows_registry.value(QStringLiteral("DisplayVersion")).toString(); + const u32 ubr = windows_registry.value(QStringLiteral("UBR")).toUInt(); + const u32 version = current_build >= 22000 ? 11 : 10; + return QStringLiteral("Windows %1 Version %2 (Build %3.%4)") + .arg(QString::number(version), display_version, QString::number(current_build), + QString::number(ubr)); + } +#endif + return QSysInfo::prettyProductName(); +} + GMainWindow::GMainWindow() : ui{std::make_unique()}, config{std::make_unique()}, emu_thread{ nullptr} { @@ -198,6 +221,7 @@ GMainWindow::GMainWindow() ConnectMenuEvents(); ConnectWidgetEvents(); + Connect3DStateEvents(); LOG_INFO(Frontend, "Citra Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc); @@ -217,7 +241,7 @@ GMainWindow::GMainWindow() } LOG_INFO(Frontend, "Host CPU: {}", cpu_string); #endif - LOG_INFO(Frontend, "Host OS: {}", QSysInfo::prettyProductName().toStdString()); + LOG_INFO(Frontend, "Host OS: {}", PrettyProductName().toStdString()); const auto& mem_info = Common::GetMemInfo(); using namespace Common::Literals; LOG_INFO(Frontend, "Host RAM: {:.2f} GiB", mem_info.total_physical_memory / f64{1_GiB}); @@ -338,6 +362,20 @@ void GMainWindow::InitializeWidgets() { statusBar()->insertPermanentWidget(0, graphics_api_button); + option_3d_button = new QPushButton(); + option_3d_button->setObjectName(QStringLiteral("3DOptionStatusBarButton")); + option_3d_button->setFocusPolicy(Qt::NoFocus); + option_3d_button->setToolTip(tr("Indicates the current 3D setting. Click to toggle.")); + + factor_3d_slider = new QSlider(Qt::Orientation::Horizontal, this); + factor_3d_slider->setStyleSheet(QStringLiteral("QSlider { padding: 4px; }")); + factor_3d_slider->setToolTip(tr("Current 3D factor while 3D is enabled.")); + factor_3d_slider->setRange(0, 100); + + Update3DState(); + statusBar()->insertPermanentWidget(1, option_3d_button); + statusBar()->insertPermanentWidget(2, factor_3d_slider); + statusBar()->addPermanentWidget(multiplayer_state->GetStatusText()); statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon()); @@ -588,6 +626,35 @@ void GMainWindow::InitializeHotkeys() { }); connect_shortcut(QStringLiteral("Mute Audio"), [] { Settings::values.audio_muted = !Settings::values.audio_muted; }); + + connect_shortcut(QStringLiteral("Toggle 3D"), &GMainWindow::Toggle3D); + + // We use "static" here in order to avoid capturing by lambda due to a MSVC bug, which makes the + // variable hold a garbage value after this function exits + static constexpr u16 FACTOR_3D_STEP = 5; + connect_shortcut(QStringLiteral("Decrease 3D Factor"), [this] { + const auto factor_3d = Settings::values.factor_3d.GetValue(); + if (factor_3d > 0) { + if (factor_3d % FACTOR_3D_STEP != 0) { + Settings::values.factor_3d = factor_3d - (factor_3d % FACTOR_3D_STEP); + } else { + Settings::values.factor_3d = factor_3d - FACTOR_3D_STEP; + } + UpdateStatusBar(); + } + }); + connect_shortcut(QStringLiteral("Increase 3D Factor"), [this] { + const auto factor_3d = Settings::values.factor_3d.GetValue(); + if (factor_3d < 100) { + if (factor_3d % FACTOR_3D_STEP != 0) { + Settings::values.factor_3d = + factor_3d + FACTOR_3D_STEP - (factor_3d % FACTOR_3D_STEP); + } else { + Settings::values.factor_3d = factor_3d + FACTOR_3D_STEP; + } + UpdateStatusBar(); + } + }); } void GMainWindow::ShowUpdaterWidgets() { @@ -818,6 +885,12 @@ void GMainWindow::UpdateMenuState() { } } +void GMainWindow::Connect3DStateEvents() { + connect(option_3d_button, &QPushButton::clicked, this, &GMainWindow::Toggle3D); + connect(factor_3d_slider, qOverload(&QSlider::valueChanged), this, + [](int value) { Settings::values.factor_3d = value; }); +} + void GMainWindow::OnDisplayTitleBars(bool show) { QList widgets = findChildren(); @@ -1864,6 +1937,7 @@ void GMainWindow::OnLoadState() { } void GMainWindow::OnConfigure() { + game_list->SetDirectoryWatcherEnabled(false); Settings::SetConfiguringGlobal(true); ConfigureDialog configureDialog(this, hotkey_registry, !multiplayer_state->IsHostingPublicRoom()); @@ -1875,6 +1949,7 @@ void GMainWindow::OnConfigure() { const auto old_touch_from_button_maps = Settings::values.touch_from_button_maps; const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue(); auto result = configureDialog.exec(); + game_list->SetDirectoryWatcherEnabled(true); if (result == QDialog::Accepted) { configureDialog.ApplyConfiguration(); InitializeHotkeys(); @@ -1896,6 +1971,7 @@ void GMainWindow::OnConfigure() { } UpdateSecondaryWindowVisibility(); UpdateAPIIndicator(false); + Update3DState(); } else { Settings::values.input_profiles = old_input_profiles; Settings::values.touch_from_button_maps = old_touch_from_button_maps; @@ -2156,22 +2232,18 @@ void GMainWindow::UpdateStatusBar() { const auto play_mode = Core::Movie::GetInstance().GetPlayMode(); if (play_mode == Core::Movie::PlayMode::Recording) { message_label->setText(tr("Recording %1").arg(current)); - message_label->setVisible(true); message_label_used_for_movie = true; ui->action_Save_Movie->setEnabled(true); } else if (play_mode == Core::Movie::PlayMode::Playing) { message_label->setText(tr("Playing %1 / %2").arg(current, total)); - message_label->setVisible(true); message_label_used_for_movie = true; ui->action_Save_Movie->setEnabled(false); } else if (play_mode == Core::Movie::PlayMode::MovieFinished) { message_label->setText(tr("Movie Finished")); - message_label->setVisible(true); message_label_used_for_movie = true; ui->action_Save_Movie->setEnabled(false); } else if (message_label_used_for_movie) { // Clear the label if movie was just closed message_label->setText(QString{}); - message_label->setVisible(false); message_label_used_for_movie = false; ui->action_Save_Movie->setEnabled(false); } @@ -2193,6 +2265,18 @@ void GMainWindow::UpdateStatusBar() { emu_frametime_label->setVisible(true); } +void GMainWindow::Update3DState() { + static const std::array options_3d = {tr("Off"), tr("Side by Side"), tr("Anaglyph"), + tr("Interlaced"), tr("Reverse Interlaced")}; + + option_3d_button->setText( + tr("3D: %1").arg(options_3d[static_cast(Settings::values.render_3d.GetValue())])); + + factor_3d_slider->setValue(Settings::values.factor_3d.GetValue()); + factor_3d_slider->setVisible(Settings::values.render_3d.GetValue() != + Settings::StereoRenderOption::Off); +} + void GMainWindow::HideMouseCursor() { if (emu_thread == nullptr || !UISettings::values.hide_mouse.GetValue()) { mouse_hide_timer.stop(); @@ -2315,7 +2399,6 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det if (emu_thread) { emu_thread->SetRunning(true); message_label->setText(status_message); - message_label->setVisible(true); message_label_used_for_movie = false; } } @@ -2325,6 +2408,12 @@ void GMainWindow::OnMenuAboutCitra() { about.exec(); } +void GMainWindow::Toggle3D() { + Settings::values.render_3d = static_cast( + (static_cast(Settings::values.render_3d.GetValue()) + 1) % num_options_3d); + Update3DState(); +} + bool GMainWindow::ConfirmClose() { if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) return true; @@ -2441,6 +2530,8 @@ void GMainWindow::UpdateUITheme() { qApp->setStyleSheet(ts.readAll()); setStyleSheet(ts.readAll()); } else { + LOG_ERROR(Frontend, + "Unable to open default stylesheet, falling back to empty stylesheet"); qApp->setStyleSheet({}); setStyleSheet({}); } @@ -2610,6 +2701,55 @@ void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { #undef main #endif +static void SetHighDPIAttributes() { +#ifdef _WIN32 + // For Windows, we want to avoid scaling artifacts on fractional scaling ratios. + // This is done by setting the optimal scaling policy for the primary screen. + + // Create a temporary QApplication. + int temp_argc = 0; + char** temp_argv = nullptr; + QApplication temp{temp_argc, temp_argv}; + + // Get the current screen geometry. + const QScreen* primary_screen = QGuiApplication::primaryScreen(); + if (primary_screen == nullptr) { + return; + } + + const QRect screen_rect = primary_screen->geometry(); + const int real_width = screen_rect.width(); + const int real_height = screen_rect.height(); + const float real_ratio = primary_screen->logicalDotsPerInch() / 96.0f; + + // Recommended minimum width and height for proper window fit. + // Any screen with a lower resolution than this will still have a scale of 1. + constexpr float minimum_width = 1350.0f; + constexpr float minimum_height = 900.0f; + + const float width_ratio = std::max(1.0f, real_width / minimum_width); + const float height_ratio = std::max(1.0f, real_height / minimum_height); + + // Get the lower of the 2 ratios and truncate, this is the maximum integer scale. + const float max_ratio = std::trunc(std::min(width_ratio, height_ratio)); + + if (max_ratio > real_ratio) { + QApplication::setHighDpiScaleFactorRoundingPolicy( + Qt::HighDpiScaleFactorRoundingPolicy::Round); + } else { + QApplication::setHighDpiScaleFactorRoundingPolicy( + Qt::HighDpiScaleFactorRoundingPolicy::Floor); + } +#else + // Other OSes should be better than Windows at fractional scaling. + QApplication::setHighDpiScaleFactorRoundingPolicy( + Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); +#endif + + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); +} + int main(int argc, char* argv[]) { Common::DetachedTasks detached_tasks; MicroProfileOnThreadCreate("Frontend"); @@ -2619,6 +2759,8 @@ int main(int argc, char* argv[]) { QCoreApplication::setOrganizationName(QStringLiteral("Citra team")); QCoreApplication::setApplicationName(QStringLiteral("Citra")); + SetHighDPIAttributes(); + #ifdef __APPLE__ std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + ".."; chdir(bin_path.c_str()); diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index 297a8065c..6dbaa8313 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -41,6 +41,8 @@ template class QFutureWatcher; class QLabel; class QProgressBar; +class QPushButton; +class QSlider; class RegistersWidget; class Updater; class WaitTreeWidget; @@ -119,6 +121,7 @@ private: void RestoreUIState(); void ConnectWidgetEvents(); + void Connect3DStateEvents(); void ConnectMenuEvents(); void UpdateMenuState(); @@ -226,6 +229,7 @@ private slots: void OnStopVideoDumping(); #endif void OnCoreError(Core::System::ResultStatus, std::string); + void Toggle3D(); /// Called whenever a user selects Help->About Citra void OnMenuAboutCitra(); void OnUpdateFound(bool found, bool error); @@ -237,6 +241,7 @@ private slots: private: Q_INVOKABLE void OnMoviePlaybackCompleted(); void UpdateStatusBar(); + void Update3DState(); void LoadTranslation(); void UpdateWindowTitle(); void UpdateUISettings(); @@ -262,6 +267,8 @@ private: QLabel* game_fps_label = nullptr; QLabel* emu_frametime_label = nullptr; QPushButton* graphics_api_button = nullptr; + QPushButton* option_3d_button = nullptr; + QSlider* factor_3d_slider = nullptr; QTimer status_bar_update_timer; bool message_label_used_for_movie = false; diff --git a/src/common/misc.cpp b/src/common/misc.cpp index 68cb86cd1..59258749b 100644 --- a/src/common/misc.cpp +++ b/src/common/misc.cpp @@ -16,16 +16,19 @@ // Call directly after the command or use the error num. // This function might change the error code. std::string GetLastErrorMsg() { - static const std::size_t buff_size = 255; + constexpr std::size_t buff_size = 255; char err_str[buff_size]; + std::size_t msg_len; #ifdef _WIN32 - FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, GetLastError(), - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), err_str, buff_size, nullptr); + msg_len = + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), err_str, buff_size, nullptr); #else // Thread safe (XSI-compliant) strerror_r(errno, err_str, buff_size); + msg_len = strnlen(err_str, buff_size); #endif - return std::string(err_str, buff_size); + return std::string(err_str, msg_len); } diff --git a/src/common/settings.h b/src/common/settings.h index 0974af5ba..50f30a7d7 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -475,6 +475,7 @@ struct Values { Setting custom_bottom_top{240, "custom_bottom_top"}; Setting custom_bottom_right{360, "custom_bottom_right"}; Setting custom_bottom_bottom{480, "custom_bottom_bottom"}; + Setting custom_second_layer_opacity{100, "custom_second_layer_opacity"}; SwitchableSetting bg_red{0.f, "bg_red"}; SwitchableSetting bg_green{0.f, "bg_green"}; diff --git a/src/core/file_sys/archive_sdmc.cpp b/src/core/file_sys/archive_sdmc.cpp index a68e6bf34..9164d48c6 100644 --- a/src/core/file_sys/archive_sdmc.cpp +++ b/src/core/file_sys/archive_sdmc.cpp @@ -106,7 +106,7 @@ ResultVal> SDMCArchive::OpenFileBase(const Path& pa FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb"); if (!file.IsOpen()) { - LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening {}", full_path); + LOG_CRITICAL(Service_FS, "Error opening {}: {}", full_path, GetLastErrorMsg()); return ERROR_NOT_FOUND; } diff --git a/src/core/file_sys/cia_container.cpp b/src/core/file_sys/cia_container.cpp index 64ed8bbb0..5764a7632 100644 --- a/src/core/file_sys/cia_container.cpp +++ b/src/core/file_sys/cia_container.cpp @@ -16,8 +16,6 @@ namespace FileSys { -constexpr u32 CIA_SECTION_ALIGNMENT = 0x40; - Loader::ResultStatus CIAContainer::Load(const FileBackend& backend) { std::vector header_data(sizeof(Header)); diff --git a/src/core/file_sys/cia_container.h b/src/core/file_sys/cia_container.h index 332f2715f..021e8b7cd 100644 --- a/src/core/file_sys/cia_container.h +++ b/src/core/file_sys/cia_container.h @@ -29,6 +29,7 @@ constexpr std::size_t CIA_CONTENT_BITS_SIZE = (CIA_CONTENT_MAX_COUNT / 8); constexpr std::size_t CIA_HEADER_SIZE = 0x2020; constexpr std::size_t CIA_DEPENDENCY_SIZE = 0x300; constexpr std::size_t CIA_METADATA_SIZE = 0x400; +constexpr u32 CIA_SECTION_ALIGNMENT = 0x40; /** * Helper which implements an interface to read and write CTR Installable Archive (CIA) files. @@ -69,7 +70,6 @@ public: void Print() const; -private: struct Header { u32_le header_size; u16_le type; @@ -87,10 +87,14 @@ private: // The bits in the content index are arranged w/ index 0 as the MSB, 7 as the LSB, etc. return (content_present[index >> 3] & (0x80 >> (index & 7))) != 0; } + void SetContentPresent(u16 index) { + content_present[index >> 3] |= (0x80 >> (index & 7)); + } }; static_assert(sizeof(Header) == CIA_HEADER_SIZE, "CIA Header structure size is wrong"); +private: struct Metadata { std::array dependencies; std::array reserved; diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index 2222ece02..acb81fdaa 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -178,7 +178,7 @@ void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height, layout_option.GetValue(), Settings::values.upright_screen.GetValue()); if (Settings::values.custom_layout.GetValue() == true) { - layout = Layout::CustomFrameLayout(width, height); + layout = Layout::CustomFrameLayout(width, height, Settings::values.swap_screen.GetValue()); } else { width = std::max(width, min_size.first); height = std::max(height, min_size.second); diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp index 4b3512d6a..75f330f18 100644 --- a/src/core/frontend/framebuffer_layout.cpp +++ b/src/core/frontend/framebuffer_layout.cpp @@ -349,7 +349,7 @@ FramebufferLayout SeparateWindowsLayout(u32 width, u32 height, bool is_secondary return SingleFrameLayout(width, height, is_secondary, upright); } -FramebufferLayout CustomFrameLayout(u32 width, u32 height) { +FramebufferLayout CustomFrameLayout(u32 width, u32 height, bool is_swapped) { ASSERT(width > 0); ASSERT(height > 0); @@ -364,8 +364,13 @@ FramebufferLayout CustomFrameLayout(u32 width, u32 height) { Settings::values.custom_bottom_right.GetValue(), Settings::values.custom_bottom_bottom.GetValue()}; - res.top_screen = top_screen; - res.bottom_screen = bot_screen; + if (is_swapped) { + res.top_screen = bot_screen; + res.bottom_screen = top_screen; + } else { + res.top_screen = top_screen; + res.bottom_screen = bot_screen; + } return res; } @@ -375,7 +380,8 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar layout = CustomFrameLayout(std::max(Settings::values.custom_top_right.GetValue(), Settings::values.custom_bottom_right.GetValue()), std::max(Settings::values.custom_top_bottom.GetValue(), - Settings::values.custom_bottom_bottom.GetValue())); + Settings::values.custom_bottom_bottom.GetValue()), + Settings::values.swap_screen.GetValue()); } else { int width, height; switch (Settings::values.layout_option.GetValue()) { diff --git a/src/core/frontend/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h index 92b43f285..763262663 100644 --- a/src/core/frontend/framebuffer_layout.h +++ b/src/core/frontend/framebuffer_layout.h @@ -114,7 +114,7 @@ FramebufferLayout SeparateWindowsLayout(u32 width, u32 height, bool is_secondary * @param height Window framebuffer height in pixels * @return Newly created FramebufferLayout object with default screen regions initialized */ -FramebufferLayout CustomFrameLayout(u32 width, u32 height); +FramebufferLayout CustomFrameLayout(u32 width, u32 height, bool is_swapped); /** * Convenience method to get frame layout by resolution scale diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 71a09e9e5..4578ffc23 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -9,6 +9,7 @@ #include #include #include +#include "common/alignment.h" #include "common/common_paths.h" #include "common/file_util.h" #include "common/logging/log.h" @@ -31,6 +32,9 @@ #include "core/hle/service/fs/fs_user.h" #include "core/loader/loader.h" #include "core/loader/smdh.h" +#ifdef ENABLE_WEB_SERVICE +#include "web_service/nus_download.h" +#endif namespace Service::AM { @@ -138,6 +142,8 @@ ResultCode CIAFile::WriteTitleMetadata() { decryption_state->content[i].SetKeyWithIV(title_key->data(), title_key->size(), ctr.data()); } + } else { + LOG_ERROR(Service_AM, "Can't get title key from ticket"); } install_state = CIAInstallState::TMDLoaded; @@ -180,6 +186,11 @@ ResultVal CIAFile::WriteContentData(u64 offset, std::size_t length, buffer + (range_min - offset) + available_to_write); if ((tmd.GetContentTypeByIndex(i) & FileSys::TMDContentTypeFlag::Encrypted) != 0) { + if (decryption_state->content.size() <= i) { + // TODO: There is probably no correct error to return here. What error should be + // returned? + return FileSys::ERROR_INSUFFICIENT_SPACE; + } decryption_state->content[i].ProcessData(temp.data(), temp.data(), temp.size()); } @@ -235,7 +246,7 @@ ResultVal CIAFile::Write(u64 offset, std::size_t length, bool flush std::size_t buf_offset = buf_loaded - offset; std::size_t buf_copy_size = std::min(length, static_cast(container.GetContentOffset() - offset)) - - buf_loaded; + buf_offset; std::size_t buf_max_size = std::min(offset + length, container.GetContentOffset()); data.resize(buf_max_size); memcpy(data.data() + copy_offset, buffer + buf_offset, buf_copy_size); @@ -418,6 +429,99 @@ InstallStatus InstallCIA(const std::string& path, return InstallStatus::ErrorInvalid; } +InstallStatus InstallFromNus(u64 title_id, int version) { +#ifdef ENABLE_WEB_SERVICE + LOG_DEBUG(Service_AM, "Downloading {:X}", title_id); + + CIAFile install_file{GetTitleMediaType(title_id)}; + + std::string path = fmt::format("/ccs/download/{:016X}/tmd", title_id); + if (version != -1) { + path += fmt::format(".{}", version); + } + auto tmd_response = WebService::NUS::Download(path); + if (!tmd_response) { + LOG_ERROR(Service_AM, "Failed to download tmd for {:016X}", title_id); + return InstallStatus::ErrorFileNotFound; + } + FileSys::TitleMetadata tmd; + tmd.Load(*tmd_response); + + path = fmt::format("/ccs/download/{:016X}/cetk", title_id); + auto cetk_response = WebService::NUS::Download(path); + if (!cetk_response) { + LOG_ERROR(Service_AM, "Failed to download cetk for {:016X}", title_id); + return InstallStatus::ErrorFileNotFound; + } + + std::vector content; + const auto content_count = tmd.GetContentCount(); + for (std::size_t i = 0; i < content_count; ++i) { + const std::string filename = fmt::format("{:08x}", tmd.GetContentIDByIndex(i)); + path = fmt::format("/ccs/download/{:016X}/{}", title_id, filename); + const auto temp_response = WebService::NUS::Download(path); + if (!temp_response) { + LOG_ERROR(Service_AM, "Failed to download content for {:016X}", title_id); + return InstallStatus::ErrorFileNotFound; + } + content.insert(content.end(), temp_response->begin(), temp_response->end()); + } + + FileSys::CIAContainer::Header fake_header{ + .header_size = sizeof(FileSys::CIAContainer::Header), + .type = 0, + .version = 0, + .cert_size = 0, + .tik_size = static_cast(cetk_response->size()), + .tmd_size = static_cast(tmd_response->size()), + .meta_size = 0, + }; + for (u16 i = 0; i < content_count; ++i) { + fake_header.SetContentPresent(i); + } + std::vector header_data(sizeof(fake_header)); + std::memcpy(header_data.data(), &fake_header, sizeof(fake_header)); + + std::size_t current_offset = 0; + const auto write_to_cia_file_aligned = [&install_file, ¤t_offset](std::vector& data) { + const u64 offset = + Common::AlignUp(current_offset + data.size(), FileSys::CIA_SECTION_ALIGNMENT); + data.resize(offset - current_offset, 0); + const auto result = install_file.Write(current_offset, data.size(), true, data.data()); + if (result.Failed()) { + LOG_ERROR(Service_AM, "CIA file installation aborted with error code {:08x}", + result.Code().raw); + return InstallStatus::ErrorAborted; + } + current_offset += data.size(); + return InstallStatus::Success; + }; + + auto result = write_to_cia_file_aligned(header_data); + if (result != InstallStatus::Success) { + return result; + } + + result = write_to_cia_file_aligned(*cetk_response); + if (result != InstallStatus::Success) { + return result; + } + + result = write_to_cia_file_aligned(*tmd_response); + if (result != InstallStatus::Success) { + return result; + } + + result = write_to_cia_file_aligned(content); + if (result != InstallStatus::Success) { + return result; + } + return InstallStatus::Success; +#else + return InstallStatus::ErrorFileNotFound; +#endif +} + Service::FS::MediaType GetTitleMediaType(u64 titleId) { u16 platform = static_cast(titleId >> 48); u16 category = static_cast((titleId >> 32) & 0xFFFF); diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 6e2dae6fc..cd8f4dcf7 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -110,6 +110,13 @@ private: InstallStatus InstallCIA(const std::string& path, std::function&& update_callback = nullptr); +/** + * Downloads and installs title form the Nintendo Update Service. + * @param title_id the title_id to download + * @returns whether the install was successful or error code + */ +InstallStatus InstallFromNus(u64 title_id, int version = -1); + /** * Get the mediatype for an installed title * @param titleId the installed title ID diff --git a/src/core/hle/service/apt/applet_manager.cpp b/src/core/hle/service/apt/applet_manager.cpp index 73d4e6d89..9ba7e8110 100644 --- a/src/core/hle/service/apt/applet_manager.cpp +++ b/src/core/hle/service/apt/applet_manager.cpp @@ -283,8 +283,15 @@ ResultVal AppletManager::Initialize(AppletId ap slot_data->title_id = system.Kernel().GetCurrentProcess()->codeset->program_id; slot_data->attributes.raw = attributes.raw; - if (slot_data->applet_id == AppletId::Application || - slot_data->applet_id == AppletId::HomeMenu) { + const auto* home_menu_slot = GetAppletSlotData(AppletId::HomeMenu); + + // Applications need to receive a Wakeup signal to actually start up, this signal is usually + // sent by the Home Menu after starting the app by way of APT::WakeupApplication. In some cases + // such as when starting a game directly or the Home Menu itself, we have to send the signal + // ourselves since the Home Menu is not running yet. We detect if the Home Menu is running by + // checking if there's an applet registered in the HomeMenu slot. + if (slot_data->applet_id == AppletId::HomeMenu || + (slot_data->applet_id == AppletId::Application && !home_menu_slot)) { // Initialize the APT parameter to wake up the application. next_parameter.emplace(); next_parameter->signal = SignalType::Wakeup; @@ -310,6 +317,12 @@ ResultCode AppletManager::Enable(AppletAttributes attributes) { slot_data->registered = true; + // Send any outstanding parameters to the newly-registered application + if (delayed_parameter && delayed_parameter->destination_id == slot_data->applet_id) { + CancelAndSendParameter(*delayed_parameter); + delayed_parameter.reset(); + } + return RESULT_SUCCESS; } @@ -580,6 +593,89 @@ ResultCode AppletManager::DoApplicationJump(DeliverArg arg) { */ } +ResultCode AppletManager::PrepareToStartApplication(u64 title_id, FS::MediaType media_type) { + // TODO(Subv): This should check that the current applet is of System type and return 0xc8a0cc04 + // if not. + + // TODO(Subv): This should return 0xc8a0cff0 if the applet preparation state is already set + + const auto& application_slot = applet_slots[static_cast(AppletSlot::Application)]; + + if (application_slot.registered) { + // TODO(Subv): Convert this to the enum constructor of ResultCode + return ResultCode(0xc8a0cffc); + } + + ASSERT_MSG(!app_start_parameters, + "Trying to prepare an application when another is already prepared"); + + app_start_parameters.emplace(); + app_start_parameters->next_title_id = title_id; + app_start_parameters->next_media_type = media_type; + + return RESULT_SUCCESS; +} + +ResultCode AppletManager::StartApplication(const std::vector& parameter, + const std::vector& hmac, bool paused) { + // The delivery argument is always unconditionally set. + deliver_arg.emplace(DeliverArg{parameter, hmac}); + + // Note: APT first checks if we can launch the application via AM::CheckDemoLaunchRights and + // returns 0xc8a12403 if we can't. We intentionally do not implement that check. + + // TODO(Subv): The APT service performs several checks here related to the exheader flags of the + // process we're launching and other things like title id blacklists. We do not yet implement + // any of that. + + // TODO(Subv): The real APT service doesn't seem to check whether the titleid to launch is set + // or not, it either launches NATIVE_FIRM if some internal state is set, or fails when calling + // PM::LaunchTitle. We should research more about that. + ASSERT_MSG(app_start_parameters, "Trying to start an application without preparing it first."); + + // Launch the title directly. + const auto process = + NS::LaunchTitle(app_start_parameters->next_media_type, app_start_parameters->next_title_id); + if (!process) { + LOG_CRITICAL(Service_APT, "Failed to launch title during application start, exiting."); + system.RequestShutdown(); + } + + app_start_parameters.reset(); + + if (!paused) { + return WakeupApplication(); + } + + return RESULT_SUCCESS; +} + +ResultCode AppletManager::WakeupApplication() { + // Send a Wakeup signal via the apt parameter to the application once it registers itself. + // The real APT service does this by spinwaiting on another thread until the application is + // registered. + MessageParameter wakeup_parameter{}; + wakeup_parameter.signal = SignalType::Wakeup; + wakeup_parameter.sender_id = AppletId::HomeMenu; + wakeup_parameter.destination_id = AppletId::Application; + SendApplicationParameterAfterRegistration(wakeup_parameter); + + return RESULT_SUCCESS; +} + +void AppletManager::SendApplicationParameterAfterRegistration(const MessageParameter& parameter) { + const auto* slot = GetAppletSlotData(AppletId::Application); + + // If the application is already registered, immediately send the parameter + if (slot && slot->registered) { + CancelAndSendParameter(parameter); + return; + } + + // Otherwise queue it until the Application calls APT::Enable + delayed_parameter = parameter; +} + void AppletManager::EnsureHomeMenuLoaded() { const auto& system_slot = applet_slots[static_cast(AppletSlot::SystemApplet)]; // TODO(Subv): The real APT service sends signal 12 (WakeupByCancel) to the currently running diff --git a/src/core/hle/service/apt/applet_manager.h b/src/core/hle/service/apt/applet_manager.h index 830efa20f..78b6ed32c 100644 --- a/src/core/hle/service/apt/applet_manager.h +++ b/src/core/hle/service/apt/applet_manager.h @@ -185,6 +185,11 @@ public: deliver_arg = std::move(arg); } + ResultCode PrepareToStartApplication(u64 title_id, FS::MediaType media_type); + ResultCode StartApplication(const std::vector& parameter, const std::vector& hmac, + bool paused); + ResultCode WakeupApplication(); + struct AppletInfo { u64 title_id; Service::FS::MediaType media_type; @@ -221,11 +226,28 @@ public: return app_jump_parameters; } + struct ApplicationStartParameters { + u64 next_title_id; + FS::MediaType next_media_type; + + private: + template + void serialize(Archive& ar, const unsigned int) { + ar& next_title_id; + ar& next_media_type; + } + friend class boost::serialization::access; + }; + private: /// Parameter data to be returned in the next call to Glance/ReceiveParameter. // NOTE: A bug in gcc prevents serializing std::optional boost::optional next_parameter; + /// This parameter will be sent to the application/applet once they register themselves by using + /// APT::Initialize. + boost::optional delayed_parameter; + static constexpr std::size_t NumAppletSlot = 4; enum class AppletSlot : u8 { @@ -271,6 +293,7 @@ private: }; ApplicationJumpParameters app_jump_parameters{}; + boost::optional app_start_parameters{}; boost::optional deliver_arg{}; // Holds data about the concurrently running applets in the system. @@ -280,6 +303,10 @@ private: AppletSlotData* GetAppletSlotData(AppletId id); AppletSlotData* GetAppletSlotData(AppletAttributes attributes); + /// Checks if the Application slot has already been registered and sends the parameter to it, + /// otherwise it queues for sending when the application registers itself with APT::Enable. + void SendApplicationParameterAfterRegistration(const MessageParameter& parameter); + void EnsureHomeMenuLoaded(); // Command that will be sent to the application when a library applet calls CloseLibraryApplet. @@ -293,6 +320,8 @@ private: ar& next_parameter; ar& app_jump_parameters; if (file_version > 0) { + ar& delayed_parameter; + ar& app_start_parameters; ar& deliver_arg; } ar& applet_slots; diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index ce094a8fe..1983622ae 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp @@ -540,39 +540,40 @@ void Module::APTInterface::ReceiveDeliverArg(Kernel::HLERequestContext& ctx) { void Module::APTInterface::PrepareToStartApplication(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x15, 5, 0); // 0x00150140 - u32 title_info1 = rp.Pop(); - u32 title_info2 = rp.Pop(); - u32 title_info3 = rp.Pop(); - u32 title_info4 = rp.Pop(); - u32 flags = rp.Pop(); - - if (flags & 0x00000100) { - apt->unknown_ns_state_field = 1; - } + const u64 title_id = rp.Pop(); + const auto media_type = rp.PopEnum(); + rp.Skip(1, false); // Padding + const u32 flags = rp.Pop(); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(RESULT_SUCCESS); // No error + rb.Push(apt->applet_manager->PrepareToStartApplication(title_id, media_type)); - LOG_WARNING(Service_APT, - "(STUBBED) called title_info1={:#010X}, title_info2={:#010X}, title_info3={:#010X}," - "title_info4={:#010X}, flags={:#010X}", - title_info1, title_info2, title_info3, title_info4, flags); + LOG_INFO(Service_APT, "called title_id={:#010X} media_type={} flags={:#010X}", title_id, + media_type, flags); } void Module::APTInterface::StartApplication(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x1B, 3, 4); // 0x001B00C4 - const auto buffer1_size = rp.Pop(); - const auto buffer2_size = rp.Pop(); - const auto flag = rp.Pop(); - [[maybe_unused]] const std::vector buffer1 = rp.PopStaticBuffer(); - [[maybe_unused]] const std::vector buffer2 = rp.PopStaticBuffer(); + const u32 parameter_size = rp.Pop(); + const u32 hmac_size = rp.Pop(); + const bool paused = rp.Pop(); + const std::vector parameter = rp.PopStaticBuffer(); + const std::vector hmac = rp.PopStaticBuffer(); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(RESULT_SUCCESS); // No error + rb.Push(apt->applet_manager->StartApplication(parameter, hmac, paused)); - LOG_WARNING(Service_APT, - "(STUBBED) called buffer1_size={:#010X}, buffer2_size={:#010X}, flag={:#010X}", - buffer1_size, buffer2_size, flag); + LOG_INFO(Service_APT, "called parameter_size={:#010X}, hmac_size={:#010X}, paused={}", + parameter_size, hmac_size, paused); +} + +void Module::APTInterface::WakeupApplication(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x1C, 0, 0); // 0x001C0000 + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(apt->applet_manager->WakeupApplication()); + + LOG_DEBUG(Service_APT, "called"); } void Module::APTInterface::AppletUtility(Kernel::HLERequestContext& ctx) { diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h index 851136ad0..78aa636a5 100644 --- a/src/core/hle/service/apt/apt.h +++ b/src/core/hle/service/apt/apt.h @@ -361,6 +361,16 @@ public: */ void StartApplication(Kernel::HLERequestContext& ctx); + /** + * APT::WakeupApplication service function. + * Inputs: + * 0 : Command header [0x001C0000] + * Outputs: + * 0 : Return Header + * 1 : Result of function, 0 on success, otherwise error code + */ + void WakeupApplication(Kernel::HLERequestContext& ctx); + /** * APT::AppletUtility service function * Inputs: diff --git a/src/core/hle/service/apt/apt_a.cpp b/src/core/hle/service/apt/apt_a.cpp index 87e454f1c..98bee76d4 100644 --- a/src/core/hle/service/apt/apt_a.cpp +++ b/src/core/hle/service/apt/apt_a.cpp @@ -36,8 +36,8 @@ APT_A::APT_A(std::shared_ptr apt) {0x00180040, &APT_A::PrepareToStartLibraryApplet, "PrepareToStartLibraryApplet"}, {0x00190040, nullptr, "PrepareToStartSystemApplet"}, {0x001A0000, nullptr, "PrepareToStartNewestHomeMenu"}, - {0x001B00C4, nullptr, "StartApplication"}, - {0x001C0000, nullptr, "WakeupApplication"}, + {0x001B00C4, &APT_A::StartApplication, "StartApplication"}, + {0x001C0000, &APT_A::WakeupApplication, "WakeupApplication"}, {0x001D0000, nullptr, "CancelApplication"}, {0x001E0084, &APT_A::StartLibraryApplet, "StartLibraryApplet"}, {0x001F0084, nullptr, "StartSystemApplet"}, diff --git a/src/core/hle/service/apt/apt_s.cpp b/src/core/hle/service/apt/apt_s.cpp index 732b55a23..9cb6f1b0f 100644 --- a/src/core/hle/service/apt/apt_s.cpp +++ b/src/core/hle/service/apt/apt_s.cpp @@ -36,8 +36,8 @@ APT_S::APT_S(std::shared_ptr apt) {0x00180040, &APT_S::PrepareToStartLibraryApplet, "PrepareToStartLibraryApplet"}, {0x00190040, nullptr, "PrepareToStartSystemApplet"}, {0x001A0000, &APT_S::PrepareToStartNewestHomeMenu, "PrepareToStartNewestHomeMenu"}, - {0x001B00C4, nullptr, "StartApplication"}, - {0x001C0000, nullptr, "WakeupApplication"}, + {0x001B00C4, &APT_S::StartApplication, "StartApplication"}, + {0x001C0000, &APT_S::WakeupApplication, "WakeupApplication"}, {0x001D0000, nullptr, "CancelApplication"}, {0x001E0084, &APT_S::StartLibraryApplet, "StartLibraryApplet"}, {0x001F0084, nullptr, "StartSystemApplet"}, diff --git a/src/core/hle/service/apt/apt_u.cpp b/src/core/hle/service/apt/apt_u.cpp index ec1dbe291..da119a177 100644 --- a/src/core/hle/service/apt/apt_u.cpp +++ b/src/core/hle/service/apt/apt_u.cpp @@ -36,8 +36,8 @@ APT_U::APT_U(std::shared_ptr apt) {0x00180040, &APT_U::PrepareToStartLibraryApplet, "PrepareToStartLibraryApplet"}, {0x00190040, nullptr, "PrepareToStartSystemApplet"}, {0x001A0000, nullptr, "PrepareToStartNewestHomeMenu"}, - {0x001B00C4, nullptr, "StartApplication"}, - {0x001C0000, nullptr, "WakeupApplication"}, + {0x001B00C4, &APT_U::StartApplication, "StartApplication"}, + {0x001C0000, &APT_U::WakeupApplication, "WakeupApplication"}, {0x001D0000, nullptr, "CancelApplication"}, {0x001E0084, &APT_U::StartLibraryApplet, "StartLibraryApplet"}, {0x001F0084, nullptr, "StartSystemApplet"}, diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 0b37c5d56..14ab57f49 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -153,6 +153,20 @@ struct ConsoleCountryInfo { u8 country_code; ///< The country code of the console }; static_assert(sizeof(ConsoleCountryInfo) == 4, "ConsoleCountryInfo must be exactly 4 bytes"); + +struct BacklightControls { + u8 power_saving_enabled; ///< Whether power saving mode is enabled. + u8 brightness_level; ///< The configured brightness level. +}; +static_assert(sizeof(BacklightControls) == 2, "BacklightControls must be exactly 2 bytes"); + +struct New3dsBacklightControls { + u8 unknown_1[4]; ///< Unknown data + u8 auto_brightness_enabled; ///< Whether auto brightness is enabled. + u8 unknown_2[3]; ///< Unknown data +}; +static_assert(sizeof(New3dsBacklightControls) == 8, + "New3dsBacklightControls must be exactly 8 bytes"); } // namespace constexpr EULAVersion MAX_EULA_VERSION{0x7F, 0x7F}; @@ -166,6 +180,8 @@ constexpr u8 UNITED_STATES_COUNTRY_ID = 49; constexpr u8 WASHINGTON_DC_STATE_ID = 2; /// TODO(Subv): Find what the other bytes are constexpr ConsoleCountryInfo COUNTRY_INFO{{0, 0}, WASHINGTON_DC_STATE_ID, UNITED_STATES_COUNTRY_ID}; +constexpr BacklightControls BACKLIGHT_CONTROLS{0, 2}; +constexpr New3dsBacklightControls NEW_3DS_BACKLIGHT_CONTROLS{{0, 0, 0, 0}, 0, {0, 0, 0}}; /** * TODO(Subv): Find out what this actually is, these values fix some NaN uniforms in some games, @@ -508,11 +524,23 @@ ResultCode Module::FormatConfig() { if (!res.IsSuccess()) return res; + // 0x00050001 - Backlight controls + res = CreateConfigInfoBlk(BacklightControlsBlockID, sizeof(BACKLIGHT_CONTROLS), 0xC, + &BACKLIGHT_CONTROLS); + if (!res.IsSuccess()) + return res; + res = CreateConfigInfoBlk(StereoCameraSettingsBlockID, sizeof(STEREO_CAMERA_SETTINGS), 0xE, STEREO_CAMERA_SETTINGS.data()); if (!res.IsSuccess()) return res; + // 0x00050009 - New 3DS backlight controls + res = CreateConfigInfoBlk(BacklightControlNew3dsBlockID, sizeof(NEW_3DS_BACKLIGHT_CONTROLS), + 0xC, &NEW_3DS_BACKLIGHT_CONTROLS); + if (!res.IsSuccess()) + return res; + res = CreateConfigInfoBlk(SoundOutputModeBlockID, sizeof(SOUND_OUTPUT_MODE), 0xE, &SOUND_OUTPUT_MODE); if (!res.IsSuccess()) diff --git a/src/core/hw/aes/key.cpp b/src/core/hw/aes/key.cpp index e406aed71..8fdb7f514 100644 --- a/src/core/hw/aes/key.cpp +++ b/src/core/hw/aes/key.cpp @@ -88,7 +88,7 @@ struct KeySlot { }; std::array key_slots; -std::array, 6> common_key_y_slots; +std::array, MaxCommonKeySlot> common_key_y_slots; enum class FirmwareType : u32 { ARM9 = 0, // uses NDMA @@ -494,9 +494,9 @@ void LoadPresetKeys() { } // namespace -void InitKeys() { +void InitKeys(bool force) { static bool initialized = false; - if (initialized) + if (initialized && !force) return; initialized = true; HW::RSA::InitSlots(); diff --git a/src/core/hw/aes/key.h b/src/core/hw/aes/key.h index 0e1530f0c..fa21ea783 100644 --- a/src/core/hw/aes/key.h +++ b/src/core/hw/aes/key.h @@ -48,11 +48,13 @@ enum KeySlotID : std::size_t { MaxKeySlotID = 0x40, }; +constexpr std::size_t MaxCommonKeySlot = 6; + constexpr std::size_t AES_BLOCK_SIZE = 16; using AESKey = std::array; -void InitKeys(); +void InitKeys(bool force = false); void SetGeneratorConstant(const AESKey& key); void SetKeyX(std::size_t slot_id, const AESKey& key); diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 2486d94b6..c5255481b 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -876,134 +876,161 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f } glUniform1i(uniform_layer, 0); - if (layout.top_screen_enabled) { - if (layout.is_rotated) { - if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Off) { - int eye = static_cast(Settings::values.mono_render_option.GetValue()); - DrawSingleScreenRotated(screen_infos[eye], (float)top_screen.left, - (float)top_screen.top, (float)top_screen.GetWidth(), - (float)top_screen.GetHeight()); - } else if (Settings::values.render_3d.GetValue() == - Settings::StereoRenderOption::SideBySide) { - DrawSingleScreenRotated(screen_infos[0], (float)top_screen.left / 2, - (float)top_screen.top, (float)top_screen.GetWidth() / 2, - (float)top_screen.GetHeight()); - glUniform1i(uniform_layer, 1); - DrawSingleScreenRotated(screen_infos[1], - ((float)top_screen.left / 2) + ((float)layout.width / 2), - (float)top_screen.top, (float)top_screen.GetWidth() / 2, - (float)top_screen.GetHeight()); - } else if (Settings::values.render_3d.GetValue() == - Settings::StereoRenderOption::CardboardVR) { - DrawSingleScreenRotated(screen_infos[0], layout.top_screen.left, - layout.top_screen.top, layout.top_screen.GetWidth(), - layout.top_screen.GetHeight()); - glUniform1i(uniform_layer, 1); - DrawSingleScreenRotated(screen_infos[1], - layout.cardboard.top_screen_right_eye + - ((float)layout.width / 2), - layout.top_screen.top, layout.top_screen.GetWidth(), - layout.top_screen.GetHeight()); - } else if (stereo_single_screen) { - DrawSingleScreenStereoRotated( - screen_infos[0], screen_infos[1], (float)top_screen.left, (float)top_screen.top, - (float)top_screen.GetWidth(), (float)top_screen.GetHeight()); - } - } else { - if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Off) { - int eye = static_cast(Settings::values.mono_render_option.GetValue()); - DrawSingleScreen(screen_infos[eye], (float)top_screen.left, (float)top_screen.top, - (float)top_screen.GetWidth(), (float)top_screen.GetHeight()); - } else if (Settings::values.render_3d.GetValue() == - Settings::StereoRenderOption::SideBySide) { - DrawSingleScreen(screen_infos[0], (float)top_screen.left / 2, (float)top_screen.top, - (float)top_screen.GetWidth() / 2, (float)top_screen.GetHeight()); - glUniform1i(uniform_layer, 1); - DrawSingleScreen(screen_infos[1], - ((float)top_screen.left / 2) + ((float)layout.width / 2), - (float)top_screen.top, (float)top_screen.GetWidth() / 2, - (float)top_screen.GetHeight()); - } else if (Settings::values.render_3d.GetValue() == - Settings::StereoRenderOption::CardboardVR) { - DrawSingleScreen(screen_infos[0], layout.top_screen.left, layout.top_screen.top, - layout.top_screen.GetWidth(), layout.top_screen.GetHeight()); - glUniform1i(uniform_layer, 1); - DrawSingleScreen(screen_infos[1], - layout.cardboard.top_screen_right_eye + ((float)layout.width / 2), - layout.top_screen.top, layout.top_screen.GetWidth(), - layout.top_screen.GetHeight()); - } else if (stereo_single_screen) { - DrawSingleScreenStereo(screen_infos[0], screen_infos[1], (float)top_screen.left, - (float)top_screen.top, (float)top_screen.GetWidth(), - (float)top_screen.GetHeight()); - } + if (!Settings::values.swap_screen) { + DrawTopScreen(layout, top_screen, stereo_single_screen); + glUniform1i(uniform_layer, 0); + ApplySecondLayerOpacity(); + DrawBottomScreen(layout, bottom_screen, stereo_single_screen); + } else { + DrawBottomScreen(layout, bottom_screen, stereo_single_screen); + glUniform1i(uniform_layer, 0); + ApplySecondLayerOpacity(); + DrawTopScreen(layout, top_screen, stereo_single_screen); + } + state.blend.enabled = false; +} + +void RendererOpenGL::ApplySecondLayerOpacity() { + if (Settings::values.custom_layout && + Settings::values.custom_second_layer_opacity.GetValue() < 100) { + state.blend.enabled = true; + state.blend.src_rgb_func = GL_CONSTANT_ALPHA; + state.blend.src_a_func = GL_CONSTANT_ALPHA; + state.blend.dst_a_func = GL_ONE_MINUS_CONSTANT_ALPHA; + state.blend.dst_rgb_func = GL_ONE_MINUS_CONSTANT_ALPHA; + state.blend.color.alpha = Settings::values.custom_second_layer_opacity.GetValue() / 100.0f; + } +} + +void RendererOpenGL::DrawTopScreen(const Layout::FramebufferLayout& layout, + const Common::Rectangle& top_screen, + const bool stereo_single_screen) { + if (!layout.top_screen_enabled) { + return; + } + + if (layout.is_rotated) { + if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Off) { + DrawSingleScreenRotated(screen_infos[0], (float)top_screen.left, (float)top_screen.top, + (float)top_screen.GetWidth(), (float)top_screen.GetHeight()); + } else if (Settings::values.render_3d.GetValue() == + Settings::StereoRenderOption::SideBySide) { + DrawSingleScreenRotated(screen_infos[0], (float)top_screen.left / 2, + (float)top_screen.top, (float)top_screen.GetWidth() / 2, + (float)top_screen.GetHeight()); + glUniform1i(uniform_layer, 1); + DrawSingleScreenRotated(screen_infos[1], + ((float)top_screen.left / 2) + ((float)layout.width / 2), + (float)top_screen.top, (float)top_screen.GetWidth() / 2, + (float)top_screen.GetHeight()); + } else if (Settings::values.render_3d.GetValue() == + Settings::StereoRenderOption::CardboardVR) { + DrawSingleScreenRotated(screen_infos[0], layout.top_screen.left, layout.top_screen.top, + layout.top_screen.GetWidth(), layout.top_screen.GetHeight()); + glUniform1i(uniform_layer, 1); + DrawSingleScreenRotated( + screen_infos[1], layout.cardboard.top_screen_right_eye + ((float)layout.width / 2), + layout.top_screen.top, layout.top_screen.GetWidth(), layout.top_screen.GetHeight()); + } else if (stereo_single_screen) { + DrawSingleScreenStereoRotated(screen_infos[0], screen_infos[1], (float)top_screen.left, + (float)top_screen.top, (float)top_screen.GetWidth(), + (float)top_screen.GetHeight()); + } + } else { + if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Off) { + DrawSingleScreen(screen_infos[0], (float)top_screen.left, (float)top_screen.top, + (float)top_screen.GetWidth(), (float)top_screen.GetHeight()); + } else if (Settings::values.render_3d.GetValue() == + Settings::StereoRenderOption::SideBySide) { + DrawSingleScreen(screen_infos[0], (float)top_screen.left / 2, (float)top_screen.top, + (float)top_screen.GetWidth() / 2, (float)top_screen.GetHeight()); + glUniform1i(uniform_layer, 1); + DrawSingleScreen(screen_infos[1], + ((float)top_screen.left / 2) + ((float)layout.width / 2), + (float)top_screen.top, (float)top_screen.GetWidth() / 2, + (float)top_screen.GetHeight()); + } else if (Settings::values.render_3d.GetValue() == + Settings::StereoRenderOption::CardboardVR) { + DrawSingleScreen(screen_infos[0], layout.top_screen.left, layout.top_screen.top, + layout.top_screen.GetWidth(), layout.top_screen.GetHeight()); + glUniform1i(uniform_layer, 1); + DrawSingleScreen( + screen_infos[1], layout.cardboard.top_screen_right_eye + ((float)layout.width / 2), + layout.top_screen.top, layout.top_screen.GetWidth(), layout.top_screen.GetHeight()); + } else if (stereo_single_screen) { + DrawSingleScreenStereo(screen_infos[0], screen_infos[1], (float)top_screen.left, + (float)top_screen.top, (float)top_screen.GetWidth(), + (float)top_screen.GetHeight()); } } - glUniform1i(uniform_layer, 0); - if (layout.bottom_screen_enabled) { - if (layout.is_rotated) { - if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Off) { - DrawSingleScreenRotated(screen_infos[2], (float)bottom_screen.left, - (float)bottom_screen.top, (float)bottom_screen.GetWidth(), - (float)bottom_screen.GetHeight()); - } else if (Settings::values.render_3d.GetValue() == - Settings::StereoRenderOption::SideBySide) { - DrawSingleScreenRotated( - screen_infos[2], (float)bottom_screen.left / 2, (float)bottom_screen.top, - (float)bottom_screen.GetWidth() / 2, (float)bottom_screen.GetHeight()); - glUniform1i(uniform_layer, 1); - DrawSingleScreenRotated( - screen_infos[2], ((float)bottom_screen.left / 2) + ((float)layout.width / 2), - (float)bottom_screen.top, (float)bottom_screen.GetWidth() / 2, - (float)bottom_screen.GetHeight()); - } else if (Settings::values.render_3d.GetValue() == - Settings::StereoRenderOption::CardboardVR) { - DrawSingleScreenRotated(screen_infos[2], layout.bottom_screen.left, - layout.bottom_screen.top, layout.bottom_screen.GetWidth(), - layout.bottom_screen.GetHeight()); - glUniform1i(uniform_layer, 1); - DrawSingleScreenRotated(screen_infos[2], - layout.cardboard.bottom_screen_right_eye + - ((float)layout.width / 2), - layout.bottom_screen.top, layout.bottom_screen.GetWidth(), - layout.bottom_screen.GetHeight()); - } else if (stereo_single_screen) { - DrawSingleScreenStereoRotated(screen_infos[2], screen_infos[2], - (float)bottom_screen.left, (float)bottom_screen.top, - (float)bottom_screen.GetWidth(), - (float)bottom_screen.GetHeight()); - } - } else { - if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Off) { - DrawSingleScreen(screen_infos[2], (float)bottom_screen.left, - (float)bottom_screen.top, (float)bottom_screen.GetWidth(), - (float)bottom_screen.GetHeight()); - } else if (Settings::values.render_3d.GetValue() == - Settings::StereoRenderOption::SideBySide) { - DrawSingleScreen(screen_infos[2], (float)bottom_screen.left / 2, - (float)bottom_screen.top, (float)bottom_screen.GetWidth() / 2, - (float)bottom_screen.GetHeight()); - glUniform1i(uniform_layer, 1); - DrawSingleScreen(screen_infos[2], - ((float)bottom_screen.left / 2) + ((float)layout.width / 2), - (float)bottom_screen.top, (float)bottom_screen.GetWidth() / 2, - (float)bottom_screen.GetHeight()); - } else if (Settings::values.render_3d.GetValue() == - Settings::StereoRenderOption::CardboardVR) { - DrawSingleScreen(screen_infos[2], layout.bottom_screen.left, - layout.bottom_screen.top, layout.bottom_screen.GetWidth(), - layout.bottom_screen.GetHeight()); - glUniform1i(uniform_layer, 1); - DrawSingleScreen(screen_infos[2], - layout.cardboard.bottom_screen_right_eye + - ((float)layout.width / 2), - layout.bottom_screen.top, layout.bottom_screen.GetWidth(), - layout.bottom_screen.GetHeight()); - } else if (stereo_single_screen) { - DrawSingleScreenStereo(screen_infos[2], screen_infos[2], (float)bottom_screen.left, - (float)bottom_screen.top, (float)bottom_screen.GetWidth(), - (float)bottom_screen.GetHeight()); - } +} + +void RendererOpenGL::DrawBottomScreen(const Layout::FramebufferLayout& layout, + const Common::Rectangle& bottom_screen, + const bool stereo_single_screen) { + if (!layout.bottom_screen_enabled) { + return; + } + + if (layout.is_rotated) { + if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Off) { + DrawSingleScreenRotated(screen_infos[2], (float)bottom_screen.left, + (float)bottom_screen.top, (float)bottom_screen.GetWidth(), + (float)bottom_screen.GetHeight()); + } else if (Settings::values.render_3d.GetValue() == + Settings::StereoRenderOption::SideBySide) { + DrawSingleScreenRotated(screen_infos[2], (float)bottom_screen.left / 2, + (float)bottom_screen.top, (float)bottom_screen.GetWidth() / 2, + (float)bottom_screen.GetHeight()); + glUniform1i(uniform_layer, 1); + DrawSingleScreenRotated(screen_infos[2], + ((float)bottom_screen.left / 2) + ((float)layout.width / 2), + (float)bottom_screen.top, (float)bottom_screen.GetWidth() / 2, + (float)bottom_screen.GetHeight()); + } else if (Settings::values.render_3d.GetValue() == + Settings::StereoRenderOption::CardboardVR) { + DrawSingleScreenRotated(screen_infos[2], layout.bottom_screen.left, + layout.bottom_screen.top, layout.bottom_screen.GetWidth(), + layout.bottom_screen.GetHeight()); + glUniform1i(uniform_layer, 1); + DrawSingleScreenRotated(screen_infos[2], + layout.cardboard.bottom_screen_right_eye + + ((float)layout.width / 2), + layout.bottom_screen.top, layout.bottom_screen.GetWidth(), + layout.bottom_screen.GetHeight()); + } else if (stereo_single_screen) { + DrawSingleScreenStereoRotated(screen_infos[2], screen_infos[2], + (float)bottom_screen.left, (float)bottom_screen.top, + (float)bottom_screen.GetWidth(), + (float)bottom_screen.GetHeight()); + } + } else { + if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Off) { + DrawSingleScreen(screen_infos[2], (float)bottom_screen.left, (float)bottom_screen.top, + (float)bottom_screen.GetWidth(), (float)bottom_screen.GetHeight()); + } else if (Settings::values.render_3d.GetValue() == + Settings::StereoRenderOption::SideBySide) { + DrawSingleScreen(screen_infos[2], (float)bottom_screen.left / 2, + (float)bottom_screen.top, (float)bottom_screen.GetWidth() / 2, + (float)bottom_screen.GetHeight()); + glUniform1i(uniform_layer, 1); + DrawSingleScreen(screen_infos[2], + ((float)bottom_screen.left / 2) + ((float)layout.width / 2), + (float)bottom_screen.top, (float)bottom_screen.GetWidth() / 2, + (float)bottom_screen.GetHeight()); + } else if (Settings::values.render_3d.GetValue() == + Settings::StereoRenderOption::CardboardVR) { + DrawSingleScreen(screen_infos[2], layout.bottom_screen.left, layout.bottom_screen.top, + layout.bottom_screen.GetWidth(), layout.bottom_screen.GetHeight()); + glUniform1i(uniform_layer, 1); + DrawSingleScreen(screen_infos[2], + layout.cardboard.bottom_screen_right_eye + ((float)layout.width / 2), + layout.bottom_screen.top, layout.bottom_screen.GetWidth(), + layout.bottom_screen.GetHeight()); + } else if (stereo_single_screen) { + DrawSingleScreenStereo(screen_infos[2], screen_infos[2], (float)bottom_screen.left, + (float)bottom_screen.top, (float)bottom_screen.GetWidth(), + (float)bottom_screen.GetHeight()); } } } diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index 8c745b240..c52e4f422 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -92,6 +92,12 @@ private: * Draws the emulated screens to the emulator window. */ void DrawScreens(const Layout::FramebufferLayout& layout, bool flipped); + void ApplySecondLayerOpacity(); + void DrawBottomScreen(const Layout::FramebufferLayout& layout, + const Common::Rectangle& bottom_screen, + const bool stereo_single_screen); + void DrawTopScreen(const Layout::FramebufferLayout& layout, + const Common::Rectangle& top_screen, const bool stereo_single_screen); /** * Draws a single texture to the emulator window. diff --git a/src/web_service/CMakeLists.txt b/src/web_service/CMakeLists.txt index 1fd61ffa0..553b36298 100644 --- a/src/web_service/CMakeLists.txt +++ b/src/web_service/CMakeLists.txt @@ -1,6 +1,9 @@ add_library(web_service STATIC announce_room_json.cpp announce_room_json.h + nus_download.cpp + nus_download.h + nus_titles.h precompiled_headers.h telemetry_json.cpp telemetry_json.h diff --git a/src/web_service/nus_download.cpp b/src/web_service/nus_download.cpp new file mode 100644 index 000000000..6811f5c23 --- /dev/null +++ b/src/web_service/nus_download.cpp @@ -0,0 +1,47 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "common/logging/log.h" +#include "web_service/nus_download.h" + +namespace WebService::NUS { + +std::optional> Download(const std::string& path) { + constexpr auto HOST = "http://nus.cdn.c.shop.nintendowifi.net"; + + std::unique_ptr client = std::make_unique(HOST); + if (client == nullptr) { + LOG_ERROR(WebService, "Invalid URL {}{}", HOST, path); + return {}; + } + + httplib::Request request{ + .method = "GET", + .path = path, + }; + + client->set_follow_location(true); + const auto result = client->send(request); + if (!result) { + LOG_ERROR(WebService, "GET to {}{} returned null", HOST, path); + return {}; + } + + const auto& response = result.value(); + if (response.status >= 400) { + LOG_ERROR(WebService, "GET to {}{} returned error status code: {}", HOST, path, + response.status); + return {}; + } + if (!response.headers.contains("content-type")) { + LOG_ERROR(WebService, "GET to {}{} returned no content", HOST, path); + return {}; + } + + return std::vector(response.body.begin(), response.body.end()); +} + +} // namespace WebService::NUS diff --git a/src/web_service/nus_download.h b/src/web_service/nus_download.h new file mode 100644 index 000000000..34633b767 --- /dev/null +++ b/src/web_service/nus_download.h @@ -0,0 +1,15 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "common/common_types.h" + +namespace WebService::NUS { + +std::optional> Download(const std::string& path); + +} diff --git a/src/web_service/nus_titles.h b/src/web_service/nus_titles.h new file mode 100644 index 000000000..fe7b6d183 --- /dev/null +++ b/src/web_service/nus_titles.h @@ -0,0 +1,761 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +constexpr u32 SYSTEM_FIRMWARE_UPPER_TITLE_ID = 0x00040138; +constexpr u32 SYSTEM_APPLICATION_UPPER_TITLE_ID = 0x00040010; +constexpr u32 SYSTEM_DATA_ARCHIVE_UPPER_TITLE_ID = 0x0004001B; +constexpr u32 SYSTEM_APPLET_UPPER_TITLE_ID = 0x00040030; +constexpr u32 SHARED_DATA_ARCHIVE_UPPER_TITLE_ID = 0x0004009B; +constexpr u32 SYSTEM_DATA_ARCHIVE_2_UPPER_TITLE_ID = 0x000400DB; +constexpr u32 SYSTEM_MODULE_UPPER_TITLE_ID = 0x00040130; + +struct Title { + enum Mode { All, Recommended, Minimal }; + std::string name; + u32 upper_title_id; + std::array lower_title_id; + Mode mode = Mode::All; +}; + +static const std::array SYSTEM_FIRMWARE = { + {{"Safe Mode Native Firmware", + SYSTEM_FIRMWARE_UPPER_TITLE_ID, + {{0x00000003, 0x00000003, 0x00000003, 0x00000003, 0x00000003, 0x00000003}}, + Title::Mode::Minimal}, + {"New_3DS Safe Mode Native Firmware", + SYSTEM_FIRMWARE_UPPER_TITLE_ID, + {{0x20000003, 0x20000003, 0x20000003, 0x20000003, 0x20000003, 0x20000003}}, + Title::Mode::Minimal}, + {"Native Firmware", + SYSTEM_FIRMWARE_UPPER_TITLE_ID, + {{0x00000002, 0x00000002, 0x00000002, 0x00000002, 0x00000002, 0x00000002}}, + Title::Mode::Minimal}, + {"New_3DS Native Firmware", + SYSTEM_FIRMWARE_UPPER_TITLE_ID, + {{0x20000002, 0x20000002, 0x20000002, 0x20000002, 0x20000002, 0x20000002}}, + Title::Mode::Minimal}}}; +static const std::array SYSTEM_APPLICATIONS = { + {{"System Settings", + SYSTEM_APPLICATION_UPPER_TITLE_ID, + {{0x00020000, 0x00021000, 0x00022000, 0x00026000, 0x00027000, 0x00028000}}, + Title::Mode::All}, + {"Download Play", + SYSTEM_APPLICATION_UPPER_TITLE_ID, + {{0x00020100, 0x00021100, 0x00022100, 0x00026100, 0x00027100, 0x00028100}}, + Title::Mode::Recommended}, + {"Activity Log", + SYSTEM_APPLICATION_UPPER_TITLE_ID, + {{0x00020200, 0x00021200, 0x00022200, 0x00026200, 0x00027200, 0x00028200}}, + Title::Mode::All}, + {"Health and Safety Information", + SYSTEM_APPLICATION_UPPER_TITLE_ID, + {{0x00020300, 0x00021300, 0x00022300, 0x00026300, 0x00027300, 0x00028300}}, + Title::Mode::All}, + {"New_3DS Health and Safety Information", + SYSTEM_APPLICATION_UPPER_TITLE_ID, + {{0x20020300, 0x20021300, 0x20022300, 0x0, 0x20027300, 0x0}}, + Title::Mode::All}, + {"Nintendo 3DS Camera", + SYSTEM_APPLICATION_UPPER_TITLE_ID, + {{0x00020400, 0x00021400, 0x00022400, 0x00026400, 0x00027400, 0x00028400}}, + Title::Mode::All}, + {"Nintendo 3DS Sound", + SYSTEM_APPLICATION_UPPER_TITLE_ID, + {{0x00020500, 0x00021500, 0x00022500, 0x00026500, 0x00027500, 0x00028500}}, + Title::Mode::All}, + {"Mii Maker", + SYSTEM_APPLICATION_UPPER_TITLE_ID, + {{0x00020700, 0x00021700, 0x00022700, 0x00026700, 0x00027700, 0x00028700}}, + Title::Mode::Recommended}, + {"StreetPass Mii Plaza", + SYSTEM_APPLICATION_UPPER_TITLE_ID, + {{0x00020800, 0x00021800, 0x00022800, 0x00026800, 0x00027800, 0x00028800}}, + Title::Mode::All}, + {"eShop", + SYSTEM_APPLICATION_UPPER_TITLE_ID, + {{0x00020900, 0x00021900, 0x00022900, 0x0, 0x00027900, 0x00028900}}, + Title::Mode::Recommended}, + {"System Transfer", + SYSTEM_APPLICATION_UPPER_TITLE_ID, + {{0x00020A00, 0x00021A00, 0x00022A00, 0x0, 0x00027A00, 0x00028A00}}, + Title::Mode::All}, + {"Nintendo Zone", + SYSTEM_APPLICATION_UPPER_TITLE_ID, + {{0x00020B00, 0x00021B00, 0x00022B00, 0x0, 0x0, 0x0}}, + Title::Mode::All}, + {"Face Raiders", + SYSTEM_APPLICATION_UPPER_TITLE_ID, + {{0x00020D00, 0x00021D00, 0x00022D00, 0x00026D00, 0x00027D00, 0x00028D00}}, + Title::Mode::All}, + {"New_3DS Face Raiders", + SYSTEM_APPLICATION_UPPER_TITLE_ID, + {{0x20020D00, 0x20021D00, 0x20022D00, 0x0, 0x20027D00, 0x0}}, + Title::Mode::All}, + {"AR Games", + SYSTEM_APPLICATION_UPPER_TITLE_ID, + {{0x00020E00, 0x00021E00, 0x00022E00, 0x00026E00, 0x00027E00, 0x00028E00}}, + Title::Mode::All}, + {"Nintendo Network ID Settings", + SYSTEM_APPLICATION_UPPER_TITLE_ID, + {{0x0002BF00, 0x0002C000, 0x0002C100, 0x0, 0x0, 0x0}}, + Title::Mode::Recommended}, + {"microSD Management", + SYSTEM_APPLICATION_UPPER_TITLE_ID, + {{0x20023100, 0x20024100, 0x20025100, 0x0, 0x0, 0x0}}, + Title::Mode::All}}}; + +static const std::array SYSTEM_DATA_ARCHIVES = { + {{"ClCertA", + SYSTEM_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x00010002, 0x00010002, 0x00010002, 0x00010002, 0x00010002, 0x00010002}}, + Title::Mode::Recommended}, + {"NS CFA", + SYSTEM_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x00010702, 0x00010702, 0x00010702, 0x00010702, 0x00010702, 0x00010702}}, + Title::Mode::All}, + {"dummy.txt", + SYSTEM_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x00010802, 0x00010802, 0x00010802, 0x00010802, 0x00010802, 0x00010802}}, + Title::Mode::All}, + {"CFA web-browser data", + SYSTEM_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x00018002, 0x00018002, 0x00018002, 0x00018002, 0x00018002, 0x00018002}}, + Title::Mode::All}, + {"local web-browser data", + SYSTEM_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x00018102, 0x00018102, 0x00018102, 0x00018102, 0x00018102, 0x00018102}}, + Title::Mode::All}, + {"webkit/OSS CROs", + SYSTEM_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x00018202, 0x00018202, 0x00018202, 0x00018202, 0x00018202, 0x00018202}}, + Title::Mode::All}, + {"Fangate_updater", + SYSTEM_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x00019002, 0x00019002, 0x00019002, 0x00019002, 0x00019002, 0x00019002}}, + Title::Mode::All}}}; + +static const std::array SYSTEM_APPLETS = { + {{"Home Menu", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x00008202, 0x00008F02, 0x00009802, 0x0000A102, 0x0000A902, 0x0000B102}}, + Title::Mode::All}, + {"Camera applet", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x00008402, 0x00009002, 0x00009902, 0x0000A202, 0x0000AA02, 0x0000B202}}, + Title::Mode::All}, + {"Instruction Manual", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x00008602, 0x00009202, 0x00009B02, 0x0000A402, 0x0000AC02, 0x0000B402}}, + Title::Mode::Recommended}, + {"Game Notes", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x00008702, 0x00009302, 0x00009C02, 0x0000A502, 0x0000AD02, 0x0000B502}}, + Title::Mode::All}, + {"Internet Browser", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x00008802, 0x00009402, 0x00009D02, 0x0000A602, 0x0000AE02, 0x0000B602}}, + Title::Mode::All}, + {"New 3DS Internet Browser", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x20008802, 0x20009402, 0x20009D02, 0x0, 0x2000AE02, 0x0}}, + Title::Mode::All}, + {"Fatal error viewer", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x00008A02, 0x00008A02, 0x00008A02, 0x00008A02, 0x00008A02, 0x00008A02}}, + Title::Mode::All}, + {"Safe Mode Fatal error viewer", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x00008A03, 0x00008A03, 0x00008A03, 0x00008A03, 0x00008A03, 0x00008A03}}, + Title::Mode::All}, + {"New 3DS Safe Mode Fatal error viewer", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x20008A03, 0x20008A03, 0x20008A03, 0x0, 0x20008A03, 0x0}}, + Title::Mode::All}, + {"Friend List", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x00008D02, 0x00009602, 0x00009F02, 0x0000A702, 0x0000AF02, 0x0000B702}}, + Title::Mode::Recommended}, + {"Notifications", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x00008E02, 0x000009702, 0x0000A002, 0x0000A802, 0x0000B002, 0x0000B802}}, + Title::Mode::All}, + {"Software Keyboard", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x00000C002, 0x0000C802, 0x0000D002, 0x0000D802, 0x0000DE02, 0x0000E402}}, + Title::Mode::Recommended}, + {"Safe Mode Software Keyboard", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x00000C003, 0x0000C803, 0x0000D003, 0x0000D803, 0x0000DE03, 0x0000E403}}, + Title::Mode::All}, + {"New 3DS Safe Mode Software Keyboard", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x2000C003, 0x2000C803, 0x2000D003, 0x0, 0x2000DE03, 0x0}}, + Title::Mode::All}, + {"Mii picker", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x0000C102, 0x0000C902, 0x0000D102, 0x0000D902, 0x0000DF02, 0x0000E502}}, + Title::Mode::Recommended}, + {"Picture picker", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x0000C302, 0x0000CB02, 0x0000D302, 0x0000DB02, 0x0000E102, 0x0000E702}}, + Title::Mode::All}, + {"Voice memo picker", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x0000C402, 0x0000CC02, 0x0000D402, 0x0000DC02, 0x0000E202, 0x0000E802}}, + Title::Mode::All}, + {"Error display", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x0000C502, 0x0000C502, 0x0000C502, 0x0000CF02, 0x0000CF02, 0x0000CF02}}, + Title::Mode::All}, + {"Safe mode error display", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x0000C503, 0x0000C503, 0x0000C503, 0x0000CF03, 0x0000CF03, 0x0000CF03}}, + Title::Mode::All}, + {"New 3DS safe mode error display", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x2000C503, 0x2000C503, 0x2000C503, 0x0, 0x2000CF03, 0x0}}, + Title::Mode::All}, + {"Circle Pad Pro test/calibration applet", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x0000CD02, 0x0000CD02, 0x0000CD02, 0x0000D502, 0x0000D502, 0x0000D502}}, + Title::Mode::All}, + {"eShop applet", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x0000C602, 0x0000CE02, 0x0000D602, 0x0, 0x0000E302, 0x0000E902}}, + Title::Mode::Recommended}, + {"Miiverse", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x0000BC02, 0x0000BC02, 0x0000BC02, 0x0, 0x0, 0x0}}, + Title::Mode::All}, + {"Miiverse system library", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x0000F602, 0x0000F602, 0x0000F602, 0x0, 0x0, 0x0}}, + Title::Mode::All}, + {"Miiverse-posting applet", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x00008302, 0x00008B02, 0x0000BA02, 0x0, 0x0, 0x0}}, + Title::Mode::All}, + {"Amiibo Settings", + SYSTEM_APPLET_UPPER_TITLE_ID, + {{0x00009502, 0x00009E02, 0x0000B902, 0x0, 0x00008C02, 0x0000BF02}}, + Title::Mode::All}}}; + +static const std::array SHARED_DATA_ARCHIVES = { + {{"CFL_Res.dat", + SHARED_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x00010202, 0x00010202, 0x00010202, 0x00010202, 0x00010202, 0x00010202}}, + Title::Mode::All}, + {"Region Manifest", + SHARED_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x00010402, 0x00010402, 0x00010402, 0x00010402, 0x00010402, 0x00010402}}, + Title::Mode::All}, + {"Non-Nintendo TLS Root-CA Certificates", + SHARED_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x00010602, 0x00010602, 0x00010602, 0x00010602, 0x00010602, 0x00010602}}, + Title::Mode::Recommended}, + {"CHN/CN Dictionary", + SHARED_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x0, 0x0, 0x0, 0x00011002, 0x0, 0x0}}, + Title::Mode::All}, + {"TWN/TN dictionary", + SHARED_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x0, 0x0, 0x0, 0x0, 0x0, 0x00011102}}, + Title::Mode::All}, + {"NL/NL dictionary", + SHARED_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x0, 0x0, 0x00011202, 0x0, 0x0, 0x0}}, + Title::Mode::All}, + {"EN/GB dictionary", + SHARED_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x0, 0x0, 0x00011302, 0x0, 0x0, 0x0}}, + Title::Mode::All}, + {"EN/US dictionary", + SHARED_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x0, 0x00011402, 0x0, 0x0, 0x0, 0x0}}, + Title::Mode::All}, + {"FR/FR/regular dictionary", + SHARED_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x0, 0x0, 0x00011502, 0x0, 0x0, 0x0}}, + Title::Mode::All}, + {"FR/CA/regular dictionary", + SHARED_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x0, 0x00011602, 0x0, 0x0, 0x0, 0x0}}, + Title::Mode::All}, + {"DE/regular dictionary", + SHARED_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x0, 0x0, 0x00011702, 0x0, 0x0, 0x0}}, + Title::Mode::All}, + {"IT/IT dictionary", + SHARED_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x0, 0x0, 0x00011802, 0x0, 0x0, 0x0}}, + Title::Mode::All}, + {"JA_small/32 dictionary", + SHARED_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x00011902, 0x0, 0x0, 0x0, 0x0, 0x0}}, + Title::Mode::All}, + {"KO/KO dictionary", + SHARED_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x0, 0x0, 0x0, 0x0, 0x00011A02, 0x0}}, + Title::Mode::All}, + {"PT/PT/regular dictionary", + SHARED_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x0, 0x0, 0x00011B02, 0x0, 0x0, 0x0}}, + Title::Mode::All}, + {"RU/regular dictionary", + SHARED_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x0, 0x0, 0x00011C02, 0x0, 0x0, 0x0}}, + Title::Mode::All}, + {"ES/ES dictionary", + SHARED_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x0, 0x00011D02, 0x00011D02, 0x0, 0x0, 0x0}}, + Title::Mode::All}, + {"PT/BR/regular dictionary", + SHARED_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x0, 0x00011E02, 0x0, 0x0, 0x0, 0x0}}, + Title::Mode::All}, + {"error strings", + SHARED_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x00012202, 0x00012302, 0x00012102, 0x00012402, 0x00012502, 0x00012602}}, + Title::Mode::All}, + {"eula", + SHARED_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x00013202, 0x00013302, 0x00013102, 0x00013502, 0x0, 0x0}}, + Title::Mode::All}, + {"JPN/EUR/USA System Font", + SHARED_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x00014002, 0x00014002, 0x00014002, 0x00014002, 0x00014002, 0x00014002}}, + Title::Mode::Recommended}, + {"CHN System Font", + SHARED_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x00014102, 0x00014102, 0x00014102, 0x00014102, 0x00014102, 0x00014102}}, + Title::Mode::Recommended}, + {"KOR System Font", + SHARED_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x00014202, 0x00014202, 0x00014202, 0x00014202, 0x00014202, 0x00014202}}, + Title::Mode::Recommended}, + {"TWN System Font", + SHARED_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x00014302, 0x00014302, 0x00014302, 0x00014302, 0x00014302, 0x00014302}}, + Title::Mode::Recommended}, + {"rate", + SHARED_DATA_ARCHIVE_UPPER_TITLE_ID, + {{0x00015202, 0x00015302, 0x00015102, 0x0, 0x0015502, 0x00015602}}, + Title::Mode::All}}}; + +static const std::array SYSTEM_DATA_ARCHIVES_2 = { + {{"bad word list", + SYSTEM_DATA_ARCHIVE_2_UPPER_TITLE_ID, + {{0x00010302, 0x00010302, 0x00010302, 0x00010302, 0x00010302, 0x00010302}}, + Title::Mode::All}, + {"Nintendo Zone hotspot list", + SYSTEM_DATA_ARCHIVE_2_UPPER_TITLE_ID, + {{0x00010502, 0x00010502, 0x00010502, 0x00010502, 0x00010502, 0x00010502}}, + Title::Mode::All}, + {"NVer", + SYSTEM_DATA_ARCHIVE_2_UPPER_TITLE_ID, + {{0x00016102, 0x00016202, 0x00016302, 0x00016402, 0x00016502, 0x00016602}}, + Title::Mode::All}, + {"New_3DS NVer", + SYSTEM_DATA_ARCHIVE_2_UPPER_TITLE_ID, + {{0x20016102, 0x20016202, 0x20016302, 0x0, 0x20016502, 0x0}}, + Title::Mode::All}, + {"CVer", + SYSTEM_DATA_ARCHIVE_2_UPPER_TITLE_ID, + {{0x00017102, 0x00017202, 0x00017302, 0x00017402, 0x00017502, 0x00017602}}, + Title::Mode::All}}}; + +static const std::array SYSTEM_MODULES = { + {{"AM ( Application Manager )", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00001502, 0x00001502, 0x00001502, 0x00001502, 0x00001502, 0x00001502}}, + Title::Mode::All}, + {"Safe Mode AM", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00001503, 0x00001503, 0x00001503, 0x00001503, 0x00001503, 0x00001503}}, + Title::Mode::All}, + {"New_3DS Safe Mode AM", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20001503, 0x20001503, 0x20001503, 0x20001503, 0x20001503, 0x20001503}}, + Title::Mode::All}, + {"Camera", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00001602, 0x00001602, 0x00001602, 0x00001602, 0x00001602, 0x00001602}}, + Title::Mode::All}, + {"New_3DS Camera", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20001602, 0x20001602, 0x20001602, 0x20001602, 0x20001602, 0x20001602}}, + Title::Mode::All}, + {"Config (cfg)", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00001702, 0x00001702, 0x00001702, 0x00001702, 0x00001702, 0x00001702}}, + Title::Mode::All}, + {"Safe Mode Config (cfg)", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00001703, 0x00001703, 0x00001703, 0x00001703, 0x00001703, 0x00001703}}, + Title::Mode::All}, + {"New_3DS Safe Mode Config (cfg)", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20001703, 0x20001703, 0x20001703, 0x20001703, 0x20001703, 0x20001703}}, + Title::Mode::All}, + {"Codec", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00001802, 0x00001802, 0x00001802, 0x00001802, 0x00001802, 0x00001802}}, + Title::Mode::All}, + {"Safe Mode Codec", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00001803, 0x00001803, 0x00001803, 0x00001803, 0x00001803, 0x00001803}}, + Title::Mode::All}, + {"New_3DS Safe Mode Codec", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20001803, 0x20001803, 0x20001803, 0x20001803, 0x20001803, 0x20001803}}, + Title::Mode::All}, + {"DSP", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00001A02, 0x00001A02, 0x00001A02, 0x00001A02, 0x00001A02, 0x00001A02}}, + Title::Mode::All}, + {"Safe Mode DSP", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00001A03, 0x00001A03, 0x00001A03, 0x00001A03, 0x00001A03, 0x00001A03}}, + Title::Mode::All}, + {"New_3DS Safe Mode DSP", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20001A03, 0x20001A03, 0x20001A03, 0x20001A03, 0x20001A03, 0x20001A03}}, + Title::Mode::All}, + {"GPIO", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00001B02, 0x00001B02, 0x00001B02, 0x00001B02, 0x00001B02, 0x00001B02}}, + Title::Mode::All}, + {"Safe Mode GPIO", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00001B03, 0x00001B03, 0x00001B03, 0x00001B03, 0x00001B03, 0x00001B03}}, + Title::Mode::All}, + {"New_3DS Safe Mode GPIO", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20001B03, 0x20001B03, 0x20001B03, 0x20001B03, 0x20001B03, 0x20001B03}}, + Title::Mode::All}, + {"GSP", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00001C02, 0x00001C02, 0x00001C02, 0x00001C02, 0x00001C02, 0x00001C02}}, + Title::Mode::All}, + {"New_3DS GSP", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20001C02, 0x20001C02, 0x20001C02, 0x20001C02, 0x20001C02, 0x20001C02}}, + Title::Mode::All}, + {"Safe Mode GSP", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00001C03, 0x00001C03, 0x00001C03, 0x00001C03, 0x00001C03, 0x00001C03}}, + Title::Mode::All}, + {"New_3DS Safe Mode GSP", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20001C03, 0x20001C03, 0x20001C03, 0x20001C03, 0x20001C03, 0x20001C03}}, + Title::Mode::All}, + {"HID (Human Interface Devices)", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00001D02, 0x00001D02, 0x00001D02, 0x00001D02, 0x00001D02, 0x00001D02}}, + Title::Mode::All}, + {"Safe Mode HID", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00001D03, 0x00001D03, 0x00001D03, 0x00001D03, 0x00001D03, 0x00001D03}}, + Title::Mode::All}, + {"New_3DS Safe Mode HID", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20001D03, 0x20001D03, 0x20001D03, 0x20001D03, 0x20001D03, 0x20001D03}}, + Title::Mode::All}, + {"i2c", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00001E02, 0x00001E02, 0x00001E02, 0x00001E02, 0x00001E02, 0x00001E02}}, + Title::Mode::All}, + {"New_3DS i2c", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20001E02, 0x20001E02, 0x20001E02, 0x20001E02, 0x20001E02, 0x20001E02}}, + Title::Mode::All}, + {"Safe Mode i2c", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00001E03, 0x00001E03, 0x00001E03, 0x00001E03, 0x00001E03, 0x00001E03}}, + Title::Mode::All}, + {"New_3DS Safe Mode i2c", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20001E03, 0x20001E03, 0x20001E03, 0x20001E03, 0x20001E03, 0x20001E03}}, + Title::Mode::All}, + {"MCU", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00001F02, 0x00001F02, 0x00001F02, 0x00001F02, 0x00001F02, 0x00001F02}}, + Title::Mode::All}, + {"New_3DS MCU", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20001F02, 0x20001F02, 0x20001F02, 0x20001F02, 0x20001F02, 0x20001F02}}, + Title::Mode::All}, + {"Safe Mode MCU", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00001F03, 0x00001F03, 0x00001F03, 0x00001F03, 0x00001F03, 0x00001F03}}, + Title::Mode::All}, + {"New_3DS Safe Mode MCU", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20001F03, 0x20001F03, 0x20001F03, 0x20001F03, 0x20001F03, 0x20001F03}}, + Title::Mode::All}, + {"MIC (Microphone)", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002002, 0x00002002, 0x00002002, 0x00002002, 0x00002002, 0x00002002}}, + Title::Mode::All}, + {"PDN", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002102, 0x00002102, 0x00002102, 0x00002102, 0x00002102, 0x00002102}}, + Title::Mode::All}, + {"Safe Mode PDN", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002103, 0x00002103, 0x00002103, 0x00002103, 0x00002103, 0x00002103}}, + Title::Mode::All}, + {"New_3DS Safe Mode PDN", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20002103, 0x20002103, 0x20002103, 0x20002103, 0x20002103, 0x20002103}}, + Title::Mode::All}, + {"PTM (Play time, pedometer, and battery manager)", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002202, 0x00002202, 0x00002202, 0x00002202, 0x00002202, 0x00002202}}, + Title::Mode::All}, + {"New_3DS PTM (Play time, pedometer, and battery manager)", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20002202, 0x20002202, 0x20002202, 0x20002202, 0x20002202, 0x20002202}}, + Title::Mode::All}, + {"Safe Mode PTM", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002203, 0x00002203, 0x00002203, 0x00002203, 0x00002203, 0x00002203}}, + Title::Mode::All}, + {"New_3DS Safe Mode PTM", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20002203, 0x20002203, 0x20002203, 0x20002203, 0x20002203, 0x20002203}}, + Title::Mode::All}, + {"spi", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002302, 0x00002302, 0x00002302, 0x00002302, 0x00002302, 0x00002302}}, + Title::Mode::All}, + {"New_3DS spi", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20002302, 0x20002302, 0x20002302, 0x20002302, 0x20002302, 0x20002302}}, + Title::Mode::All}, + {"Safe Mode spi", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002303, 0x00002303, 0x00002303, 0x00002303, 0x00002303, 0x00002303}}, + Title::Mode::All}, + {"New_3DS Safe Mode spi", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20002303, 0x20002303, 0x20002303, 0x20002303, 0x20002303, 0x20002303}}, + Title::Mode::All}, + {"AC (Network manager)", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002402, 0x00002402, 0x00002402, 0x00002402, 0x00002402, 0x00002402}}, + Title::Mode::All}, + {"Safe Mode AC", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002403, 0x00002403, 0x00002403, 0x00002403, 0x00002403, 0x00002403}}, + Title::Mode::All}, + {"New_3DS Safe Mode AC", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20002403, 0x20002403, 0x20002403, 0x20002403, 0x20002403, 0x20002403}}, + Title::Mode::All}, + {"Cecd (StreetPass)", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002602, 0x00002602, 0x00002602, 0x00002602, 0x00002602, 0x00002602}}, + Title::Mode::All}, + {"CSND", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002702, 0x00002702, 0x00002702, 0x00002702, 0x00002702, 0x00002702}}, + Title::Mode::All}, + {"Safe Mode CSND", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002703, 0x00002703, 0x00002703, 0x00002703, 0x00002703, 0x00002703}}, + Title::Mode::All}, + {"New_3DS Safe Mode CSND", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20002703, 0x20002703, 0x20002703, 0x20002703, 0x20002703, 0x20002703}}, + Title::Mode::All}, + {"DLP (Download Play)", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002802, 0x00002802, 0x00002802, 0x00002802, 0x00002802, 0x00002802}}, + Title::Mode::Recommended}, + {"HTTP", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002902, 0x00002902, 0x00002902, 0x00002902, 0x00002902, 0x00002902}}, + Title::Mode::All}, + {"Safe Mode HTTP", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002903, 0x00002903, 0x00002903, 0x00002903, 0x00002903, 0x00002903}}, + Title::Mode::All}, + {"New_3DS Safe Mode HTTP", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20002903, 0x20002903, 0x20002903, 0x20002903, 0x20002903, 0x20002903}}, + Title::Mode::All}, + {"MP", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002A02, 0x00002A02, 0x00002A02, 0x00002A02, 0x00002A02, 0x00002A02}}, + Title::Mode::All}, + {"Safe Mode MP", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002A03, 0x00002A03, 0x00002A03, 0x00002A03, 0x00002A03, 0x00002A03}}, + Title::Mode::All}, + {"NDM", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002B02, 0x00002B02, 0x00002B02, 0x00002B02, 0x00002B02, 0x00002B02}}, + Title::Mode::All}, + {"NIM", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002C02, 0x00002C02, 0x00002C02, 0x00002C02, 0x00002C02, 0x00002C02}}, + Title::Mode::All}, + {"Safe Mode NIM", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002C03, 0x00002C03, 0x00002C03, 0x00002C03, 0x00002C03, 0x00002C03}}, + Title::Mode::All}, + {"New_3DS Safe Mode NIM", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20002C03, 0x20002C03, 0x20002C03, 0x20002C03, 0x20002C03, 0x20002C03}}, + Title::Mode::All}, + {"NWM ( Low-level wifi manager )", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002D02, 0x00002D02, 0x00002D02, 0x00002D02, 0x00002D02, 0x00002D02}}, + Title::Mode::All}, + {"Safe Mode NWM", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002D03, 0x00002D03, 0x00002D03, 0x00002D03, 0x00002D03, 0x00002D03}}, + Title::Mode::All}, + {"New_3DS Safe Mode NWM", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20002D03, 0x20002D03, 0x20002D03, 0x20002D03, 0x20002D03, 0x20002D03}}, + Title::Mode::All}, + {"Sockets", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002E02, 0x00002E02, 0x00002E02, 0x00002E02, 0x00002E02, 0x00002E02}}, + Title::Mode::All}, + {"Safe Mode Sockets", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002E03, 0x00002E03, 0x00002E03, 0x00002E03, 0x00002E03, 0x00002E03}}, + Title::Mode::All}, + {"New_3DS Safe Mode Sockets", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20002E03, 0x20002E03, 0x20002E03, 0x20002E03, 0x20002E03, 0x20002E03}}, + Title::Mode::All}, + {"SSL", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002F02, 0x00002F02, 0x00002F02, 0x00002F02, 0x00002F02, 0x00002F02}}, + Title::Mode::All}, + {"Safe Mode SSL", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00002F03, 0x00002F03, 0x00002F03, 0x00002F03, 0x00002F03, 0x00002F03}}, + Title::Mode::All}, + {"New_3DS Safe Mode SSL", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20002F03, 0x20002F03, 0x20002F03, 0x20002F03, 0x20002F03, 0x20002F03}}, + Title::Mode::All}, + {"PS ( Process Manager )", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00003102, 0x00003102, 0x00003102, 0x00003102, 0x00003102, 0x00003102}}, + Title::Mode::All}, + {"Safe Mode PS", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00003103, 0x00003103, 0x00003103, 0x00003103, 0x00003103, 0x00003103}}, + Title::Mode::All}, + {"New_3DS Safe Mode PS", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20003103, 0x20003103, 0x20003103, 0x20003103, 0x20003103, 0x20003103}}, + Title::Mode::All}, + {"friends (Friends list)", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00003202, 0x00003202, 0x00003202, 0x00003202, 0x00003202, 0x00003202}}, + Title::Mode::All}, + {"Safe Mode friends (Friends list)", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00003203, 0x00003203, 0x00003203, 0x00003203, 0x00003203, 0x00003203}}, + Title::Mode::All}, + {"New_3DS Safe Mode friends (Friends list)", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20003203, 0x20003203, 0x20003203, 0x20003203, 0x20003203, 0x20003203}}, + Title::Mode::All}, + {"IR (Infrared)", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00003302, 0x00003302, 0x00003302, 0x00003302, 0x00003302, 0x00003302}}, + Title::Mode::All}, + {"Safe Mode IR", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00003303, 0x00003303, 0x00003303, 0x00003303, 0x00003303, 0x00003303}}, + Title::Mode::All}, + {"New_3DS Safe Mode IR", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20003303, 0x20003303, 0x20003303, 0x20003303, 0x20003303, 0x20003303}}, + Title::Mode::All}, + {"BOSS (SpotPass)", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00003402, 0x00003402, 0x00003402, 0x00003402, 0x00003402, 0x00003402}}, + Title::Mode::All}, + {"News (Notifications)", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00003502, 0x00003502, 0x00003502, 0x00003502, 0x00003502, 0x00003502}}, + Title::Mode::All}, + {"RO", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00003702, 0x00003702, 0x00003702, 0x00003702, 0x00003702, 0x00003702}}, + Title::Mode::All}, + {"act", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00003802, 0x00003802, 0x00003802, 0x00003802, 0x00003802, 0x00003802}}, + Title::Mode::All}, + {"nfc", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00004002, 0x00004002, 0x00004002, 0x00004002, 0x00004002, 0x00004002}}, + Title::Mode::All}, + {"New_3DS mvd", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20004102, 0x20004102, 0x20004102, 0x20004102, 0x20004102, 0x20004102}}, + Title::Mode::All}, + {"New_3DS qtm", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20004202, 0x20004202, 0x20004202, 0x20004202, 0x20004202, 0x20004202}}, + Title::Mode::All}, + {"NS", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00008002, 0x00008002, 0x00008002, 0x00008002, 0x00008002, 0x00008002}}, + Title::Mode::All}, + {"Safe Mode NS", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x00008003, 0x00008003, 0x00008003, 0x00008003, 0x00008003, 0x00008003}}, + Title::Mode::All}, + {"New_3DS Safe Mode NS", + SYSTEM_MODULE_UPPER_TITLE_ID, + {{0x20008003, 0x20008003, 0x20008003, 0x20008003, 0x20008003, 0x20008003}}, + Title::Mode::All}}}; + +std::vector BuildFirmwareTitleList(const Title::Mode& mode, u32 region) { + // Since Australia and Europe share the same title, + // offset down by one for Australia and above. + const u32 region_index = region >= 3 ? region - 1 : region; + + const auto titles_with_mode = [mode, region_index](const Title& title) { + return mode <= title.mode && title.lower_title_id[region_index] != 0; + }; + + std::vector titles; + const auto inserter = std::back_inserter(titles); + std::copy_if(SYSTEM_FIRMWARE.begin(), SYSTEM_FIRMWARE.end(), inserter, titles_with_mode); + std::copy_if(SYSTEM_APPLICATIONS.begin(), SYSTEM_APPLICATIONS.end(), inserter, + titles_with_mode); + std::copy_if(SYSTEM_DATA_ARCHIVES.begin(), SYSTEM_DATA_ARCHIVES.end(), inserter, + titles_with_mode); + std::copy_if(SYSTEM_APPLETS.begin(), SYSTEM_APPLETS.end(), inserter, titles_with_mode); + std::copy_if(SHARED_DATA_ARCHIVES.begin(), SHARED_DATA_ARCHIVES.end(), inserter, + titles_with_mode); + std::copy_if(SYSTEM_DATA_ARCHIVES_2.begin(), SYSTEM_DATA_ARCHIVES_2.end(), inserter, + titles_with_mode); + std::copy_if(SYSTEM_MODULES.begin(), SYSTEM_MODULES.end(), inserter, titles_with_mode); + + const auto get_title_id = [region_index](const Title& title) { + return (static_cast<u64>(title.upper_title_id) << 32) + + static_cast<u64>(title.lower_title_id[region_index]); + }; + + std::vector<u64> title_ids; + std::transform(titles.begin(), titles.end(), std::back_inserter(title_ids), get_title_id); + return title_ids; +}