From e3a21c8ef15a9f4d3f5fe8276a99083118f63b00 Mon Sep 17 00:00:00 2001
From: PabloMK7
Date: Mon, 10 Mar 2025 11:48:11 +0100
Subject: [PATCH] Add 'Set Up System Files' option (#642)
* Add 'Set Up System Files' option
* Fix CIA installation and HLE module loading when no console unique data provided.
---
src/citra_qt/citra_qt.cpp | 107 +++-
src/citra_qt/citra_qt.h | 1 +
.../configuration/configure_system.cpp | 74 +--
src/citra_qt/configuration/configure_system.h | 6 +-
.../configuration/configure_system.ui | 115 +----
src/citra_qt/main.ui | 7 +
src/common/settings.cpp | 5 +-
src/common/settings.h | 8 +-
src/core/core.cpp | 15 +-
src/core/core.h | 4 +-
src/core/hle/kernel/shared_page.h | 5 +-
src/core/hle/kernel/svc.cpp | 53 +-
src/core/hle/service/am/am.cpp | 148 ++++--
src/core/hle/service/am/am.h | 23 +-
src/core/hle/service/am/am_net.cpp | 14 +-
src/core/hle/service/am/am_u.cpp | 34 +-
src/core/hle/service/cfg/cfg.cpp | 107 +++-
src/core/hle/service/cfg/cfg.h | 23 +-
src/core/hle/service/fs/fs_user.cpp | 8 +-
src/core/hle/service/nim/nim.cpp | 6 +-
src/core/hle/service/nwm/nwm_uds.cpp | 16 +-
src/core/hle/service/ptm/ptm.cpp | 10 +-
src/core/hle/service/service.cpp | 15 +-
src/core/hle/service/service.h | 4 +-
src/core/loader/artic.cpp | 457 ++++++++++++------
src/core/loader/artic.h | 37 +-
src/core/loader/loader.cpp | 17 +-
src/core/loader/loader.h | 6 +-
src/core/perf_stats.cpp | 6 +-
src/core/system_titles.cpp | 80 ++-
src/core/system_titles.h | 8 +-
src/network/artic_base/artic_base_client.cpp | 26 +-
src/network/artic_base/artic_base_client.h | 7 +-
.../renderer_vulkan/vk_swapchain.cpp | 4 +-
34 files changed, 1006 insertions(+), 450 deletions(-)
diff --git a/src/citra_qt/citra_qt.cpp b/src/citra_qt/citra_qt.cpp
index 797034387..b0148ba27 100644
--- a/src/citra_qt/citra_qt.cpp
+++ b/src/citra_qt/citra_qt.cpp
@@ -489,7 +489,7 @@ void GMainWindow::InitializeWidgets() {
artic_traffic_label = new QLabel();
artic_traffic_label->setToolTip(
- tr("Current Artic Base traffic speed. Higher values indicate bigger transfer loads."));
+ tr("Current Artic traffic speed. Higher values indicate bigger transfer loads."));
emu_speed_label = new QLabel();
emu_speed_label->setToolTip(tr("Current emulation speed. Values higher or lower than 100% "
@@ -982,6 +982,7 @@ void GMainWindow::ConnectMenuEvents() {
connect_menu(ui->action_Load_File, &GMainWindow::OnMenuLoadFile);
connect_menu(ui->action_Install_CIA, &GMainWindow::OnMenuInstallCIA);
connect_menu(ui->action_Connect_Artic, &GMainWindow::OnMenuConnectArticBase);
+ connect_menu(ui->action_Setup_System_Files, &GMainWindow::OnMenuSetUpSystemFiles);
for (u32 region = 0; region < Core::NUM_SYSTEM_TITLE_REGIONS; region++) {
connect_menu(ui->menu_Boot_Home_Menu->actions().at(region),
[this, region] { OnMenuBootHomeMenu(region); });
@@ -1367,9 +1368,9 @@ bool GMainWindow::LoadROM(const QString& filename) {
case Core::System::ResultStatus::ErrorArticDisconnected:
QMessageBox::critical(
- this, tr("Artic Base Server"),
+ this, tr("Artic Server"),
tr(fmt::format(
- "An error has occurred whilst communicating with the Artic Base Server.\n{}",
+ "An error has occurred whilst communicating with the Artic Server.\n{}",
system.GetStatusDetails())
.c_str()));
break;
@@ -1401,7 +1402,9 @@ void GMainWindow::BootGame(const QString& filename) {
ShutdownGame();
}
- const bool is_artic = filename.startsWith(QString::fromStdString("articbase://"));
+ const bool is_artic = filename.startsWith(QString::fromStdString("articbase:/")) ||
+ filename.startsWith(QString::fromStdString("articinio:/")) ||
+ filename.startsWith(QString::fromStdString("articinin:/"));
if (!is_artic && filename.endsWith(QStringLiteral(".cia"))) {
const auto answer = QMessageBox::question(
@@ -1444,7 +1447,7 @@ void GMainWindow::BootGame(const QString& filename) {
QtConfig per_game_config(config_file_name, QtConfig::ConfigType::PerGameConfig);
}
- // Artic Base Server cannot accept a client multiple times, so multiple loaders are not
+ // Artic Server cannot accept a client multiple times, so multiple loaders are not
// possible. Instead register the app loader early and do not create it again on system load.
if (!loader->SupportsMultipleInstancesForSameFile()) {
system.RegisterAppLoaderEarly(loader);
@@ -2192,6 +2195,92 @@ void GMainWindow::OnMenuLoadFile() {
BootGame(filename);
}
+void GMainWindow::OnMenuSetUpSystemFiles() {
+ QDialog dialog(this);
+ dialog.setWindowTitle(tr("Set Up System Files"));
+
+ QVBoxLayout layout(&dialog);
+
+ QLabel label_description(
+ tr("Azahar needs files from a real console to be able to use some of its features.
"
+ "You can get such files with the Azahar "
+ "Artic Setup Tool
Notes:
- This operation will install console unique "
+ "files "
+ "to Azahar, do not share your user or nand folders
after performing the setup "
+ "process! - Old 3DS setup is needed for the New 3DS setup to "
+ "work.
- Both setup modes will work regardless of the model of the console "
+ "running the setup tool.
"),
+ &dialog);
+ label_description.setOpenExternalLinks(true);
+ layout.addWidget(&label_description);
+
+ QHBoxLayout layout_h(&dialog);
+ layout.addLayout(&layout_h);
+
+ QLabel label_enter(tr("Enter Azahar Artic Setup Tool address:"), &dialog);
+
+ layout_h.addWidget(&label_enter);
+
+ QLineEdit textInput(UISettings::values.last_artic_base_addr, &dialog);
+ layout_h.addWidget(&textInput);
+
+ QLabel label_select(tr("
Choose setup mode:"), &dialog);
+ layout.addWidget(&label_select);
+
+ std::pair install_state = Core::AreSystemTitlesInstalled();
+
+ QRadioButton radio1(&dialog);
+ QRadioButton radio2(&dialog);
+ if (!install_state.first) {
+ radio1.setText(tr("(\u2139\uFE0F) Old 3DS setup"));
+ radio1.setToolTip(tr("Setup is possible."));
+
+ radio2.setText(tr("(\u26A0) New 3DS setup"));
+ radio2.setToolTip(tr("Old 3DS setup is required first."));
+ radio2.setEnabled(false);
+ } else {
+ radio1.setText(tr("(\u2705) Old 3DS setup"));
+ radio1.setToolTip(tr("Setup completed."));
+
+ if (!install_state.second) {
+ radio2.setText(tr("(\u2139\uFE0F) New 3DS setup"));
+ radio2.setToolTip(tr("Setup is possible."));
+ } else {
+ radio2.setText(tr("(\u2705) New 3DS setup"));
+ radio2.setToolTip(tr("Setup completed."));
+ }
+ }
+ radio1.setChecked(true);
+ layout.addWidget(&radio1);
+ layout.addWidget(&radio2);
+
+ QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dialog);
+ connect(&buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
+ connect(&buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
+ layout.addWidget(&buttonBox);
+
+ int res = dialog.exec();
+ if (res == QDialog::Accepted) {
+ bool is_o3ds = radio1.isChecked();
+ if ((is_o3ds && install_state.first) || (!is_o3ds && install_state.second)) {
+ QMessageBox::StandardButton answer =
+ QMessageBox::question(this, tr("Set Up System Files"),
+ tr("The system files for the selected mode are already set "
+ "up.\nReinstall the files anyway?"),
+ QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
+ if (answer != QMessageBox::Yes) {
+ return;
+ }
+ }
+ Core::UninstallSystemFiles(is_o3ds ? Core::SystemTitleSet::Old3ds
+ : Core::SystemTitleSet::New3ds);
+ QString addr = textInput.text();
+ UISettings::values.last_artic_base_addr = addr;
+ BootGame(QString::fromStdString(is_o3ds ? "articinio://" : "articinin://").append(addr));
+ }
+}
+
void GMainWindow::OnMenuInstallCIA() {
QStringList filepaths = QFileDialog::getOpenFileNames(
this, tr("Load Files"), UISettings::values.roms_path,
@@ -3131,16 +3220,16 @@ void GMainWindow::UpdateStatusBar() {
QStringLiteral("QLabel { color: %0; }").arg(label_color[style_index]);
artic_traffic_label->setText(
- tr("Artic Base Traffic: %1 %2%3").arg(value, 0, 'f', 0).arg(unit).arg(event));
+ tr("Artic Traffic: %1 %2%3").arg(value, 0, 'f', 0).arg(unit).arg(event));
artic_traffic_label->setStyleSheet(style_sheet);
}
- if (Settings::values.frame_limit.GetValue() == 0) {
+ if (Settings::GetFrameLimit() == 0) {
emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0));
} else {
emu_speed_label->setText(tr("Speed: %1% / %2%")
.arg(results.emulation_speed * 100.0, 0, 'f', 0)
- .arg(Settings::values.frame_limit.GetValue()));
+ .arg(Settings::GetFrameLimit()));
}
game_fps_label->setText(tr("App: %1 FPS").arg(results.game_fps, 0, 'f', 0));
emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2));
@@ -3321,7 +3410,7 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
message = QString::fromStdString(details);
error_severity_icon = QMessageBox::Icon::Warning;
} else if (result == Core::System::ResultStatus::ErrorArticDisconnected) {
- title = tr("Artic Base Server");
+ title = tr("Artic Server");
message =
tr(fmt::format("A communication error has occurred. The game will quit.\n{}", details)
.c_str());
diff --git a/src/citra_qt/citra_qt.h b/src/citra_qt/citra_qt.h
index 07ef831da..19a3ff6a9 100644
--- a/src/citra_qt/citra_qt.h
+++ b/src/citra_qt/citra_qt.h
@@ -244,6 +244,7 @@ private slots:
void OnGameListOpenPerGameProperties(const QString& file);
void OnConfigurePerGame();
void OnMenuLoadFile();
+ void OnMenuSetUpSystemFiles();
void OnMenuInstallCIA();
void OnMenuConnectArticBase();
void OnMenuBootHomeMenu(u32 region);
diff --git a/src/citra_qt/configuration/configure_system.cpp b/src/citra_qt/configuration/configure_system.cpp
index f7e5b2ef8..920e1ce70 100644
--- a/src/citra_qt/configuration/configure_system.cpp
+++ b/src/citra_qt/configuration/configure_system.cpp
@@ -237,8 +237,7 @@ ConfigureSystem::ConfigureSystem(Core::System& system_, QWidget* parent)
&ConfigureSystem::UpdateInitTicks);
connect(ui->button_regenerate_console_id, &QPushButton::clicked, this,
&ConfigureSystem::RefreshConsoleID);
- connect(ui->button_start_download, &QPushButton::clicked, this,
- &ConfigureSystem::DownloadFromNUS);
+ connect(ui->button_regenerate_mac, &QPushButton::clicked, this, &ConfigureSystem::RefreshMAC);
connect(ui->button_secure_info, &QPushButton::clicked, this, [this] {
ui->button_secure_info->setEnabled(false);
@@ -281,34 +280,6 @@ ConfigureSystem::ConfigureSystem(Core::System& system_, QWidget* parent)
}
SetupPerGameUI();
-
- ui->combo_download_set->setCurrentIndex(0); // set to Minimal
- ui->combo_download_region->setCurrentIndex(0); // set to the base region
-
- HW::AES::InitKeys(true);
- bool keys_available = HW::AES::IsKeyXAvailable(HW::AES::KeySlotID::NCCHSecure1) &&
- HW::AES::IsKeyXAvailable(HW::AES::KeySlotID::NCCHSecure2);
- for (u8 i = 0; i < HW::AES::MaxCommonKeySlot && keys_available; 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_set->setEnabled(true);
- ui->combo_download_region->setEnabled(true);
- ui->label_nus_download->setText(tr("Download System Files from Nintendo servers"));
- } else {
- ui->button_start_download->setEnabled(false);
- ui->combo_download_set->setEnabled(false);
- ui->combo_download_region->setEnabled(false);
- ui->label_nus_download->setTextInteractionFlags(Qt::TextBrowserInteraction);
- ui->label_nus_download->setOpenExternalLinks(true);
- ui->label_nus_download->setText(tr("Azahar is missing keys to download system files."));
- }
-
ConfigureTime();
}
@@ -385,14 +356,13 @@ void ConfigureSystem::ReadSystemSettings() {
u64 console_id = cfg->GetConsoleUniqueId();
ui->label_console_id->setText(
tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper()));
+ mac_address = cfg->GetMacAddress();
+ ui->label_mac->setText(tr("MAC: %1").arg(QString::fromStdString(mac_address)));
// set play coin
play_coin = Service::PTM::Module::GetPlayCoins();
ui->spinBox_play_coins->setValue(play_coin);
- // set firmware download region
- ui->combo_download_region->setCurrentIndex(static_cast(cfg->GetRegionValue()));
-
// Refresh secure data status
RefreshSecureDataStatus();
}
@@ -484,6 +454,9 @@ void ConfigureSystem::ApplyConfiguration() {
Settings::values.plugin_loader_enabled.SetValue(ui->plugin_loader->isChecked());
Settings::values.allow_plugin_loader.SetValue(ui->allow_plugin_loader->isChecked());
+
+ cfg->GetMacAddress() = mac_address;
+ cfg->SaveMacAddress();
}
}
@@ -548,10 +521,11 @@ void ConfigureSystem::UpdateInitTicks(int init_ticks_type) {
void ConfigureSystem::RefreshConsoleID() {
QMessageBox::StandardButton reply;
- QString warning_text = tr("This will replace your current virtual 3DS with a new one. "
- "Your current virtual 3DS will not be recoverable. "
- "This might have unexpected effects in applications. This might fail "
- "if you use an outdated config save. Continue?");
+ QString warning_text =
+ tr("This will replace your current virtual 3DS console ID with a new one. "
+ "Your current virtual 3DS console ID will not be recoverable. "
+ "This might have unexpected effects in applications. This might fail "
+ "if you use an outdated config save. Continue?");
reply = QMessageBox::critical(this, tr("Warning"), warning_text,
QMessageBox::No | QMessageBox::Yes);
if (reply == QMessageBox::No) {
@@ -565,6 +539,21 @@ void ConfigureSystem::RefreshConsoleID() {
tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper()));
}
+void ConfigureSystem::RefreshMAC() {
+ QMessageBox::StandardButton reply;
+ QString warning_text = tr("This will replace your current MAC address with a new one. "
+ "It is not recommended to do this if you got the MAC address from "
+ "your real console using the setup tool. Continue?");
+ reply =
+ QMessageBox::warning(this, tr("Warning"), warning_text, QMessageBox::No | QMessageBox::Yes);
+ if (reply == QMessageBox::No) {
+ return;
+ }
+
+ mac_address = Service::CFG::GenerateRandomMAC();
+ ui->label_mac->setText(tr("MAC: %1").arg(QString::fromStdString(mac_address)));
+}
+
void ConfigureSystem::InstallSecureData(const std::string& from_path, const std::string& to_path) {
std::string from =
FileUtil::SanitizePath(from_path, FileUtil::DirectorySeparator::PlatformDefault);
@@ -655,20 +644,9 @@ void ConfigureSystem::SetupPerGameUI() {
ui->label_plugin_loader->setVisible(false);
ui->plugin_loader->setVisible(false);
ui->allow_plugin_loader->setVisible(false);
- // Disable the system firmware downloader.
- ui->label_nus_download->setVisible(false);
- ui->body_nus_download->setVisible(false);
ConfigurationShared::SetColoredTristate(ui->toggle_new_3ds, Settings::values.is_new_3ds,
is_new_3ds);
ConfigurationShared::SetColoredTristate(ui->toggle_lle_applets, Settings::values.lle_applets,
lle_applets);
}
-
-void ConfigureSystem::DownloadFromNUS() {
- ui->button_start_download->setEnabled(false);
-
- QMessageBox::critical(this, tr("Azahar"), tr("Downloading from NUS has been deprecated."));
-
- ui->button_start_download->setEnabled(true);
-}
diff --git a/src/citra_qt/configuration/configure_system.h b/src/citra_qt/configuration/configure_system.h
index 3041b20a0..f4b721527 100644
--- a/src/citra_qt/configuration/configure_system.h
+++ b/src/citra_qt/configuration/configure_system.h
@@ -1,4 +1,4 @@
-// Copyright 2016 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -51,14 +51,13 @@ private:
void UpdateInitTime(int init_clock);
void UpdateInitTicks(int init_ticks_type);
void RefreshConsoleID();
+ void RefreshMAC();
void InstallSecureData(const std::string& from_path, const std::string& to_path);
void RefreshSecureDataStatus();
void SetupPerGameUI();
- void DownloadFromNUS();
-
private:
std::unique_ptr ui;
Core::System& system;
@@ -75,4 +74,5 @@ private:
u8 country_code;
u16 play_coin;
bool system_setup;
+ std::string mac_address;
};
diff --git a/src/citra_qt/configuration/configure_system.ui b/src/citra_qt/configuration/configure_system.ui
index ae09d111a..ff4739245 100644
--- a/src/citra_qt/configuration/configure_system.ui
+++ b/src/citra_qt/configuration/configure_system.ui
@@ -455,113 +455,49 @@
-
+
+
+ MAC:
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Qt::RightToLeft
+
+
+ Regenerate
+
+
+
+ -
3GX Plugin Loader:
- -
+
-
Enable 3GX plugin loader
- -
+
-
Allow applications to change plugin loader state
- -
-
-
- Download System Files from Nintendo servers
-
-
-
- -
-
-
-
-
-
-
-
-
- Minimal
-
-
- -
-
- Old 3DS
-
-
- -
-
- New 3DS
-
-
-
-
- -
-
-
-
-
- JPN
-
-
- -
-
- USA
-
-
- -
-
- EUR
-
-
- -
-
- AUS
-
-
- -
-
- CHN
-
-
- -
-
- KOR
-
-
- -
-
- TWN
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Qt::RightToLeft
-
-
- Download
-
-
-
-
-
-
@@ -762,6 +698,7 @@
spinBox_play_coins
spinBox_steps_per_hour
button_regenerate_console_id
+ button_regenerate_mac
diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui
index 100189ee8..3e93ebaaf 100644
--- a/src/citra_qt/main.ui
+++ b/src/citra_qt/main.ui
@@ -79,6 +79,8 @@
+
+
@@ -238,6 +240,11 @@
Connect to Artic Base...
+
+
+ Set Up System Files...
+
+
JPN
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index d5e57fbc2..c26771018 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -1,4 +1,4 @@
-// Copyright 2014 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -74,6 +74,8 @@ std::string_view GetTextureSamplingName(TextureSampling sampling) {
Values values = {};
static bool configuring_global = true;
+bool is_temporary_frame_limit;
+double temporary_frame_limit;
void LogSettings() {
const auto log_setting = [](std::string_view name, const auto& value) {
@@ -247,5 +249,4 @@ void DeleteProfile(int index) {
void RenameCurrentProfile(std::string new_name) {
Settings::values.current_input_profile.name = std::move(new_name);
}
-
} // namespace Settings
diff --git a/src/common/settings.h b/src/common/settings.h
index f8e9e3e2e..e484c1cd2 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -1,4 +1,4 @@
-// Copyright 2014 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -618,4 +618,10 @@ void CreateProfile(std::string name);
void DeleteProfile(int index);
void RenameCurrentProfile(std::string new_name);
+extern bool is_temporary_frame_limit;
+extern double temporary_frame_limit;
+static inline double GetFrameLimit() {
+ return is_temporary_frame_limit ? temporary_frame_limit : values.frame_limit.GetValue();
+}
+
} // namespace Settings
diff --git a/src/core/core.cpp b/src/core/core.cpp
index bc822ac09..78f82eadd 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -1,4 +1,4 @@
-// Copyright 2014 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -110,9 +110,14 @@ System::ResultStatus System::RunLoop(bool tight_loop) {
}
}
switch (signal) {
- case Signal::Reset:
+ case Signal::Reset: {
+ if (app_loader && app_loader->DoingInitialSetup()) {
+ // Treat reset as shutdown if we are doing the initial setup
+ return ResultStatus::ShutdownRequested;
+ }
Reset();
return ResultStatus::Success;
+ }
case Signal::Shutdown:
return ResultStatus::ShutdownRequested;
case Signal::Load: {
@@ -471,7 +476,7 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window,
archive_manager = std::make_unique(*this);
HW::AES::InitKeys();
- Service::Init(*this);
+ Service::Init(*this, !app_loader->DoingInitialSetup());
GDBStub::DeferStart();
if (!registered_image_interface) {
@@ -708,6 +713,10 @@ void System::RegisterAppLoaderEarly(std::unique_ptr& loader)
early_app_loader = std::move(loader);
}
+bool System::IsInitialSetup() {
+ return app_loader && app_loader->DoingInitialSetup();
+}
+
template
void System::serialize(Archive& ar, const unsigned int file_version) {
diff --git a/src/core/core.h b/src/core/core.h
index 60f7605dd..5c0e14a9c 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -1,4 +1,4 @@
-// Copyright 2014 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -363,6 +363,8 @@ public:
void RegisterAppLoaderEarly(std::unique_ptr& loader);
+ bool IsInitialSetup();
+
private:
/**
* Initialize the emulated system.
diff --git a/src/core/hle/kernel/shared_page.h b/src/core/hle/kernel/shared_page.h
index d39c4c43d..17ae8d2eb 100644
--- a/src/core/hle/kernel/shared_page.h
+++ b/src/core/hle/kernel/shared_page.h
@@ -1,4 +1,4 @@
-// Copyright 2015 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -47,9 +47,6 @@ union BatteryState {
using MacAddress = std::array;
-// Default MAC address in the Nintendo 3DS range
-constexpr MacAddress DefaultMac = {0x40, 0xF4, 0x07, 0x00, 0x00, 0x00};
-
enum class WifiLinkLevel : u8 {
Off = 0,
Poor = 1,
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index ba0917d86..164e46feb 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -1,4 +1,4 @@
-// Copyright 2014 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -274,8 +274,8 @@ enum class SystemInfoMemUsageRegion {
* Accepted by svcGetSystemInfo param with CITRA_INFORMATION type. Selects which information
* to fetch from Citra. Some string params don't fit in 7 bytes, so they are split.
*/
-enum class SystemInfoCitraInformation {
- IS_CITRA = 0, // Always set the output to 1, signaling the app is running on Citra.
+enum class SystemInfoEmulatorInformation {
+ EMULATOR_ID = 0, // Always set the output to 1, signaling the app is running on Citra.
HOST_TICK = 1, // Tick reference from the host in ns, unaffected by lag or cpu speed.
EMULATION_SPEED = 2, // Gets the emulation speed set by the user or by KernelSetState.
BUILD_NAME = 10, // (ie: Nightly, Canary).
@@ -291,6 +291,15 @@ enum class SystemInfoCitraInformation {
BUILD_GIT_DESCRIPTION_PART2 = 41, // Git description (commit) last 7 characters.
};
+/**
+ * Used by the IS_EMULATOR information
+ */
+enum class EmulatorIDs {
+ NONE = 0,
+ CITRA_EMULATOR = 1,
+ AZAHAR_EMULATOR = 2,
+};
+
/**
* Current officially supported platforms.
*/
@@ -1443,7 +1452,11 @@ Result SVC::KernelSetState(u32 kernel_state, u32 varg1, u32 varg2) {
// Citra specific states.
case KernelState::KERNEL_STATE_CITRA_EMULATION_SPEED: {
u16 new_value = static_cast(varg1);
- Settings::values.frame_limit.SetValue(new_value);
+ if (new_value == 0xFFFF) {
+ Settings::is_temporary_frame_limit = false;
+ } else {
+ Settings::temporary_frame_limit = static_cast(new_value);
+ }
} break;
default:
LOG_ERROR(Kernel_SVC, "Unknown KernelSetState state={} varg1={} varg2={}", kernel_state,
@@ -1811,25 +1824,25 @@ Result SVC::GetSystemInfo(s64* out, u32 type, s32 param) {
*out = 0;
return (system.GetNumCores() == 4) ? ResultSuccess : ResultInvalidEnumValue;
case SystemInfoType::CITRA_INFORMATION:
- switch ((SystemInfoCitraInformation)param) {
- case SystemInfoCitraInformation::IS_CITRA:
- *out = 1;
+ switch ((SystemInfoEmulatorInformation)param) {
+ case SystemInfoEmulatorInformation::EMULATOR_ID:
+ *out = static_cast(EmulatorIDs::AZAHAR_EMULATOR);
break;
- case SystemInfoCitraInformation::HOST_TICK:
+ case SystemInfoEmulatorInformation::HOST_TICK:
*out = static_cast(std::chrono::duration_cast(
std::chrono::steady_clock::now().time_since_epoch())
.count());
break;
- case SystemInfoCitraInformation::EMULATION_SPEED:
+ case SystemInfoEmulatorInformation::EMULATION_SPEED:
*out = static_cast(Settings::values.frame_limit.GetValue());
break;
- case SystemInfoCitraInformation::BUILD_NAME:
+ case SystemInfoEmulatorInformation::BUILD_NAME:
CopyStringPart(reinterpret_cast(out), Common::g_build_name, 0, sizeof(s64));
break;
- case SystemInfoCitraInformation::BUILD_VERSION:
+ case SystemInfoEmulatorInformation::BUILD_VERSION:
CopyStringPart(reinterpret_cast(out), Common::g_build_version, 0, sizeof(s64));
break;
- case SystemInfoCitraInformation::BUILD_PLATFORM: {
+ case SystemInfoEmulatorInformation::BUILD_PLATFORM: {
#if defined(_WIN32)
*out = static_cast(SystemInfoCitraPlatform::PLATFORM_WINDOWS);
#elif defined(ANDROID)
@@ -1843,35 +1856,35 @@ Result SVC::GetSystemInfo(s64* out, u32 type, s32 param) {
#endif
break;
}
- case SystemInfoCitraInformation::BUILD_DATE_PART1:
+ case SystemInfoEmulatorInformation::BUILD_DATE_PART1:
CopyStringPart(reinterpret_cast(out), Common::g_build_date,
(sizeof(s64) - 1) * 0, sizeof(s64));
break;
- case SystemInfoCitraInformation::BUILD_DATE_PART2:
+ case SystemInfoEmulatorInformation::BUILD_DATE_PART2:
CopyStringPart(reinterpret_cast(out), Common::g_build_date,
(sizeof(s64) - 1) * 1, sizeof(s64));
break;
- case SystemInfoCitraInformation::BUILD_DATE_PART3:
+ case SystemInfoEmulatorInformation::BUILD_DATE_PART3:
CopyStringPart(reinterpret_cast(out), Common::g_build_date,
(sizeof(s64) - 1) * 2, sizeof(s64));
break;
- case SystemInfoCitraInformation::BUILD_DATE_PART4:
+ case SystemInfoEmulatorInformation::BUILD_DATE_PART4:
CopyStringPart(reinterpret_cast(out), Common::g_build_date,
(sizeof(s64) - 1) * 3, sizeof(s64));
break;
- case SystemInfoCitraInformation::BUILD_GIT_BRANCH_PART1:
+ case SystemInfoEmulatorInformation::BUILD_GIT_BRANCH_PART1:
CopyStringPart(reinterpret_cast(out), Common::g_scm_branch,
(sizeof(s64) - 1) * 0, sizeof(s64));
break;
- case SystemInfoCitraInformation::BUILD_GIT_BRANCH_PART2:
+ case SystemInfoEmulatorInformation::BUILD_GIT_BRANCH_PART2:
CopyStringPart(reinterpret_cast(out), Common::g_scm_branch,
(sizeof(s64) - 1) * 1, sizeof(s64));
break;
- case SystemInfoCitraInformation::BUILD_GIT_DESCRIPTION_PART1:
+ case SystemInfoEmulatorInformation::BUILD_GIT_DESCRIPTION_PART1:
CopyStringPart(reinterpret_cast(out), Common::g_scm_desc, (sizeof(s64) - 1) * 0,
sizeof(s64));
break;
- case SystemInfoCitraInformation::BUILD_GIT_DESCRIPTION_PART2:
+ case SystemInfoEmulatorInformation::BUILD_GIT_DESCRIPTION_PART2:
CopyStringPart(reinterpret_cast(out), Common::g_scm_desc, (sizeof(s64) - 1) * 1,
sizeof(s64));
break;
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 29c7bc6c3..fe693e7e9 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -94,12 +94,17 @@ public:
std::vector::Decryption> content;
};
-NCCHCryptoFile::NCCHCryptoFile(const std::string& out_file) {
- // A console unique crypto file is used to store the decrypted NCCH file. This is done
- // to prevent Azahar being used as a tool to download easy shareable decrypted contents
- // from the eshop.
- file = HW::UniqueData::OpenUniqueCryptoFile(out_file, "wb",
- HW::UniqueData::UniqueCryptoFileID::NCCH);
+NCCHCryptoFile::NCCHCryptoFile(const std::string& out_file, bool encrypted_content) {
+ if (encrypted_content) {
+ // A console unique crypto file is used to store the decrypted NCCH file. This is done
+ // to prevent Azahar being used as a tool to download easy shareable decrypted contents
+ // from the eshop.
+ file = HW::UniqueData::OpenUniqueCryptoFile(out_file, "wb",
+ HW::UniqueData::UniqueCryptoFileID::NCCH);
+ } else {
+ file = std::make_unique(out_file, "wb");
+ }
+
if (!file->IsOpen()) {
is_error = true;
}
@@ -573,7 +578,8 @@ ResultVal CIAFile::WriteContentData(u64 offset, std::size_t length,
const FileSys::TitleMetadata& tmd = container.GetTitleMetadata();
if (i != current_content_index) {
current_content_index = static_cast(i);
- current_content_file = std::make_unique(content_file_paths[i]);
+ current_content_file =
+ std::make_unique(content_file_paths[i], decryption_authorized);
current_content_file->decryption_authorized = decryption_authorized;
}
auto& file = *current_content_file;
@@ -709,7 +715,8 @@ ResultVal CIAFile::WriteContentDataIndexed(u16 content_index, u64 o
if (content_index != current_content_index) {
current_content_index = content_index;
- current_content_file = std::make_unique(content_file_paths[content_index]);
+ current_content_file = std::make_unique(content_file_paths[content_index],
+ decryption_authorized);
current_content_file->decryption_authorized = decryption_authorized;
}
auto& file = *current_content_file;
@@ -1663,9 +1670,12 @@ Result GetTitleInfoFromList(std::span title_id_list, Service::FS::Med
title_info.version = tmd.GetTitleVersion();
title_info.type = tmd.GetTitleType();
} else {
+ LOG_DEBUG(Service_AM, "not found title_id={:016X}", title_id_list[i]);
return Result(ErrorDescription::NotFound, ErrorModule::AM, ErrorSummary::InvalidState,
ErrorLevel::Permanent);
}
+ LOG_DEBUG(Service_AM, "found title_id={:016X} version={:04X}", title_id_list[i],
+ title_info.version);
title_info_out.Write(&title_info, write_offset, sizeof(TitleInfo));
write_offset += sizeof(TitleInfo);
}
@@ -1679,7 +1689,7 @@ void Module::Interface::GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool
auto media_type = static_cast(rp.Pop());
u32 title_count = rp.Pop();
- LOG_DEBUG(Service_AM, "media_type={}", media_type);
+ LOG_DEBUG(Service_AM, "media_type={}, ignore_platform={}", media_type, ignore_platform);
if (artic_client.get()) {
struct AsyncData {
@@ -1688,7 +1698,7 @@ void Module::Interface::GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool
std::vector title_id_list;
Result res{0};
- std::vector out;
+ std::vector out;
Kernel::MappedBuffer* title_id_list_buffer;
Kernel::MappedBuffer* title_info_out;
};
@@ -1730,7 +1740,7 @@ void Module::Interface::GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool
return 0;
}
- async_data->out.resize(title_infos->second);
+ async_data->out.resize(title_infos->second / sizeof(TitleInfo));
memcpy(async_data->out.data(), title_infos->first, title_infos->second);
return 0;
},
@@ -1744,7 +1754,7 @@ void Module::Interface::GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool
}
} else {
async_data->title_info_out->Write(async_data->out.data(), 0,
- async_data->out.size());
+ async_data->out.size() / sizeof(TitleInfo));
IPC::RequestBuilder rb(ctx, 1, async_data->ignore_platform ? 0 : 4);
rb.Push(async_data->res);
@@ -1763,16 +1773,20 @@ void Module::Interface::GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool
std::vector title_id_list(title_count);
title_id_list_buffer.Read(title_id_list.data(), 0, title_count * sizeof(u64));
- // eShop checks if the current importing title already exists during installation.
+ // nim checks if the current importing title already exists during installation.
// Normally, since the program wouldn't be commited, getting the title info returns not
// found. However, since GetTitleInfoFromList does not care if the program was commited and
// only checks for the tmd, it will detect the title and return information while it
- // shouldn't. To prevent this, we check if the title ID corresponds to the currently
- // importing title and return not found if so.
+ // shouldn't. To prevent this, we check if the importing context is present and not
+ // committed. If that's the case, return not found
Result result = ResultSuccess;
- if (am->importing_title) {
- for (auto tid : title_id_list) {
- if (tid == am->importing_title->title_id) {
+ for (auto tid : title_id_list) {
+ for (auto& import_ctx : am->import_title_contexts) {
+ if (import_ctx.first == tid &&
+ (import_ctx.second.state == ImportTitleContextState::WAITING_FOR_IMPORT ||
+ import_ctx.second.state == ImportTitleContextState::WAITING_FOR_COMMIT ||
+ import_ctx.second.state == ImportTitleContextState::RESUMABLE)) {
+ LOG_DEBUG(Service_AM, "title pending commit title_id={:016X}", tid);
result = Result(ErrorDescription::NotFound, ErrorModule::AM,
ErrorSummary::InvalidState, ErrorLevel::Permanent);
}
@@ -2545,9 +2559,25 @@ void Module::Interface::NeedsCleanup(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "(STUBBED) media_type=0x{:02x}", media_type);
+ bool needs_cleanup = false;
+ for (auto& import_ctx : am->import_title_contexts) {
+ if (import_ctx.second.state == ImportTitleContextState::NEEDS_CLEANUP) {
+ needs_cleanup = true;
+ break;
+ }
+ }
+
+ if (!needs_cleanup) {
+ for (auto& import_ctx : am->import_content_contexts) {
+ if (import_ctx.second.state == ImportTitleContextState::NEEDS_CLEANUP) {
+ needs_cleanup = true;
+ }
+ }
+ }
+
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess);
- rb.Push(false);
+ rb.Push(needs_cleanup);
}
void Module::Interface::DoCleanup(Kernel::HLERequestContext& ctx) {
@@ -2556,6 +2586,22 @@ void Module::Interface::DoCleanup(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "(STUBBED) called, media_type={:#02x}", media_type);
+ for (auto it = am->import_content_contexts.begin(); it != am->import_content_contexts.end();) {
+ if (it->second.state == ImportTitleContextState::NEEDS_CLEANUP) {
+ it = am->import_content_contexts.erase(it);
+ } else {
+ it++;
+ }
+ }
+
+ for (auto it = am->import_title_contexts.begin(); it != am->import_title_contexts.end();) {
+ if (it->second.state == ImportTitleContextState::NEEDS_CLEANUP) {
+ it = am->import_title_contexts.erase(it);
+ } else {
+ it++;
+ }
+ }
+
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
}
@@ -2746,21 +2792,7 @@ void Module::Interface::EndImportProgramWithoutCommit(Kernel::HLERequestContext&
}
void Module::Interface::CommitImportPrograms(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp(ctx);
- [[maybe_unused]] const auto media_type = static_cast(rp.Pop());
- [[maybe_unused]] const u32 title_count = rp.Pop();
- [[maybe_unused]] const u8 database = rp.Pop();
- const auto buffer = rp.PopMappedBuffer();
-
- // Note: This function is basically a no-op for us since we don't use title.db or ticket.db
- // files to keep track of installed titles.
- am->ScanForAllTitles();
-
- IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
- rb.Push(ResultSuccess);
- rb.PushMappedBuffer(buffer);
-
- LOG_WARNING(Service_AM, "(STUBBED)");
+ CommitImportTitlesImpl(ctx, false, false);
}
/// Wraps all File operations to allow adding an offset to them.
@@ -3074,6 +3106,46 @@ void Module::Interface::GetRequiredSizeFromCia(Kernel::HLERequestContext& ctx) {
rb.Push(container.GetTitleMetadata().GetContentSizeByIndex(FileSys::TMDContentIndex::Main));
}
+void Module::Interface::CommitImportProgramsAndUpdateFirmwareAuto(Kernel::HLERequestContext& ctx) {
+ CommitImportTitlesImpl(ctx, true, false);
+}
+
+void Module::Interface::CommitImportTitlesImpl(Kernel::HLERequestContext& ctx,
+ bool is_update_firm_auto, bool is_titles) {
+ IPC::RequestParser rp(ctx);
+ const auto media_type = static_cast(rp.Pop());
+ [[maybe_unused]] u32 count = rp.Pop();
+ [[maybe_unused]] u8 database = rp.Pop();
+
+ LOG_WARNING(Service_AM, "(STUBBED) update_firm_auto={} is_titles={}", is_update_firm_auto,
+ is_titles);
+
+ auto& title_id_buf = rp.PopMappedBuffer();
+
+ std::vector title_ids(title_id_buf.GetSize() / sizeof(u64));
+ title_id_buf.Read(title_ids.data(), 0, title_id_buf.GetSize());
+
+ for (auto& key_value : am->import_content_contexts) {
+ if (std::find(title_ids.begin(), title_ids.end(), key_value.first) != title_ids.end() &&
+ key_value.second.state == ImportTitleContextState::WAITING_FOR_COMMIT) {
+ key_value.second.state = ImportTitleContextState::NEEDS_CLEANUP;
+ }
+ }
+
+ for (auto tid : title_ids) {
+ auto it = am->import_title_contexts.find(tid);
+ if (it != am->import_title_contexts.end() &&
+ it->second.state == ImportTitleContextState::WAITING_FOR_COMMIT) {
+ it->second.state = ImportTitleContextState::NEEDS_CLEANUP;
+ }
+ }
+
+ am->ScanForTitles(media_type);
+
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(ResultSuccess);
+}
+
Result UninstallProgram(const FS::MediaType media_type, const u64 title_id) {
// Use the content folder so we don't delete the user's save data.
const auto path = GetTitlePath(media_type, title_id) + "content/";
@@ -3351,6 +3423,10 @@ void Module::Interface::EndImportTitle(Kernel::HLERequestContext& ctx) {
rb.Push(ResultSuccess);
}
+void Module::Interface::CommitImportTitles(Kernel::HLERequestContext& ctx) {
+ CommitImportTitlesImpl(ctx, false, true);
+}
+
void Module::Interface::BeginImportTmd(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
@@ -3720,6 +3796,10 @@ void Module::Interface::GetDeviceCert(Kernel::HLERequestContext& ctx) {
rb.PushMappedBuffer(buffer);
}
+void Module::Interface::CommitImportTitlesAndUpdateFirmwareAuto(Kernel::HLERequestContext& ctx) {
+ CommitImportTitlesImpl(ctx, true, true);
+}
+
void Module::Interface::DeleteTicketId(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
u64 title_id = rp.Pop();
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index 253044a57..e3fcbacf0 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -112,7 +112,7 @@ using ProgressCallback = void(std::size_t, std::size_t);
class NCCHCryptoFile final {
public:
- NCCHCryptoFile(const std::string& out_file);
+ NCCHCryptoFile(const std::string& out_file, bool encrypted_content);
void Write(const u8* buffer, std::size_t length);
bool IsError() {
@@ -128,7 +128,7 @@ private:
std::size_t written = 0;
- NCCH_Header ncch_header;
+ NCCH_Header ncch_header{};
std::size_t header_size = 0;
bool header_parsed = false;
@@ -156,7 +156,7 @@ private:
std::vector regions;
- ExeFs_Header exefs_header;
+ ExeFs_Header exefs_header{};
std::size_t exefs_header_written = 0;
bool exefs_header_processed = false;
};
@@ -414,6 +414,9 @@ public:
protected:
void GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool ignore_platform);
+ void CommitImportTitlesImpl(Kernel::HLERequestContext& ctx, bool is_update_firm_auto,
+ bool is_titles);
+
/**
* AM::GetNumPrograms service function
* Gets the number of installed titles in the requested media type
@@ -873,6 +876,8 @@ public:
*/
void GetRequiredSizeFromCia(Kernel::HLERequestContext& ctx);
+ void CommitImportProgramsAndUpdateFirmwareAuto(Kernel::HLERequestContext& ctx);
+
/**
* AM::DeleteProgram service function
* Deletes a program
@@ -949,6 +954,8 @@ public:
void EndImportTitle(Kernel::HLERequestContext& ctx);
+ void CommitImportTitles(Kernel::HLERequestContext& ctx);
+
void BeginImportTmd(Kernel::HLERequestContext& ctx);
void EndImportTmd(Kernel::HLERequestContext& ctx);
@@ -983,6 +990,8 @@ public:
*/
void GetDeviceCert(Kernel::HLERequestContext& ctx);
+ void CommitImportTitlesAndUpdateFirmwareAuto(Kernel::HLERequestContext& ctx);
+
void DeleteTicketId(Kernel::HLERequestContext& ctx);
void GetNumTicketIds(Kernel::HLERequestContext& ctx);
@@ -1002,6 +1011,14 @@ public:
std::shared_ptr artic_client = nullptr;
};
+ void ForceO3DSDeviceID() {
+ force_old_device_id = true;
+ }
+
+ void ForceN3DSDeviceID() {
+ force_new_device_id = true;
+ }
+
private:
void ScanForTickets();
diff --git a/src/core/hle/service/am/am_net.cpp b/src/core/hle/service/am/am_net.cpp
index e21ad5ebd..4c39cf62b 100644
--- a/src/core/hle/service/am/am_net.cpp
+++ b/src/core/hle/service/am/am_net.cpp
@@ -1,4 +1,4 @@
-// Copyright 2014 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -57,18 +57,18 @@ AM_NET::AM_NET(std::shared_ptr am) : Module::Interface(std::move(am), "a
{0x002D, &AM_NET::CheckContentRightsIgnorePlatform, "CheckContentRightsIgnorePlatform"},
{0x0401, nullptr, "UpdateFirmwareTo"},
{0x0402, &AM_NET::BeginImportProgram, "BeginImportProgram"},
- {0x0403, nullptr, "BeginImportProgramTemporarily"},
+ {0x0403, &AM_NET::BeginImportProgramTemporarily, "BeginImportProgramTemporarily"},
{0x0404, nullptr, "CancelImportProgram"},
{0x0405, &AM_NET::EndImportProgram, "EndImportProgram"},
- {0x0406, nullptr, "EndImportProgramWithoutCommit"},
- {0x0407, nullptr, "CommitImportPrograms"},
+ {0x0406, &AM_NET::EndImportProgramWithoutCommit, "EndImportProgramWithoutCommit"},
+ {0x0407, &AM_NET::CommitImportPrograms, "CommitImportPrograms"},
{0x0408, &AM_NET::GetProgramInfoFromCia, "GetProgramInfoFromCia"},
{0x0409, &AM_NET::GetSystemMenuDataFromCia, "GetSystemMenuDataFromCia"},
{0x040A, &AM_NET::GetDependencyListFromCia, "GetDependencyListFromCia"},
{0x040B, &AM_NET::GetTransferSizeFromCia, "GetTransferSizeFromCia"},
{0x040C, &AM_NET::GetCoreVersionFromCia, "GetCoreVersionFromCia"},
{0x040D, &AM_NET::GetRequiredSizeFromCia, "GetRequiredSizeFromCia"},
- {0x040E, nullptr, "CommitImportProgramsAndUpdateFirmwareAuto"},
+ {0x040E, &AM_NET::CommitImportProgramsAndUpdateFirmwareAuto, "CommitImportProgramsAndUpdateFirmwareAuto"},
{0x040F, nullptr, "UpdateFirmwareAuto"},
{0x0410, &AM_NET::DeleteProgram, "DeleteProgram"},
{0x0411, nullptr, "GetTwlProgramListForReboot"},
@@ -88,7 +88,7 @@ AM_NET::AM_NET(std::shared_ptr am) : Module::Interface(std::move(am), "a
{0x0806, &AM_NET::ResumeImportTitle, "ResumeImportTitle"},
{0x0807, &AM_NET::CancelImportTitle, "CancelImportTitle"},
{0x0808, &AM_NET::EndImportTitle, "EndImportTitle"},
- {0x0809, nullptr, "CommitImportTitles"},
+ {0x0809, &AM_NET::CommitImportTitles, "CommitImportTitles"},
{0x080A, &AM_NET::BeginImportTmd, "BeginImportTmd"},
{0x080B, nullptr, "CancelImportTmd"},
{0x080C, &AM_NET::EndImportTmd, "EndImportTmd"},
@@ -106,7 +106,7 @@ AM_NET::AM_NET(std::shared_ptr am) : Module::Interface(std::move(am), "a
{0x0818, &AM_NET::GetDeviceCert, "GetDeviceCert"},
{0x0819, nullptr, "ImportCertificates"},
{0x081A, nullptr, "ImportCertificate"},
- {0x081B, nullptr, "CommitImportTitlesAndUpdateFirmwareAuto"},
+ {0x081B, &AM_NET::CommitImportTitlesAndUpdateFirmwareAuto, "CommitImportTitlesAndUpdateFirmwareAuto"},
{0x081C, &AM_NET::DeleteTicketId, "DeleteTicketId"},
{0x081D, &AM_NET::GetNumTicketIds, "GetNumTicketIds"},
{0x081E, &AM_NET::GetTicketIdList, "GetTicketIdList"},
diff --git a/src/core/hle/service/am/am_u.cpp b/src/core/hle/service/am/am_u.cpp
index 79a8fad95..32395d75a 100644
--- a/src/core/hle/service/am/am_u.cpp
+++ b/src/core/hle/service/am/am_u.cpp
@@ -1,4 +1,4 @@
-// Copyright 2015 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -20,21 +20,21 @@ AM_U::AM_U(std::shared_ptr am) : Module::Interface(std::move(am), "am:u"
{0x0008, &AM_U::GetNumTickets, "GetNumTickets"},
{0x0009, &AM_U::GetTicketList, "GetTicketList"},
{0x000A, &AM_U::GetDeviceID, "GetDeviceID"},
- {0x000B, nullptr, "GetNumImportTitleContexts"},
- {0x000C, nullptr, "GetImportTitleContextList"},
- {0x000D, nullptr, "GetImportTitleContexts"},
- {0x000E, nullptr, "DeleteImportTitleContext"},
- {0x000F, nullptr, "GetNumImportContentContexts"},
- {0x0010, nullptr, "GetImportContentContextList"},
- {0x0011, nullptr, "GetImportContentContexts"},
+ {0x000B, &AM_U::GetNumImportTitleContexts, "GetNumImportTitleContexts"},
+ {0x000C, &AM_U::GetImportTitleContextList, "GetImportTitleContextList"},
+ {0x000D, &AM_U::GetImportTitleContexts, "GetImportTitleContexts"},
+ {0x000E, &AM_U::DeleteImportTitleContext, "DeleteImportTitleContext"},
+ {0x000F, &AM_U::GetNumImportContentContexts, "GetNumImportContentContexts"},
+ {0x0010, &AM_U::GetImportContentContextList, "GetImportContentContextList"},
+ {0x0011, &AM_U::GetImportContentContexts, "GetImportContentContexts"},
{0x0012, nullptr, "DeleteImportContentContexts"},
{0x0013, &AM_U::NeedsCleanup, "NeedsCleanup"},
- {0x0014, nullptr, "DoCleanup"},
+ {0x0014, &AM_U::DoCleanup, "DoCleanup"},
{0x0015, nullptr, "DeleteAllImportContexts"},
{0x0016, nullptr, "DeleteAllTemporaryPrograms"},
{0x0017, nullptr, "ImportTwlBackupLegacy"},
{0x0018, nullptr, "InitializeTitleDatabase"},
- {0x0019, nullptr, "QueryAvailableTitleDatabase"},
+ {0x0019, &AM_U::QueryAvailableTitleDatabase, "QueryAvailableTitleDatabase"},
{0x001A, nullptr, "CalcTwlBackupSize"},
{0x001B, nullptr, "ExportTwlBackup"},
{0x001C, nullptr, "ImportTwlBackup"},
@@ -42,19 +42,19 @@ AM_U::AM_U(std::shared_ptr am) : Module::Interface(std::move(am), "am:u"
{0x001E, nullptr, "ReadTwlBackupInfo"},
{0x001F, nullptr, "DeleteAllExpiredUserPrograms"},
{0x0020, nullptr, "GetTwlArchiveResourceInfo"},
- {0x0021, nullptr, "GetPersonalizedTicketInfoList"},
+ {0x0021, &AM_U::GetPersonalizedTicketInfoList, "GetPersonalizedTicketInfoList"},
{0x0022, nullptr, "DeleteAllImportContextsFiltered"},
- {0x0023, nullptr, "GetNumImportTitleContextsFiltered"},
- {0x0024, nullptr, "GetImportTitleContextListFiltered"},
- {0x0025, nullptr, "CheckContentRights"},
+ {0x0023, &AM_U::GetNumImportTitleContextsFiltered, "GetNumImportTitleContextsFiltered"},
+ {0x0024, &AM_U::GetImportTitleContextListFiltered, "GetImportTitleContextListFiltered"},
+ {0x0025, &AM_U::CheckContentRights, "CheckContentRights"},
{0x0026, nullptr, "GetTicketLimitInfos"},
{0x0027, nullptr, "GetDemoLaunchInfos"},
{0x0028, nullptr, "ReadTwlBackupInfoEx"},
{0x0029, nullptr, "DeleteUserProgramsAtomically"},
{0x002A, nullptr, "GetNumExistingContentInfosSystem"},
{0x002B, nullptr, "ListExistingContentInfosSystem"},
- {0x002C, nullptr, "GetProgramInfosIgnorePlatform"},
- {0x002D, nullptr, "CheckContentRightsIgnorePlatform"},
+ {0x002C, &AM_U::GetProgramInfosIgnorePlatform, "GetProgramInfosIgnorePlatform"},
+ {0x002D, &AM_U::CheckContentRightsIgnorePlatform, "CheckContentRightsIgnorePlatform"},
{0x0401, nullptr, "UpdateFirmwareTo"},
{0x0402, &AM_U::BeginImportProgram, "BeginImportProgram"},
{0x0403, &AM_U::BeginImportProgramTemporarily, "BeginImportProgramTemporarily"},
@@ -68,7 +68,7 @@ AM_U::AM_U(std::shared_ptr am) : Module::Interface(std::move(am), "am:u"
{0x040B, &AM_U::GetTransferSizeFromCia, "GetTransferSizeFromCia"},
{0x040C, &AM_U::GetCoreVersionFromCia, "GetCoreVersionFromCia"},
{0x040D, &AM_U::GetRequiredSizeFromCia, "GetRequiredSizeFromCia"},
- {0x040E, nullptr, "CommitImportProgramsAndUpdateFirmwareAuto"},
+ {0x040E, &AM_U::CommitImportProgramsAndUpdateFirmwareAuto, "CommitImportProgramsAndUpdateFirmwareAuto"},
{0x040F, nullptr, "UpdateFirmwareAuto"},
{0x0410, &AM_U::DeleteProgram, "DeleteProgram"},
{0x0411, nullptr, "GetTwlProgramListForReboot"},
diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp
index 380a18abb..e3c0fc033 100644
--- a/src/core/hle/service/cfg/cfg.cpp
+++ b/src/core/hle/service/cfg/cfg.cpp
@@ -1,4 +1,4 @@
-// Copyright 2014 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -40,6 +40,7 @@ template
void Module::serialize(Archive& ar, const unsigned int) {
ar & cfg_config_file_buffer;
ar & cfg_system_save_data_archive;
+ ar & mac_address;
ar & preferred_region_code;
ar & preferred_region_chosen;
}
@@ -722,6 +723,7 @@ void Module::SaveMCUConfig() {
Module::Module(Core::System& system_) : system(system_) {
LoadConfigNANDSaveFile();
LoadMCUConfig();
+ (void)GetMacAddress();
// Check the config savegame EULA Version and update it to 0x7F7F if necessary
// so users will never get a prompt to accept EULA
auto version = GetEULAVersion();
@@ -771,6 +773,45 @@ static std::tuple AdjustLanguageInfoBlock(
return {default_region, region_languages[default_region][0]};
}
+std::string& Module::GetMacAddress() {
+ if (!mac_address.empty()) {
+ return mac_address;
+ }
+
+ FileUtil::IOFile mac_address_file(
+ fmt::format("{}/mac.txt", FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir)), "rb");
+ if (!mac_address_file.IsOpen() || mac_address_file.GetSize() > 100) {
+ LOG_INFO(Service_CFG, "Cannot open mac address file for read, generating a new one");
+ mac_address = GenerateRandomMAC();
+ SaveMacAddress();
+ return mac_address;
+ }
+
+ size_t file_size = mac_address_file.GetSize();
+ mac_address.resize(file_size);
+ mac_address_file.ReadBytes(mac_address.data(), file_size);
+ if (mac_address != MacToString(MacToU64(mac_address))) {
+ LOG_WARNING(Service_CFG, "Imvalid saved MAC address, generating a new one");
+ mac_address = GenerateRandomMAC();
+ SaveMacAddress();
+ return mac_address;
+ }
+
+ return mac_address;
+}
+
+void Module::SaveMacAddress() {
+ FileUtil::IOFile mac_address_file(
+ fmt::format("{}/mac.txt", FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir)), "wb");
+
+ if (!mac_address_file.IsOpen()) {
+ LOG_ERROR(Service_CFG, "Cannot open mac address file for write");
+ return;
+ }
+
+ mac_address_file.WriteBytes(mac_address.data(), mac_address.size());
+}
+
void Module::UpdatePreferredRegionCode() {
if (preferred_region_chosen || !system.IsPoweredOn()) {
return;
@@ -883,8 +924,13 @@ std::pair Module::GenerateConsoleUniqueId() const {
const u32 random_number = rng.GenerateWord32(0, 0xFFFF);
u64_le local_friend_code_seed;
- rng.GenerateBlock(reinterpret_cast(&local_friend_code_seed),
- sizeof(local_friend_code_seed));
+ auto& lfcs = HW::UniqueData::GetLocalFriendCodeSeedB();
+ if (lfcs.IsValid()) {
+ local_friend_code_seed = lfcs.body.friend_code_seed;
+ } else {
+ rng.GenerateBlock(reinterpret_cast(&local_friend_code_seed),
+ sizeof(local_friend_code_seed));
+ }
const u64 console_id =
(local_friend_code_seed & 0x3FFFFFFFF) | (static_cast(random_number) << 48);
@@ -976,4 +1022,59 @@ std::string GetConsoleIdHash(Core::System& system) {
return fmt::format("{:02x}", fmt::join(hash.begin(), hash.end(), ""));
}
+std::array MacToArray(const std::string& mac) {
+ std::array ret;
+ int last = -1;
+ int rc = sscanf(mac.c_str(), "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx%n", ret.data() + 0, ret.data() + 1,
+ ret.data() + 2, ret.data() + 3, ret.data() + 4, ret.data() + 5, &last);
+ if (rc != 6 || static_cast(mac.size()) != last) {
+ return MacToArray(GenerateRandomMAC());
+ }
+ return ret;
+}
+
+std::string MacToString(u64 mac) {
+ return fmt::format("{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", (mac >> (5 * 8)) & 0xFF,
+ (mac >> (4 * 8)) & 0xFF, (mac >> (3 * 8)) & 0xFF, (mac >> (2 * 8)) & 0xFF,
+ (mac >> (1 * 8)) & 0xFF, (mac >> (0 * 8)) & 0xFF);
+}
+
+std::string MacToString(const std::array& mac) {
+ u64 mac_u64 = u64(mac[0]) << 40 | u64(mac[1]) << 32 | u64(mac[2]) << 24 | u64(mac[3]) << 16 |
+ u64(mac[4]) << 8 | u64(mac[5]);
+ return MacToString(mac_u64);
+}
+
+u64 MacToU64(const std::string& mac) {
+ auto ret = MacToArray(mac);
+ return u64(ret[0]) << 40 | u64(ret[1]) << 32 | u64(ret[2]) << 24 | u64(ret[3]) << 16 |
+ u64(ret[4]) << 8 | u64(ret[5]);
+}
+
+std::string GenerateRandomMAC() {
+ static const std::array, 16> ranges = {{
+ {0x182A7B000000ULL, 0x182A7BFFFFFFULL},
+ {0x2C10C1000000ULL, 0x2C10C1FFFFFFULL},
+ {0x342FBD000000ULL, 0x342FBDFFFFFFULL},
+ {0x34AF2C000000ULL, 0x34AF2CFFFFFFULL},
+ {0x40D28A000000ULL, 0x40D28AFFFFFFULL},
+ {0x40F407000000ULL, 0x40F407FFFFFFULL},
+ {0x48A5E7000000ULL, 0x48A5E7FFFFFFULL},
+ {0x582F40000000ULL, 0x582F40FFFFFFULL},
+ {0x68BD13000000ULL, 0x68BD13FFFFFFULL},
+ {0x58BDA3000000ULL, 0x58BDA3FFFFFFULL},
+ {0x5C521E000000ULL, 0x5C521EFFFFFFULL},
+ {0x606BFF000000ULL, 0x606BFFFFFFFFULL},
+ {0x64B5C6000000ULL, 0x64B5C6FFFFFFULL},
+ {0x78A2A0000000ULL, 0x78A2A0FFFFFFULL},
+ {0x7CBB8A000000ULL, 0x7CBB8AFFFFFFULL},
+ {0x8CCDE8000000ULL, 0x8CCDE8FFFFFFULL},
+ }};
+ CryptoPP::AutoSeededRandomPool rng;
+ auto& range = ranges[rng.GenerateWord32(0, static_cast(ranges.size() - 1))];
+ u64 mac = range.first +
+ rng.GenerateWord32(0, static_cast(range.second - range.first));
+ return MacToString(mac);
+}
+
} // namespace Service::CFG
diff --git a/src/core/hle/service/cfg/cfg.h b/src/core/hle/service/cfg/cfg.h
index f545518e6..32b1dd40c 100644
--- a/src/core/hle/service/cfg/cfg.h
+++ b/src/core/hle/service/cfg/cfg.h
@@ -1,4 +1,4 @@
-// Copyright 2014 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -622,6 +622,16 @@ public:
*/
void SaveMCUConfig();
+ /**
+ * Get a reference to the console's MAC address
+ */
+ std::string& GetMacAddress();
+
+ /**
+ * Saves the current MAC address to the filesystem
+ */
+ void SaveMacAddress();
+
private:
void UpdatePreferredRegionCode();
SystemLanguage GetRawSystemLanguage();
@@ -634,6 +644,7 @@ private:
u32 preferred_region_code = 0;
bool preferred_region_chosen = false;
MCUData mcu_data{};
+ std::string mac_address{};
std::shared_ptr artic_client = nullptr;
@@ -649,6 +660,16 @@ void InstallInterfaces(Core::System& system);
/// Convenience function for getting a SHA256 hash of the Console ID
std::string GetConsoleIdHash(Core::System& system);
+std::array MacToArray(const std::string& mac);
+
+std::string MacToString(u64 mac);
+
+std::string MacToString(const std::array& mac);
+
+u64 MacToU64(const std::string& mac);
+
+std::string GenerateRandomMAC();
+
} // namespace Service::CFG
SERVICE_CONSTRUCT(Service::CFG::Module)
diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp
index 76e76d720..df824e0bb 100644
--- a/src/core/hle/service/fs/fs_user.cpp
+++ b/src/core/hle/service/fs/fs_user.cpp
@@ -1,4 +1,4 @@
-// Copyright 2014 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -916,7 +916,7 @@ void FS_USER::GetFreeBytes(Kernel::HLERequestContext& ctx) {
void FS_USER::GetSdmcArchiveResource(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
- LOG_WARNING(Service_FS, "(STUBBED) called");
+ LOG_DEBUG(Service_FS, "(STUBBED) called");
auto resource = archives.GetArchiveResource(MediaType::SDMC);
@@ -934,7 +934,7 @@ void FS_USER::GetSdmcArchiveResource(Kernel::HLERequestContext& ctx) {
void FS_USER::GetNandArchiveResource(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
- LOG_WARNING(Service_FS, "(STUBBED) called");
+ LOG_DEBUG(Service_FS, "(STUBBED) called");
auto resource = archives.GetArchiveResource(MediaType::NAND);
if (resource.Failed()) {
@@ -1103,7 +1103,7 @@ void FS_USER::GetArchiveResource(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
auto media_type = rp.PopEnum();
- LOG_WARNING(Service_FS, "(STUBBED) called Media type=0x{:08X}", media_type);
+ LOG_DEBUG(Service_FS, "(STUBBED) called Media type=0x{:08X}", media_type);
auto resource = archives.GetArchiveResource(media_type);
if (resource.Failed()) {
diff --git a/src/core/hle/service/nim/nim.cpp b/src/core/hle/service/nim/nim.cpp
index a0b84bd03..0678884d2 100644
--- a/src/core/hle/service/nim/nim.cpp
+++ b/src/core/hle/service/nim/nim.cpp
@@ -1,4 +1,4 @@
-// Copyright 2015 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -11,6 +11,10 @@
namespace Service::NIM {
void InstallInterfaces(Core::System& system) {
+ // Don't register HLE nim on initial setup
+ if (system.IsInitialSetup()) {
+ return;
+ }
auto& service_manager = system.ServiceManager();
std::make_shared()->InstallAsService(service_manager);
std::make_shared()->InstallAsService(service_manager);
diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp
index 64ebafaab..830e1ffc7 100644
--- a/src/core/hle/service/nwm/nwm_uds.cpp
+++ b/src/core/hle/service/nwm/nwm_uds.cpp
@@ -1,4 +1,4 @@
-// Copyright 2017 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -10,6 +10,7 @@
#include "common/archives.h"
#include "common/common_types.h"
#include "common/logging/log.h"
+#include "common/settings.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/ipc_helpers.h"
@@ -17,6 +18,8 @@
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/kernel/shared_page.h"
#include "core/hle/result.h"
+#include "core/hle/service/cfg/cfg.h"
+#include "core/hle/service/cfg/cfg_u.h"
#include "core/hle/service/nwm/nwm_uds.h"
#include "core/hle/service/nwm/uds_beacon.h"
#include "core/hle/service/nwm/uds_connection.h"
@@ -1494,10 +1497,13 @@ NWM_UDS::NWM_UDS(Core::System& system) : ServiceFramework("nwm::UDS"), system(sy
BeaconBroadcastCallback(user_data, cycles_late);
});
- CryptoPP::AutoSeededRandomPool rng;
- auto mac = SharedPage::DefaultMac;
- // Keep the Nintendo 3DS MAC header and randomly generate the last 3 bytes
- rng.GenerateBlock(static_cast(mac.data() + 3), 3);
+ MacAddress mac;
+
+ auto cfg = system.ServiceManager().GetService("cfg:u");
+ if (cfg.get()) {
+ auto cfg_module = cfg->GetModule();
+ mac = Service::CFG::MacToArray(cfg_module->GetMacAddress());
+ }
if (auto room_member = Network::GetRoomMember().lock()) {
if (room_member->IsConnected()) {
diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp
index f3caba55d..950c1ccb6 100644
--- a/src/core/hle/service/ptm/ptm.cpp
+++ b/src/core/hle/service/ptm/ptm.cpp
@@ -1,4 +1,4 @@
-// Copyright 2015 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -34,7 +34,7 @@ void Module::Interface::GetAdapterState(Kernel::HLERequestContext& ctx) {
rb.Push(ResultSuccess);
rb.Push(ptm->battery_is_charging);
- LOG_WARNING(Service_PTM, "(STUBBED) called");
+ LOG_DEBUG(Service_PTM, "(STUBBED) called");
}
void Module::Interface::GetShellState(Kernel::HLERequestContext& ctx) {
@@ -52,7 +52,7 @@ void Module::Interface::GetBatteryLevel(Kernel::HLERequestContext& ctx) {
rb.Push(ResultSuccess);
rb.Push(static_cast(ChargeLevels::CompletelyFull)); // Set to a completely full battery
- LOG_WARNING(Service_PTM, "(STUBBED) called");
+ LOG_DEBUG(Service_PTM, "(STUBBED) called");
}
void Module::Interface::GetBatteryChargeState(Kernel::HLERequestContext& ctx) {
@@ -62,7 +62,7 @@ void Module::Interface::GetBatteryChargeState(Kernel::HLERequestContext& ctx) {
rb.Push(ResultSuccess);
rb.Push(ptm->battery_is_charging);
- LOG_WARNING(Service_PTM, "(STUBBED) called");
+ LOG_DEBUG(Service_PTM, "(STUBBED) called");
}
void Module::Interface::GetPedometerState(Kernel::HLERequestContext& ctx) {
@@ -72,7 +72,7 @@ void Module::Interface::GetPedometerState(Kernel::HLERequestContext& ctx) {
rb.Push(ResultSuccess);
rb.Push(ptm->pedometer_is_counting);
- LOG_WARNING(Service_PTM, "(STUBBED) called");
+ LOG_DEBUG(Service_PTM, "(STUBBED) called");
}
void Module::Interface::GetStepHistory(Kernel::HLERequestContext& ctx) {
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 49b394bcd..68e1bfa47 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -1,4 +1,4 @@
-// Copyright 2014 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -207,19 +207,26 @@ static bool AttemptLLE(const ServiceModuleInfo& service_module) {
return false;
}
std::shared_ptr process;
- loader->Load(process);
+ Loader::ResultStatus load_result = loader->Load(process);
+ if (load_result != Loader::ResultStatus::Success) {
+ LOG_ERROR(Service,
+ "Service module \"{}\" could not be loaded (ResultStatus={}); Defaulting to HLE "
+ "implementation.",
+ service_module.name, static_cast(load_result));
+ return false;
+ }
LOG_DEBUG(Service, "Service module \"{}\" has been successfully loaded.", service_module.name);
return true;
}
/// Initialize ServiceManager
-void Init(Core::System& core) {
+void Init(Core::System& core, bool allow_lle) {
SM::ServiceManager::InstallInterfaces(core);
core.Kernel().SetAppMainThreadExtendedSleep(false);
bool lle_module_present = false;
for (const auto& service_module : service_module_map) {
- const bool has_lle = AttemptLLE(service_module);
+ const bool has_lle = allow_lle && AttemptLLE(service_module);
if (!has_lle && service_module.init_function != nullptr) {
service_module.init_function(core);
}
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h
index 0b4b3ec54..51a2b200e 100644
--- a/src/core/hle/service/service.h
+++ b/src/core/hle/service/service.h
@@ -1,4 +1,4 @@
-// Copyright 2014 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -183,7 +183,7 @@ private:
};
/// Initialize ServiceManager
-void Init(Core::System& system);
+void Init(Core::System& system, bool allow_lle);
struct ServiceModuleInfo {
std::string name;
diff --git a/src/core/loader/artic.cpp b/src/core/loader/artic.cpp
index 80365ed6e..73065f606 100644
--- a/src/core/loader/artic.cpp
+++ b/src/core/loader/artic.cpp
@@ -1,4 +1,4 @@
-// Copyright 2024 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -13,21 +13,26 @@
#include "common/string_util.h"
#include "common/swap.h"
#include "core/core.h"
+#include "core/file_sys/certificate.h"
#include "core/file_sys/ncch_container.h"
+#include "core/file_sys/otp.h"
#include "core/file_sys/romfs_reader.h"
#include "core/file_sys/secure_value_backend_artic.h"
#include "core/file_sys/title_metadata.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
+#include "core/hle/kernel/shared_page.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/am_app.h"
#include "core/hle/service/am/am_net.h"
+#include "core/hle/service/apt/apt.h"
#include "core/hle/service/cfg/cfg.h"
#include "core/hle/service/cfg/cfg_u.h"
#include "core/hle/service/fs/archive.h"
#include "core/hle/service/fs/fs_user.h"
#include "core/hle/service/hid/hid_user.h"
+#include "core/hw/unique_data.h"
#include "core/loader/artic.h"
#include "core/loader/smdh.h"
#include "core/memory.h"
@@ -38,6 +43,26 @@ namespace Loader {
using namespace Common::Literals;
+Apploader_Artic::Apploader_Artic(Core::System& system_, const std::string& server_addr,
+ u16 server_port, ArticInitMode init_mode)
+ : AppLoader(system_, FileUtil::IOFile()) {
+ client = std::make_shared(server_addr, server_port);
+ client->SetCommunicationErrorCallback([&system_](const std::string& msg) {
+ system_.SetStatus(Core::System::ResultStatus::ErrorArticDisconnected,
+ msg.empty() ? nullptr : msg.c_str());
+ });
+ client->SetArticReportTrafficCallback(
+ [&system_](u32 bytes) { system_.ReportArticTraffic(bytes); });
+ client->SetReportArticEventCallback([&system_](u64 event) {
+ Core::PerfStats::PerfArticEventBits ev =
+ static_cast(event & 0xFFFFFFFF);
+ bool set = (event > 32) != 0;
+ system_.ReportPerfArticEvent(ev, set);
+ });
+ is_initial_setup = init_mode != ArticInitMode::NONE;
+ artic_init_mode = init_mode;
+}
+
Apploader_Artic::~Apploader_Artic() {
// TODO(PabloMK7) Find memory leak that prevents the romfs readers being destroyed
// when emulation stops. Looks like the mem leak comes from IVFCFile objects
@@ -110,7 +135,6 @@ Apploader_Artic::LoadNew3dsHwCapabilities() {
}
ResultStatus Apploader_Artic::LoadExec(std::shared_ptr& process) {
- using Kernel::CodeSet;
if (!is_loaded)
return ResultStatus::ErrorNotLoaded;
@@ -119,108 +143,110 @@ ResultStatus Apploader_Artic::LoadExec(std::shared_ptr& process
u64_le program_id;
if (ResultStatus::Success == ReadCode(code) &&
ResultStatus::Success == ReadProgramId(program_id)) {
-
- std::string process_name = Common::StringFromFixedZeroTerminatedBuffer(
- (const char*)program_exheader.codeset_info.name, 8);
-
- std::shared_ptr codeset = system.Kernel().CreateCodeSet(process_name, program_id);
-
- codeset->CodeSegment().offset = 0;
- codeset->CodeSegment().addr = program_exheader.codeset_info.text.address;
- codeset->CodeSegment().size =
- program_exheader.codeset_info.text.num_max_pages * Memory::CITRA_PAGE_SIZE;
-
- codeset->RODataSegment().offset =
- codeset->CodeSegment().offset + codeset->CodeSegment().size;
- codeset->RODataSegment().addr = program_exheader.codeset_info.ro.address;
- codeset->RODataSegment().size =
- program_exheader.codeset_info.ro.num_max_pages * Memory::CITRA_PAGE_SIZE;
-
- // TODO(yuriks): Not sure if the bss size is added to the page-aligned .data size or just
- // to the regular size. Playing it safe for now.
- u32 bss_page_size = (program_exheader.codeset_info.bss_size + 0xFFF) & ~0xFFF;
- code.resize(code.size() + bss_page_size, 0);
-
- codeset->DataSegment().offset =
- codeset->RODataSegment().offset + codeset->RODataSegment().size;
- codeset->DataSegment().addr = program_exheader.codeset_info.data.address;
- codeset->DataSegment().size =
- program_exheader.codeset_info.data.num_max_pages * Memory::CITRA_PAGE_SIZE +
- bss_page_size;
-
- // Apply patches now that the entire codeset (including .bss) has been allocated
- // const ResultStatus patch_result = overlay_ncch->ApplyCodePatch(code);
- // if (patch_result != ResultStatus::Success && patch_result != ResultStatus::ErrorNotUsed)
- // return patch_result;
-
- codeset->entrypoint = codeset->CodeSegment().addr;
- codeset->memory = std::move(code);
-
- process = system.Kernel().CreateProcess(std::move(codeset));
-
- // Attach a resource limit to the process based on the resource limit category
- const auto category = static_cast(
- program_exheader.arm11_system_local_caps.resource_limit_category);
- process->resource_limit = system.Kernel().ResourceLimit().GetForCategory(category);
-
- // When running N3DS-unaware titles pm will lie about the amount of memory available.
- // This means RESLIMIT_COMMIT = APPMEMALLOC doesn't correspond to the actual size of
- // APPLICATION. See:
- // https://github.com/LumaTeam/Luma3DS/blob/e2778a45/sysmodules/pm/source/launch.c#L237
- auto& ncch_caps = program_exheader.arm11_system_local_caps;
- const auto o3ds_mode = *LoadKernelMemoryMode().first;
- const auto n3ds_mode = static_cast(ncch_caps.n3ds_mode);
- const bool is_new_3ds = Settings::values.is_new_3ds.GetValue();
- if (is_new_3ds && n3ds_mode == Kernel::New3dsMemoryMode::Legacy &&
- category == Kernel::ResourceLimitCategory::Application) {
- u64 new_limit = 0;
- switch (o3ds_mode) {
- case Kernel::MemoryMode::Prod:
- new_limit = 64_MiB;
- break;
- case Kernel::MemoryMode::Dev1:
- new_limit = 96_MiB;
- break;
- case Kernel::MemoryMode::Dev2:
- new_limit = 80_MiB;
- break;
- default:
- break;
- }
- process->resource_limit->SetLimitValue(Kernel::ResourceLimitType::Commit,
- static_cast(new_limit));
- }
-
- // Set the default CPU core for this process
- process->ideal_processor = program_exheader.arm11_system_local_caps.ideal_processor;
-
- // Copy data while converting endianness
- using KernelCaps = std::array;
- KernelCaps kernel_caps;
- std::copy_n(program_exheader.arm11_kernel_caps.descriptors, kernel_caps.size(),
- begin(kernel_caps));
- process->ParseKernelCaps(kernel_caps.data(), kernel_caps.size());
-
- s32 priority = program_exheader.arm11_system_local_caps.priority;
- u32 stack_size = program_exheader.codeset_info.stack_size;
-
- // On real HW this is done with FS:Reg, but we can be lazy
- auto fs_user = system.ServiceManager().GetService("fs:USER");
- fs_user->RegisterProgramInfo(process->process_id, process->codeset->program_id,
- "articbase://");
-
- Service::FS::FS_USER::ProductInfo product_info{};
- if (LoadProductInfo(product_info) != ResultStatus::Success) {
- return ResultStatus::ErrorArtic;
- }
- fs_user->RegisterProductInfo(process->process_id, product_info);
-
- process->Run(priority, stack_size);
- return ResultStatus::Success;
+ return LoadExecImpl(process, program_id, program_exheader, code);
}
return ResultStatus::ErrorArtic;
}
+ResultStatus Apploader_Artic::LoadExecImpl(std::shared_ptr& process,
+ u64_le program_id, const ExHeader_Header& exheader,
+ std::vector& code) {
+ using Kernel::CodeSet;
+
+ std::string process_name =
+ Common::StringFromFixedZeroTerminatedBuffer((const char*)exheader.codeset_info.name, 8);
+
+ std::shared_ptr codeset = system.Kernel().CreateCodeSet(process_name, program_id);
+
+ codeset->CodeSegment().offset = 0;
+ codeset->CodeSegment().addr = exheader.codeset_info.text.address;
+ codeset->CodeSegment().size =
+ exheader.codeset_info.text.num_max_pages * Memory::CITRA_PAGE_SIZE;
+
+ codeset->RODataSegment().offset = codeset->CodeSegment().offset + codeset->CodeSegment().size;
+ codeset->RODataSegment().addr = exheader.codeset_info.ro.address;
+ codeset->RODataSegment().size =
+ exheader.codeset_info.ro.num_max_pages * Memory::CITRA_PAGE_SIZE;
+
+ // TODO(yuriks): Not sure if the bss size is added to the page-aligned .data size or just
+ // to the regular size. Playing it safe for now.
+ u32 bss_page_size = (exheader.codeset_info.bss_size + 0xFFF) & ~0xFFF;
+ code.resize(code.size() + bss_page_size, 0);
+
+ codeset->DataSegment().offset = codeset->RODataSegment().offset + codeset->RODataSegment().size;
+ codeset->DataSegment().addr = exheader.codeset_info.data.address;
+ codeset->DataSegment().size =
+ exheader.codeset_info.data.num_max_pages * Memory::CITRA_PAGE_SIZE + bss_page_size;
+
+ // Apply patches now that the entire codeset (including .bss) has been allocated
+ // const ResultStatus patch_result = overlay_ncch->ApplyCodePatch(code);
+ // if (patch_result != ResultStatus::Success && patch_result != ResultStatus::ErrorNotUsed)
+ // return patch_result;
+
+ codeset->entrypoint = codeset->CodeSegment().addr;
+ codeset->memory = std::move(code);
+
+ process = system.Kernel().CreateProcess(std::move(codeset));
+
+ // Attach a resource limit to the process based on the resource limit category
+ const auto category = static_cast(
+ exheader.arm11_system_local_caps.resource_limit_category);
+ process->resource_limit = system.Kernel().ResourceLimit().GetForCategory(category);
+
+ // When running N3DS-unaware titles pm will lie about the amount of memory available.
+ // This means RESLIMIT_COMMIT = APPMEMALLOC doesn't correspond to the actual size of
+ // APPLICATION. See:
+ // https://github.com/LumaTeam/Luma3DS/blob/e2778a45/sysmodules/pm/source/launch.c#L237
+ auto& ncch_caps = exheader.arm11_system_local_caps;
+ const auto o3ds_mode = *LoadKernelMemoryMode().first;
+ const auto n3ds_mode = static_cast(ncch_caps.n3ds_mode);
+ const bool is_new_3ds = Settings::values.is_new_3ds.GetValue();
+ if (is_new_3ds && n3ds_mode == Kernel::New3dsMemoryMode::Legacy &&
+ category == Kernel::ResourceLimitCategory::Application) {
+ u64 new_limit = 0;
+ switch (o3ds_mode) {
+ case Kernel::MemoryMode::Prod:
+ new_limit = 64_MiB;
+ break;
+ case Kernel::MemoryMode::Dev1:
+ new_limit = 96_MiB;
+ break;
+ case Kernel::MemoryMode::Dev2:
+ new_limit = 80_MiB;
+ break;
+ default:
+ break;
+ }
+ process->resource_limit->SetLimitValue(Kernel::ResourceLimitType::Commit,
+ static_cast(new_limit));
+ }
+
+ // Set the default CPU core for this process
+ process->ideal_processor = exheader.arm11_system_local_caps.ideal_processor;
+
+ // Copy data while converting endianness
+ using KernelCaps = std::array;
+ KernelCaps kernel_caps;
+ std::copy_n(exheader.arm11_kernel_caps.descriptors, kernel_caps.size(), begin(kernel_caps));
+ process->ParseKernelCaps(kernel_caps.data(), kernel_caps.size());
+
+ s32 priority = exheader.arm11_system_local_caps.priority;
+ u32 stack_size = exheader.codeset_info.stack_size;
+
+ // On real HW this is done with FS:Reg, but we can be lazy
+ auto fs_user = system.ServiceManager().GetService("fs:USER");
+ fs_user->RegisterProgramInfo(process->process_id, process->codeset->program_id, "articbase://");
+
+ Service::FS::FS_USER::ProductInfo product_info{};
+ if (LoadProductInfo(product_info) != ResultStatus::Success) {
+ return ResultStatus::ErrorArtic;
+ }
+ fs_user->RegisterProductInfo(process->process_id, product_info);
+
+ process->Run(priority, stack_size);
+ return ResultStatus::Success;
+}
+
void Apploader_Artic::ParseRegionLockoutInfo(u64 program_id) {
if (Settings::values.region_value.GetValue() != Settings::REGION_VALUE_AUTO_SELECT) {
return;
@@ -252,8 +278,7 @@ bool Apploader_Artic::LoadExheader() {
if (program_exheader_loaded)
return true;
- if (!client_connected)
- client_connected = client->Connect();
+ EnsureClientConnected();
if (!client_connected)
return false;
@@ -287,8 +312,7 @@ ResultStatus Apploader_Artic::LoadProductInfo(Service::FS::FS_USER::ProductInfo&
return ResultStatus::Success;
}
- if (!client_connected)
- client_connected = client->Connect();
+ EnsureClientConnected();
if (!client_connected)
return ResultStatus::ErrorArtic;
@@ -307,6 +331,34 @@ ResultStatus Apploader_Artic::LoadProductInfo(Service::FS::FS_USER::ProductInfo&
return ResultStatus::Success;
}
+void Apploader_Artic::EnsureClientConnected() {
+ if (client_connected) {
+ return;
+ }
+ client_connected = client->Connect();
+ if (!client_connected) {
+ return;
+ }
+
+ if (is_initial_setup) {
+ // Ensure we are running the initial setup app in the correct version
+ auto req = client->NewRequest("System_IsAzaharInitialSetup");
+ auto resp = client->Send(req);
+ if (!resp.has_value()) {
+ client_connected = false;
+ return;
+ }
+
+ auto ret_buf = resp->GetResponseBuffer(0);
+ if (!ret_buf.has_value() || ret_buf->second != sizeof(u32)) {
+ client_connected = false;
+ return;
+ }
+
+ client_connected = *reinterpret_cast(ret_buf->first) == INITIAL_SETUP_APP_VERSION;
+ }
+}
+
ResultStatus Apploader_Artic::Load(std::shared_ptr& process) {
u64_le ncch_program_id;
@@ -331,41 +383,173 @@ ResultStatus Apploader_Artic::Load(std::shared_ptr& process) {
is_loaded = true; // Set state to loaded
+ if (is_initial_setup) {
+
+ // Request console unique data
+ for (int i = 0; i < 6; i++) {
+ std::string path;
+ std::size_t expected_size;
+
+ switch (i) {
+ case 0:
+ path = HW::UniqueData::GetSecureInfoAPath();
+ expected_size = sizeof(HW::UniqueData::SecureInfoA);
+ break;
+ case 1:
+ path = HW::UniqueData::GetLocalFriendCodeSeedBPath();
+ expected_size = sizeof(HW::UniqueData::LocalFriendCodeSeedB);
+ break;
+ case 2:
+ path = HW::UniqueData::GetMovablePath();
+ expected_size = sizeof(HW::UniqueData::MovableSedFull);
+ break;
+ case 3:
+ path = HW::UniqueData::GetOTPPath();
+ expected_size = sizeof(FileSys::OTP::OTPBin);
+ break;
+ case 4:
+ expected_size = sizeof(u64) + sizeof(u32);
+ break;
+ case 5:
+ expected_size = sizeof(u8) * 6;
+ break;
+ }
+
+ auto req = client->NewRequest("System_GetSystemFile");
+ req.AddParameterU8(static_cast(i));
+ auto resp = client->Send(req);
+ if (!resp.has_value() || !resp->Succeeded())
+ return ResultStatus::ErrorArtic;
+
+ if (resp->GetMethodResult() != 0)
+ return ResultStatus::ErrorArtic;
+
+ auto resp_buff = resp->GetResponseBuffer(0);
+ if (!resp_buff.has_value() || resp_buff->second != expected_size)
+ return ResultStatus::ErrorArtic;
+
+ if (i < 4) {
+ FileUtil::CreateFullPath(path);
+ FileUtil::IOFile out_file(path, "wb");
+ if (!out_file.IsOpen()) {
+ return ResultStatus::ErrorArtic;
+ }
+ out_file.WriteBytes(reinterpret_cast(resp_buff->first), resp_buff->second);
+ } else if (i == 4) {
+ u64 console_id;
+ u32 random_id;
+ memcpy(&console_id, resp_buff->first, sizeof(u64));
+ memcpy(&random_id, reinterpret_cast(resp_buff->first) + sizeof(u64),
+ sizeof(u32));
+ auto cfg = system.ServiceManager().GetService("cfg:u");
+ if (cfg.get()) {
+ auto cfg_module = cfg->GetModule();
+ cfg_module->SetConsoleUniqueId(random_id, console_id);
+ cfg_module->UpdateConfigNANDSavegame();
+ }
+ } else if (i == 5) {
+ std::array mac;
+ memcpy(mac.data(), resp_buff->first, mac.size());
+ auto cfg = system.ServiceManager().GetService("cfg:u");
+ if (cfg.get()) {
+ auto cfg_module = cfg->GetModule();
+ cfg_module->GetMacAddress() = Service::CFG::MacToString(mac);
+ cfg_module->SaveMacAddress();
+ }
+ system.Kernel().GetSharedPageHandler().SetMacAddress(mac);
+ }
+ }
+
+ HW::UniqueData::InvalidateSecureData();
+ if (!HW::UniqueData::GetCTCert().IsValid() || !HW::UniqueData::GetMovableSed().IsValid() ||
+ !HW::UniqueData::GetSecureInfoA().IsValid() ||
+ !HW::UniqueData::GetLocalFriendCodeSeedB().IsValid()) {
+ LOG_CRITICAL(Loader, "Some console unique data is invalid, aborting...");
+ return ResultStatus::ErrorArtic;
+ }
+
+ // Set deliver arg so that System Settings goes to the update screen directly
+ auto apt = Service::APT::GetModule(system);
+ Service::APT::DeliverArg arg;
+ arg.param.push_back(0x7a);
+ apt->GetAppletManager()->SetDeliverArg(arg);
+
+ // Load NIM
+ auto req = client->NewRequest("System_GetNIM");
+ auto resp = client->Send(req);
+ if (!resp.has_value() || !resp->Succeeded())
+ return ResultStatus::ErrorArtic;
+
+ if (resp->GetMethodResult() != 0)
+ return ResultStatus::ErrorArtic;
+
+ auto resp_buff = resp->GetResponseBuffer(0);
+ if (!resp_buff.has_value() || resp_buff->second != sizeof(ExHeader_Header))
+ return ResultStatus::ErrorArtic;
+
+ ExHeader_Header nim_exheader;
+ memcpy(&nim_exheader, resp_buff->first, resp_buff->second);
+
+ resp_buff = resp->GetResponseBuffer(1);
+ if (!resp_buff.has_value())
+ return ResultStatus::ErrorArtic;
+
+ std::vector code(resp_buff->second);
+ memcpy(code.data(), resp_buff->first, resp_buff->second);
+
+ std::shared_ptr nim_process;
+ result = LoadExecImpl(nim_process, 0x0004013000002C02, nim_exheader, code);
+ if (ResultStatus::Success != result)
+ return result;
+
+ // Force O3DS mode so that NIM fetches O3DS titles
+ auto am = Service::AM::GetModule(system);
+ if (am) {
+ if (artic_init_mode == ArticInitMode::O3DS) {
+ am->ForceO3DSDeviceID();
+ } else if (artic_init_mode == ArticInitMode::N3DS) {
+ am->ForceN3DSDeviceID();
+ }
+ }
+ }
+
result = LoadExec(process); // Load the executable into memory for booting
if (ResultStatus::Success != result)
return result;
system.ArchiveManager().RegisterSelfNCCH(*this);
- system.ArchiveManager().RegisterArticSaveDataSource(client);
- system.ArchiveManager().RegisterArticExtData(client);
- system.ArchiveManager().RegisterArticNCCH(client);
- system.ArchiveManager().RegisterArticSystemSaveData(client);
+ if (!is_initial_setup) {
+ system.ArchiveManager().RegisterArticSaveDataSource(client);
+ system.ArchiveManager().RegisterArticExtData(client);
+ system.ArchiveManager().RegisterArticNCCH(client);
+ system.ArchiveManager().RegisterArticSystemSaveData(client);
- auto fs_user = system.ServiceManager().GetService("fs:USER");
- if (fs_user.get()) {
- fs_user->RegisterSecureValueBackend(
- std::make_shared(client));
- }
+ auto fs_user = system.ServiceManager().GetService("fs:USER");
+ if (fs_user.get()) {
+ fs_user->RegisterSecureValueBackend(
+ std::make_shared(client));
+ }
- auto cfg = system.ServiceManager().GetService("cfg:u");
- if (cfg.get()) {
- cfg->UseArticClient(client);
- }
+ auto cfg = system.ServiceManager().GetService("cfg:u");
+ if (cfg.get()) {
+ cfg->UseArticClient(client);
+ }
- auto amnet = system.ServiceManager().GetService("am:net");
- if (amnet.get()) {
- amnet->UseArticClient(client);
- }
+ auto amnet = system.ServiceManager().GetService("am:net");
+ if (amnet.get()) {
+ amnet->UseArticClient(client);
+ }
- auto amapp = system.ServiceManager().GetService("am:app");
- if (amapp.get()) {
- amapp->UseArticClient(client);
- }
+ auto amapp = system.ServiceManager().GetService("am:app");
+ if (amapp.get()) {
+ amapp->UseArticClient(client);
+ }
- if (Settings::values.use_artic_base_controller.GetValue()) {
- auto hid_user = system.ServiceManager().GetService("hid:USER");
- if (hid_user.get()) {
- hid_user->GetModule()->UseArticClient(client);
+ if (Settings::values.use_artic_base_controller.GetValue()) {
+ auto hid_user = system.ServiceManager().GetService("hid:USER");
+ if (hid_user.get()) {
+ hid_user->GetModule()->UseArticClient(client);
+ }
}
}
@@ -382,8 +566,7 @@ ResultStatus Apploader_Artic::IsExecutable(bool& out_executable) {
ResultStatus Apploader_Artic::ReadCode(std::vector& buffer) {
// Code is only read once, there is no need to cache it.
- if (!client_connected)
- client_connected = client->Connect();
+ EnsureClientConnected();
if (!client_connected)
return ResultStatus::ErrorArtic;
@@ -423,8 +606,7 @@ ResultStatus Apploader_Artic::ReadIcon(std::vector& buffer) {
return ResultStatus::Success;
}
- if (!client_connected)
- client_connected = client->Connect();
+ EnsureClientConnected();
if (!client_connected)
return ResultStatus::ErrorArtic;
@@ -450,8 +632,7 @@ ResultStatus Apploader_Artic::ReadBanner(std::vector& buffer) {
return ResultStatus::Success;
}
- if (!client_connected)
- client_connected = client->Connect();
+ EnsureClientConnected();
if (!client_connected)
return ResultStatus::ErrorArtic;
@@ -477,8 +658,7 @@ ResultStatus Apploader_Artic::ReadLogo(std::vector& buffer) {
return ResultStatus::Success;
}
- if (!client_connected)
- client_connected = client->Connect();
+ EnsureClientConnected();
if (!client_connected)
return ResultStatus::ErrorArtic;
@@ -504,8 +684,7 @@ ResultStatus Apploader_Artic::ReadProgramId(u64& out_program_id) {
return ResultStatus::Success;
}
- if (!client_connected)
- client_connected = client->Connect();
+ EnsureClientConnected();
if (!client_connected)
return ResultStatus::ErrorArtic;
diff --git a/src/core/loader/artic.h b/src/core/loader/artic.h
index 477153f08..d102babcc 100644
--- a/src/core/loader/artic.h
+++ b/src/core/loader/artic.h
@@ -1,4 +1,4 @@
-// Copyright 2024 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -18,22 +18,13 @@ namespace Loader {
/// Loads an NCCH file (e.g. from a CCI, or the first NCCH in a CXI)
class Apploader_Artic final : public AppLoader {
public:
- Apploader_Artic(Core::System& system_, const std::string& server_addr, u16 server_port)
- : AppLoader(system_, FileUtil::IOFile()) {
- client = std::make_shared(server_addr, server_port);
- client->SetCommunicationErrorCallback([&system_](const std::string& msg) {
- system_.SetStatus(Core::System::ResultStatus::ErrorArticDisconnected,
- msg.empty() ? nullptr : msg.c_str());
- });
- client->SetArticReportTrafficCallback(
- [&system_](u32 bytes) { system_.ReportArticTraffic(bytes); });
- client->SetReportArticEventCallback([&system_](u64 event) {
- Core::PerfStats::PerfArticEventBits ev =
- static_cast(event & 0xFFFFFFFF);
- bool set = (event > 32) != 0;
- system_.ReportPerfArticEvent(ev, set);
- });
- }
+ enum class ArticInitMode {
+ NONE,
+ O3DS,
+ N3DS,
+ };
+ Apploader_Artic(Core::System& system_, const std::string& server_addr, u16 server_port,
+ ArticInitMode init_mode);
~Apploader_Artic() override;
@@ -97,7 +88,12 @@ public:
return false;
}
+ bool DoingInitialSetup() override {
+ return is_initial_setup;
+ }
+
private:
+ static constexpr u32 INITIAL_SETUP_APP_VERSION = 0;
/**
* Loads .code section into memory for booting
* @param process The newly created process
@@ -105,6 +101,9 @@ private:
*/
ResultStatus LoadExec(std::shared_ptr& process);
+ ResultStatus LoadExecImpl(std::shared_ptr& process, u64_le program_id,
+ const ExHeader_Header& exheader, std::vector& code);
+
/// Reads the region lockout info in the SMDH and send it to CFG service
/// If an SMDH is not present, the program ID is compared against a list
/// of known system titles to determine the region.
@@ -114,8 +113,12 @@ private:
ResultStatus LoadProductInfo(Service::FS::FS_USER::ProductInfo& out);
+ void EnsureClientConnected();
+
ExHeader_Header program_exheader{};
bool program_exheader_loaded = false;
+ bool is_initial_setup = false;
+ ArticInitMode artic_init_mode = ArticInitMode::NONE;
std::optional cached_title_id = std::nullopt;
std::optional cached_product_info = std::nullopt;
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index c1f848b47..c09760e36 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -1,4 +1,4 @@
-// Copyright 2014 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -112,6 +112,12 @@ static std::unique_ptr GetFileLoader(Core::System& system, FileUtil::
return std::make_unique(system, std::move(file), filepath);
case FileType::ARTIC: {
+ Apploader_Artic::ArticInitMode mode = Apploader_Artic::ArticInitMode::NONE;
+ if (filename.starts_with("articinio://")) {
+ mode = Apploader_Artic::ArticInitMode::O3DS;
+ } else if (filename.starts_with("articinin://")) {
+ mode = Apploader_Artic::ArticInitMode::N3DS;
+ }
auto strToUInt = [](const std::string& str) -> int {
char* pEnd = NULL;
unsigned long ul = ::strtoul(str.c_str(), &pEnd, 10);
@@ -121,7 +127,7 @@ static std::unique_ptr GetFileLoader(Core::System& system, FileUtil::
};
u16 port = 5543;
- std::string server_addr = filename;
+ std::string server_addr = filename.substr(12);
auto pos = server_addr.find(":");
if (pos != server_addr.npos) {
int newVal = strToUInt(server_addr.substr(pos + 1));
@@ -130,7 +136,7 @@ static std::unique_ptr GetFileLoader(Core::System& system, FileUtil::
server_addr = server_addr.substr(0, pos);
}
}
- return std::make_unique(system, server_addr, port);
+ return std::make_unique(system, server_addr, port, mode);
}
default:
@@ -139,9 +145,10 @@ static std::unique_ptr GetFileLoader(Core::System& system, FileUtil::
}
std::unique_ptr GetLoader(const std::string& filename) {
- if (filename.starts_with("articbase://")) {
+ if (filename.starts_with("articbase://") || filename.starts_with("articinio://") ||
+ filename.starts_with("articinin://")) {
return GetFileLoader(Core::System::GetInstance(), FileUtil::IOFile(), FileType::ARTIC,
- filename.substr(12), "");
+ filename, "");
}
FileUtil::IOFile file(filename, "rb");
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index 20824944d..76a7ea2df 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -1,4 +1,4 @@
-// Copyright 2014 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -275,6 +275,10 @@ public:
return true;
}
+ virtual bool DoingInitialSetup() {
+ return false;
+ }
+
protected:
Core::System& system;
FileUtil::IOFile file;
diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp
index 5da7a55f0..d65ac2a24 100644
--- a/src/core/perf_stats.cpp
+++ b/src/core/perf_stats.cpp
@@ -1,4 +1,4 @@
-// Copyright 2017 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -159,9 +159,9 @@ void FrameLimiter::DoFrameLimiting(microseconds current_system_time_us) {
}
auto now = Clock::now();
- double sleep_scale = Settings::values.frame_limit.GetValue() / 100.0;
+ double sleep_scale = Settings::GetFrameLimit() / 100.0;
- if (Settings::values.frame_limit.GetValue() == 0) {
+ if (Settings::GetFrameLimit() == 0) {
return;
}
diff --git a/src/core/system_titles.cpp b/src/core/system_titles.cpp
index 54c861b5d..9014c9ca7 100644
--- a/src/core/system_titles.cpp
+++ b/src/core/system_titles.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -1208,4 +1208,82 @@ std::optional GetSystemTitleRegion(u64 title_id) {
return std::nullopt;
}
+std::pair AreSystemTitlesInstalled() {
+ std::array o_installed_titles{};
+ std::array o_total_titles{};
+
+ std::array n_installed_titles{};
+ std::array n_total_titles{};
+
+ for (auto categ = system_titles.begin(); categ != system_titles.end(); categ++) {
+ for (auto title = categ->titles.begin(); title != categ->titles.end(); title++) {
+
+ for (u32 i = 0; i < NUM_SYSTEM_TITLE_REGIONS; i++) {
+ if (title->title_id_lows[i] == 0) {
+ continue;
+ }
+ u64 program_id =
+ static_cast(categ->title_id_high) << 32 | title->title_id_lows[i];
+ if (title->sets & SystemTitleSet::Old3ds) {
+ o_total_titles[i]++;
+ }
+ if (title->sets & SystemTitleSet::New3ds) {
+ n_total_titles[i]++;
+ }
+
+ // TODO(PabloMK7) Switch to a better solution once available, so it's not as slow as
+ // checking everything
+ if (FileUtil::Exists(Service::AM::GetTitleMetadataPath(Service::FS::MediaType::NAND,
+ program_id, false))) {
+ if (title->sets & SystemTitleSet::Old3ds) {
+ o_installed_titles[i]++;
+ }
+ if (title->sets & SystemTitleSet::New3ds) {
+ n_installed_titles[i]++;
+ }
+ }
+ }
+ }
+ }
+
+ bool o_all = false;
+ bool n_all = false;
+
+ for (size_t i = 0; i < o_installed_titles.size(); i++) {
+ if (o_installed_titles[i] == o_total_titles[i]) {
+ o_all = true;
+ break;
+ }
+ }
+ for (size_t i = 0; i < n_installed_titles.size(); i++) {
+ if (n_installed_titles[i] == n_total_titles[i]) {
+ n_all = true;
+ break;
+ }
+ }
+
+ return std::make_pair(o_all, n_all);
+}
+
+void UninstallSystemFiles(SystemTitleSet set) {
+ for (auto categ = system_titles.begin(); categ != system_titles.end(); categ++) {
+ for (auto title = categ->titles.begin(); title != categ->titles.end(); title++) {
+ if (((set & SystemTitleSet::Old3ds) != 0 &&
+ (title->sets & SystemTitleSet::Old3ds) != 0) ||
+ ((set & SystemTitleSet::New3ds) != 0 &&
+ (title->sets & (SystemTitleSet::Old3ds | SystemTitleSet::New3ds)) ==
+ SystemTitleSet::New3ds)) {
+ for (u32 i = 0; i < NUM_SYSTEM_TITLE_REGIONS; i++) {
+ if (title->title_id_lows[i] == 0) {
+ continue;
+ }
+ u64 program_id =
+ static_cast(categ->title_id_high) << 32 | title->title_id_lows[i];
+ Service::AM::UninstallProgram(Service::FS::MediaType::NAND, program_id);
+ }
+ }
+ }
+ }
+}
+
} // namespace Core
diff --git a/src/core/system_titles.h b/src/core/system_titles.h
index a8b640f75..9eb7caab8 100644
--- a/src/core/system_titles.h
+++ b/src/core/system_titles.h
@@ -1,10 +1,11 @@
-// Copyright 2023 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include
+#include
#include
#include "common/common_types.h"
@@ -26,4 +27,9 @@ std::string GetHomeMenuNcchPath(u32 region);
/// Returns the region of a system title, if it can be determined.
std::optional GetSystemTitleRegion(u64 title_id);
+/// Determines if all system titles are installed for o3ds and n3ds.
+std::pair AreSystemTitlesInstalled();
+
+void UninstallSystemFiles(SystemTitleSet set);
+
} // namespace Core
diff --git a/src/network/artic_base/artic_base_client.cpp b/src/network/artic_base/artic_base_client.cpp
index 4084df4bb..fe7a0578a 100644
--- a/src/network/artic_base/artic_base_client.cpp
+++ b/src/network/artic_base/artic_base_client.cpp
@@ -1,4 +1,4 @@
-// Copyright 2024 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -43,8 +43,6 @@
#define closesocket(x) close(x)
#endif
-// #define DISABLE_PING_TIMEOUT
-
namespace Network::ArticBase {
using namespace std::chrono_literals;
@@ -229,7 +227,7 @@ bool Client::Connect() {
hints.ai_socktype = SOCK_STREAM;
hints.ai_family = AF_INET;
- LOG_INFO(Network, "Starting Artic Base Client");
+ LOG_INFO(Network, "Starting Artic Client");
if (getaddrinfo(address.data(), NULL, &hints, &addrinfo) != 0) {
LOG_ERROR(Network, "Failed to get server address");
@@ -273,8 +271,8 @@ bool Client::Connect() {
shutdown(main_socket, SHUT_RDWR);
closesocket(main_socket);
LOG_ERROR(Network, "Incompatible server version: {}", version_value);
- SignalCommunicationError("\nIncompatible Artic Base Server version.\nCheck for updates "
- "to Artic Base Server or Azahar.");
+ SignalCommunicationError("\nIncompatible Artic Server version.\nCheck for updates "
+ "to the Artic Server or Azahar.");
return false;
}
} else {
@@ -485,15 +483,15 @@ void Client::PingFunction() {
while (ping_run) {
std::chrono::time_point last = last_sent_request;
if (std::chrono::steady_clock::now() - last > std::chrono::seconds(7)) {
-#ifdef DISABLE_PING_TIMEOUT
- client->last_sent_request = std::chrono::steady_clock::now();
-#else
- auto ping_reply = SendSimpleRequest("PING");
- if (!ping_reply.has_value()) {
- SignalCommunicationError();
- break;
+ if (ping_enabled) {
+ auto ping_reply = SendSimpleRequest("PING");
+ if (!ping_reply.has_value()) {
+ SignalCommunicationError();
+ break;
+ }
+ } else {
+ last_sent_request = std::chrono::steady_clock::now();
}
-#endif // DISABLE_PING_TIMEOUT
}
std::unique_lock lk(ping_cv_mutex);
diff --git a/src/network/artic_base/artic_base_client.h b/src/network/artic_base/artic_base_client.h
index 040d50c9c..a14578f6d 100644
--- a/src/network/artic_base/artic_base_client.h
+++ b/src/network/artic_base/artic_base_client.h
@@ -1,4 +1,4 @@
-// Copyright 2024 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -161,6 +161,10 @@ public:
return last_sockaddr_in;
}
+ void SetPingEnabled(bool enable) {
+ ping_enabled = enable;
+ }
+
private:
static constexpr const int SERVER_VERSION = 2;
@@ -190,6 +194,7 @@ private:
std::condition_variable ping_cv;
std::mutex ping_cv_mutex;
std::atomic ping_run = true;
+ bool ping_enabled = true;
void StopImpl(bool from_error);
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp
index 12b12d27d..8c4725917 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.cpp
+++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -185,7 +185,7 @@ void Swapchain::SetPresentMode() {
}
// If vsync is enabled attempt to use mailbox mode in case the user wants to speedup/slowdown
// the game. If mailbox is not available use immediate and warn about it.
- if (use_vsync && Settings::values.frame_limit.GetValue() > 100) {
+ if (use_vsync && Settings::GetFrameLimit() > 100) {
present_mode = has_mailbox ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eImmediate;
if (!has_mailbox) {
LOG_WARNING(