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.
This commit is contained in:
PabloMK7 2025-03-10 11:48:11 +01:00 committed by GitHub
parent 6262ddafa6
commit e3a21c8ef1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 1006 additions and 450 deletions

View File

@ -489,7 +489,7 @@ void GMainWindow::InitializeWidgets() {
artic_traffic_label = new QLabel(); artic_traffic_label = new QLabel();
artic_traffic_label->setToolTip( 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 = new QLabel();
emu_speed_label->setToolTip(tr("Current emulation speed. Values higher or lower than 100% " 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_Load_File, &GMainWindow::OnMenuLoadFile);
connect_menu(ui->action_Install_CIA, &GMainWindow::OnMenuInstallCIA); connect_menu(ui->action_Install_CIA, &GMainWindow::OnMenuInstallCIA);
connect_menu(ui->action_Connect_Artic, &GMainWindow::OnMenuConnectArticBase); 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++) { for (u32 region = 0; region < Core::NUM_SYSTEM_TITLE_REGIONS; region++) {
connect_menu(ui->menu_Boot_Home_Menu->actions().at(region), connect_menu(ui->menu_Boot_Home_Menu->actions().at(region),
[this, region] { OnMenuBootHomeMenu(region); }); [this, region] { OnMenuBootHomeMenu(region); });
@ -1367,9 +1368,9 @@ bool GMainWindow::LoadROM(const QString& filename) {
case Core::System::ResultStatus::ErrorArticDisconnected: case Core::System::ResultStatus::ErrorArticDisconnected:
QMessageBox::critical( QMessageBox::critical(
this, tr("Artic Base Server"), this, tr("Artic Server"),
tr(fmt::format( 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()) system.GetStatusDetails())
.c_str())); .c_str()));
break; break;
@ -1401,7 +1402,9 @@ void GMainWindow::BootGame(const QString& filename) {
ShutdownGame(); 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"))) { if (!is_artic && filename.endsWith(QStringLiteral(".cia"))) {
const auto answer = QMessageBox::question( 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); 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. // possible. Instead register the app loader early and do not create it again on system load.
if (!loader->SupportsMultipleInstancesForSameFile()) { if (!loader->SupportsMultipleInstancesForSameFile()) {
system.RegisterAppLoaderEarly(loader); system.RegisterAppLoaderEarly(loader);
@ -2192,6 +2195,92 @@ void GMainWindow::OnMenuLoadFile() {
BootGame(filename); BootGame(filename);
} }
void GMainWindow::OnMenuSetUpSystemFiles() {
QDialog dialog(this);
dialog.setWindowTitle(tr("Set Up System Files"));
QVBoxLayout layout(&dialog);
QLabel label_description(
tr("<p>Azahar needs files from a real console to be able to use some of its features.<br>"
"You can get such files with the <a "
"href=https://github.com/azahar-emu/ArticSetupTool>Azahar "
"Artic Setup Tool</a><br> Notes:<ul><li><b>This operation will install console unique "
"files "
"to Azahar, do not share your user or nand folders<br>after performing the setup "
"process!</b></li><li>Old 3DS setup is needed for the New 3DS setup to "
"work.</li><li>Both setup modes will work regardless of the model of the console "
"running the setup tool.</li></ul><hr></p>"),
&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("<br>Choose setup mode:"), &dialog);
layout.addWidget(&label_select);
std::pair<bool, bool> 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() { void GMainWindow::OnMenuInstallCIA() {
QStringList filepaths = QFileDialog::getOpenFileNames( QStringList filepaths = QFileDialog::getOpenFileNames(
this, tr("Load Files"), UISettings::values.roms_path, this, tr("Load Files"), UISettings::values.roms_path,
@ -3131,16 +3220,16 @@ void GMainWindow::UpdateStatusBar() {
QStringLiteral("QLabel { color: %0; }").arg(label_color[style_index]); QStringLiteral("QLabel { color: %0; }").arg(label_color[style_index]);
artic_traffic_label->setText( 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); 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)); emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0));
} else { } else {
emu_speed_label->setText(tr("Speed: %1% / %2%") emu_speed_label->setText(tr("Speed: %1% / %2%")
.arg(results.emulation_speed * 100.0, 0, 'f', 0) .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)); 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)); 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); message = QString::fromStdString(details);
error_severity_icon = QMessageBox::Icon::Warning; error_severity_icon = QMessageBox::Icon::Warning;
} else if (result == Core::System::ResultStatus::ErrorArticDisconnected) { } else if (result == Core::System::ResultStatus::ErrorArticDisconnected) {
title = tr("Artic Base Server"); title = tr("Artic Server");
message = message =
tr(fmt::format("A communication error has occurred. The game will quit.\n{}", details) tr(fmt::format("A communication error has occurred. The game will quit.\n{}", details)
.c_str()); .c_str());

View File

@ -244,6 +244,7 @@ private slots:
void OnGameListOpenPerGameProperties(const QString& file); void OnGameListOpenPerGameProperties(const QString& file);
void OnConfigurePerGame(); void OnConfigurePerGame();
void OnMenuLoadFile(); void OnMenuLoadFile();
void OnMenuSetUpSystemFiles();
void OnMenuInstallCIA(); void OnMenuInstallCIA();
void OnMenuConnectArticBase(); void OnMenuConnectArticBase();
void OnMenuBootHomeMenu(u32 region); void OnMenuBootHomeMenu(u32 region);

View File

@ -237,8 +237,7 @@ ConfigureSystem::ConfigureSystem(Core::System& system_, QWidget* parent)
&ConfigureSystem::UpdateInitTicks); &ConfigureSystem::UpdateInitTicks);
connect(ui->button_regenerate_console_id, &QPushButton::clicked, this, connect(ui->button_regenerate_console_id, &QPushButton::clicked, this,
&ConfigureSystem::RefreshConsoleID); &ConfigureSystem::RefreshConsoleID);
connect(ui->button_start_download, &QPushButton::clicked, this, connect(ui->button_regenerate_mac, &QPushButton::clicked, this, &ConfigureSystem::RefreshMAC);
&ConfigureSystem::DownloadFromNUS);
connect(ui->button_secure_info, &QPushButton::clicked, this, [this] { connect(ui->button_secure_info, &QPushButton::clicked, this, [this] {
ui->button_secure_info->setEnabled(false); ui->button_secure_info->setEnabled(false);
@ -281,34 +280,6 @@ ConfigureSystem::ConfigureSystem(Core::System& system_, QWidget* parent)
} }
SetupPerGameUI(); 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(); ConfigureTime();
} }
@ -385,14 +356,13 @@ void ConfigureSystem::ReadSystemSettings() {
u64 console_id = cfg->GetConsoleUniqueId(); u64 console_id = cfg->GetConsoleUniqueId();
ui->label_console_id->setText( ui->label_console_id->setText(
tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper())); 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 // set play coin
play_coin = Service::PTM::Module::GetPlayCoins(); play_coin = Service::PTM::Module::GetPlayCoins();
ui->spinBox_play_coins->setValue(play_coin); ui->spinBox_play_coins->setValue(play_coin);
// set firmware download region
ui->combo_download_region->setCurrentIndex(static_cast<int>(cfg->GetRegionValue()));
// Refresh secure data status // Refresh secure data status
RefreshSecureDataStatus(); RefreshSecureDataStatus();
} }
@ -484,6 +454,9 @@ void ConfigureSystem::ApplyConfiguration() {
Settings::values.plugin_loader_enabled.SetValue(ui->plugin_loader->isChecked()); Settings::values.plugin_loader_enabled.SetValue(ui->plugin_loader->isChecked());
Settings::values.allow_plugin_loader.SetValue(ui->allow_plugin_loader->isChecked()); Settings::values.allow_plugin_loader.SetValue(ui->allow_plugin_loader->isChecked());
cfg->GetMacAddress() = mac_address;
cfg->SaveMacAddress();
} }
} }
@ -548,8 +521,9 @@ void ConfigureSystem::UpdateInitTicks(int init_ticks_type) {
void ConfigureSystem::RefreshConsoleID() { void ConfigureSystem::RefreshConsoleID() {
QMessageBox::StandardButton reply; QMessageBox::StandardButton reply;
QString warning_text = tr("This will replace your current virtual 3DS with a new one. " QString warning_text =
"Your current virtual 3DS will not be recoverable. " 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 " "This might have unexpected effects in applications. This might fail "
"if you use an outdated config save. Continue?"); "if you use an outdated config save. Continue?");
reply = QMessageBox::critical(this, tr("Warning"), warning_text, reply = QMessageBox::critical(this, tr("Warning"), warning_text,
@ -565,6 +539,21 @@ void ConfigureSystem::RefreshConsoleID() {
tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper())); 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) { void ConfigureSystem::InstallSecureData(const std::string& from_path, const std::string& to_path) {
std::string from = std::string from =
FileUtil::SanitizePath(from_path, FileUtil::DirectorySeparator::PlatformDefault); FileUtil::SanitizePath(from_path, FileUtil::DirectorySeparator::PlatformDefault);
@ -655,20 +644,9 @@ void ConfigureSystem::SetupPerGameUI() {
ui->label_plugin_loader->setVisible(false); ui->label_plugin_loader->setVisible(false);
ui->plugin_loader->setVisible(false); ui->plugin_loader->setVisible(false);
ui->allow_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, ConfigurationShared::SetColoredTristate(ui->toggle_new_3ds, Settings::values.is_new_3ds,
is_new_3ds); is_new_3ds);
ConfigurationShared::SetColoredTristate(ui->toggle_lle_applets, Settings::values.lle_applets, ConfigurationShared::SetColoredTristate(ui->toggle_lle_applets, Settings::values.lle_applets,
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);
}

View File

@ -1,4 +1,4 @@
// Copyright 2016 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -51,14 +51,13 @@ private:
void UpdateInitTime(int init_clock); void UpdateInitTime(int init_clock);
void UpdateInitTicks(int init_ticks_type); void UpdateInitTicks(int init_ticks_type);
void RefreshConsoleID(); void RefreshConsoleID();
void RefreshMAC();
void InstallSecureData(const std::string& from_path, const std::string& to_path); void InstallSecureData(const std::string& from_path, const std::string& to_path);
void RefreshSecureDataStatus(); void RefreshSecureDataStatus();
void SetupPerGameUI(); void SetupPerGameUI();
void DownloadFromNUS();
private: private:
std::unique_ptr<Ui::ConfigureSystem> ui; std::unique_ptr<Ui::ConfigureSystem> ui;
Core::System& system; Core::System& system;
@ -75,4 +74,5 @@ private:
u8 country_code; u8 country_code;
u16 play_coin; u16 play_coin;
bool system_setup; bool system_setup;
std::string mac_address;
}; };

View File

@ -455,96 +455,14 @@
</widget> </widget>
</item> </item>
<item row="16" column="0"> <item row="16" column="0">
<widget class="QLabel" name="label_plugin_loader"> <widget class="QLabel" name="label_mac">
<property name="text"> <property name="text">
<string>3GX Plugin Loader:</string> <string>MAC:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="16" column="1"> <item row="16" column="1">
<widget class="QCheckBox" name="plugin_loader"> <widget class="QPushButton" name="button_regenerate_mac">
<property name="text">
<string>Enable 3GX plugin loader</string>
</property>
</widget>
</item>
<item row="17" column="1">
<widget class="QCheckBox" name="allow_plugin_loader">
<property name="text">
<string>Allow applications to change plugin loader state</string>
</property>
</widget>
</item>
<item row="18" column="0">
<widget class="QLabel" name="label_nus_download">
<property name="text">
<string>Download System Files from Nintendo servers</string>
</property>
</widget>
</item>
<item row="18" column="1">
<widget class="QWidget" name="body_nus_download">
<layout class="QHBoxLayout" name="horizontalLayout_nus_download">
<item>
<widget class="QComboBox" name="combo_download_set">
<item>
<property name="text">
<string>Minimal</string>
</property>
</item>
<item>
<property name="text">
<string>Old 3DS</string>
</property>
</item>
<item>
<property name="text">
<string>New 3DS</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QComboBox" name="combo_download_region">
<item>
<property name="text">
<string>JPN</string>
</property>
</item>
<item>
<property name="text">
<string>USA</string>
</property>
</item>
<item>
<property name="text">
<string>EUR</string>
</property>
</item>
<item>
<property name="text">
<string>AUS</string>
</property>
</item>
<item>
<property name="text">
<string>CHN</string>
</property>
</item>
<item>
<property name="text">
<string>KOR</string>
</property>
</item>
<item>
<property name="text">
<string>TWN</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_start_download">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"> <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>
@ -555,11 +473,29 @@
<enum>Qt::RightToLeft</enum> <enum>Qt::RightToLeft</enum>
</property> </property>
<property name="text"> <property name="text">
<string>Download</string> <string>Regenerate</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> <item row="17" column="0">
<widget class="QLabel" name="label_plugin_loader">
<property name="text">
<string>3GX Plugin Loader:</string>
</property>
</widget>
</item>
<item row="17" column="1">
<widget class="QCheckBox" name="plugin_loader">
<property name="text">
<string>Enable 3GX plugin loader</string>
</property>
</widget>
</item>
<item row="18" column="1">
<widget class="QCheckBox" name="allow_plugin_loader">
<property name="text">
<string>Allow applications to change plugin loader state</string>
</property>
</widget> </widget>
</item> </item>
</layout> </layout>
@ -762,6 +698,7 @@
<tabstop>spinBox_play_coins</tabstop> <tabstop>spinBox_play_coins</tabstop>
<tabstop>spinBox_steps_per_hour</tabstop> <tabstop>spinBox_steps_per_hour</tabstop>
<tabstop>button_regenerate_console_id</tabstop> <tabstop>button_regenerate_console_id</tabstop>
<tabstop>button_regenerate_mac</tabstop>
</tabstops> </tabstops>
<resources/> <resources/>
<connections/> <connections/>

View File

@ -79,6 +79,8 @@
<addaction name="action_Load_File"/> <addaction name="action_Load_File"/>
<addaction name="action_Install_CIA"/> <addaction name="action_Install_CIA"/>
<addaction name="action_Connect_Artic"/> <addaction name="action_Connect_Artic"/>
<addaction name="separator"/>
<addaction name="action_Setup_System_Files"/>
<addaction name="menu_Boot_Home_Menu"/> <addaction name="menu_Boot_Home_Menu"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="menu_recent_files"/> <addaction name="menu_recent_files"/>
@ -238,6 +240,11 @@
<string>Connect to Artic Base...</string> <string>Connect to Artic Base...</string>
</property> </property>
</action> </action>
<action name="action_Setup_System_Files">
<property name="text">
<string>Set Up System Files...</string>
</property>
</action>
<action name="action_Boot_Home_Menu_JPN"> <action name="action_Boot_Home_Menu_JPN">
<property name="text"> <property name="text">
<string>JPN</string> <string>JPN</string>

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -74,6 +74,8 @@ std::string_view GetTextureSamplingName(TextureSampling sampling) {
Values values = {}; Values values = {};
static bool configuring_global = true; static bool configuring_global = true;
bool is_temporary_frame_limit;
double temporary_frame_limit;
void LogSettings() { void LogSettings() {
const auto log_setting = [](std::string_view name, const auto& value) { 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) { void RenameCurrentProfile(std::string new_name) {
Settings::values.current_input_profile.name = std::move(new_name); Settings::values.current_input_profile.name = std::move(new_name);
} }
} // namespace Settings } // namespace Settings

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -618,4 +618,10 @@ void CreateProfile(std::string name);
void DeleteProfile(int index); void DeleteProfile(int index);
void RenameCurrentProfile(std::string new_name); 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 } // namespace Settings

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -110,9 +110,14 @@ System::ResultStatus System::RunLoop(bool tight_loop) {
} }
} }
switch (signal) { 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(); Reset();
return ResultStatus::Success; return ResultStatus::Success;
}
case Signal::Shutdown: case Signal::Shutdown:
return ResultStatus::ShutdownRequested; return ResultStatus::ShutdownRequested;
case Signal::Load: { case Signal::Load: {
@ -471,7 +476,7 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window,
archive_manager = std::make_unique<Service::FS::ArchiveManager>(*this); archive_manager = std::make_unique<Service::FS::ArchiveManager>(*this);
HW::AES::InitKeys(); HW::AES::InitKeys();
Service::Init(*this); Service::Init(*this, !app_loader->DoingInitialSetup());
GDBStub::DeferStart(); GDBStub::DeferStart();
if (!registered_image_interface) { if (!registered_image_interface) {
@ -708,6 +713,10 @@ void System::RegisterAppLoaderEarly(std::unique_ptr<Loader::AppLoader>& loader)
early_app_loader = std::move(loader); early_app_loader = std::move(loader);
} }
bool System::IsInitialSetup() {
return app_loader && app_loader->DoingInitialSetup();
}
template <class Archive> template <class Archive>
void System::serialize(Archive& ar, const unsigned int file_version) { void System::serialize(Archive& ar, const unsigned int file_version) {

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -363,6 +363,8 @@ public:
void RegisterAppLoaderEarly(std::unique_ptr<Loader::AppLoader>& loader); void RegisterAppLoaderEarly(std::unique_ptr<Loader::AppLoader>& loader);
bool IsInitialSetup();
private: private:
/** /**
* Initialize the emulated system. * Initialize the emulated system.

View File

@ -1,4 +1,4 @@
// Copyright 2015 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -47,9 +47,6 @@ union BatteryState {
using MacAddress = std::array<u8, 6>; using MacAddress = std::array<u8, 6>;
// Default MAC address in the Nintendo 3DS range
constexpr MacAddress DefaultMac = {0x40, 0xF4, 0x07, 0x00, 0x00, 0x00};
enum class WifiLinkLevel : u8 { enum class WifiLinkLevel : u8 {
Off = 0, Off = 0,
Poor = 1, Poor = 1,

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // 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 * 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. * to fetch from Citra. Some string params don't fit in 7 bytes, so they are split.
*/ */
enum class SystemInfoCitraInformation { enum class SystemInfoEmulatorInformation {
IS_CITRA = 0, // Always set the output to 1, signaling the app is running on Citra. 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. 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. EMULATION_SPEED = 2, // Gets the emulation speed set by the user or by KernelSetState.
BUILD_NAME = 10, // (ie: Nightly, Canary). BUILD_NAME = 10, // (ie: Nightly, Canary).
@ -291,6 +291,15 @@ enum class SystemInfoCitraInformation {
BUILD_GIT_DESCRIPTION_PART2 = 41, // Git description (commit) last 7 characters. 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. * Current officially supported platforms.
*/ */
@ -1443,7 +1452,11 @@ Result SVC::KernelSetState(u32 kernel_state, u32 varg1, u32 varg2) {
// Citra specific states. // Citra specific states.
case KernelState::KERNEL_STATE_CITRA_EMULATION_SPEED: { case KernelState::KERNEL_STATE_CITRA_EMULATION_SPEED: {
u16 new_value = static_cast<u16>(varg1); u16 new_value = static_cast<u16>(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<double>(new_value);
}
} break; } break;
default: default:
LOG_ERROR(Kernel_SVC, "Unknown KernelSetState state={} varg1={} varg2={}", kernel_state, 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; *out = 0;
return (system.GetNumCores() == 4) ? ResultSuccess : ResultInvalidEnumValue; return (system.GetNumCores() == 4) ? ResultSuccess : ResultInvalidEnumValue;
case SystemInfoType::CITRA_INFORMATION: case SystemInfoType::CITRA_INFORMATION:
switch ((SystemInfoCitraInformation)param) { switch ((SystemInfoEmulatorInformation)param) {
case SystemInfoCitraInformation::IS_CITRA: case SystemInfoEmulatorInformation::EMULATOR_ID:
*out = 1; *out = static_cast<s64>(EmulatorIDs::AZAHAR_EMULATOR);
break; break;
case SystemInfoCitraInformation::HOST_TICK: case SystemInfoEmulatorInformation::HOST_TICK:
*out = static_cast<s64>(std::chrono::duration_cast<std::chrono::nanoseconds>( *out = static_cast<s64>(std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now().time_since_epoch()) std::chrono::steady_clock::now().time_since_epoch())
.count()); .count());
break; break;
case SystemInfoCitraInformation::EMULATION_SPEED: case SystemInfoEmulatorInformation::EMULATION_SPEED:
*out = static_cast<s64>(Settings::values.frame_limit.GetValue()); *out = static_cast<s64>(Settings::values.frame_limit.GetValue());
break; break;
case SystemInfoCitraInformation::BUILD_NAME: case SystemInfoEmulatorInformation::BUILD_NAME:
CopyStringPart(reinterpret_cast<char*>(out), Common::g_build_name, 0, sizeof(s64)); CopyStringPart(reinterpret_cast<char*>(out), Common::g_build_name, 0, sizeof(s64));
break; break;
case SystemInfoCitraInformation::BUILD_VERSION: case SystemInfoEmulatorInformation::BUILD_VERSION:
CopyStringPart(reinterpret_cast<char*>(out), Common::g_build_version, 0, sizeof(s64)); CopyStringPart(reinterpret_cast<char*>(out), Common::g_build_version, 0, sizeof(s64));
break; break;
case SystemInfoCitraInformation::BUILD_PLATFORM: { case SystemInfoEmulatorInformation::BUILD_PLATFORM: {
#if defined(_WIN32) #if defined(_WIN32)
*out = static_cast<s64>(SystemInfoCitraPlatform::PLATFORM_WINDOWS); *out = static_cast<s64>(SystemInfoCitraPlatform::PLATFORM_WINDOWS);
#elif defined(ANDROID) #elif defined(ANDROID)
@ -1843,35 +1856,35 @@ Result SVC::GetSystemInfo(s64* out, u32 type, s32 param) {
#endif #endif
break; break;
} }
case SystemInfoCitraInformation::BUILD_DATE_PART1: case SystemInfoEmulatorInformation::BUILD_DATE_PART1:
CopyStringPart(reinterpret_cast<char*>(out), Common::g_build_date, CopyStringPart(reinterpret_cast<char*>(out), Common::g_build_date,
(sizeof(s64) - 1) * 0, sizeof(s64)); (sizeof(s64) - 1) * 0, sizeof(s64));
break; break;
case SystemInfoCitraInformation::BUILD_DATE_PART2: case SystemInfoEmulatorInformation::BUILD_DATE_PART2:
CopyStringPart(reinterpret_cast<char*>(out), Common::g_build_date, CopyStringPart(reinterpret_cast<char*>(out), Common::g_build_date,
(sizeof(s64) - 1) * 1, sizeof(s64)); (sizeof(s64) - 1) * 1, sizeof(s64));
break; break;
case SystemInfoCitraInformation::BUILD_DATE_PART3: case SystemInfoEmulatorInformation::BUILD_DATE_PART3:
CopyStringPart(reinterpret_cast<char*>(out), Common::g_build_date, CopyStringPart(reinterpret_cast<char*>(out), Common::g_build_date,
(sizeof(s64) - 1) * 2, sizeof(s64)); (sizeof(s64) - 1) * 2, sizeof(s64));
break; break;
case SystemInfoCitraInformation::BUILD_DATE_PART4: case SystemInfoEmulatorInformation::BUILD_DATE_PART4:
CopyStringPart(reinterpret_cast<char*>(out), Common::g_build_date, CopyStringPart(reinterpret_cast<char*>(out), Common::g_build_date,
(sizeof(s64) - 1) * 3, sizeof(s64)); (sizeof(s64) - 1) * 3, sizeof(s64));
break; break;
case SystemInfoCitraInformation::BUILD_GIT_BRANCH_PART1: case SystemInfoEmulatorInformation::BUILD_GIT_BRANCH_PART1:
CopyStringPart(reinterpret_cast<char*>(out), Common::g_scm_branch, CopyStringPart(reinterpret_cast<char*>(out), Common::g_scm_branch,
(sizeof(s64) - 1) * 0, sizeof(s64)); (sizeof(s64) - 1) * 0, sizeof(s64));
break; break;
case SystemInfoCitraInformation::BUILD_GIT_BRANCH_PART2: case SystemInfoEmulatorInformation::BUILD_GIT_BRANCH_PART2:
CopyStringPart(reinterpret_cast<char*>(out), Common::g_scm_branch, CopyStringPart(reinterpret_cast<char*>(out), Common::g_scm_branch,
(sizeof(s64) - 1) * 1, sizeof(s64)); (sizeof(s64) - 1) * 1, sizeof(s64));
break; break;
case SystemInfoCitraInformation::BUILD_GIT_DESCRIPTION_PART1: case SystemInfoEmulatorInformation::BUILD_GIT_DESCRIPTION_PART1:
CopyStringPart(reinterpret_cast<char*>(out), Common::g_scm_desc, (sizeof(s64) - 1) * 0, CopyStringPart(reinterpret_cast<char*>(out), Common::g_scm_desc, (sizeof(s64) - 1) * 0,
sizeof(s64)); sizeof(s64));
break; break;
case SystemInfoCitraInformation::BUILD_GIT_DESCRIPTION_PART2: case SystemInfoEmulatorInformation::BUILD_GIT_DESCRIPTION_PART2:
CopyStringPart(reinterpret_cast<char*>(out), Common::g_scm_desc, (sizeof(s64) - 1) * 1, CopyStringPart(reinterpret_cast<char*>(out), Common::g_scm_desc, (sizeof(s64) - 1) * 1,
sizeof(s64)); sizeof(s64));
break; break;

View File

@ -94,12 +94,17 @@ public:
std::vector<CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption> content; std::vector<CryptoPP::CBC_Mode<CryptoPP::AES>::Decryption> content;
}; };
NCCHCryptoFile::NCCHCryptoFile(const std::string& out_file) { 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 // 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 // to prevent Azahar being used as a tool to download easy shareable decrypted contents
// from the eshop. // from the eshop.
file = HW::UniqueData::OpenUniqueCryptoFile(out_file, "wb", file = HW::UniqueData::OpenUniqueCryptoFile(out_file, "wb",
HW::UniqueData::UniqueCryptoFileID::NCCH); HW::UniqueData::UniqueCryptoFileID::NCCH);
} else {
file = std::make_unique<FileUtil::IOFile>(out_file, "wb");
}
if (!file->IsOpen()) { if (!file->IsOpen()) {
is_error = true; is_error = true;
} }
@ -573,7 +578,8 @@ ResultVal<std::size_t> CIAFile::WriteContentData(u64 offset, std::size_t length,
const FileSys::TitleMetadata& tmd = container.GetTitleMetadata(); const FileSys::TitleMetadata& tmd = container.GetTitleMetadata();
if (i != current_content_index) { if (i != current_content_index) {
current_content_index = static_cast<u16>(i); current_content_index = static_cast<u16>(i);
current_content_file = std::make_unique<NCCHCryptoFile>(content_file_paths[i]); current_content_file =
std::make_unique<NCCHCryptoFile>(content_file_paths[i], decryption_authorized);
current_content_file->decryption_authorized = decryption_authorized; current_content_file->decryption_authorized = decryption_authorized;
} }
auto& file = *current_content_file; auto& file = *current_content_file;
@ -709,7 +715,8 @@ ResultVal<std::size_t> CIAFile::WriteContentDataIndexed(u16 content_index, u64 o
if (content_index != current_content_index) { if (content_index != current_content_index) {
current_content_index = content_index; current_content_index = content_index;
current_content_file = std::make_unique<NCCHCryptoFile>(content_file_paths[content_index]); current_content_file = std::make_unique<NCCHCryptoFile>(content_file_paths[content_index],
decryption_authorized);
current_content_file->decryption_authorized = decryption_authorized; current_content_file->decryption_authorized = decryption_authorized;
} }
auto& file = *current_content_file; auto& file = *current_content_file;
@ -1663,9 +1670,12 @@ Result GetTitleInfoFromList(std::span<const u64> title_id_list, Service::FS::Med
title_info.version = tmd.GetTitleVersion(); title_info.version = tmd.GetTitleVersion();
title_info.type = tmd.GetTitleType(); title_info.type = tmd.GetTitleType();
} else { } else {
LOG_DEBUG(Service_AM, "not found title_id={:016X}", title_id_list[i]);
return Result(ErrorDescription::NotFound, ErrorModule::AM, ErrorSummary::InvalidState, return Result(ErrorDescription::NotFound, ErrorModule::AM, ErrorSummary::InvalidState,
ErrorLevel::Permanent); 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)); title_info_out.Write(&title_info, write_offset, sizeof(TitleInfo));
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<Service::FS::MediaType>(rp.Pop<u8>()); auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>());
u32 title_count = rp.Pop<u32>(); u32 title_count = rp.Pop<u32>();
LOG_DEBUG(Service_AM, "media_type={}", media_type); LOG_DEBUG(Service_AM, "media_type={}, ignore_platform={}", media_type, ignore_platform);
if (artic_client.get()) { if (artic_client.get()) {
struct AsyncData { struct AsyncData {
@ -1688,7 +1698,7 @@ void Module::Interface::GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool
std::vector<u64> title_id_list; std::vector<u64> title_id_list;
Result res{0}; Result res{0};
std::vector<u8> out; std::vector<TitleInfo> out;
Kernel::MappedBuffer* title_id_list_buffer; Kernel::MappedBuffer* title_id_list_buffer;
Kernel::MappedBuffer* title_info_out; Kernel::MappedBuffer* title_info_out;
}; };
@ -1730,7 +1740,7 @@ void Module::Interface::GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool
return 0; 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); memcpy(async_data->out.data(), title_infos->first, title_infos->second);
return 0; return 0;
}, },
@ -1744,7 +1754,7 @@ void Module::Interface::GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool
} }
} else { } else {
async_data->title_info_out->Write(async_data->out.data(), 0, 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); IPC::RequestBuilder rb(ctx, 1, async_data->ignore_platform ? 0 : 4);
rb.Push(async_data->res); rb.Push(async_data->res);
@ -1763,16 +1773,20 @@ void Module::Interface::GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool
std::vector<u64> title_id_list(title_count); std::vector<u64> title_id_list(title_count);
title_id_list_buffer.Read(title_id_list.data(), 0, title_count * sizeof(u64)); 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 // 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 // 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 // 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 // shouldn't. To prevent this, we check if the importing context is present and not
// importing title and return not found if so. // committed. If that's the case, return not found
Result result = ResultSuccess; Result result = ResultSuccess;
if (am->importing_title) {
for (auto tid : title_id_list) { for (auto tid : title_id_list) {
if (tid == am->importing_title->title_id) { 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, result = Result(ErrorDescription::NotFound, ErrorModule::AM,
ErrorSummary::InvalidState, ErrorLevel::Permanent); 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); 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); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.Push<bool>(false); rb.Push<bool>(needs_cleanup);
} }
void Module::Interface::DoCleanup(Kernel::HLERequestContext& ctx) { 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); 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); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
} }
@ -2746,21 +2792,7 @@ void Module::Interface::EndImportProgramWithoutCommit(Kernel::HLERequestContext&
} }
void Module::Interface::CommitImportPrograms(Kernel::HLERequestContext& ctx) { void Module::Interface::CommitImportPrograms(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); CommitImportTitlesImpl(ctx, false, false);
[[maybe_unused]] const auto media_type = static_cast<FS::MediaType>(rp.Pop<u8>());
[[maybe_unused]] const u32 title_count = rp.Pop<u32>();
[[maybe_unused]] const u8 database = rp.Pop<u8>();
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)");
} }
/// Wraps all File operations to allow adding an offset to them. /// 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)); 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<FS::MediaType>(rp.Pop<u8>());
[[maybe_unused]] u32 count = rp.Pop<u32>();
[[maybe_unused]] u8 database = rp.Pop<u8>();
LOG_WARNING(Service_AM, "(STUBBED) update_firm_auto={} is_titles={}", is_update_firm_auto,
is_titles);
auto& title_id_buf = rp.PopMappedBuffer();
std::vector<u64> 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) { 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. // Use the content folder so we don't delete the user's save data.
const auto path = GetTitlePath(media_type, title_id) + "content/"; const auto path = GetTitlePath(media_type, title_id) + "content/";
@ -3351,6 +3423,10 @@ void Module::Interface::EndImportTitle(Kernel::HLERequestContext& ctx) {
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
} }
void Module::Interface::CommitImportTitles(Kernel::HLERequestContext& ctx) {
CommitImportTitlesImpl(ctx, false, true);
}
void Module::Interface::BeginImportTmd(Kernel::HLERequestContext& ctx) { void Module::Interface::BeginImportTmd(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
@ -3720,6 +3796,10 @@ void Module::Interface::GetDeviceCert(Kernel::HLERequestContext& ctx) {
rb.PushMappedBuffer(buffer); rb.PushMappedBuffer(buffer);
} }
void Module::Interface::CommitImportTitlesAndUpdateFirmwareAuto(Kernel::HLERequestContext& ctx) {
CommitImportTitlesImpl(ctx, true, true);
}
void Module::Interface::DeleteTicketId(Kernel::HLERequestContext& ctx) { void Module::Interface::DeleteTicketId(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
u64 title_id = rp.Pop<u64>(); u64 title_id = rp.Pop<u64>();

View File

@ -112,7 +112,7 @@ using ProgressCallback = void(std::size_t, std::size_t);
class NCCHCryptoFile final { class NCCHCryptoFile final {
public: 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); void Write(const u8* buffer, std::size_t length);
bool IsError() { bool IsError() {
@ -128,7 +128,7 @@ private:
std::size_t written = 0; std::size_t written = 0;
NCCH_Header ncch_header; NCCH_Header ncch_header{};
std::size_t header_size = 0; std::size_t header_size = 0;
bool header_parsed = false; bool header_parsed = false;
@ -156,7 +156,7 @@ private:
std::vector<CryptoRegion> regions; std::vector<CryptoRegion> regions;
ExeFs_Header exefs_header; ExeFs_Header exefs_header{};
std::size_t exefs_header_written = 0; std::size_t exefs_header_written = 0;
bool exefs_header_processed = false; bool exefs_header_processed = false;
}; };
@ -414,6 +414,9 @@ public:
protected: protected:
void GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool ignore_platform); 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 * AM::GetNumPrograms service function
* Gets the number of installed titles in the requested media type * Gets the number of installed titles in the requested media type
@ -873,6 +876,8 @@ public:
*/ */
void GetRequiredSizeFromCia(Kernel::HLERequestContext& ctx); void GetRequiredSizeFromCia(Kernel::HLERequestContext& ctx);
void CommitImportProgramsAndUpdateFirmwareAuto(Kernel::HLERequestContext& ctx);
/** /**
* AM::DeleteProgram service function * AM::DeleteProgram service function
* Deletes a program * Deletes a program
@ -949,6 +954,8 @@ public:
void EndImportTitle(Kernel::HLERequestContext& ctx); void EndImportTitle(Kernel::HLERequestContext& ctx);
void CommitImportTitles(Kernel::HLERequestContext& ctx);
void BeginImportTmd(Kernel::HLERequestContext& ctx); void BeginImportTmd(Kernel::HLERequestContext& ctx);
void EndImportTmd(Kernel::HLERequestContext& ctx); void EndImportTmd(Kernel::HLERequestContext& ctx);
@ -983,6 +990,8 @@ public:
*/ */
void GetDeviceCert(Kernel::HLERequestContext& ctx); void GetDeviceCert(Kernel::HLERequestContext& ctx);
void CommitImportTitlesAndUpdateFirmwareAuto(Kernel::HLERequestContext& ctx);
void DeleteTicketId(Kernel::HLERequestContext& ctx); void DeleteTicketId(Kernel::HLERequestContext& ctx);
void GetNumTicketIds(Kernel::HLERequestContext& ctx); void GetNumTicketIds(Kernel::HLERequestContext& ctx);
@ -1002,6 +1011,14 @@ public:
std::shared_ptr<Network::ArticBase::Client> artic_client = nullptr; std::shared_ptr<Network::ArticBase::Client> artic_client = nullptr;
}; };
void ForceO3DSDeviceID() {
force_old_device_id = true;
}
void ForceN3DSDeviceID() {
force_new_device_id = true;
}
private: private:
void ScanForTickets(); void ScanForTickets();

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -57,18 +57,18 @@ AM_NET::AM_NET(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
{0x002D, &AM_NET::CheckContentRightsIgnorePlatform, "CheckContentRightsIgnorePlatform"}, {0x002D, &AM_NET::CheckContentRightsIgnorePlatform, "CheckContentRightsIgnorePlatform"},
{0x0401, nullptr, "UpdateFirmwareTo"}, {0x0401, nullptr, "UpdateFirmwareTo"},
{0x0402, &AM_NET::BeginImportProgram, "BeginImportProgram"}, {0x0402, &AM_NET::BeginImportProgram, "BeginImportProgram"},
{0x0403, nullptr, "BeginImportProgramTemporarily"}, {0x0403, &AM_NET::BeginImportProgramTemporarily, "BeginImportProgramTemporarily"},
{0x0404, nullptr, "CancelImportProgram"}, {0x0404, nullptr, "CancelImportProgram"},
{0x0405, &AM_NET::EndImportProgram, "EndImportProgram"}, {0x0405, &AM_NET::EndImportProgram, "EndImportProgram"},
{0x0406, nullptr, "EndImportProgramWithoutCommit"}, {0x0406, &AM_NET::EndImportProgramWithoutCommit, "EndImportProgramWithoutCommit"},
{0x0407, nullptr, "CommitImportPrograms"}, {0x0407, &AM_NET::CommitImportPrograms, "CommitImportPrograms"},
{0x0408, &AM_NET::GetProgramInfoFromCia, "GetProgramInfoFromCia"}, {0x0408, &AM_NET::GetProgramInfoFromCia, "GetProgramInfoFromCia"},
{0x0409, &AM_NET::GetSystemMenuDataFromCia, "GetSystemMenuDataFromCia"}, {0x0409, &AM_NET::GetSystemMenuDataFromCia, "GetSystemMenuDataFromCia"},
{0x040A, &AM_NET::GetDependencyListFromCia, "GetDependencyListFromCia"}, {0x040A, &AM_NET::GetDependencyListFromCia, "GetDependencyListFromCia"},
{0x040B, &AM_NET::GetTransferSizeFromCia, "GetTransferSizeFromCia"}, {0x040B, &AM_NET::GetTransferSizeFromCia, "GetTransferSizeFromCia"},
{0x040C, &AM_NET::GetCoreVersionFromCia, "GetCoreVersionFromCia"}, {0x040C, &AM_NET::GetCoreVersionFromCia, "GetCoreVersionFromCia"},
{0x040D, &AM_NET::GetRequiredSizeFromCia, "GetRequiredSizeFromCia"}, {0x040D, &AM_NET::GetRequiredSizeFromCia, "GetRequiredSizeFromCia"},
{0x040E, nullptr, "CommitImportProgramsAndUpdateFirmwareAuto"}, {0x040E, &AM_NET::CommitImportProgramsAndUpdateFirmwareAuto, "CommitImportProgramsAndUpdateFirmwareAuto"},
{0x040F, nullptr, "UpdateFirmwareAuto"}, {0x040F, nullptr, "UpdateFirmwareAuto"},
{0x0410, &AM_NET::DeleteProgram, "DeleteProgram"}, {0x0410, &AM_NET::DeleteProgram, "DeleteProgram"},
{0x0411, nullptr, "GetTwlProgramListForReboot"}, {0x0411, nullptr, "GetTwlProgramListForReboot"},
@ -88,7 +88,7 @@ AM_NET::AM_NET(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
{0x0806, &AM_NET::ResumeImportTitle, "ResumeImportTitle"}, {0x0806, &AM_NET::ResumeImportTitle, "ResumeImportTitle"},
{0x0807, &AM_NET::CancelImportTitle, "CancelImportTitle"}, {0x0807, &AM_NET::CancelImportTitle, "CancelImportTitle"},
{0x0808, &AM_NET::EndImportTitle, "EndImportTitle"}, {0x0808, &AM_NET::EndImportTitle, "EndImportTitle"},
{0x0809, nullptr, "CommitImportTitles"}, {0x0809, &AM_NET::CommitImportTitles, "CommitImportTitles"},
{0x080A, &AM_NET::BeginImportTmd, "BeginImportTmd"}, {0x080A, &AM_NET::BeginImportTmd, "BeginImportTmd"},
{0x080B, nullptr, "CancelImportTmd"}, {0x080B, nullptr, "CancelImportTmd"},
{0x080C, &AM_NET::EndImportTmd, "EndImportTmd"}, {0x080C, &AM_NET::EndImportTmd, "EndImportTmd"},
@ -106,7 +106,7 @@ AM_NET::AM_NET(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
{0x0818, &AM_NET::GetDeviceCert, "GetDeviceCert"}, {0x0818, &AM_NET::GetDeviceCert, "GetDeviceCert"},
{0x0819, nullptr, "ImportCertificates"}, {0x0819, nullptr, "ImportCertificates"},
{0x081A, nullptr, "ImportCertificate"}, {0x081A, nullptr, "ImportCertificate"},
{0x081B, nullptr, "CommitImportTitlesAndUpdateFirmwareAuto"}, {0x081B, &AM_NET::CommitImportTitlesAndUpdateFirmwareAuto, "CommitImportTitlesAndUpdateFirmwareAuto"},
{0x081C, &AM_NET::DeleteTicketId, "DeleteTicketId"}, {0x081C, &AM_NET::DeleteTicketId, "DeleteTicketId"},
{0x081D, &AM_NET::GetNumTicketIds, "GetNumTicketIds"}, {0x081D, &AM_NET::GetNumTicketIds, "GetNumTicketIds"},
{0x081E, &AM_NET::GetTicketIdList, "GetTicketIdList"}, {0x081E, &AM_NET::GetTicketIdList, "GetTicketIdList"},

View File

@ -1,4 +1,4 @@
// Copyright 2015 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -20,21 +20,21 @@ AM_U::AM_U(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "am:u"
{0x0008, &AM_U::GetNumTickets, "GetNumTickets"}, {0x0008, &AM_U::GetNumTickets, "GetNumTickets"},
{0x0009, &AM_U::GetTicketList, "GetTicketList"}, {0x0009, &AM_U::GetTicketList, "GetTicketList"},
{0x000A, &AM_U::GetDeviceID, "GetDeviceID"}, {0x000A, &AM_U::GetDeviceID, "GetDeviceID"},
{0x000B, nullptr, "GetNumImportTitleContexts"}, {0x000B, &AM_U::GetNumImportTitleContexts, "GetNumImportTitleContexts"},
{0x000C, nullptr, "GetImportTitleContextList"}, {0x000C, &AM_U::GetImportTitleContextList, "GetImportTitleContextList"},
{0x000D, nullptr, "GetImportTitleContexts"}, {0x000D, &AM_U::GetImportTitleContexts, "GetImportTitleContexts"},
{0x000E, nullptr, "DeleteImportTitleContext"}, {0x000E, &AM_U::DeleteImportTitleContext, "DeleteImportTitleContext"},
{0x000F, nullptr, "GetNumImportContentContexts"}, {0x000F, &AM_U::GetNumImportContentContexts, "GetNumImportContentContexts"},
{0x0010, nullptr, "GetImportContentContextList"}, {0x0010, &AM_U::GetImportContentContextList, "GetImportContentContextList"},
{0x0011, nullptr, "GetImportContentContexts"}, {0x0011, &AM_U::GetImportContentContexts, "GetImportContentContexts"},
{0x0012, nullptr, "DeleteImportContentContexts"}, {0x0012, nullptr, "DeleteImportContentContexts"},
{0x0013, &AM_U::NeedsCleanup, "NeedsCleanup"}, {0x0013, &AM_U::NeedsCleanup, "NeedsCleanup"},
{0x0014, nullptr, "DoCleanup"}, {0x0014, &AM_U::DoCleanup, "DoCleanup"},
{0x0015, nullptr, "DeleteAllImportContexts"}, {0x0015, nullptr, "DeleteAllImportContexts"},
{0x0016, nullptr, "DeleteAllTemporaryPrograms"}, {0x0016, nullptr, "DeleteAllTemporaryPrograms"},
{0x0017, nullptr, "ImportTwlBackupLegacy"}, {0x0017, nullptr, "ImportTwlBackupLegacy"},
{0x0018, nullptr, "InitializeTitleDatabase"}, {0x0018, nullptr, "InitializeTitleDatabase"},
{0x0019, nullptr, "QueryAvailableTitleDatabase"}, {0x0019, &AM_U::QueryAvailableTitleDatabase, "QueryAvailableTitleDatabase"},
{0x001A, nullptr, "CalcTwlBackupSize"}, {0x001A, nullptr, "CalcTwlBackupSize"},
{0x001B, nullptr, "ExportTwlBackup"}, {0x001B, nullptr, "ExportTwlBackup"},
{0x001C, nullptr, "ImportTwlBackup"}, {0x001C, nullptr, "ImportTwlBackup"},
@ -42,19 +42,19 @@ AM_U::AM_U(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "am:u"
{0x001E, nullptr, "ReadTwlBackupInfo"}, {0x001E, nullptr, "ReadTwlBackupInfo"},
{0x001F, nullptr, "DeleteAllExpiredUserPrograms"}, {0x001F, nullptr, "DeleteAllExpiredUserPrograms"},
{0x0020, nullptr, "GetTwlArchiveResourceInfo"}, {0x0020, nullptr, "GetTwlArchiveResourceInfo"},
{0x0021, nullptr, "GetPersonalizedTicketInfoList"}, {0x0021, &AM_U::GetPersonalizedTicketInfoList, "GetPersonalizedTicketInfoList"},
{0x0022, nullptr, "DeleteAllImportContextsFiltered"}, {0x0022, nullptr, "DeleteAllImportContextsFiltered"},
{0x0023, nullptr, "GetNumImportTitleContextsFiltered"}, {0x0023, &AM_U::GetNumImportTitleContextsFiltered, "GetNumImportTitleContextsFiltered"},
{0x0024, nullptr, "GetImportTitleContextListFiltered"}, {0x0024, &AM_U::GetImportTitleContextListFiltered, "GetImportTitleContextListFiltered"},
{0x0025, nullptr, "CheckContentRights"}, {0x0025, &AM_U::CheckContentRights, "CheckContentRights"},
{0x0026, nullptr, "GetTicketLimitInfos"}, {0x0026, nullptr, "GetTicketLimitInfos"},
{0x0027, nullptr, "GetDemoLaunchInfos"}, {0x0027, nullptr, "GetDemoLaunchInfos"},
{0x0028, nullptr, "ReadTwlBackupInfoEx"}, {0x0028, nullptr, "ReadTwlBackupInfoEx"},
{0x0029, nullptr, "DeleteUserProgramsAtomically"}, {0x0029, nullptr, "DeleteUserProgramsAtomically"},
{0x002A, nullptr, "GetNumExistingContentInfosSystem"}, {0x002A, nullptr, "GetNumExistingContentInfosSystem"},
{0x002B, nullptr, "ListExistingContentInfosSystem"}, {0x002B, nullptr, "ListExistingContentInfosSystem"},
{0x002C, nullptr, "GetProgramInfosIgnorePlatform"}, {0x002C, &AM_U::GetProgramInfosIgnorePlatform, "GetProgramInfosIgnorePlatform"},
{0x002D, nullptr, "CheckContentRightsIgnorePlatform"}, {0x002D, &AM_U::CheckContentRightsIgnorePlatform, "CheckContentRightsIgnorePlatform"},
{0x0401, nullptr, "UpdateFirmwareTo"}, {0x0401, nullptr, "UpdateFirmwareTo"},
{0x0402, &AM_U::BeginImportProgram, "BeginImportProgram"}, {0x0402, &AM_U::BeginImportProgram, "BeginImportProgram"},
{0x0403, &AM_U::BeginImportProgramTemporarily, "BeginImportProgramTemporarily"}, {0x0403, &AM_U::BeginImportProgramTemporarily, "BeginImportProgramTemporarily"},
@ -68,7 +68,7 @@ AM_U::AM_U(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "am:u"
{0x040B, &AM_U::GetTransferSizeFromCia, "GetTransferSizeFromCia"}, {0x040B, &AM_U::GetTransferSizeFromCia, "GetTransferSizeFromCia"},
{0x040C, &AM_U::GetCoreVersionFromCia, "GetCoreVersionFromCia"}, {0x040C, &AM_U::GetCoreVersionFromCia, "GetCoreVersionFromCia"},
{0x040D, &AM_U::GetRequiredSizeFromCia, "GetRequiredSizeFromCia"}, {0x040D, &AM_U::GetRequiredSizeFromCia, "GetRequiredSizeFromCia"},
{0x040E, nullptr, "CommitImportProgramsAndUpdateFirmwareAuto"}, {0x040E, &AM_U::CommitImportProgramsAndUpdateFirmwareAuto, "CommitImportProgramsAndUpdateFirmwareAuto"},
{0x040F, nullptr, "UpdateFirmwareAuto"}, {0x040F, nullptr, "UpdateFirmwareAuto"},
{0x0410, &AM_U::DeleteProgram, "DeleteProgram"}, {0x0410, &AM_U::DeleteProgram, "DeleteProgram"},
{0x0411, nullptr, "GetTwlProgramListForReboot"}, {0x0411, nullptr, "GetTwlProgramListForReboot"},

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -40,6 +40,7 @@ template <class Archive>
void Module::serialize(Archive& ar, const unsigned int) { void Module::serialize(Archive& ar, const unsigned int) {
ar & cfg_config_file_buffer; ar & cfg_config_file_buffer;
ar & cfg_system_save_data_archive; ar & cfg_system_save_data_archive;
ar & mac_address;
ar & preferred_region_code; ar & preferred_region_code;
ar & preferred_region_chosen; ar & preferred_region_chosen;
} }
@ -722,6 +723,7 @@ void Module::SaveMCUConfig() {
Module::Module(Core::System& system_) : system(system_) { Module::Module(Core::System& system_) : system(system_) {
LoadConfigNANDSaveFile(); LoadConfigNANDSaveFile();
LoadMCUConfig(); LoadMCUConfig();
(void)GetMacAddress();
// Check the config savegame EULA Version and update it to 0x7F7F if necessary // Check the config savegame EULA Version and update it to 0x7F7F if necessary
// so users will never get a prompt to accept EULA // so users will never get a prompt to accept EULA
auto version = GetEULAVersion(); auto version = GetEULAVersion();
@ -771,6 +773,45 @@ static std::tuple<u32 /*region*/, SystemLanguage> AdjustLanguageInfoBlock(
return {default_region, region_languages[default_region][0]}; 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() { void Module::UpdatePreferredRegionCode() {
if (preferred_region_chosen || !system.IsPoweredOn()) { if (preferred_region_chosen || !system.IsPoweredOn()) {
return; return;
@ -883,8 +924,13 @@ std::pair<u32, u64> Module::GenerateConsoleUniqueId() const {
const u32 random_number = rng.GenerateWord32(0, 0xFFFF); const u32 random_number = rng.GenerateWord32(0, 0xFFFF);
u64_le local_friend_code_seed; u64_le 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<CryptoPP::byte*>(&local_friend_code_seed), rng.GenerateBlock(reinterpret_cast<CryptoPP::byte*>(&local_friend_code_seed),
sizeof(local_friend_code_seed)); sizeof(local_friend_code_seed));
}
const u64 console_id = const u64 console_id =
(local_friend_code_seed & 0x3FFFFFFFF) | (static_cast<u64>(random_number) << 48); (local_friend_code_seed & 0x3FFFFFFFF) | (static_cast<u64>(random_number) << 48);
@ -976,4 +1022,59 @@ std::string GetConsoleIdHash(Core::System& system) {
return fmt::format("{:02x}", fmt::join(hash.begin(), hash.end(), "")); return fmt::format("{:02x}", fmt::join(hash.begin(), hash.end(), ""));
} }
std::array<u8, 6> MacToArray(const std::string& mac) {
std::array<u8, 6> 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<int>(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<u8, 6>& 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<std::pair<u64, u64>, 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<CryptoPP::word32>(ranges.size() - 1))];
u64 mac = range.first +
rng.GenerateWord32(0, static_cast<CryptoPP::word32>(range.second - range.first));
return MacToString(mac);
}
} // namespace Service::CFG } // namespace Service::CFG

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -622,6 +622,16 @@ public:
*/ */
void SaveMCUConfig(); 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: private:
void UpdatePreferredRegionCode(); void UpdatePreferredRegionCode();
SystemLanguage GetRawSystemLanguage(); SystemLanguage GetRawSystemLanguage();
@ -634,6 +644,7 @@ private:
u32 preferred_region_code = 0; u32 preferred_region_code = 0;
bool preferred_region_chosen = false; bool preferred_region_chosen = false;
MCUData mcu_data{}; MCUData mcu_data{};
std::string mac_address{};
std::shared_ptr<Network::ArticBase::Client> artic_client = nullptr; std::shared_ptr<Network::ArticBase::Client> artic_client = nullptr;
@ -649,6 +660,16 @@ void InstallInterfaces(Core::System& system);
/// Convenience function for getting a SHA256 hash of the Console ID /// Convenience function for getting a SHA256 hash of the Console ID
std::string GetConsoleIdHash(Core::System& system); std::string GetConsoleIdHash(Core::System& system);
std::array<u8, 6> MacToArray(const std::string& mac);
std::string MacToString(u64 mac);
std::string MacToString(const std::array<u8, 6>& mac);
u64 MacToU64(const std::string& mac);
std::string GenerateRandomMAC();
} // namespace Service::CFG } // namespace Service::CFG
SERVICE_CONSTRUCT(Service::CFG::Module) SERVICE_CONSTRUCT(Service::CFG::Module)

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // 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) { void FS_USER::GetSdmcArchiveResource(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
LOG_WARNING(Service_FS, "(STUBBED) called"); LOG_DEBUG(Service_FS, "(STUBBED) called");
auto resource = archives.GetArchiveResource(MediaType::SDMC); auto resource = archives.GetArchiveResource(MediaType::SDMC);
@ -934,7 +934,7 @@ void FS_USER::GetSdmcArchiveResource(Kernel::HLERequestContext& ctx) {
void FS_USER::GetNandArchiveResource(Kernel::HLERequestContext& ctx) { void FS_USER::GetNandArchiveResource(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
LOG_WARNING(Service_FS, "(STUBBED) called"); LOG_DEBUG(Service_FS, "(STUBBED) called");
auto resource = archives.GetArchiveResource(MediaType::NAND); auto resource = archives.GetArchiveResource(MediaType::NAND);
if (resource.Failed()) { if (resource.Failed()) {
@ -1103,7 +1103,7 @@ void FS_USER::GetArchiveResource(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx); IPC::RequestParser rp(ctx);
auto media_type = rp.PopEnum<MediaType>(); auto media_type = rp.PopEnum<MediaType>();
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); auto resource = archives.GetArchiveResource(media_type);
if (resource.Failed()) { if (resource.Failed()) {

View File

@ -1,4 +1,4 @@
// Copyright 2015 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -11,6 +11,10 @@
namespace Service::NIM { namespace Service::NIM {
void InstallInterfaces(Core::System& system) { void InstallInterfaces(Core::System& system) {
// Don't register HLE nim on initial setup
if (system.IsInitialSetup()) {
return;
}
auto& service_manager = system.ServiceManager(); auto& service_manager = system.ServiceManager();
std::make_shared<NIM_AOC>()->InstallAsService(service_manager); std::make_shared<NIM_AOC>()->InstallAsService(service_manager);
std::make_shared<NIM_S>()->InstallAsService(service_manager); std::make_shared<NIM_S>()->InstallAsService(service_manager);

View File

@ -1,4 +1,4 @@
// Copyright 2017 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -10,6 +10,7 @@
#include "common/archives.h" #include "common/archives.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/settings.h"
#include "core/core.h" #include "core/core.h"
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/hle/ipc_helpers.h" #include "core/hle/ipc_helpers.h"
@ -17,6 +18,8 @@
#include "core/hle/kernel/shared_memory.h" #include "core/hle/kernel/shared_memory.h"
#include "core/hle/kernel/shared_page.h" #include "core/hle/kernel/shared_page.h"
#include "core/hle/result.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/nwm_uds.h"
#include "core/hle/service/nwm/uds_beacon.h" #include "core/hle/service/nwm/uds_beacon.h"
#include "core/hle/service/nwm/uds_connection.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); BeaconBroadcastCallback(user_data, cycles_late);
}); });
CryptoPP::AutoSeededRandomPool rng; MacAddress mac;
auto mac = SharedPage::DefaultMac;
// Keep the Nintendo 3DS MAC header and randomly generate the last 3 bytes auto cfg = system.ServiceManager().GetService<Service::CFG::CFG_U>("cfg:u");
rng.GenerateBlock(static_cast<CryptoPP::byte*>(mac.data() + 3), 3); if (cfg.get()) {
auto cfg_module = cfg->GetModule();
mac = Service::CFG::MacToArray(cfg_module->GetMacAddress());
}
if (auto room_member = Network::GetRoomMember().lock()) { if (auto room_member = Network::GetRoomMember().lock()) {
if (room_member->IsConnected()) { if (room_member->IsConnected()) {

View File

@ -1,4 +1,4 @@
// Copyright 2015 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -34,7 +34,7 @@ void Module::Interface::GetAdapterState(Kernel::HLERequestContext& ctx) {
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.Push(ptm->battery_is_charging); 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) { void Module::Interface::GetShellState(Kernel::HLERequestContext& ctx) {
@ -52,7 +52,7 @@ void Module::Interface::GetBatteryLevel(Kernel::HLERequestContext& ctx) {
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.Push(static_cast<u32>(ChargeLevels::CompletelyFull)); // Set to a completely full battery rb.Push(static_cast<u32>(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) { void Module::Interface::GetBatteryChargeState(Kernel::HLERequestContext& ctx) {
@ -62,7 +62,7 @@ void Module::Interface::GetBatteryChargeState(Kernel::HLERequestContext& ctx) {
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.Push(ptm->battery_is_charging); 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) { void Module::Interface::GetPedometerState(Kernel::HLERequestContext& ctx) {
@ -72,7 +72,7 @@ void Module::Interface::GetPedometerState(Kernel::HLERequestContext& ctx) {
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.Push(ptm->pedometer_is_counting); 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) { void Module::Interface::GetStepHistory(Kernel::HLERequestContext& ctx) {

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -207,19 +207,26 @@ static bool AttemptLLE(const ServiceModuleInfo& service_module) {
return false; return false;
} }
std::shared_ptr<Kernel::Process> process; std::shared_ptr<Kernel::Process> 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<int>(load_result));
return false;
}
LOG_DEBUG(Service, "Service module \"{}\" has been successfully loaded.", service_module.name); LOG_DEBUG(Service, "Service module \"{}\" has been successfully loaded.", service_module.name);
return true; return true;
} }
/// Initialize ServiceManager /// Initialize ServiceManager
void Init(Core::System& core) { void Init(Core::System& core, bool allow_lle) {
SM::ServiceManager::InstallInterfaces(core); SM::ServiceManager::InstallInterfaces(core);
core.Kernel().SetAppMainThreadExtendedSleep(false); core.Kernel().SetAppMainThreadExtendedSleep(false);
bool lle_module_present = false; bool lle_module_present = false;
for (const auto& service_module : service_module_map) { 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) { if (!has_lle && service_module.init_function != nullptr) {
service_module.init_function(core); service_module.init_function(core);
} }

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -183,7 +183,7 @@ private:
}; };
/// Initialize ServiceManager /// Initialize ServiceManager
void Init(Core::System& system); void Init(Core::System& system, bool allow_lle);
struct ServiceModuleInfo { struct ServiceModuleInfo {
std::string name; std::string name;

View File

@ -1,4 +1,4 @@
// Copyright 2024 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -13,21 +13,26 @@
#include "common/string_util.h" #include "common/string_util.h"
#include "common/swap.h" #include "common/swap.h"
#include "core/core.h" #include "core/core.h"
#include "core/file_sys/certificate.h"
#include "core/file_sys/ncch_container.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/romfs_reader.h"
#include "core/file_sys/secure_value_backend_artic.h" #include "core/file_sys/secure_value_backend_artic.h"
#include "core/file_sys/title_metadata.h" #include "core/file_sys/title_metadata.h"
#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h" #include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.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.h"
#include "core/hle/service/am/am_app.h" #include "core/hle/service/am/am_app.h"
#include "core/hle/service/am/am_net.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.h"
#include "core/hle/service/cfg/cfg_u.h" #include "core/hle/service/cfg/cfg_u.h"
#include "core/hle/service/fs/archive.h" #include "core/hle/service/fs/archive.h"
#include "core/hle/service/fs/fs_user.h" #include "core/hle/service/fs/fs_user.h"
#include "core/hle/service/hid/hid_user.h" #include "core/hle/service/hid/hid_user.h"
#include "core/hw/unique_data.h"
#include "core/loader/artic.h" #include "core/loader/artic.h"
#include "core/loader/smdh.h" #include "core/loader/smdh.h"
#include "core/memory.h" #include "core/memory.h"
@ -38,6 +43,26 @@ namespace Loader {
using namespace Common::Literals; 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<Network::ArticBase::Client>(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<Core::PerfStats::PerfArticEventBits>(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() { Apploader_Artic::~Apploader_Artic() {
// TODO(PabloMK7) Find memory leak that prevents the romfs readers being destroyed // TODO(PabloMK7) Find memory leak that prevents the romfs readers being destroyed
// when emulation stops. Looks like the mem leak comes from IVFCFile objects // 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<Kernel::Process>& process) { ResultStatus Apploader_Artic::LoadExec(std::shared_ptr<Kernel::Process>& process) {
using Kernel::CodeSet;
if (!is_loaded) if (!is_loaded)
return ResultStatus::ErrorNotLoaded; return ResultStatus::ErrorNotLoaded;
@ -119,34 +143,40 @@ ResultStatus Apploader_Artic::LoadExec(std::shared_ptr<Kernel::Process>& process
u64_le program_id; u64_le program_id;
if (ResultStatus::Success == ReadCode(code) && if (ResultStatus::Success == ReadCode(code) &&
ResultStatus::Success == ReadProgramId(program_id)) { ResultStatus::Success == ReadProgramId(program_id)) {
return LoadExecImpl(process, program_id, program_exheader, code);
}
return ResultStatus::ErrorArtic;
}
std::string process_name = Common::StringFromFixedZeroTerminatedBuffer( ResultStatus Apploader_Artic::LoadExecImpl(std::shared_ptr<Kernel::Process>& process,
(const char*)program_exheader.codeset_info.name, 8); u64_le program_id, const ExHeader_Header& exheader,
std::vector<u8>& code) {
using Kernel::CodeSet;
std::string process_name =
Common::StringFromFixedZeroTerminatedBuffer((const char*)exheader.codeset_info.name, 8);
std::shared_ptr<CodeSet> codeset = system.Kernel().CreateCodeSet(process_name, program_id); std::shared_ptr<CodeSet> codeset = system.Kernel().CreateCodeSet(process_name, program_id);
codeset->CodeSegment().offset = 0; codeset->CodeSegment().offset = 0;
codeset->CodeSegment().addr = program_exheader.codeset_info.text.address; codeset->CodeSegment().addr = exheader.codeset_info.text.address;
codeset->CodeSegment().size = codeset->CodeSegment().size =
program_exheader.codeset_info.text.num_max_pages * Memory::CITRA_PAGE_SIZE; exheader.codeset_info.text.num_max_pages * Memory::CITRA_PAGE_SIZE;
codeset->RODataSegment().offset = codeset->RODataSegment().offset = codeset->CodeSegment().offset + codeset->CodeSegment().size;
codeset->CodeSegment().offset + codeset->CodeSegment().size; codeset->RODataSegment().addr = exheader.codeset_info.ro.address;
codeset->RODataSegment().addr = program_exheader.codeset_info.ro.address;
codeset->RODataSegment().size = codeset->RODataSegment().size =
program_exheader.codeset_info.ro.num_max_pages * Memory::CITRA_PAGE_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 // 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. // to the regular size. Playing it safe for now.
u32 bss_page_size = (program_exheader.codeset_info.bss_size + 0xFFF) & ~0xFFF; u32 bss_page_size = (exheader.codeset_info.bss_size + 0xFFF) & ~0xFFF;
code.resize(code.size() + bss_page_size, 0); code.resize(code.size() + bss_page_size, 0);
codeset->DataSegment().offset = codeset->DataSegment().offset = codeset->RODataSegment().offset + codeset->RODataSegment().size;
codeset->RODataSegment().offset + codeset->RODataSegment().size; codeset->DataSegment().addr = exheader.codeset_info.data.address;
codeset->DataSegment().addr = program_exheader.codeset_info.data.address;
codeset->DataSegment().size = codeset->DataSegment().size =
program_exheader.codeset_info.data.num_max_pages * Memory::CITRA_PAGE_SIZE + exheader.codeset_info.data.num_max_pages * Memory::CITRA_PAGE_SIZE + bss_page_size;
bss_page_size;
// Apply patches now that the entire codeset (including .bss) has been allocated // Apply patches now that the entire codeset (including .bss) has been allocated
// const ResultStatus patch_result = overlay_ncch->ApplyCodePatch(code); // const ResultStatus patch_result = overlay_ncch->ApplyCodePatch(code);
@ -160,14 +190,14 @@ ResultStatus Apploader_Artic::LoadExec(std::shared_ptr<Kernel::Process>& process
// Attach a resource limit to the process based on the resource limit category // Attach a resource limit to the process based on the resource limit category
const auto category = static_cast<Kernel::ResourceLimitCategory>( const auto category = static_cast<Kernel::ResourceLimitCategory>(
program_exheader.arm11_system_local_caps.resource_limit_category); exheader.arm11_system_local_caps.resource_limit_category);
process->resource_limit = system.Kernel().ResourceLimit().GetForCategory(category); process->resource_limit = system.Kernel().ResourceLimit().GetForCategory(category);
// When running N3DS-unaware titles pm will lie about the amount of memory available. // 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 // This means RESLIMIT_COMMIT = APPMEMALLOC doesn't correspond to the actual size of
// APPLICATION. See: // APPLICATION. See:
// https://github.com/LumaTeam/Luma3DS/blob/e2778a45/sysmodules/pm/source/launch.c#L237 // https://github.com/LumaTeam/Luma3DS/blob/e2778a45/sysmodules/pm/source/launch.c#L237
auto& ncch_caps = program_exheader.arm11_system_local_caps; auto& ncch_caps = exheader.arm11_system_local_caps;
const auto o3ds_mode = *LoadKernelMemoryMode().first; const auto o3ds_mode = *LoadKernelMemoryMode().first;
const auto n3ds_mode = static_cast<Kernel::New3dsMemoryMode>(ncch_caps.n3ds_mode); const auto n3ds_mode = static_cast<Kernel::New3dsMemoryMode>(ncch_caps.n3ds_mode);
const bool is_new_3ds = Settings::values.is_new_3ds.GetValue(); const bool is_new_3ds = Settings::values.is_new_3ds.GetValue();
@ -192,22 +222,20 @@ ResultStatus Apploader_Artic::LoadExec(std::shared_ptr<Kernel::Process>& process
} }
// Set the default CPU core for this process // Set the default CPU core for this process
process->ideal_processor = program_exheader.arm11_system_local_caps.ideal_processor; process->ideal_processor = exheader.arm11_system_local_caps.ideal_processor;
// Copy data while converting endianness // Copy data while converting endianness
using KernelCaps = std::array<u32, ExHeader_ARM11_KernelCaps::NUM_DESCRIPTORS>; using KernelCaps = std::array<u32, ExHeader_ARM11_KernelCaps::NUM_DESCRIPTORS>;
KernelCaps kernel_caps; KernelCaps kernel_caps;
std::copy_n(program_exheader.arm11_kernel_caps.descriptors, kernel_caps.size(), std::copy_n(exheader.arm11_kernel_caps.descriptors, kernel_caps.size(), begin(kernel_caps));
begin(kernel_caps));
process->ParseKernelCaps(kernel_caps.data(), kernel_caps.size()); process->ParseKernelCaps(kernel_caps.data(), kernel_caps.size());
s32 priority = program_exheader.arm11_system_local_caps.priority; s32 priority = exheader.arm11_system_local_caps.priority;
u32 stack_size = program_exheader.codeset_info.stack_size; u32 stack_size = exheader.codeset_info.stack_size;
// On real HW this is done with FS:Reg, but we can be lazy // On real HW this is done with FS:Reg, but we can be lazy
auto fs_user = system.ServiceManager().GetService<Service::FS::FS_USER>("fs:USER"); auto fs_user = system.ServiceManager().GetService<Service::FS::FS_USER>("fs:USER");
fs_user->RegisterProgramInfo(process->process_id, process->codeset->program_id, fs_user->RegisterProgramInfo(process->process_id, process->codeset->program_id, "articbase://");
"articbase://");
Service::FS::FS_USER::ProductInfo product_info{}; Service::FS::FS_USER::ProductInfo product_info{};
if (LoadProductInfo(product_info) != ResultStatus::Success) { if (LoadProductInfo(product_info) != ResultStatus::Success) {
@ -217,8 +245,6 @@ ResultStatus Apploader_Artic::LoadExec(std::shared_ptr<Kernel::Process>& process
process->Run(priority, stack_size); process->Run(priority, stack_size);
return ResultStatus::Success; return ResultStatus::Success;
}
return ResultStatus::ErrorArtic;
} }
void Apploader_Artic::ParseRegionLockoutInfo(u64 program_id) { void Apploader_Artic::ParseRegionLockoutInfo(u64 program_id) {
@ -252,8 +278,7 @@ bool Apploader_Artic::LoadExheader() {
if (program_exheader_loaded) if (program_exheader_loaded)
return true; return true;
if (!client_connected) EnsureClientConnected();
client_connected = client->Connect();
if (!client_connected) if (!client_connected)
return false; return false;
@ -287,8 +312,7 @@ ResultStatus Apploader_Artic::LoadProductInfo(Service::FS::FS_USER::ProductInfo&
return ResultStatus::Success; return ResultStatus::Success;
} }
if (!client_connected) EnsureClientConnected();
client_connected = client->Connect();
if (!client_connected) if (!client_connected)
return ResultStatus::ErrorArtic; return ResultStatus::ErrorArtic;
@ -307,6 +331,34 @@ ResultStatus Apploader_Artic::LoadProductInfo(Service::FS::FS_USER::ProductInfo&
return ResultStatus::Success; 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<u32*>(ret_buf->first) == INITIAL_SETUP_APP_VERSION;
}
}
ResultStatus Apploader_Artic::Load(std::shared_ptr<Kernel::Process>& process) { ResultStatus Apploader_Artic::Load(std::shared_ptr<Kernel::Process>& process) {
u64_le ncch_program_id; u64_le ncch_program_id;
@ -331,11 +383,142 @@ ResultStatus Apploader_Artic::Load(std::shared_ptr<Kernel::Process>& process) {
is_loaded = true; // Set state to loaded 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<u8>(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<u8*>(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<u8*>(resp_buff->first) + sizeof(u64),
sizeof(u32));
auto cfg = system.ServiceManager().GetService<Service::CFG::CFG_U>("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<u8, 6> mac;
memcpy(mac.data(), resp_buff->first, mac.size());
auto cfg = system.ServiceManager().GetService<Service::CFG::CFG_U>("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<u8> code(resp_buff->second);
memcpy(code.data(), resp_buff->first, resp_buff->second);
std::shared_ptr<Kernel::Process> 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 result = LoadExec(process); // Load the executable into memory for booting
if (ResultStatus::Success != result) if (ResultStatus::Success != result)
return result; return result;
system.ArchiveManager().RegisterSelfNCCH(*this); system.ArchiveManager().RegisterSelfNCCH(*this);
if (!is_initial_setup) {
system.ArchiveManager().RegisterArticSaveDataSource(client); system.ArchiveManager().RegisterArticSaveDataSource(client);
system.ArchiveManager().RegisterArticExtData(client); system.ArchiveManager().RegisterArticExtData(client);
system.ArchiveManager().RegisterArticNCCH(client); system.ArchiveManager().RegisterArticNCCH(client);
@ -368,6 +551,7 @@ ResultStatus Apploader_Artic::Load(std::shared_ptr<Kernel::Process>& process) {
hid_user->GetModule()->UseArticClient(client); hid_user->GetModule()->UseArticClient(client);
} }
} }
}
ParseRegionLockoutInfo(ncch_program_id); ParseRegionLockoutInfo(ncch_program_id);
@ -382,8 +566,7 @@ ResultStatus Apploader_Artic::IsExecutable(bool& out_executable) {
ResultStatus Apploader_Artic::ReadCode(std::vector<u8>& buffer) { ResultStatus Apploader_Artic::ReadCode(std::vector<u8>& buffer) {
// Code is only read once, there is no need to cache it. // Code is only read once, there is no need to cache it.
if (!client_connected) EnsureClientConnected();
client_connected = client->Connect();
if (!client_connected) if (!client_connected)
return ResultStatus::ErrorArtic; return ResultStatus::ErrorArtic;
@ -423,8 +606,7 @@ ResultStatus Apploader_Artic::ReadIcon(std::vector<u8>& buffer) {
return ResultStatus::Success; return ResultStatus::Success;
} }
if (!client_connected) EnsureClientConnected();
client_connected = client->Connect();
if (!client_connected) if (!client_connected)
return ResultStatus::ErrorArtic; return ResultStatus::ErrorArtic;
@ -450,8 +632,7 @@ ResultStatus Apploader_Artic::ReadBanner(std::vector<u8>& buffer) {
return ResultStatus::Success; return ResultStatus::Success;
} }
if (!client_connected) EnsureClientConnected();
client_connected = client->Connect();
if (!client_connected) if (!client_connected)
return ResultStatus::ErrorArtic; return ResultStatus::ErrorArtic;
@ -477,8 +658,7 @@ ResultStatus Apploader_Artic::ReadLogo(std::vector<u8>& buffer) {
return ResultStatus::Success; return ResultStatus::Success;
} }
if (!client_connected) EnsureClientConnected();
client_connected = client->Connect();
if (!client_connected) if (!client_connected)
return ResultStatus::ErrorArtic; return ResultStatus::ErrorArtic;
@ -504,8 +684,7 @@ ResultStatus Apploader_Artic::ReadProgramId(u64& out_program_id) {
return ResultStatus::Success; return ResultStatus::Success;
} }
if (!client_connected) EnsureClientConnected();
client_connected = client->Connect();
if (!client_connected) if (!client_connected)
return ResultStatus::ErrorArtic; return ResultStatus::ErrorArtic;

View File

@ -1,4 +1,4 @@
// Copyright 2024 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // 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) /// Loads an NCCH file (e.g. from a CCI, or the first NCCH in a CXI)
class Apploader_Artic final : public AppLoader { class Apploader_Artic final : public AppLoader {
public: public:
Apploader_Artic(Core::System& system_, const std::string& server_addr, u16 server_port) enum class ArticInitMode {
: AppLoader(system_, FileUtil::IOFile()) { NONE,
client = std::make_shared<Network::ArticBase::Client>(server_addr, server_port); O3DS,
client->SetCommunicationErrorCallback([&system_](const std::string& msg) { N3DS,
system_.SetStatus(Core::System::ResultStatus::ErrorArticDisconnected, };
msg.empty() ? nullptr : msg.c_str()); Apploader_Artic(Core::System& system_, const std::string& server_addr, u16 server_port,
}); ArticInitMode init_mode);
client->SetArticReportTrafficCallback(
[&system_](u32 bytes) { system_.ReportArticTraffic(bytes); });
client->SetReportArticEventCallback([&system_](u64 event) {
Core::PerfStats::PerfArticEventBits ev =
static_cast<Core::PerfStats::PerfArticEventBits>(event & 0xFFFFFFFF);
bool set = (event > 32) != 0;
system_.ReportPerfArticEvent(ev, set);
});
}
~Apploader_Artic() override; ~Apploader_Artic() override;
@ -97,7 +88,12 @@ public:
return false; return false;
} }
bool DoingInitialSetup() override {
return is_initial_setup;
}
private: private:
static constexpr u32 INITIAL_SETUP_APP_VERSION = 0;
/** /**
* Loads .code section into memory for booting * Loads .code section into memory for booting
* @param process The newly created process * @param process The newly created process
@ -105,6 +101,9 @@ private:
*/ */
ResultStatus LoadExec(std::shared_ptr<Kernel::Process>& process); ResultStatus LoadExec(std::shared_ptr<Kernel::Process>& process);
ResultStatus LoadExecImpl(std::shared_ptr<Kernel::Process>& process, u64_le program_id,
const ExHeader_Header& exheader, std::vector<u8>& code);
/// Reads the region lockout info in the SMDH and send it to CFG service /// 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 /// If an SMDH is not present, the program ID is compared against a list
/// of known system titles to determine the region. /// of known system titles to determine the region.
@ -114,8 +113,12 @@ private:
ResultStatus LoadProductInfo(Service::FS::FS_USER::ProductInfo& out); ResultStatus LoadProductInfo(Service::FS::FS_USER::ProductInfo& out);
void EnsureClientConnected();
ExHeader_Header program_exheader{}; ExHeader_Header program_exheader{};
bool program_exheader_loaded = false; bool program_exheader_loaded = false;
bool is_initial_setup = false;
ArticInitMode artic_init_mode = ArticInitMode::NONE;
std::optional<u64> cached_title_id = std::nullopt; std::optional<u64> cached_title_id = std::nullopt;
std::optional<Service::FS::FS_USER::ProductInfo> cached_product_info = std::nullopt; std::optional<Service::FS::FS_USER::ProductInfo> cached_product_info = std::nullopt;

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -112,6 +112,12 @@ static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileUtil::
return std::make_unique<AppLoader_NCCH>(system, std::move(file), filepath); return std::make_unique<AppLoader_NCCH>(system, std::move(file), filepath);
case FileType::ARTIC: { 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 { auto strToUInt = [](const std::string& str) -> int {
char* pEnd = NULL; char* pEnd = NULL;
unsigned long ul = ::strtoul(str.c_str(), &pEnd, 10); unsigned long ul = ::strtoul(str.c_str(), &pEnd, 10);
@ -121,7 +127,7 @@ static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileUtil::
}; };
u16 port = 5543; u16 port = 5543;
std::string server_addr = filename; std::string server_addr = filename.substr(12);
auto pos = server_addr.find(":"); auto pos = server_addr.find(":");
if (pos != server_addr.npos) { if (pos != server_addr.npos) {
int newVal = strToUInt(server_addr.substr(pos + 1)); int newVal = strToUInt(server_addr.substr(pos + 1));
@ -130,7 +136,7 @@ static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileUtil::
server_addr = server_addr.substr(0, pos); server_addr = server_addr.substr(0, pos);
} }
} }
return std::make_unique<Apploader_Artic>(system, server_addr, port); return std::make_unique<Apploader_Artic>(system, server_addr, port, mode);
} }
default: default:
@ -139,9 +145,10 @@ static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileUtil::
} }
std::unique_ptr<AppLoader> GetLoader(const std::string& filename) { std::unique_ptr<AppLoader> 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, return GetFileLoader(Core::System::GetInstance(), FileUtil::IOFile(), FileType::ARTIC,
filename.substr(12), ""); filename, "");
} }
FileUtil::IOFile file(filename, "rb"); FileUtil::IOFile file(filename, "rb");

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -275,6 +275,10 @@ public:
return true; return true;
} }
virtual bool DoingInitialSetup() {
return false;
}
protected: protected:
Core::System& system; Core::System& system;
FileUtil::IOFile file; FileUtil::IOFile file;

View File

@ -1,4 +1,4 @@
// Copyright 2017 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -159,9 +159,9 @@ void FrameLimiter::DoFrameLimiting(microseconds current_system_time_us) {
} }
auto now = Clock::now(); 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; return;
} }

View File

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -1208,4 +1208,82 @@ std::optional<u32> GetSystemTitleRegion(u64 title_id) {
return std::nullopt; return std::nullopt;
} }
std::pair<bool, bool> AreSystemTitlesInstalled() {
std::array<u32, NUM_SYSTEM_TITLE_REGIONS> o_installed_titles{};
std::array<u32, NUM_SYSTEM_TITLE_REGIONS> o_total_titles{};
std::array<u32, NUM_SYSTEM_TITLE_REGIONS> n_installed_titles{};
std::array<u32, NUM_SYSTEM_TITLE_REGIONS> 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<u64>(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<u64>(categ->title_id_high) << 32 | title->title_id_lows[i];
Service::AM::UninstallProgram(Service::FS::MediaType::NAND, program_id);
}
}
}
}
}
} // namespace Core } // namespace Core

View File

@ -1,10 +1,11 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#pragma once #pragma once
#include <optional> #include <optional>
#include <utility>
#include <vector> #include <vector>
#include "common/common_types.h" #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. /// Returns the region of a system title, if it can be determined.
std::optional<u32> GetSystemTitleRegion(u64 title_id); std::optional<u32> GetSystemTitleRegion(u64 title_id);
/// Determines if all system titles are installed for o3ds and n3ds.
std::pair<bool, bool> AreSystemTitlesInstalled();
void UninstallSystemFiles(SystemTitleSet set);
} // namespace Core } // namespace Core

View File

@ -1,4 +1,4 @@
// Copyright 2024 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -43,8 +43,6 @@
#define closesocket(x) close(x) #define closesocket(x) close(x)
#endif #endif
// #define DISABLE_PING_TIMEOUT
namespace Network::ArticBase { namespace Network::ArticBase {
using namespace std::chrono_literals; using namespace std::chrono_literals;
@ -229,7 +227,7 @@ bool Client::Connect() {
hints.ai_socktype = SOCK_STREAM; hints.ai_socktype = SOCK_STREAM;
hints.ai_family = AF_INET; 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) { if (getaddrinfo(address.data(), NULL, &hints, &addrinfo) != 0) {
LOG_ERROR(Network, "Failed to get server address"); LOG_ERROR(Network, "Failed to get server address");
@ -273,8 +271,8 @@ bool Client::Connect() {
shutdown(main_socket, SHUT_RDWR); shutdown(main_socket, SHUT_RDWR);
closesocket(main_socket); closesocket(main_socket);
LOG_ERROR(Network, "Incompatible server version: {}", version_value); LOG_ERROR(Network, "Incompatible server version: {}", version_value);
SignalCommunicationError("\nIncompatible Artic Base Server version.\nCheck for updates " SignalCommunicationError("\nIncompatible Artic Server version.\nCheck for updates "
"to Artic Base Server or Azahar."); "to the Artic Server or Azahar.");
return false; return false;
} }
} else { } else {
@ -485,15 +483,15 @@ void Client::PingFunction() {
while (ping_run) { while (ping_run) {
std::chrono::time_point<std::chrono::steady_clock> last = last_sent_request; std::chrono::time_point<std::chrono::steady_clock> last = last_sent_request;
if (std::chrono::steady_clock::now() - last > std::chrono::seconds(7)) { if (std::chrono::steady_clock::now() - last > std::chrono::seconds(7)) {
#ifdef DISABLE_PING_TIMEOUT if (ping_enabled) {
client->last_sent_request = std::chrono::steady_clock::now();
#else
auto ping_reply = SendSimpleRequest("PING"); auto ping_reply = SendSimpleRequest("PING");
if (!ping_reply.has_value()) { if (!ping_reply.has_value()) {
SignalCommunicationError(); SignalCommunicationError();
break; break;
} }
#endif // DISABLE_PING_TIMEOUT } else {
last_sent_request = std::chrono::steady_clock::now();
}
} }
std::unique_lock lk(ping_cv_mutex); std::unique_lock lk(ping_cv_mutex);

View File

@ -1,4 +1,4 @@
// Copyright 2024 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -161,6 +161,10 @@ public:
return last_sockaddr_in; return last_sockaddr_in;
} }
void SetPingEnabled(bool enable) {
ping_enabled = enable;
}
private: private:
static constexpr const int SERVER_VERSION = 2; static constexpr const int SERVER_VERSION = 2;
@ -190,6 +194,7 @@ private:
std::condition_variable ping_cv; std::condition_variable ping_cv;
std::mutex ping_cv_mutex; std::mutex ping_cv_mutex;
std::atomic<bool> ping_run = true; std::atomic<bool> ping_run = true;
bool ping_enabled = true;
void StopImpl(bool from_error); void StopImpl(bool from_error);

View File

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // 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 // 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. // 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; present_mode = has_mailbox ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eImmediate;
if (!has_mailbox) { if (!has_mailbox) {
LOG_WARNING( LOG_WARNING(