Merge branch 'master' of https://github.com/GPUCode/citra into vulkan-2

This commit is contained in:
GPUCode
2023-02-16 12:59:33 +02:00
48 changed files with 1815 additions and 248 deletions

View File

@ -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"

View File

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

64
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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})

View File

@ -17,6 +17,7 @@ function(copy_citra_Qt5_deps target_dir)
Qt5Core$<$<CONFIG:Debug>:d>.*
Qt5Gui$<$<CONFIG:Debug>:d>.*
Qt5Widgets$<$<CONFIG:Debug>:d>.*
Qt5Concurrent$<$<CONFIG:Debug>:d>.*
Qt5Multimedia$<$<CONFIG:Debug>:d>.*
Qt5Network$<$<CONFIG:Debug>:d>.*
)

View File

@ -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!!!**
<h1 align="center">
<br>
<a href="https://citra-emu.org/"><img src="https://raw.githubusercontent.com/citra-emu/citra-assets/master/Main/citra_logo.svg" alt="Citra" width="200"></a>
<br>
<b>Citra</b>
<br>
</h1>
# Citra
<h4 align="center"><b>Citra</b> is the world's most popular, open-source, Nintendo 3DS emulator.
<br>
It is written in C++ with portability in mind and builds are actively maintained for Windows, Linux, Android and macOS.
</h4>
[![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)
<p align="center">
<a href="https://github.com/citra-emu/citra/actions/">
<img src="https://github.com/citra-emu/citra/workflows/citra-ci/badge.svg"
alt="GitHub Actions Build Status">
</a>
<a href="https://discord.gg/FAXfZV9">
<img src="https://img.shields.io/discord/220740965957107713?color=%237289DA&label=Citra&logo=discord&logoColor=white"
alt="Discord">
</a>
</p>
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.
<p align="center">
<a href="#compatibility">Compatibility</a> |
<a href="#releases">Releases</a> |
<a href="#development">Development</a> |
<a href="#building">Building</a> |
<a href="#support">Support</a> |
<a href="#license">License</a>
</p>
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!
<a href="https://www.patreon.com/citraemu">
<img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" width="160">
</a>
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.

View File

@ -10,4 +10,19 @@ QPushButton#GraphicsAPIStatusBarButton {
QPushButton#GraphicsAPIStatusBarButton:hover {
border: 1px solid #76797C;
}
}
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;
}

View File

@ -1249,4 +1249,19 @@ QPushButton#GraphicsAPIStatusBarButton {
QPushButton#GraphicsAPIStatusBarButton:hover {
border: 1px solid #76797C;
}
}
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;
}

View File

@ -178,6 +178,8 @@ void Config::ReadValues() {
static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_bottom_right", 360));
Settings::values.custom_bottom_bottom =
static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_bottom_bottom", 480));
Settings::values.custom_second_layer_opacity =
static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_second_layer_opacity", 100));
// Utility
Settings::values.dump_textures = sdl2_config->GetBoolean("Utility", "dump_textures", false);

View File

@ -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

View File

@ -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)

View File

@ -9,6 +9,7 @@
#include <QSettings>
#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<std::array<int, 5>, 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<UISettings::Shortcut, 24> Config::default_hotkeys {{
const std::array<UISettings::Shortcut, 27> 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<UISettings::Shortcut, 24> 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();

View File

@ -26,7 +26,7 @@ public:
static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs;
static const std::array<UISettings::Shortcut, 24> default_hotkeys;
static const std::array<UISettings::Shortcut, 27> default_hotkeys;
private:
void Initialize(const std::string& config_name);

View File

@ -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);

View File

@ -3,14 +3,22 @@
// Refer to the license.txt file included.
#include <cstring>
#include <QFutureWatcher>
#include <QMessageBox>
#include <QProgressDialog>
#include <QtConcurrent/QtConcurrentMap>
#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<int, 12> 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. <br><a "
"href='https://citra-emu.org/wiki/aes-keys/'><span style=\"text-decoration: "
"underline; color:#039be5;\">How to get keys?</span></a>"));
}
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<Title::Mode>(ui->combo_download_mode->currentIndex());
const std::vector<u64> titles = BuildFirmwareTitleList(mode, cfg->GetRegionValue());
QProgressDialog progress(tr("Downloading files..."), tr("Cancel"), 0,
static_cast<int>(titles.size()), this);
progress.setWindowModality(Qt::WindowModal);
QFutureWatcher<void> future_watcher;
QObject::connect(&future_watcher, &QFutureWatcher<void>::finished, &progress,
&QProgressDialog::reset);
QObject::connect(&progress, &QProgressDialog::canceled, &future_watcher,
&QFutureWatcher<void>::cancel);
QObject::connect(&future_watcher, &QFutureWatcher<void>::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
}

View File

@ -43,6 +43,8 @@ private:
void SetupPerGameUI();
void DownloadFromNUS();
ConfigurationShared::CheckState is_new_3ds;
std::unique_ptr<Ui::ConfigureSystem> ui;
bool enabled = false;

View File

@ -361,6 +361,52 @@
</property>
</widget>
</item>
<item row="15" column="0">
<widget class="QLabel" name="label_nus_download">
<property name="text">
<string>Download System Files from Nitendo servers</string>
</property>
</widget>
</item>
<item row="15" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_nus_download">
<item>
<widget class="QComboBox" name="combo_download_mode">
<item>
<property name="text">
<string>All</string>
</property>
</item>
<item>
<property name="text">
<string>Recommended</string>
</property>
</item>
<item>
<property name="text">
<string>Minimal</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_start_download">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>Download</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>

View File

@ -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(

View File

@ -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<Ui::MainWindow>()}, config{std::make_unique<Config>()}, 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<int>(&QSlider::valueChanged), this,
[](int value) { Settings::values.factor_3d = value; });
}
void GMainWindow::OnDisplayTitleBars(bool show) {
QList<QDockWidget*> widgets = findChildren<QDockWidget*>();
@ -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<int>(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<Settings::StereoRenderOption>(
(static_cast<int>(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());

View File

@ -41,6 +41,8 @@ template <typename>
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;

View File

@ -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);
}

View File

@ -475,6 +475,7 @@ struct Values {
Setting<u16> custom_bottom_top{240, "custom_bottom_top"};
Setting<u16> custom_bottom_right{360, "custom_bottom_right"};
Setting<u16> custom_bottom_bottom{480, "custom_bottom_bottom"};
Setting<u16> custom_second_layer_opacity{100, "custom_second_layer_opacity"};
SwitchableSetting<double> bg_red{0.f, "bg_red"};
SwitchableSetting<double> bg_green{0.f, "bg_green"};

View File

@ -106,7 +106,7 @@ ResultVal<std::unique_ptr<FileBackend>> 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;
}

View File

@ -16,8 +16,6 @@
namespace FileSys {
constexpr u32 CIA_SECTION_ALIGNMENT = 0x40;
Loader::ResultStatus CIAContainer::Load(const FileBackend& backend) {
std::vector<u8> header_data(sizeof(Header));

View File

@ -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<u64_le, 0x30> dependencies;
std::array<u8, 0x180> reserved;

View File

@ -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);

View File

@ -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()) {

View File

@ -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

View File

@ -9,6 +9,7 @@
#include <cryptopp/aes.h>
#include <cryptopp/modes.h>
#include <fmt/format.h>
#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<std::size_t> 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<std::size_t> 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<std::size_t>(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<u8> 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<u32_le>(cetk_response->size()),
.tmd_size = static_cast<u32_le>(tmd_response->size()),
.meta_size = 0,
};
for (u16 i = 0; i < content_count; ++i) {
fake_header.SetContentPresent(i);
}
std::vector<u8> 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, &current_offset](std::vector<u8>& 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<u16>(titleId >> 48);
u16 category = static_cast<u16>((titleId >> 32) & 0xFFFF);

View File

@ -110,6 +110,13 @@ private:
InstallStatus InstallCIA(const std::string& path,
std::function<ProgressCallback>&& 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

View File

@ -283,8 +283,15 @@ ResultVal<AppletManager::InitializeResult> 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<size_t>(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<u8>& parameter,
const std::vector<u8>& 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<size_t>(AppletSlot::SystemApplet)];
// TODO(Subv): The real APT service sends signal 12 (WakeupByCancel) to the currently running

View File

@ -185,6 +185,11 @@ public:
deliver_arg = std::move(arg);
}
ResultCode PrepareToStartApplication(u64 title_id, FS::MediaType media_type);
ResultCode StartApplication(const std::vector<u8>& parameter, const std::vector<u8>& 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 <class Archive>
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<MessageParameter> next_parameter;
/// This parameter will be sent to the application/applet once they register themselves by using
/// APT::Initialize.
boost::optional<MessageParameter> delayed_parameter;
static constexpr std::size_t NumAppletSlot = 4;
enum class AppletSlot : u8 {
@ -271,6 +293,7 @@ private:
};
ApplicationJumpParameters app_jump_parameters{};
boost::optional<ApplicationStartParameters> app_start_parameters{};
boost::optional<DeliverArg> 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;

View File

@ -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>();
u32 title_info2 = rp.Pop<u32>();
u32 title_info3 = rp.Pop<u32>();
u32 title_info4 = rp.Pop<u32>();
u32 flags = rp.Pop<u32>();
if (flags & 0x00000100) {
apt->unknown_ns_state_field = 1;
}
const u64 title_id = rp.Pop<u64>();
const auto media_type = rp.PopEnum<FS::MediaType>();
rp.Skip(1, false); // Padding
const u32 flags = rp.Pop<u32>();
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<u32>();
const auto buffer2_size = rp.Pop<u32>();
const auto flag = rp.Pop<u32>();
[[maybe_unused]] const std::vector<u8> buffer1 = rp.PopStaticBuffer();
[[maybe_unused]] const std::vector<u8> buffer2 = rp.PopStaticBuffer();
const u32 parameter_size = rp.Pop<u32>();
const u32 hmac_size = rp.Pop<u32>();
const bool paused = rp.Pop<bool>();
const std::vector<u8> parameter = rp.PopStaticBuffer();
const std::vector<u8> 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) {

View File

@ -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:

View File

@ -36,8 +36,8 @@ APT_A::APT_A(std::shared_ptr<Module> 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"},

View File

@ -36,8 +36,8 @@ APT_S::APT_S(std::shared_ptr<Module> 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"},

View File

@ -36,8 +36,8 @@ APT_U::APT_U(std::shared_ptr<Module> 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"},

View File

@ -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())

View File

@ -88,7 +88,7 @@ struct KeySlot {
};
std::array<KeySlot, KeySlotID::MaxKeySlotID> key_slots;
std::array<std::optional<AESKey>, 6> common_key_y_slots;
std::array<std::optional<AESKey>, 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();

View File

@ -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<u8, AES_BLOCK_SIZE>;
void InitKeys();
void InitKeys(bool force = false);
void SetGeneratorConstant(const AESKey& key);
void SetKeyX(std::size_t slot_id, const AESKey& key);

View File

@ -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<int>(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<int>(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<u32>& 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<u32>& 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());
}
}
}

View File

@ -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<u32>& bottom_screen,
const bool stereo_single_screen);
void DrawTopScreen(const Layout::FramebufferLayout& layout,
const Common::Rectangle<u32>& top_screen, const bool stereo_single_screen);
/**
* Draws a single texture to the emulator window.

View File

@ -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

View File

@ -0,0 +1,47 @@
// Copyright 2020 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <memory>
#include <httplib.h>
#include "common/logging/log.h"
#include "web_service/nus_download.h"
namespace WebService::NUS {
std::optional<std::vector<u8>> Download(const std::string& path) {
constexpr auto HOST = "http://nus.cdn.c.shop.nintendowifi.net";
std::unique_ptr<httplib::Client> client = std::make_unique<httplib::Client>(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<u8>(response.body.begin(), response.body.end());
}
} // namespace WebService::NUS

View File

@ -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 <optional>
#include <vector>
#include "common/common_types.h"
namespace WebService::NUS {
std::optional<std::vector<u8>> Download(const std::string& path);
}

View File

@ -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<u32, 6> lower_title_id;
Mode mode = Mode::All;
};
static const std::array<Title, 9> 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<Title, 17> 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<Title, 7> 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<Title, 27> 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<Title, 25> 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<Title, 5> 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<Title, 100> 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<u64> 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<Title> 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;
}