mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2025-03-13 09:12:27 +01:00
Add 'Set Up System Files' option
This commit is contained in:
parent
6262ddafa6
commit
16519e055e
@ -489,7 +489,7 @@ void GMainWindow::InitializeWidgets() {
|
||||
|
||||
artic_traffic_label = new QLabel();
|
||||
artic_traffic_label->setToolTip(
|
||||
tr("Current Artic Base traffic speed. Higher values indicate bigger transfer loads."));
|
||||
tr("Current Artic traffic speed. Higher values indicate bigger transfer loads."));
|
||||
|
||||
emu_speed_label = new QLabel();
|
||||
emu_speed_label->setToolTip(tr("Current emulation speed. Values higher or lower than 100% "
|
||||
@ -982,6 +982,7 @@ void GMainWindow::ConnectMenuEvents() {
|
||||
connect_menu(ui->action_Load_File, &GMainWindow::OnMenuLoadFile);
|
||||
connect_menu(ui->action_Install_CIA, &GMainWindow::OnMenuInstallCIA);
|
||||
connect_menu(ui->action_Connect_Artic, &GMainWindow::OnMenuConnectArticBase);
|
||||
connect_menu(ui->action_Setup_System_Files, &GMainWindow::OnMenuSetUpSystemFiles);
|
||||
for (u32 region = 0; region < Core::NUM_SYSTEM_TITLE_REGIONS; region++) {
|
||||
connect_menu(ui->menu_Boot_Home_Menu->actions().at(region),
|
||||
[this, region] { OnMenuBootHomeMenu(region); });
|
||||
@ -1367,9 +1368,9 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||
|
||||
case Core::System::ResultStatus::ErrorArticDisconnected:
|
||||
QMessageBox::critical(
|
||||
this, tr("Artic Base Server"),
|
||||
this, tr("Artic Server"),
|
||||
tr(fmt::format(
|
||||
"An error has occurred whilst communicating with the Artic Base Server.\n{}",
|
||||
"An error has occurred whilst communicating with the Artic Server.\n{}",
|
||||
system.GetStatusDetails())
|
||||
.c_str()));
|
||||
break;
|
||||
@ -1401,7 +1402,9 @@ void GMainWindow::BootGame(const QString& filename) {
|
||||
ShutdownGame();
|
||||
}
|
||||
|
||||
const bool is_artic = filename.startsWith(QString::fromStdString("articbase://"));
|
||||
const bool is_artic = filename.startsWith(QString::fromStdString("articbase:/")) ||
|
||||
filename.startsWith(QString::fromStdString("articinio:/")) ||
|
||||
filename.startsWith(QString::fromStdString("articinin:/"));
|
||||
|
||||
if (!is_artic && filename.endsWith(QStringLiteral(".cia"))) {
|
||||
const auto answer = QMessageBox::question(
|
||||
@ -1444,7 +1447,7 @@ void GMainWindow::BootGame(const QString& filename) {
|
||||
QtConfig per_game_config(config_file_name, QtConfig::ConfigType::PerGameConfig);
|
||||
}
|
||||
|
||||
// Artic Base Server cannot accept a client multiple times, so multiple loaders are not
|
||||
// Artic Server cannot accept a client multiple times, so multiple loaders are not
|
||||
// possible. Instead register the app loader early and do not create it again on system load.
|
||||
if (!loader->SupportsMultipleInstancesForSameFile()) {
|
||||
system.RegisterAppLoaderEarly(loader);
|
||||
@ -2192,6 +2195,91 @@ void GMainWindow::OnMenuLoadFile() {
|
||||
BootGame(filename);
|
||||
}
|
||||
|
||||
void GMainWindow::OnMenuSetUpSystemFiles() {
|
||||
QDialog dialog(this);
|
||||
dialog.setWindowTitle(tr("Set Up System Files"));
|
||||
|
||||
QVBoxLayout layout(&dialog);
|
||||
|
||||
QLabel label_description(
|
||||
tr("<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);
|
||||
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() {
|
||||
QStringList filepaths = QFileDialog::getOpenFileNames(
|
||||
this, tr("Load Files"), UISettings::values.roms_path,
|
||||
@ -3131,16 +3219,16 @@ void GMainWindow::UpdateStatusBar() {
|
||||
QStringLiteral("QLabel { color: %0; }").arg(label_color[style_index]);
|
||||
|
||||
artic_traffic_label->setText(
|
||||
tr("Artic Base Traffic: %1 %2%3").arg(value, 0, 'f', 0).arg(unit).arg(event));
|
||||
tr("Artic Traffic: %1 %2%3").arg(value, 0, 'f', 0).arg(unit).arg(event));
|
||||
artic_traffic_label->setStyleSheet(style_sheet);
|
||||
}
|
||||
|
||||
if (Settings::values.frame_limit.GetValue() == 0) {
|
||||
if (Settings::GetFrameLimit() == 0) {
|
||||
emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0));
|
||||
} else {
|
||||
emu_speed_label->setText(tr("Speed: %1% / %2%")
|
||||
.arg(results.emulation_speed * 100.0, 0, 'f', 0)
|
||||
.arg(Settings::values.frame_limit.GetValue()));
|
||||
.arg(Settings::GetFrameLimit()));
|
||||
}
|
||||
game_fps_label->setText(tr("App: %1 FPS").arg(results.game_fps, 0, 'f', 0));
|
||||
emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2));
|
||||
@ -3321,7 +3409,7 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
|
||||
message = QString::fromStdString(details);
|
||||
error_severity_icon = QMessageBox::Icon::Warning;
|
||||
} else if (result == Core::System::ResultStatus::ErrorArticDisconnected) {
|
||||
title = tr("Artic Base Server");
|
||||
title = tr("Artic Server");
|
||||
message =
|
||||
tr(fmt::format("A communication error has occurred. The game will quit.\n{}", details)
|
||||
.c_str());
|
||||
|
@ -244,6 +244,7 @@ private slots:
|
||||
void OnGameListOpenPerGameProperties(const QString& file);
|
||||
void OnConfigurePerGame();
|
||||
void OnMenuLoadFile();
|
||||
void OnMenuSetUpSystemFiles();
|
||||
void OnMenuInstallCIA();
|
||||
void OnMenuConnectArticBase();
|
||||
void OnMenuBootHomeMenu(u32 region);
|
||||
|
@ -237,8 +237,7 @@ ConfigureSystem::ConfigureSystem(Core::System& system_, QWidget* parent)
|
||||
&ConfigureSystem::UpdateInitTicks);
|
||||
connect(ui->button_regenerate_console_id, &QPushButton::clicked, this,
|
||||
&ConfigureSystem::RefreshConsoleID);
|
||||
connect(ui->button_start_download, &QPushButton::clicked, this,
|
||||
&ConfigureSystem::DownloadFromNUS);
|
||||
connect(ui->button_regenerate_mac, &QPushButton::clicked, this, &ConfigureSystem::RefreshMAC);
|
||||
|
||||
connect(ui->button_secure_info, &QPushButton::clicked, this, [this] {
|
||||
ui->button_secure_info->setEnabled(false);
|
||||
@ -281,34 +280,6 @@ ConfigureSystem::ConfigureSystem(Core::System& system_, QWidget* parent)
|
||||
}
|
||||
|
||||
SetupPerGameUI();
|
||||
|
||||
ui->combo_download_set->setCurrentIndex(0); // set to Minimal
|
||||
ui->combo_download_region->setCurrentIndex(0); // set to the base region
|
||||
|
||||
HW::AES::InitKeys(true);
|
||||
bool keys_available = HW::AES::IsKeyXAvailable(HW::AES::KeySlotID::NCCHSecure1) &&
|
||||
HW::AES::IsKeyXAvailable(HW::AES::KeySlotID::NCCHSecure2);
|
||||
for (u8 i = 0; i < HW::AES::MaxCommonKeySlot && keys_available; i++) {
|
||||
HW::AES::SelectCommonKeyIndex(i);
|
||||
if (!HW::AES::IsNormalKeyAvailable(HW::AES::KeySlotID::TicketCommonKey)) {
|
||||
keys_available = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (keys_available) {
|
||||
ui->button_start_download->setEnabled(true);
|
||||
ui->combo_download_set->setEnabled(true);
|
||||
ui->combo_download_region->setEnabled(true);
|
||||
ui->label_nus_download->setText(tr("Download System Files from Nintendo servers"));
|
||||
} else {
|
||||
ui->button_start_download->setEnabled(false);
|
||||
ui->combo_download_set->setEnabled(false);
|
||||
ui->combo_download_region->setEnabled(false);
|
||||
ui->label_nus_download->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
||||
ui->label_nus_download->setOpenExternalLinks(true);
|
||||
ui->label_nus_download->setText(tr("Azahar is missing keys to download system files."));
|
||||
}
|
||||
|
||||
ConfigureTime();
|
||||
}
|
||||
|
||||
@ -385,14 +356,13 @@ void ConfigureSystem::ReadSystemSettings() {
|
||||
u64 console_id = cfg->GetConsoleUniqueId();
|
||||
ui->label_console_id->setText(
|
||||
tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper()));
|
||||
mac_address = cfg->GetMacAddress();
|
||||
ui->label_mac->setText(tr("MAC: %1").arg(QString::fromStdString(mac_address)));
|
||||
|
||||
// set play coin
|
||||
play_coin = Service::PTM::Module::GetPlayCoins();
|
||||
ui->spinBox_play_coins->setValue(play_coin);
|
||||
|
||||
// set firmware download region
|
||||
ui->combo_download_region->setCurrentIndex(static_cast<int>(cfg->GetRegionValue()));
|
||||
|
||||
// Refresh secure data status
|
||||
RefreshSecureDataStatus();
|
||||
}
|
||||
@ -484,6 +454,9 @@ void ConfigureSystem::ApplyConfiguration() {
|
||||
|
||||
Settings::values.plugin_loader_enabled.SetValue(ui->plugin_loader->isChecked());
|
||||
Settings::values.allow_plugin_loader.SetValue(ui->allow_plugin_loader->isChecked());
|
||||
|
||||
cfg->GetMacAddress() = mac_address;
|
||||
cfg->SaveMacAddress();
|
||||
}
|
||||
}
|
||||
|
||||
@ -548,10 +521,11 @@ void ConfigureSystem::UpdateInitTicks(int init_ticks_type) {
|
||||
|
||||
void ConfigureSystem::RefreshConsoleID() {
|
||||
QMessageBox::StandardButton reply;
|
||||
QString warning_text = tr("This will replace your current virtual 3DS with a new one. "
|
||||
"Your current virtual 3DS will not be recoverable. "
|
||||
"This might have unexpected effects in applications. This might fail "
|
||||
"if you use an outdated config save. Continue?");
|
||||
QString warning_text =
|
||||
tr("This will replace your current virtual 3DS console ID with a new one. "
|
||||
"Your current virtual 3DS console ID will not be recoverable. "
|
||||
"This might have unexpected effects in applications. This might fail "
|
||||
"if you use an outdated config save. Continue?");
|
||||
reply = QMessageBox::critical(this, tr("Warning"), warning_text,
|
||||
QMessageBox::No | QMessageBox::Yes);
|
||||
if (reply == QMessageBox::No) {
|
||||
@ -565,6 +539,21 @@ void ConfigureSystem::RefreshConsoleID() {
|
||||
tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper()));
|
||||
}
|
||||
|
||||
void ConfigureSystem::RefreshMAC() {
|
||||
QMessageBox::StandardButton reply;
|
||||
QString warning_text = tr("This will replace your current MAC with a new one. "
|
||||
"It is not recommended to do this if you got the MAC address from "
|
||||
"your real console using the setup tool. Continue?");
|
||||
reply =
|
||||
QMessageBox::warning(this, tr("Warning"), warning_text, QMessageBox::No | QMessageBox::Yes);
|
||||
if (reply == QMessageBox::No) {
|
||||
return;
|
||||
}
|
||||
|
||||
mac_address = Service::CFG::GenerateRandomMAC();
|
||||
ui->label_mac->setText(tr("MAC: %1").arg(QString::fromStdString(mac_address)));
|
||||
}
|
||||
|
||||
void ConfigureSystem::InstallSecureData(const std::string& from_path, const std::string& to_path) {
|
||||
std::string from =
|
||||
FileUtil::SanitizePath(from_path, FileUtil::DirectorySeparator::PlatformDefault);
|
||||
@ -655,20 +644,9 @@ void ConfigureSystem::SetupPerGameUI() {
|
||||
ui->label_plugin_loader->setVisible(false);
|
||||
ui->plugin_loader->setVisible(false);
|
||||
ui->allow_plugin_loader->setVisible(false);
|
||||
// Disable the system firmware downloader.
|
||||
ui->label_nus_download->setVisible(false);
|
||||
ui->body_nus_download->setVisible(false);
|
||||
|
||||
ConfigurationShared::SetColoredTristate(ui->toggle_new_3ds, Settings::values.is_new_3ds,
|
||||
is_new_3ds);
|
||||
ConfigurationShared::SetColoredTristate(ui->toggle_lle_applets, Settings::values.lle_applets,
|
||||
lle_applets);
|
||||
}
|
||||
|
||||
void ConfigureSystem::DownloadFromNUS() {
|
||||
ui->button_start_download->setEnabled(false);
|
||||
|
||||
QMessageBox::critical(this, tr("Azahar"), tr("Downloading from NUS has been deprecated."));
|
||||
|
||||
ui->button_start_download->setEnabled(true);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -51,14 +51,13 @@ private:
|
||||
void UpdateInitTime(int init_clock);
|
||||
void UpdateInitTicks(int init_ticks_type);
|
||||
void RefreshConsoleID();
|
||||
void RefreshMAC();
|
||||
|
||||
void InstallSecureData(const std::string& from_path, const std::string& to_path);
|
||||
void RefreshSecureDataStatus();
|
||||
|
||||
void SetupPerGameUI();
|
||||
|
||||
void DownloadFromNUS();
|
||||
|
||||
private:
|
||||
std::unique_ptr<Ui::ConfigureSystem> ui;
|
||||
Core::System& system;
|
||||
@ -75,4 +74,5 @@ private:
|
||||
u8 country_code;
|
||||
u16 play_coin;
|
||||
bool system_setup;
|
||||
std::string mac_address;
|
||||
};
|
||||
|
@ -455,113 +455,49 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="16" column="0">
|
||||
<widget class="QLabel" name="label_mac">
|
||||
<property name="text">
|
||||
<string>MAC:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="16" column="1">
|
||||
<widget class="QPushButton" name="button_regenerate_mac">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::RightToLeft</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Regenerate</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<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="16" column="1">
|
||||
<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="17" column="1">
|
||||
<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>
|
||||
</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">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::RightToLeft</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Download</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -762,6 +698,7 @@
|
||||
<tabstop>spinBox_play_coins</tabstop>
|
||||
<tabstop>spinBox_steps_per_hour</tabstop>
|
||||
<tabstop>button_regenerate_console_id</tabstop>
|
||||
<tabstop>button_regenerate_mac</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
@ -79,6 +79,8 @@
|
||||
<addaction name="action_Load_File"/>
|
||||
<addaction name="action_Install_CIA"/>
|
||||
<addaction name="action_Connect_Artic"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_Setup_System_Files"/>
|
||||
<addaction name="menu_Boot_Home_Menu"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="menu_recent_files"/>
|
||||
@ -238,6 +240,11 @@
|
||||
<string>Connect to Artic Base...</string>
|
||||
</property>
|
||||
</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">
|
||||
<property name="text">
|
||||
<string>JPN</string>
|
||||
|
@ -1,9 +1,10 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <cryptopp/osrng.h>
|
||||
#include "audio_core/dsp_interface.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/settings.h"
|
||||
@ -74,6 +75,8 @@ std::string_view GetTextureSamplingName(TextureSampling sampling) {
|
||||
|
||||
Values values = {};
|
||||
static bool configuring_global = true;
|
||||
bool is_temporary_frame_limit;
|
||||
double temporary_frame_limit;
|
||||
|
||||
void LogSettings() {
|
||||
const auto log_setting = [](std::string_view name, const auto& value) {
|
||||
@ -247,5 +250,4 @@ void DeleteProfile(int index) {
|
||||
void RenameCurrentProfile(std::string new_name) {
|
||||
Settings::values.current_input_profile.name = std::move(new_name);
|
||||
}
|
||||
|
||||
} // namespace Settings
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -618,4 +618,10 @@ void CreateProfile(std::string name);
|
||||
void DeleteProfile(int index);
|
||||
void RenameCurrentProfile(std::string new_name);
|
||||
|
||||
extern bool is_temporary_frame_limit;
|
||||
extern double temporary_frame_limit;
|
||||
static inline double GetFrameLimit() {
|
||||
return is_temporary_frame_limit ? temporary_frame_limit : values.frame_limit.GetValue();
|
||||
}
|
||||
|
||||
} // namespace Settings
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -110,9 +110,14 @@ System::ResultStatus System::RunLoop(bool tight_loop) {
|
||||
}
|
||||
}
|
||||
switch (signal) {
|
||||
case Signal::Reset:
|
||||
case Signal::Reset: {
|
||||
if (app_loader && app_loader->DoingInitialSetup()) {
|
||||
// Treat reset as shutdown if we are doing the initial setup
|
||||
return ResultStatus::ShutdownRequested;
|
||||
}
|
||||
Reset();
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
case Signal::Shutdown:
|
||||
return ResultStatus::ShutdownRequested;
|
||||
case Signal::Load: {
|
||||
@ -471,7 +476,7 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window,
|
||||
archive_manager = std::make_unique<Service::FS::ArchiveManager>(*this);
|
||||
|
||||
HW::AES::InitKeys();
|
||||
Service::Init(*this);
|
||||
Service::Init(*this, !app_loader->DoingInitialSetup());
|
||||
GDBStub::DeferStart();
|
||||
|
||||
if (!registered_image_interface) {
|
||||
@ -708,6 +713,10 @@ void System::RegisterAppLoaderEarly(std::unique_ptr<Loader::AppLoader>& loader)
|
||||
early_app_loader = std::move(loader);
|
||||
}
|
||||
|
||||
bool System::IsInitialSetup() {
|
||||
return app_loader && app_loader->DoingInitialSetup();
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
void System::serialize(Archive& ar, const unsigned int file_version) {
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -363,6 +363,8 @@ public:
|
||||
|
||||
void RegisterAppLoaderEarly(std::unique_ptr<Loader::AppLoader>& loader);
|
||||
|
||||
bool IsInitialSetup();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Initialize the emulated system.
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -47,9 +47,6 @@ union BatteryState {
|
||||
|
||||
using MacAddress = std::array<u8, 6>;
|
||||
|
||||
// Default MAC address in the Nintendo 3DS range
|
||||
constexpr MacAddress DefaultMac = {0x40, 0xF4, 0x07, 0x00, 0x00, 0x00};
|
||||
|
||||
enum class WifiLinkLevel : u8 {
|
||||
Off = 0,
|
||||
Poor = 1,
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -274,8 +274,8 @@ enum class SystemInfoMemUsageRegion {
|
||||
* Accepted by svcGetSystemInfo param with CITRA_INFORMATION type. Selects which information
|
||||
* to fetch from Citra. Some string params don't fit in 7 bytes, so they are split.
|
||||
*/
|
||||
enum class SystemInfoCitraInformation {
|
||||
IS_CITRA = 0, // Always set the output to 1, signaling the app is running on Citra.
|
||||
enum class SystemInfoEmulatorInformation {
|
||||
EMULATOR_ID = 0, // Always set the output to 1, signaling the app is running on Citra.
|
||||
HOST_TICK = 1, // Tick reference from the host in ns, unaffected by lag or cpu speed.
|
||||
EMULATION_SPEED = 2, // Gets the emulation speed set by the user or by KernelSetState.
|
||||
BUILD_NAME = 10, // (ie: Nightly, Canary).
|
||||
@ -291,6 +291,15 @@ enum class SystemInfoCitraInformation {
|
||||
BUILD_GIT_DESCRIPTION_PART2 = 41, // Git description (commit) last 7 characters.
|
||||
};
|
||||
|
||||
/**
|
||||
* Used by the IS_EMULATOR information
|
||||
*/
|
||||
enum class EmulatorIDs {
|
||||
NONE = 0,
|
||||
CITRA_EMULATOR = 1,
|
||||
AZAHAR_EMULATOR = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* Current officially supported platforms.
|
||||
*/
|
||||
@ -1443,7 +1452,11 @@ Result SVC::KernelSetState(u32 kernel_state, u32 varg1, u32 varg2) {
|
||||
// Citra specific states.
|
||||
case KernelState::KERNEL_STATE_CITRA_EMULATION_SPEED: {
|
||||
u16 new_value = static_cast<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;
|
||||
default:
|
||||
LOG_ERROR(Kernel_SVC, "Unknown KernelSetState state={} varg1={} varg2={}", kernel_state,
|
||||
@ -1811,25 +1824,25 @@ Result SVC::GetSystemInfo(s64* out, u32 type, s32 param) {
|
||||
*out = 0;
|
||||
return (system.GetNumCores() == 4) ? ResultSuccess : ResultInvalidEnumValue;
|
||||
case SystemInfoType::CITRA_INFORMATION:
|
||||
switch ((SystemInfoCitraInformation)param) {
|
||||
case SystemInfoCitraInformation::IS_CITRA:
|
||||
*out = 1;
|
||||
switch ((SystemInfoEmulatorInformation)param) {
|
||||
case SystemInfoEmulatorInformation::EMULATOR_ID:
|
||||
*out = static_cast<s64>(EmulatorIDs::AZAHAR_EMULATOR);
|
||||
break;
|
||||
case SystemInfoCitraInformation::HOST_TICK:
|
||||
case SystemInfoEmulatorInformation::HOST_TICK:
|
||||
*out = static_cast<s64>(std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch())
|
||||
.count());
|
||||
break;
|
||||
case SystemInfoCitraInformation::EMULATION_SPEED:
|
||||
case SystemInfoEmulatorInformation::EMULATION_SPEED:
|
||||
*out = static_cast<s64>(Settings::values.frame_limit.GetValue());
|
||||
break;
|
||||
case SystemInfoCitraInformation::BUILD_NAME:
|
||||
case SystemInfoEmulatorInformation::BUILD_NAME:
|
||||
CopyStringPart(reinterpret_cast<char*>(out), Common::g_build_name, 0, sizeof(s64));
|
||||
break;
|
||||
case SystemInfoCitraInformation::BUILD_VERSION:
|
||||
case SystemInfoEmulatorInformation::BUILD_VERSION:
|
||||
CopyStringPart(reinterpret_cast<char*>(out), Common::g_build_version, 0, sizeof(s64));
|
||||
break;
|
||||
case SystemInfoCitraInformation::BUILD_PLATFORM: {
|
||||
case SystemInfoEmulatorInformation::BUILD_PLATFORM: {
|
||||
#if defined(_WIN32)
|
||||
*out = static_cast<s64>(SystemInfoCitraPlatform::PLATFORM_WINDOWS);
|
||||
#elif defined(ANDROID)
|
||||
@ -1843,35 +1856,35 @@ Result SVC::GetSystemInfo(s64* out, u32 type, s32 param) {
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case SystemInfoCitraInformation::BUILD_DATE_PART1:
|
||||
case SystemInfoEmulatorInformation::BUILD_DATE_PART1:
|
||||
CopyStringPart(reinterpret_cast<char*>(out), Common::g_build_date,
|
||||
(sizeof(s64) - 1) * 0, sizeof(s64));
|
||||
break;
|
||||
case SystemInfoCitraInformation::BUILD_DATE_PART2:
|
||||
case SystemInfoEmulatorInformation::BUILD_DATE_PART2:
|
||||
CopyStringPart(reinterpret_cast<char*>(out), Common::g_build_date,
|
||||
(sizeof(s64) - 1) * 1, sizeof(s64));
|
||||
break;
|
||||
case SystemInfoCitraInformation::BUILD_DATE_PART3:
|
||||
case SystemInfoEmulatorInformation::BUILD_DATE_PART3:
|
||||
CopyStringPart(reinterpret_cast<char*>(out), Common::g_build_date,
|
||||
(sizeof(s64) - 1) * 2, sizeof(s64));
|
||||
break;
|
||||
case SystemInfoCitraInformation::BUILD_DATE_PART4:
|
||||
case SystemInfoEmulatorInformation::BUILD_DATE_PART4:
|
||||
CopyStringPart(reinterpret_cast<char*>(out), Common::g_build_date,
|
||||
(sizeof(s64) - 1) * 3, sizeof(s64));
|
||||
break;
|
||||
case SystemInfoCitraInformation::BUILD_GIT_BRANCH_PART1:
|
||||
case SystemInfoEmulatorInformation::BUILD_GIT_BRANCH_PART1:
|
||||
CopyStringPart(reinterpret_cast<char*>(out), Common::g_scm_branch,
|
||||
(sizeof(s64) - 1) * 0, sizeof(s64));
|
||||
break;
|
||||
case SystemInfoCitraInformation::BUILD_GIT_BRANCH_PART2:
|
||||
case SystemInfoEmulatorInformation::BUILD_GIT_BRANCH_PART2:
|
||||
CopyStringPart(reinterpret_cast<char*>(out), Common::g_scm_branch,
|
||||
(sizeof(s64) - 1) * 1, sizeof(s64));
|
||||
break;
|
||||
case SystemInfoCitraInformation::BUILD_GIT_DESCRIPTION_PART1:
|
||||
case SystemInfoEmulatorInformation::BUILD_GIT_DESCRIPTION_PART1:
|
||||
CopyStringPart(reinterpret_cast<char*>(out), Common::g_scm_desc, (sizeof(s64) - 1) * 0,
|
||||
sizeof(s64));
|
||||
break;
|
||||
case SystemInfoCitraInformation::BUILD_GIT_DESCRIPTION_PART2:
|
||||
case SystemInfoEmulatorInformation::BUILD_GIT_DESCRIPTION_PART2:
|
||||
CopyStringPart(reinterpret_cast<char*>(out), Common::g_scm_desc, (sizeof(s64) - 1) * 1,
|
||||
sizeof(s64));
|
||||
break;
|
||||
|
@ -1663,9 +1663,12 @@ Result GetTitleInfoFromList(std::span<const u64> title_id_list, Service::FS::Med
|
||||
title_info.version = tmd.GetTitleVersion();
|
||||
title_info.type = tmd.GetTitleType();
|
||||
} else {
|
||||
LOG_DEBUG(Service_AM, "not found title_id={:016X}", title_id_list[i]);
|
||||
return Result(ErrorDescription::NotFound, ErrorModule::AM, ErrorSummary::InvalidState,
|
||||
ErrorLevel::Permanent);
|
||||
}
|
||||
LOG_DEBUG(Service_AM, "found title_id={:016X} version={:04X}", title_id_list[i],
|
||||
title_info.version);
|
||||
title_info_out.Write(&title_info, write_offset, sizeof(TitleInfo));
|
||||
write_offset += sizeof(TitleInfo);
|
||||
}
|
||||
@ -1679,7 +1682,7 @@ void Module::Interface::GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool
|
||||
auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>());
|
||||
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()) {
|
||||
struct AsyncData {
|
||||
@ -1688,7 +1691,7 @@ void Module::Interface::GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool
|
||||
std::vector<u64> title_id_list;
|
||||
|
||||
Result res{0};
|
||||
std::vector<u8> out;
|
||||
std::vector<TitleInfo> out;
|
||||
Kernel::MappedBuffer* title_id_list_buffer;
|
||||
Kernel::MappedBuffer* title_info_out;
|
||||
};
|
||||
@ -1730,7 +1733,7 @@ void Module::Interface::GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool
|
||||
return 0;
|
||||
}
|
||||
|
||||
async_data->out.resize(title_infos->second);
|
||||
async_data->out.resize(title_infos->second / sizeof(TitleInfo));
|
||||
memcpy(async_data->out.data(), title_infos->first, title_infos->second);
|
||||
return 0;
|
||||
},
|
||||
@ -1744,7 +1747,7 @@ void Module::Interface::GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool
|
||||
}
|
||||
} else {
|
||||
async_data->title_info_out->Write(async_data->out.data(), 0,
|
||||
async_data->out.size());
|
||||
async_data->out.size() / sizeof(TitleInfo));
|
||||
|
||||
IPC::RequestBuilder rb(ctx, 1, async_data->ignore_platform ? 0 : 4);
|
||||
rb.Push(async_data->res);
|
||||
@ -1763,16 +1766,20 @@ void Module::Interface::GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool
|
||||
std::vector<u64> title_id_list(title_count);
|
||||
title_id_list_buffer.Read(title_id_list.data(), 0, title_count * sizeof(u64));
|
||||
|
||||
// eShop checks if the current importing title already exists during installation.
|
||||
// nim checks if the current importing title already exists during installation.
|
||||
// Normally, since the program wouldn't be commited, getting the title info returns not
|
||||
// found. However, since GetTitleInfoFromList does not care if the program was commited and
|
||||
// only checks for the tmd, it will detect the title and return information while it
|
||||
// shouldn't. To prevent this, we check if the title ID corresponds to the currently
|
||||
// importing title and return not found if so.
|
||||
// shouldn't. To prevent this, we check if the importing context is present and not
|
||||
// committed If that's the case return not found
|
||||
Result result = ResultSuccess;
|
||||
if (am->importing_title) {
|
||||
for (auto tid : title_id_list) {
|
||||
if (tid == am->importing_title->title_id) {
|
||||
for (auto tid : title_id_list) {
|
||||
for (auto& import_ctx : am->import_title_contexts) {
|
||||
if (import_ctx.first == tid &&
|
||||
(import_ctx.second.state == ImportTitleContextState::WAITING_FOR_IMPORT ||
|
||||
import_ctx.second.state == ImportTitleContextState::WAITING_FOR_COMMIT ||
|
||||
import_ctx.second.state == ImportTitleContextState::RESUMABLE)) {
|
||||
LOG_DEBUG(Service_AM, "title pending commit title_id={:016X}", tid);
|
||||
result = Result(ErrorDescription::NotFound, ErrorModule::AM,
|
||||
ErrorSummary::InvalidState, ErrorLevel::Permanent);
|
||||
}
|
||||
@ -2545,9 +2552,25 @@ void Module::Interface::NeedsCleanup(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
LOG_DEBUG(Service_AM, "(STUBBED) media_type=0x{:02x}", media_type);
|
||||
|
||||
bool needs_cleanup = false;
|
||||
for (auto& import_ctx : am->import_title_contexts) {
|
||||
if (import_ctx.second.state == ImportTitleContextState::NEEDS_CLEANUP) {
|
||||
needs_cleanup = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!needs_cleanup) {
|
||||
for (auto& import_ctx : am->import_content_contexts) {
|
||||
if (import_ctx.second.state == ImportTitleContextState::NEEDS_CLEANUP) {
|
||||
needs_cleanup = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<bool>(false);
|
||||
rb.Push<bool>(needs_cleanup);
|
||||
}
|
||||
|
||||
void Module::Interface::DoCleanup(Kernel::HLERequestContext& ctx) {
|
||||
@ -2556,6 +2579,22 @@ void Module::Interface::DoCleanup(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
LOG_DEBUG(Service_AM, "(STUBBED) called, media_type={:#02x}", media_type);
|
||||
|
||||
for (auto it = am->import_content_contexts.begin(); it != am->import_content_contexts.end();) {
|
||||
if (it->second.state == ImportTitleContextState::NEEDS_CLEANUP) {
|
||||
it = am->import_content_contexts.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = am->import_title_contexts.begin(); it != am->import_title_contexts.end();) {
|
||||
if (it->second.state == ImportTitleContextState::NEEDS_CLEANUP) {
|
||||
it = am->import_title_contexts.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
@ -2746,21 +2785,7 @@ void Module::Interface::EndImportProgramWithoutCommit(Kernel::HLERequestContext&
|
||||
}
|
||||
|
||||
void Module::Interface::CommitImportPrograms(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
[[maybe_unused]] const auto media_type = static_cast<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)");
|
||||
CommitImportTitlesImpl(ctx, false, false);
|
||||
}
|
||||
|
||||
/// Wraps all File operations to allow adding an offset to them.
|
||||
@ -3074,6 +3099,46 @@ void Module::Interface::GetRequiredSizeFromCia(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push(container.GetTitleMetadata().GetContentSizeByIndex(FileSys::TMDContentIndex::Main));
|
||||
}
|
||||
|
||||
void Module::Interface::CommitImportProgramsAndUpdateFirmwareAuto(Kernel::HLERequestContext& ctx) {
|
||||
CommitImportTitlesImpl(ctx, true, false);
|
||||
}
|
||||
|
||||
void Module::Interface::CommitImportTitlesImpl(Kernel::HLERequestContext& ctx,
|
||||
bool is_update_firm_auto, bool is_titles) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
const auto media_type = static_cast<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) {
|
||||
// Use the content folder so we don't delete the user's save data.
|
||||
const auto path = GetTitlePath(media_type, title_id) + "content/";
|
||||
@ -3351,6 +3416,10 @@ void Module::Interface::EndImportTitle(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void Module::Interface::CommitImportTitles(Kernel::HLERequestContext& ctx) {
|
||||
CommitImportTitlesImpl(ctx, false, true);
|
||||
}
|
||||
|
||||
void Module::Interface::BeginImportTmd(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
|
||||
@ -3720,6 +3789,10 @@ void Module::Interface::GetDeviceCert(Kernel::HLERequestContext& ctx) {
|
||||
rb.PushMappedBuffer(buffer);
|
||||
}
|
||||
|
||||
void Module::Interface::CommitImportTitlesAndUpdateFirmwareAuto(Kernel::HLERequestContext& ctx) {
|
||||
CommitImportTitlesImpl(ctx, true, true);
|
||||
}
|
||||
|
||||
void Module::Interface::DeleteTicketId(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
u64 title_id = rp.Pop<u64>();
|
||||
|
@ -414,6 +414,9 @@ public:
|
||||
protected:
|
||||
void GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool ignore_platform);
|
||||
|
||||
void CommitImportTitlesImpl(Kernel::HLERequestContext& ctx, bool is_update_firm_auto,
|
||||
bool is_titles);
|
||||
|
||||
/**
|
||||
* AM::GetNumPrograms service function
|
||||
* Gets the number of installed titles in the requested media type
|
||||
@ -873,6 +876,8 @@ public:
|
||||
*/
|
||||
void GetRequiredSizeFromCia(Kernel::HLERequestContext& ctx);
|
||||
|
||||
void CommitImportProgramsAndUpdateFirmwareAuto(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* AM::DeleteProgram service function
|
||||
* Deletes a program
|
||||
@ -949,6 +954,8 @@ public:
|
||||
|
||||
void EndImportTitle(Kernel::HLERequestContext& ctx);
|
||||
|
||||
void CommitImportTitles(Kernel::HLERequestContext& ctx);
|
||||
|
||||
void BeginImportTmd(Kernel::HLERequestContext& ctx);
|
||||
|
||||
void EndImportTmd(Kernel::HLERequestContext& ctx);
|
||||
@ -983,6 +990,8 @@ public:
|
||||
*/
|
||||
void GetDeviceCert(Kernel::HLERequestContext& ctx);
|
||||
|
||||
void CommitImportTitlesAndUpdateFirmwareAuto(Kernel::HLERequestContext& ctx);
|
||||
|
||||
void DeleteTicketId(Kernel::HLERequestContext& ctx);
|
||||
|
||||
void GetNumTicketIds(Kernel::HLERequestContext& ctx);
|
||||
@ -1002,6 +1011,14 @@ public:
|
||||
std::shared_ptr<Network::ArticBase::Client> artic_client = nullptr;
|
||||
};
|
||||
|
||||
void ForceO3DSDeviceID() {
|
||||
force_old_device_id = true;
|
||||
}
|
||||
|
||||
void ForceN3DSDeviceID() {
|
||||
force_new_device_id = true;
|
||||
}
|
||||
|
||||
private:
|
||||
void ScanForTickets();
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -57,18 +57,18 @@ AM_NET::AM_NET(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
|
||||
{0x002D, &AM_NET::CheckContentRightsIgnorePlatform, "CheckContentRightsIgnorePlatform"},
|
||||
{0x0401, nullptr, "UpdateFirmwareTo"},
|
||||
{0x0402, &AM_NET::BeginImportProgram, "BeginImportProgram"},
|
||||
{0x0403, nullptr, "BeginImportProgramTemporarily"},
|
||||
{0x0403, &AM_NET::BeginImportProgramTemporarily, "BeginImportProgramTemporarily"},
|
||||
{0x0404, nullptr, "CancelImportProgram"},
|
||||
{0x0405, &AM_NET::EndImportProgram, "EndImportProgram"},
|
||||
{0x0406, nullptr, "EndImportProgramWithoutCommit"},
|
||||
{0x0407, nullptr, "CommitImportPrograms"},
|
||||
{0x0406, &AM_NET::EndImportProgramWithoutCommit, "EndImportProgramWithoutCommit"},
|
||||
{0x0407, &AM_NET::CommitImportPrograms, "CommitImportPrograms"},
|
||||
{0x0408, &AM_NET::GetProgramInfoFromCia, "GetProgramInfoFromCia"},
|
||||
{0x0409, &AM_NET::GetSystemMenuDataFromCia, "GetSystemMenuDataFromCia"},
|
||||
{0x040A, &AM_NET::GetDependencyListFromCia, "GetDependencyListFromCia"},
|
||||
{0x040B, &AM_NET::GetTransferSizeFromCia, "GetTransferSizeFromCia"},
|
||||
{0x040C, &AM_NET::GetCoreVersionFromCia, "GetCoreVersionFromCia"},
|
||||
{0x040D, &AM_NET::GetRequiredSizeFromCia, "GetRequiredSizeFromCia"},
|
||||
{0x040E, nullptr, "CommitImportProgramsAndUpdateFirmwareAuto"},
|
||||
{0x040E, &AM_NET::CommitImportProgramsAndUpdateFirmwareAuto, "CommitImportProgramsAndUpdateFirmwareAuto"},
|
||||
{0x040F, nullptr, "UpdateFirmwareAuto"},
|
||||
{0x0410, &AM_NET::DeleteProgram, "DeleteProgram"},
|
||||
{0x0411, nullptr, "GetTwlProgramListForReboot"},
|
||||
@ -88,7 +88,7 @@ AM_NET::AM_NET(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
|
||||
{0x0806, &AM_NET::ResumeImportTitle, "ResumeImportTitle"},
|
||||
{0x0807, &AM_NET::CancelImportTitle, "CancelImportTitle"},
|
||||
{0x0808, &AM_NET::EndImportTitle, "EndImportTitle"},
|
||||
{0x0809, nullptr, "CommitImportTitles"},
|
||||
{0x0809, &AM_NET::CommitImportTitles, "CommitImportTitles"},
|
||||
{0x080A, &AM_NET::BeginImportTmd, "BeginImportTmd"},
|
||||
{0x080B, nullptr, "CancelImportTmd"},
|
||||
{0x080C, &AM_NET::EndImportTmd, "EndImportTmd"},
|
||||
@ -106,7 +106,7 @@ AM_NET::AM_NET(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
|
||||
{0x0818, &AM_NET::GetDeviceCert, "GetDeviceCert"},
|
||||
{0x0819, nullptr, "ImportCertificates"},
|
||||
{0x081A, nullptr, "ImportCertificate"},
|
||||
{0x081B, nullptr, "CommitImportTitlesAndUpdateFirmwareAuto"},
|
||||
{0x081B, &AM_NET::CommitImportTitlesAndUpdateFirmwareAuto, "CommitImportTitlesAndUpdateFirmwareAuto"},
|
||||
{0x081C, &AM_NET::DeleteTicketId, "DeleteTicketId"},
|
||||
{0x081D, &AM_NET::GetNumTicketIds, "GetNumTicketIds"},
|
||||
{0x081E, &AM_NET::GetTicketIdList, "GetTicketIdList"},
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -20,21 +20,21 @@ AM_U::AM_U(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "am:u"
|
||||
{0x0008, &AM_U::GetNumTickets, "GetNumTickets"},
|
||||
{0x0009, &AM_U::GetTicketList, "GetTicketList"},
|
||||
{0x000A, &AM_U::GetDeviceID, "GetDeviceID"},
|
||||
{0x000B, nullptr, "GetNumImportTitleContexts"},
|
||||
{0x000C, nullptr, "GetImportTitleContextList"},
|
||||
{0x000D, nullptr, "GetImportTitleContexts"},
|
||||
{0x000E, nullptr, "DeleteImportTitleContext"},
|
||||
{0x000F, nullptr, "GetNumImportContentContexts"},
|
||||
{0x0010, nullptr, "GetImportContentContextList"},
|
||||
{0x0011, nullptr, "GetImportContentContexts"},
|
||||
{0x000B, &AM_U::GetNumImportTitleContexts, "GetNumImportTitleContexts"},
|
||||
{0x000C, &AM_U::GetImportTitleContextList, "GetImportTitleContextList"},
|
||||
{0x000D, &AM_U::GetImportTitleContexts, "GetImportTitleContexts"},
|
||||
{0x000E, &AM_U::DeleteImportTitleContext, "DeleteImportTitleContext"},
|
||||
{0x000F, &AM_U::GetNumImportContentContexts, "GetNumImportContentContexts"},
|
||||
{0x0010, &AM_U::GetImportContentContextList, "GetImportContentContextList"},
|
||||
{0x0011, &AM_U::GetImportContentContexts, "GetImportContentContexts"},
|
||||
{0x0012, nullptr, "DeleteImportContentContexts"},
|
||||
{0x0013, &AM_U::NeedsCleanup, "NeedsCleanup"},
|
||||
{0x0014, nullptr, "DoCleanup"},
|
||||
{0x0014, &AM_U::DoCleanup, "DoCleanup"},
|
||||
{0x0015, nullptr, "DeleteAllImportContexts"},
|
||||
{0x0016, nullptr, "DeleteAllTemporaryPrograms"},
|
||||
{0x0017, nullptr, "ImportTwlBackupLegacy"},
|
||||
{0x0018, nullptr, "InitializeTitleDatabase"},
|
||||
{0x0019, nullptr, "QueryAvailableTitleDatabase"},
|
||||
{0x0019, &AM_U::QueryAvailableTitleDatabase, "QueryAvailableTitleDatabase"},
|
||||
{0x001A, nullptr, "CalcTwlBackupSize"},
|
||||
{0x001B, nullptr, "ExportTwlBackup"},
|
||||
{0x001C, nullptr, "ImportTwlBackup"},
|
||||
@ -42,19 +42,19 @@ AM_U::AM_U(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "am:u"
|
||||
{0x001E, nullptr, "ReadTwlBackupInfo"},
|
||||
{0x001F, nullptr, "DeleteAllExpiredUserPrograms"},
|
||||
{0x0020, nullptr, "GetTwlArchiveResourceInfo"},
|
||||
{0x0021, nullptr, "GetPersonalizedTicketInfoList"},
|
||||
{0x0021, &AM_U::GetPersonalizedTicketInfoList, "GetPersonalizedTicketInfoList"},
|
||||
{0x0022, nullptr, "DeleteAllImportContextsFiltered"},
|
||||
{0x0023, nullptr, "GetNumImportTitleContextsFiltered"},
|
||||
{0x0024, nullptr, "GetImportTitleContextListFiltered"},
|
||||
{0x0025, nullptr, "CheckContentRights"},
|
||||
{0x0023, &AM_U::GetNumImportTitleContextsFiltered, "GetNumImportTitleContextsFiltered"},
|
||||
{0x0024, &AM_U::GetImportTitleContextListFiltered, "GetImportTitleContextListFiltered"},
|
||||
{0x0025, &AM_U::CheckContentRights, "CheckContentRights"},
|
||||
{0x0026, nullptr, "GetTicketLimitInfos"},
|
||||
{0x0027, nullptr, "GetDemoLaunchInfos"},
|
||||
{0x0028, nullptr, "ReadTwlBackupInfoEx"},
|
||||
{0x0029, nullptr, "DeleteUserProgramsAtomically"},
|
||||
{0x002A, nullptr, "GetNumExistingContentInfosSystem"},
|
||||
{0x002B, nullptr, "ListExistingContentInfosSystem"},
|
||||
{0x002C, nullptr, "GetProgramInfosIgnorePlatform"},
|
||||
{0x002D, nullptr, "CheckContentRightsIgnorePlatform"},
|
||||
{0x002C, &AM_U::GetProgramInfosIgnorePlatform, "GetProgramInfosIgnorePlatform"},
|
||||
{0x002D, &AM_U::CheckContentRightsIgnorePlatform, "CheckContentRightsIgnorePlatform"},
|
||||
{0x0401, nullptr, "UpdateFirmwareTo"},
|
||||
{0x0402, &AM_U::BeginImportProgram, "BeginImportProgram"},
|
||||
{0x0403, &AM_U::BeginImportProgramTemporarily, "BeginImportProgramTemporarily"},
|
||||
@ -68,7 +68,7 @@ AM_U::AM_U(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "am:u"
|
||||
{0x040B, &AM_U::GetTransferSizeFromCia, "GetTransferSizeFromCia"},
|
||||
{0x040C, &AM_U::GetCoreVersionFromCia, "GetCoreVersionFromCia"},
|
||||
{0x040D, &AM_U::GetRequiredSizeFromCia, "GetRequiredSizeFromCia"},
|
||||
{0x040E, nullptr, "CommitImportProgramsAndUpdateFirmwareAuto"},
|
||||
{0x040E, &AM_U::CommitImportProgramsAndUpdateFirmwareAuto, "CommitImportProgramsAndUpdateFirmwareAuto"},
|
||||
{0x040F, nullptr, "UpdateFirmwareAuto"},
|
||||
{0x0410, &AM_U::DeleteProgram, "DeleteProgram"},
|
||||
{0x0411, nullptr, "GetTwlProgramListForReboot"},
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -40,6 +40,7 @@ template <class Archive>
|
||||
void Module::serialize(Archive& ar, const unsigned int) {
|
||||
ar & cfg_config_file_buffer;
|
||||
ar & cfg_system_save_data_archive;
|
||||
ar & mac_address;
|
||||
ar & preferred_region_code;
|
||||
ar & preferred_region_chosen;
|
||||
}
|
||||
@ -722,6 +723,7 @@ void Module::SaveMCUConfig() {
|
||||
Module::Module(Core::System& system_) : system(system_) {
|
||||
LoadConfigNANDSaveFile();
|
||||
LoadMCUConfig();
|
||||
(void)GetMacAddress();
|
||||
// Check the config savegame EULA Version and update it to 0x7F7F if necessary
|
||||
// so users will never get a prompt to accept EULA
|
||||
auto version = GetEULAVersion();
|
||||
@ -771,6 +773,45 @@ static std::tuple<u32 /*region*/, SystemLanguage> AdjustLanguageInfoBlock(
|
||||
return {default_region, region_languages[default_region][0]};
|
||||
}
|
||||
|
||||
std::string& Module::GetMacAddress() {
|
||||
if (!mac_address.empty()) {
|
||||
return mac_address;
|
||||
}
|
||||
|
||||
FileUtil::IOFile mac_address_file(
|
||||
fmt::format("{}/mac.txt", FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir)), "rb");
|
||||
if (!mac_address_file.IsOpen() || mac_address_file.GetSize() > 100) {
|
||||
LOG_INFO(Service_CFG, "Cannot open mac address file for read, generating a new one");
|
||||
mac_address = GenerateRandomMAC();
|
||||
SaveMacAddress();
|
||||
return mac_address;
|
||||
}
|
||||
|
||||
size_t file_size = mac_address_file.GetSize();
|
||||
mac_address.resize(file_size);
|
||||
mac_address_file.ReadBytes(mac_address.data(), file_size);
|
||||
if (mac_address != MacToString(MacToU64(mac_address))) {
|
||||
LOG_WARNING(Service_CFG, "Imvalid saved MAC address, generating a new one");
|
||||
mac_address = GenerateRandomMAC();
|
||||
SaveMacAddress();
|
||||
return mac_address;
|
||||
}
|
||||
|
||||
return mac_address;
|
||||
}
|
||||
|
||||
void Module::SaveMacAddress() {
|
||||
FileUtil::IOFile mac_address_file(
|
||||
fmt::format("{}/mac.txt", FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir)), "wb");
|
||||
|
||||
if (!mac_address_file.IsOpen()) {
|
||||
LOG_ERROR(Service_CFG, "Cannot open mac address file for write");
|
||||
return;
|
||||
}
|
||||
|
||||
mac_address_file.WriteBytes(mac_address.data(), mac_address.size());
|
||||
}
|
||||
|
||||
void Module::UpdatePreferredRegionCode() {
|
||||
if (preferred_region_chosen || !system.IsPoweredOn()) {
|
||||
return;
|
||||
@ -883,8 +924,13 @@ std::pair<u32, u64> Module::GenerateConsoleUniqueId() const {
|
||||
const u32 random_number = rng.GenerateWord32(0, 0xFFFF);
|
||||
|
||||
u64_le local_friend_code_seed;
|
||||
rng.GenerateBlock(reinterpret_cast<CryptoPP::byte*>(&local_friend_code_seed),
|
||||
sizeof(local_friend_code_seed));
|
||||
auto& lfcs = HW::UniqueData::GetLocalFriendCodeSeedB();
|
||||
if (lfcs.IsValid()) {
|
||||
local_friend_code_seed = lfcs.body.friend_code_seed;
|
||||
} else {
|
||||
rng.GenerateBlock(reinterpret_cast<CryptoPP::byte*>(&local_friend_code_seed),
|
||||
sizeof(local_friend_code_seed));
|
||||
}
|
||||
|
||||
const u64 console_id =
|
||||
(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(), ""));
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -622,6 +622,16 @@ public:
|
||||
*/
|
||||
void SaveMCUConfig();
|
||||
|
||||
/**
|
||||
* Get a reference to the console's MAC address
|
||||
*/
|
||||
std::string& GetMacAddress();
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void SaveMacAddress();
|
||||
|
||||
private:
|
||||
void UpdatePreferredRegionCode();
|
||||
SystemLanguage GetRawSystemLanguage();
|
||||
@ -634,6 +644,7 @@ private:
|
||||
u32 preferred_region_code = 0;
|
||||
bool preferred_region_chosen = false;
|
||||
MCUData mcu_data{};
|
||||
std::string mac_address{};
|
||||
|
||||
std::shared_ptr<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
|
||||
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
|
||||
|
||||
SERVICE_CONSTRUCT(Service::CFG::Module)
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -916,7 +916,7 @@ void FS_USER::GetFreeBytes(Kernel::HLERequestContext& ctx) {
|
||||
void FS_USER::GetSdmcArchiveResource(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_FS, "(STUBBED) called");
|
||||
|
||||
auto resource = archives.GetArchiveResource(MediaType::SDMC);
|
||||
|
||||
@ -934,7 +934,7 @@ void FS_USER::GetSdmcArchiveResource(Kernel::HLERequestContext& ctx) {
|
||||
void FS_USER::GetNandArchiveResource(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_FS, "(STUBBED) called");
|
||||
|
||||
auto resource = archives.GetArchiveResource(MediaType::NAND);
|
||||
if (resource.Failed()) {
|
||||
@ -1103,7 +1103,7 @@ void FS_USER::GetArchiveResource(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
auto media_type = rp.PopEnum<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);
|
||||
if (resource.Failed()) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -11,6 +11,10 @@
|
||||
namespace Service::NIM {
|
||||
|
||||
void InstallInterfaces(Core::System& system) {
|
||||
// Don't register HLE nim on initial setup
|
||||
if (system.IsInitialSetup()) {
|
||||
return;
|
||||
}
|
||||
auto& service_manager = system.ServiceManager();
|
||||
std::make_shared<NIM_AOC>()->InstallAsService(service_manager);
|
||||
std::make_shared<NIM_S>()->InstallAsService(service_manager);
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#include "common/archives.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
@ -17,6 +18,8 @@
|
||||
#include "core/hle/kernel/shared_memory.h"
|
||||
#include "core/hle/kernel/shared_page.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/hle/service/cfg/cfg.h"
|
||||
#include "core/hle/service/cfg/cfg_u.h"
|
||||
#include "core/hle/service/nwm/nwm_uds.h"
|
||||
#include "core/hle/service/nwm/uds_beacon.h"
|
||||
#include "core/hle/service/nwm/uds_connection.h"
|
||||
@ -1494,10 +1497,13 @@ NWM_UDS::NWM_UDS(Core::System& system) : ServiceFramework("nwm::UDS"), system(sy
|
||||
BeaconBroadcastCallback(user_data, cycles_late);
|
||||
});
|
||||
|
||||
CryptoPP::AutoSeededRandomPool rng;
|
||||
auto mac = SharedPage::DefaultMac;
|
||||
// Keep the Nintendo 3DS MAC header and randomly generate the last 3 bytes
|
||||
rng.GenerateBlock(static_cast<CryptoPP::byte*>(mac.data() + 3), 3);
|
||||
MacAddress mac;
|
||||
|
||||
auto cfg = system.ServiceManager().GetService<Service::CFG::CFG_U>("cfg:u");
|
||||
if (cfg.get()) {
|
||||
auto cfg_module = cfg->GetModule();
|
||||
mac = Service::CFG::MacToArray(cfg_module->GetMacAddress());
|
||||
}
|
||||
|
||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||
if (room_member->IsConnected()) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -34,7 +34,7 @@ void Module::Interface::GetAdapterState(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(ptm->battery_is_charging);
|
||||
|
||||
LOG_WARNING(Service_PTM, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_PTM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void Module::Interface::GetShellState(Kernel::HLERequestContext& ctx) {
|
||||
@ -52,7 +52,7 @@ void Module::Interface::GetBatteryLevel(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(static_cast<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) {
|
||||
@ -62,7 +62,7 @@ void Module::Interface::GetBatteryChargeState(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(ptm->battery_is_charging);
|
||||
|
||||
LOG_WARNING(Service_PTM, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_PTM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void Module::Interface::GetPedometerState(Kernel::HLERequestContext& ctx) {
|
||||
@ -72,7 +72,7 @@ void Module::Interface::GetPedometerState(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(ptm->pedometer_is_counting);
|
||||
|
||||
LOG_WARNING(Service_PTM, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_PTM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void Module::Interface::GetStepHistory(Kernel::HLERequestContext& ctx) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -213,13 +213,13 @@ static bool AttemptLLE(const ServiceModuleInfo& service_module) {
|
||||
}
|
||||
|
||||
/// Initialize ServiceManager
|
||||
void Init(Core::System& core) {
|
||||
void Init(Core::System& core, bool allow_lle) {
|
||||
SM::ServiceManager::InstallInterfaces(core);
|
||||
core.Kernel().SetAppMainThreadExtendedSleep(false);
|
||||
bool lle_module_present = false;
|
||||
|
||||
for (const auto& service_module : service_module_map) {
|
||||
const bool has_lle = AttemptLLE(service_module);
|
||||
const bool has_lle = allow_lle && AttemptLLE(service_module);
|
||||
if (!has_lle && service_module.init_function != nullptr) {
|
||||
service_module.init_function(core);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -183,7 +183,7 @@ private:
|
||||
};
|
||||
|
||||
/// Initialize ServiceManager
|
||||
void Init(Core::System& system);
|
||||
void Init(Core::System& system, bool allow_lle);
|
||||
|
||||
struct ServiceModuleInfo {
|
||||
std::string name;
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -13,21 +13,26 @@
|
||||
#include "common/string_util.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/certificate.h"
|
||||
#include "core/file_sys/ncch_container.h"
|
||||
#include "core/file_sys/otp.h"
|
||||
#include "core/file_sys/romfs_reader.h"
|
||||
#include "core/file_sys/secure_value_backend_artic.h"
|
||||
#include "core/file_sys/title_metadata.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/resource_limit.h"
|
||||
#include "core/hle/kernel/shared_page.h"
|
||||
#include "core/hle/service/am/am.h"
|
||||
#include "core/hle/service/am/am_app.h"
|
||||
#include "core/hle/service/am/am_net.h"
|
||||
#include "core/hle/service/apt/apt.h"
|
||||
#include "core/hle/service/cfg/cfg.h"
|
||||
#include "core/hle/service/cfg/cfg_u.h"
|
||||
#include "core/hle/service/fs/archive.h"
|
||||
#include "core/hle/service/fs/fs_user.h"
|
||||
#include "core/hle/service/hid/hid_user.h"
|
||||
#include "core/hw/unique_data.h"
|
||||
#include "core/loader/artic.h"
|
||||
#include "core/loader/smdh.h"
|
||||
#include "core/memory.h"
|
||||
@ -38,6 +43,26 @@ namespace Loader {
|
||||
|
||||
using namespace Common::Literals;
|
||||
|
||||
Apploader_Artic::Apploader_Artic(Core::System& system_, const std::string& server_addr,
|
||||
u16 server_port, ArticInitMode init_mode)
|
||||
: AppLoader(system_, FileUtil::IOFile()) {
|
||||
client = std::make_shared<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() {
|
||||
// TODO(PabloMK7) Find memory leak that prevents the romfs readers being destroyed
|
||||
// when emulation stops. Looks like the mem leak comes from IVFCFile objects
|
||||
@ -110,7 +135,6 @@ Apploader_Artic::LoadNew3dsHwCapabilities() {
|
||||
}
|
||||
|
||||
ResultStatus Apploader_Artic::LoadExec(std::shared_ptr<Kernel::Process>& process) {
|
||||
using Kernel::CodeSet;
|
||||
|
||||
if (!is_loaded)
|
||||
return ResultStatus::ErrorNotLoaded;
|
||||
@ -119,108 +143,110 @@ ResultStatus Apploader_Artic::LoadExec(std::shared_ptr<Kernel::Process>& process
|
||||
u64_le program_id;
|
||||
if (ResultStatus::Success == ReadCode(code) &&
|
||||
ResultStatus::Success == ReadProgramId(program_id)) {
|
||||
|
||||
std::string process_name = Common::StringFromFixedZeroTerminatedBuffer(
|
||||
(const char*)program_exheader.codeset_info.name, 8);
|
||||
|
||||
std::shared_ptr<CodeSet> codeset = system.Kernel().CreateCodeSet(process_name, program_id);
|
||||
|
||||
codeset->CodeSegment().offset = 0;
|
||||
codeset->CodeSegment().addr = program_exheader.codeset_info.text.address;
|
||||
codeset->CodeSegment().size =
|
||||
program_exheader.codeset_info.text.num_max_pages * Memory::CITRA_PAGE_SIZE;
|
||||
|
||||
codeset->RODataSegment().offset =
|
||||
codeset->CodeSegment().offset + codeset->CodeSegment().size;
|
||||
codeset->RODataSegment().addr = program_exheader.codeset_info.ro.address;
|
||||
codeset->RODataSegment().size =
|
||||
program_exheader.codeset_info.ro.num_max_pages * Memory::CITRA_PAGE_SIZE;
|
||||
|
||||
// TODO(yuriks): Not sure if the bss size is added to the page-aligned .data size or just
|
||||
// to the regular size. Playing it safe for now.
|
||||
u32 bss_page_size = (program_exheader.codeset_info.bss_size + 0xFFF) & ~0xFFF;
|
||||
code.resize(code.size() + bss_page_size, 0);
|
||||
|
||||
codeset->DataSegment().offset =
|
||||
codeset->RODataSegment().offset + codeset->RODataSegment().size;
|
||||
codeset->DataSegment().addr = program_exheader.codeset_info.data.address;
|
||||
codeset->DataSegment().size =
|
||||
program_exheader.codeset_info.data.num_max_pages * Memory::CITRA_PAGE_SIZE +
|
||||
bss_page_size;
|
||||
|
||||
// Apply patches now that the entire codeset (including .bss) has been allocated
|
||||
// const ResultStatus patch_result = overlay_ncch->ApplyCodePatch(code);
|
||||
// if (patch_result != ResultStatus::Success && patch_result != ResultStatus::ErrorNotUsed)
|
||||
// return patch_result;
|
||||
|
||||
codeset->entrypoint = codeset->CodeSegment().addr;
|
||||
codeset->memory = std::move(code);
|
||||
|
||||
process = system.Kernel().CreateProcess(std::move(codeset));
|
||||
|
||||
// Attach a resource limit to the process based on the resource limit category
|
||||
const auto category = static_cast<Kernel::ResourceLimitCategory>(
|
||||
program_exheader.arm11_system_local_caps.resource_limit_category);
|
||||
process->resource_limit = system.Kernel().ResourceLimit().GetForCategory(category);
|
||||
|
||||
// When running N3DS-unaware titles pm will lie about the amount of memory available.
|
||||
// This means RESLIMIT_COMMIT = APPMEMALLOC doesn't correspond to the actual size of
|
||||
// APPLICATION. See:
|
||||
// https://github.com/LumaTeam/Luma3DS/blob/e2778a45/sysmodules/pm/source/launch.c#L237
|
||||
auto& ncch_caps = program_exheader.arm11_system_local_caps;
|
||||
const auto o3ds_mode = *LoadKernelMemoryMode().first;
|
||||
const auto n3ds_mode = static_cast<Kernel::New3dsMemoryMode>(ncch_caps.n3ds_mode);
|
||||
const bool is_new_3ds = Settings::values.is_new_3ds.GetValue();
|
||||
if (is_new_3ds && n3ds_mode == Kernel::New3dsMemoryMode::Legacy &&
|
||||
category == Kernel::ResourceLimitCategory::Application) {
|
||||
u64 new_limit = 0;
|
||||
switch (o3ds_mode) {
|
||||
case Kernel::MemoryMode::Prod:
|
||||
new_limit = 64_MiB;
|
||||
break;
|
||||
case Kernel::MemoryMode::Dev1:
|
||||
new_limit = 96_MiB;
|
||||
break;
|
||||
case Kernel::MemoryMode::Dev2:
|
||||
new_limit = 80_MiB;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
process->resource_limit->SetLimitValue(Kernel::ResourceLimitType::Commit,
|
||||
static_cast<s32>(new_limit));
|
||||
}
|
||||
|
||||
// Set the default CPU core for this process
|
||||
process->ideal_processor = program_exheader.arm11_system_local_caps.ideal_processor;
|
||||
|
||||
// Copy data while converting endianness
|
||||
using KernelCaps = std::array<u32, ExHeader_ARM11_KernelCaps::NUM_DESCRIPTORS>;
|
||||
KernelCaps kernel_caps;
|
||||
std::copy_n(program_exheader.arm11_kernel_caps.descriptors, kernel_caps.size(),
|
||||
begin(kernel_caps));
|
||||
process->ParseKernelCaps(kernel_caps.data(), kernel_caps.size());
|
||||
|
||||
s32 priority = program_exheader.arm11_system_local_caps.priority;
|
||||
u32 stack_size = program_exheader.codeset_info.stack_size;
|
||||
|
||||
// On real HW this is done with FS:Reg, but we can be lazy
|
||||
auto fs_user = system.ServiceManager().GetService<Service::FS::FS_USER>("fs:USER");
|
||||
fs_user->RegisterProgramInfo(process->process_id, process->codeset->program_id,
|
||||
"articbase://");
|
||||
|
||||
Service::FS::FS_USER::ProductInfo product_info{};
|
||||
if (LoadProductInfo(product_info) != ResultStatus::Success) {
|
||||
return ResultStatus::ErrorArtic;
|
||||
}
|
||||
fs_user->RegisterProductInfo(process->process_id, product_info);
|
||||
|
||||
process->Run(priority, stack_size);
|
||||
return ResultStatus::Success;
|
||||
return LoadExecImpl(process, program_id, program_exheader, code);
|
||||
}
|
||||
return ResultStatus::ErrorArtic;
|
||||
}
|
||||
|
||||
ResultStatus Apploader_Artic::LoadExecImpl(std::shared_ptr<Kernel::Process>& process,
|
||||
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);
|
||||
|
||||
codeset->CodeSegment().offset = 0;
|
||||
codeset->CodeSegment().addr = exheader.codeset_info.text.address;
|
||||
codeset->CodeSegment().size =
|
||||
exheader.codeset_info.text.num_max_pages * Memory::CITRA_PAGE_SIZE;
|
||||
|
||||
codeset->RODataSegment().offset = codeset->CodeSegment().offset + codeset->CodeSegment().size;
|
||||
codeset->RODataSegment().addr = exheader.codeset_info.ro.address;
|
||||
codeset->RODataSegment().size =
|
||||
exheader.codeset_info.ro.num_max_pages * Memory::CITRA_PAGE_SIZE;
|
||||
|
||||
// TODO(yuriks): Not sure if the bss size is added to the page-aligned .data size or just
|
||||
// to the regular size. Playing it safe for now.
|
||||
u32 bss_page_size = (exheader.codeset_info.bss_size + 0xFFF) & ~0xFFF;
|
||||
code.resize(code.size() + bss_page_size, 0);
|
||||
|
||||
codeset->DataSegment().offset = codeset->RODataSegment().offset + codeset->RODataSegment().size;
|
||||
codeset->DataSegment().addr = exheader.codeset_info.data.address;
|
||||
codeset->DataSegment().size =
|
||||
exheader.codeset_info.data.num_max_pages * Memory::CITRA_PAGE_SIZE + bss_page_size;
|
||||
|
||||
// Apply patches now that the entire codeset (including .bss) has been allocated
|
||||
// const ResultStatus patch_result = overlay_ncch->ApplyCodePatch(code);
|
||||
// if (patch_result != ResultStatus::Success && patch_result != ResultStatus::ErrorNotUsed)
|
||||
// return patch_result;
|
||||
|
||||
codeset->entrypoint = codeset->CodeSegment().addr;
|
||||
codeset->memory = std::move(code);
|
||||
|
||||
process = system.Kernel().CreateProcess(std::move(codeset));
|
||||
|
||||
// Attach a resource limit to the process based on the resource limit category
|
||||
const auto category = static_cast<Kernel::ResourceLimitCategory>(
|
||||
exheader.arm11_system_local_caps.resource_limit_category);
|
||||
process->resource_limit = system.Kernel().ResourceLimit().GetForCategory(category);
|
||||
|
||||
// When running N3DS-unaware titles pm will lie about the amount of memory available.
|
||||
// This means RESLIMIT_COMMIT = APPMEMALLOC doesn't correspond to the actual size of
|
||||
// APPLICATION. See:
|
||||
// https://github.com/LumaTeam/Luma3DS/blob/e2778a45/sysmodules/pm/source/launch.c#L237
|
||||
auto& ncch_caps = exheader.arm11_system_local_caps;
|
||||
const auto o3ds_mode = *LoadKernelMemoryMode().first;
|
||||
const auto n3ds_mode = static_cast<Kernel::New3dsMemoryMode>(ncch_caps.n3ds_mode);
|
||||
const bool is_new_3ds = Settings::values.is_new_3ds.GetValue();
|
||||
if (is_new_3ds && n3ds_mode == Kernel::New3dsMemoryMode::Legacy &&
|
||||
category == Kernel::ResourceLimitCategory::Application) {
|
||||
u64 new_limit = 0;
|
||||
switch (o3ds_mode) {
|
||||
case Kernel::MemoryMode::Prod:
|
||||
new_limit = 64_MiB;
|
||||
break;
|
||||
case Kernel::MemoryMode::Dev1:
|
||||
new_limit = 96_MiB;
|
||||
break;
|
||||
case Kernel::MemoryMode::Dev2:
|
||||
new_limit = 80_MiB;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
process->resource_limit->SetLimitValue(Kernel::ResourceLimitType::Commit,
|
||||
static_cast<s32>(new_limit));
|
||||
}
|
||||
|
||||
// Set the default CPU core for this process
|
||||
process->ideal_processor = exheader.arm11_system_local_caps.ideal_processor;
|
||||
|
||||
// Copy data while converting endianness
|
||||
using KernelCaps = std::array<u32, ExHeader_ARM11_KernelCaps::NUM_DESCRIPTORS>;
|
||||
KernelCaps kernel_caps;
|
||||
std::copy_n(exheader.arm11_kernel_caps.descriptors, kernel_caps.size(), begin(kernel_caps));
|
||||
process->ParseKernelCaps(kernel_caps.data(), kernel_caps.size());
|
||||
|
||||
s32 priority = exheader.arm11_system_local_caps.priority;
|
||||
u32 stack_size = exheader.codeset_info.stack_size;
|
||||
|
||||
// On real HW this is done with FS:Reg, but we can be lazy
|
||||
auto fs_user = system.ServiceManager().GetService<Service::FS::FS_USER>("fs:USER");
|
||||
fs_user->RegisterProgramInfo(process->process_id, process->codeset->program_id, "articbase://");
|
||||
|
||||
Service::FS::FS_USER::ProductInfo product_info{};
|
||||
if (LoadProductInfo(product_info) != ResultStatus::Success) {
|
||||
return ResultStatus::ErrorArtic;
|
||||
}
|
||||
fs_user->RegisterProductInfo(process->process_id, product_info);
|
||||
|
||||
process->Run(priority, stack_size);
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
void Apploader_Artic::ParseRegionLockoutInfo(u64 program_id) {
|
||||
if (Settings::values.region_value.GetValue() != Settings::REGION_VALUE_AUTO_SELECT) {
|
||||
return;
|
||||
@ -252,8 +278,7 @@ bool Apploader_Artic::LoadExheader() {
|
||||
if (program_exheader_loaded)
|
||||
return true;
|
||||
|
||||
if (!client_connected)
|
||||
client_connected = client->Connect();
|
||||
EnsureClientConnected();
|
||||
if (!client_connected)
|
||||
return false;
|
||||
|
||||
@ -287,8 +312,7 @@ ResultStatus Apploader_Artic::LoadProductInfo(Service::FS::FS_USER::ProductInfo&
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
if (!client_connected)
|
||||
client_connected = client->Connect();
|
||||
EnsureClientConnected();
|
||||
if (!client_connected)
|
||||
return ResultStatus::ErrorArtic;
|
||||
|
||||
@ -307,6 +331,34 @@ ResultStatus Apploader_Artic::LoadProductInfo(Service::FS::FS_USER::ProductInfo&
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
void Apploader_Artic::EnsureClientConnected() {
|
||||
if (client_connected) {
|
||||
return;
|
||||
}
|
||||
client_connected = client->Connect();
|
||||
if (!client_connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_initial_setup) {
|
||||
// Ensure we are running the initial setup app in the correct version
|
||||
auto req = client->NewRequest("System_IsAzaharInitialSetup");
|
||||
auto resp = client->Send(req);
|
||||
if (!resp.has_value()) {
|
||||
client_connected = false;
|
||||
return;
|
||||
}
|
||||
|
||||
auto ret_buf = resp->GetResponseBuffer(0);
|
||||
if (!ret_buf.has_value() || ret_buf->second != sizeof(u32)) {
|
||||
client_connected = false;
|
||||
return;
|
||||
}
|
||||
|
||||
client_connected = *reinterpret_cast<u32*>(ret_buf->first) == INITIAL_SETUP_APP_VERSION;
|
||||
}
|
||||
}
|
||||
|
||||
ResultStatus Apploader_Artic::Load(std::shared_ptr<Kernel::Process>& process) {
|
||||
u64_le ncch_program_id;
|
||||
|
||||
@ -331,41 +383,173 @@ ResultStatus Apploader_Artic::Load(std::shared_ptr<Kernel::Process>& process) {
|
||||
|
||||
is_loaded = true; // Set state to loaded
|
||||
|
||||
if (is_initial_setup) {
|
||||
|
||||
// Request console unique data
|
||||
for (int i = 0; i < 6; i++) {
|
||||
std::string path;
|
||||
std::size_t expected_size;
|
||||
|
||||
switch (i) {
|
||||
case 0:
|
||||
path = HW::UniqueData::GetSecureInfoAPath();
|
||||
expected_size = sizeof(HW::UniqueData::SecureInfoA);
|
||||
break;
|
||||
case 1:
|
||||
path = HW::UniqueData::GetLocalFriendCodeSeedBPath();
|
||||
expected_size = sizeof(HW::UniqueData::LocalFriendCodeSeedB);
|
||||
break;
|
||||
case 2:
|
||||
path = HW::UniqueData::GetMovablePath();
|
||||
expected_size = sizeof(HW::UniqueData::MovableSedFull);
|
||||
break;
|
||||
case 3:
|
||||
path = HW::UniqueData::GetOTPPath();
|
||||
expected_size = sizeof(FileSys::OTP::OTPBin);
|
||||
break;
|
||||
case 4:
|
||||
expected_size = sizeof(u64) + sizeof(u32);
|
||||
break;
|
||||
case 5:
|
||||
expected_size = sizeof(u8) * 6;
|
||||
break;
|
||||
}
|
||||
|
||||
auto req = client->NewRequest("System_GetSystemFile");
|
||||
req.AddParameterU8(static_cast<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
|
||||
if (ResultStatus::Success != result)
|
||||
return result;
|
||||
|
||||
system.ArchiveManager().RegisterSelfNCCH(*this);
|
||||
system.ArchiveManager().RegisterArticSaveDataSource(client);
|
||||
system.ArchiveManager().RegisterArticExtData(client);
|
||||
system.ArchiveManager().RegisterArticNCCH(client);
|
||||
system.ArchiveManager().RegisterArticSystemSaveData(client);
|
||||
if (!is_initial_setup) {
|
||||
system.ArchiveManager().RegisterArticSaveDataSource(client);
|
||||
system.ArchiveManager().RegisterArticExtData(client);
|
||||
system.ArchiveManager().RegisterArticNCCH(client);
|
||||
system.ArchiveManager().RegisterArticSystemSaveData(client);
|
||||
|
||||
auto fs_user = system.ServiceManager().GetService<Service::FS::FS_USER>("fs:USER");
|
||||
if (fs_user.get()) {
|
||||
fs_user->RegisterSecureValueBackend(
|
||||
std::make_shared<FileSys::ArticSecureValueBackend>(client));
|
||||
}
|
||||
auto fs_user = system.ServiceManager().GetService<Service::FS::FS_USER>("fs:USER");
|
||||
if (fs_user.get()) {
|
||||
fs_user->RegisterSecureValueBackend(
|
||||
std::make_shared<FileSys::ArticSecureValueBackend>(client));
|
||||
}
|
||||
|
||||
auto cfg = system.ServiceManager().GetService<Service::CFG::CFG_U>("cfg:u");
|
||||
if (cfg.get()) {
|
||||
cfg->UseArticClient(client);
|
||||
}
|
||||
auto cfg = system.ServiceManager().GetService<Service::CFG::CFG_U>("cfg:u");
|
||||
if (cfg.get()) {
|
||||
cfg->UseArticClient(client);
|
||||
}
|
||||
|
||||
auto amnet = system.ServiceManager().GetService<Service::AM::AM_NET>("am:net");
|
||||
if (amnet.get()) {
|
||||
amnet->UseArticClient(client);
|
||||
}
|
||||
auto amnet = system.ServiceManager().GetService<Service::AM::AM_NET>("am:net");
|
||||
if (amnet.get()) {
|
||||
amnet->UseArticClient(client);
|
||||
}
|
||||
|
||||
auto amapp = system.ServiceManager().GetService<Service::AM::AM_APP>("am:app");
|
||||
if (amapp.get()) {
|
||||
amapp->UseArticClient(client);
|
||||
}
|
||||
auto amapp = system.ServiceManager().GetService<Service::AM::AM_APP>("am:app");
|
||||
if (amapp.get()) {
|
||||
amapp->UseArticClient(client);
|
||||
}
|
||||
|
||||
if (Settings::values.use_artic_base_controller.GetValue()) {
|
||||
auto hid_user = system.ServiceManager().GetService<Service::HID::User>("hid:USER");
|
||||
if (hid_user.get()) {
|
||||
hid_user->GetModule()->UseArticClient(client);
|
||||
if (Settings::values.use_artic_base_controller.GetValue()) {
|
||||
auto hid_user = system.ServiceManager().GetService<Service::HID::User>("hid:USER");
|
||||
if (hid_user.get()) {
|
||||
hid_user->GetModule()->UseArticClient(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -382,8 +566,7 @@ ResultStatus Apploader_Artic::IsExecutable(bool& out_executable) {
|
||||
ResultStatus Apploader_Artic::ReadCode(std::vector<u8>& buffer) {
|
||||
// Code is only read once, there is no need to cache it.
|
||||
|
||||
if (!client_connected)
|
||||
client_connected = client->Connect();
|
||||
EnsureClientConnected();
|
||||
if (!client_connected)
|
||||
return ResultStatus::ErrorArtic;
|
||||
|
||||
@ -423,8 +606,7 @@ ResultStatus Apploader_Artic::ReadIcon(std::vector<u8>& buffer) {
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
if (!client_connected)
|
||||
client_connected = client->Connect();
|
||||
EnsureClientConnected();
|
||||
if (!client_connected)
|
||||
return ResultStatus::ErrorArtic;
|
||||
|
||||
@ -450,8 +632,7 @@ ResultStatus Apploader_Artic::ReadBanner(std::vector<u8>& buffer) {
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
if (!client_connected)
|
||||
client_connected = client->Connect();
|
||||
EnsureClientConnected();
|
||||
if (!client_connected)
|
||||
return ResultStatus::ErrorArtic;
|
||||
|
||||
@ -477,8 +658,7 @@ ResultStatus Apploader_Artic::ReadLogo(std::vector<u8>& buffer) {
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
if (!client_connected)
|
||||
client_connected = client->Connect();
|
||||
EnsureClientConnected();
|
||||
if (!client_connected)
|
||||
return ResultStatus::ErrorArtic;
|
||||
|
||||
@ -504,8 +684,7 @@ ResultStatus Apploader_Artic::ReadProgramId(u64& out_program_id) {
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
if (!client_connected)
|
||||
client_connected = client->Connect();
|
||||
EnsureClientConnected();
|
||||
if (!client_connected)
|
||||
return ResultStatus::ErrorArtic;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -18,22 +18,13 @@ namespace Loader {
|
||||
/// Loads an NCCH file (e.g. from a CCI, or the first NCCH in a CXI)
|
||||
class Apploader_Artic final : public AppLoader {
|
||||
public:
|
||||
Apploader_Artic(Core::System& system_, const std::string& server_addr, u16 server_port)
|
||||
: AppLoader(system_, FileUtil::IOFile()) {
|
||||
client = std::make_shared<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);
|
||||
});
|
||||
}
|
||||
enum class ArticInitMode {
|
||||
NONE,
|
||||
O3DS,
|
||||
N3DS,
|
||||
};
|
||||
Apploader_Artic(Core::System& system_, const std::string& server_addr, u16 server_port,
|
||||
ArticInitMode init_mode);
|
||||
|
||||
~Apploader_Artic() override;
|
||||
|
||||
@ -97,7 +88,12 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DoingInitialSetup() override {
|
||||
return is_initial_setup;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr u32 INITIAL_SETUP_APP_VERSION = 0;
|
||||
/**
|
||||
* Loads .code section into memory for booting
|
||||
* @param process The newly created process
|
||||
@ -105,6 +101,9 @@ private:
|
||||
*/
|
||||
ResultStatus LoadExec(std::shared_ptr<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
|
||||
/// If an SMDH is not present, the program ID is compared against a list
|
||||
/// of known system titles to determine the region.
|
||||
@ -114,8 +113,12 @@ private:
|
||||
|
||||
ResultStatus LoadProductInfo(Service::FS::FS_USER::ProductInfo& out);
|
||||
|
||||
void EnsureClientConnected();
|
||||
|
||||
ExHeader_Header program_exheader{};
|
||||
bool program_exheader_loaded = false;
|
||||
bool is_initial_setup = false;
|
||||
ArticInitMode artic_init_mode = ArticInitMode::NONE;
|
||||
|
||||
std::optional<u64> cached_title_id = std::nullopt;
|
||||
std::optional<Service::FS::FS_USER::ProductInfo> cached_product_info = std::nullopt;
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -112,6 +112,12 @@ static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileUtil::
|
||||
return std::make_unique<AppLoader_NCCH>(system, std::move(file), filepath);
|
||||
|
||||
case FileType::ARTIC: {
|
||||
Apploader_Artic::ArticInitMode mode = Apploader_Artic::ArticInitMode::NONE;
|
||||
if (filename.starts_with("articinio://")) {
|
||||
mode = Apploader_Artic::ArticInitMode::O3DS;
|
||||
} else if (filename.starts_with("articinin://")) {
|
||||
mode = Apploader_Artic::ArticInitMode::N3DS;
|
||||
}
|
||||
auto strToUInt = [](const std::string& str) -> int {
|
||||
char* pEnd = NULL;
|
||||
unsigned long ul = ::strtoul(str.c_str(), &pEnd, 10);
|
||||
@ -121,7 +127,7 @@ static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileUtil::
|
||||
};
|
||||
|
||||
u16 port = 5543;
|
||||
std::string server_addr = filename;
|
||||
std::string server_addr = filename.substr(12);
|
||||
auto pos = server_addr.find(":");
|
||||
if (pos != server_addr.npos) {
|
||||
int newVal = strToUInt(server_addr.substr(pos + 1));
|
||||
@ -130,7 +136,7 @@ static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileUtil::
|
||||
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:
|
||||
@ -139,9 +145,10 @@ static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileUtil::
|
||||
}
|
||||
|
||||
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,
|
||||
filename.substr(12), "");
|
||||
filename, "");
|
||||
}
|
||||
|
||||
FileUtil::IOFile file(filename, "rb");
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -275,6 +275,10 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool DoingInitialSetup() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected:
|
||||
Core::System& system;
|
||||
FileUtil::IOFile file;
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -159,9 +159,9 @@ void FrameLimiter::DoFrameLimiting(microseconds current_system_time_us) {
|
||||
}
|
||||
|
||||
auto now = Clock::now();
|
||||
double sleep_scale = Settings::values.frame_limit.GetValue() / 100.0;
|
||||
double sleep_scale = Settings::GetFrameLimit() / 100.0;
|
||||
|
||||
if (Settings::values.frame_limit.GetValue() == 0) {
|
||||
if (Settings::GetFrameLimit() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -1208,4 +1208,82 @@ std::optional<u32> GetSystemTitleRegion(u64 title_id) {
|
||||
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
|
||||
|
@ -1,10 +1,11 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
||||
@ -26,4 +27,9 @@ std::string GetHomeMenuNcchPath(u32 region);
|
||||
/// Returns the region of a system title, if it can be determined.
|
||||
std::optional<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
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -43,8 +43,6 @@
|
||||
#define closesocket(x) close(x)
|
||||
#endif
|
||||
|
||||
// #define DISABLE_PING_TIMEOUT
|
||||
|
||||
namespace Network::ArticBase {
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
@ -229,7 +227,7 @@ bool Client::Connect() {
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_family = AF_INET;
|
||||
|
||||
LOG_INFO(Network, "Starting Artic Base Client");
|
||||
LOG_INFO(Network, "Starting Artic Client");
|
||||
|
||||
if (getaddrinfo(address.data(), NULL, &hints, &addrinfo) != 0) {
|
||||
LOG_ERROR(Network, "Failed to get server address");
|
||||
@ -273,8 +271,8 @@ bool Client::Connect() {
|
||||
shutdown(main_socket, SHUT_RDWR);
|
||||
closesocket(main_socket);
|
||||
LOG_ERROR(Network, "Incompatible server version: {}", version_value);
|
||||
SignalCommunicationError("\nIncompatible Artic Base Server version.\nCheck for updates "
|
||||
"to Artic Base Server or Azahar.");
|
||||
SignalCommunicationError("\nIncompatible Artic Server version.\nCheck for updates "
|
||||
"to the Artic Server or Azahar.");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
@ -485,15 +483,15 @@ void Client::PingFunction() {
|
||||
while (ping_run) {
|
||||
std::chrono::time_point<std::chrono::steady_clock> last = last_sent_request;
|
||||
if (std::chrono::steady_clock::now() - last > std::chrono::seconds(7)) {
|
||||
#ifdef DISABLE_PING_TIMEOUT
|
||||
client->last_sent_request = std::chrono::steady_clock::now();
|
||||
#else
|
||||
auto ping_reply = SendSimpleRequest("PING");
|
||||
if (!ping_reply.has_value()) {
|
||||
SignalCommunicationError();
|
||||
break;
|
||||
if (ping_enabled) {
|
||||
auto ping_reply = SendSimpleRequest("PING");
|
||||
if (!ping_reply.has_value()) {
|
||||
SignalCommunicationError();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
last_sent_request = std::chrono::steady_clock::now();
|
||||
}
|
||||
#endif // DISABLE_PING_TIMEOUT
|
||||
}
|
||||
|
||||
std::unique_lock lk(ping_cv_mutex);
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -161,6 +161,10 @@ public:
|
||||
return last_sockaddr_in;
|
||||
}
|
||||
|
||||
void SetPingEnabled(bool enable) {
|
||||
ping_enabled = enable;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr const int SERVER_VERSION = 2;
|
||||
|
||||
@ -190,6 +194,7 @@ private:
|
||||
std::condition_variable ping_cv;
|
||||
std::mutex ping_cv_mutex;
|
||||
std::atomic<bool> ping_run = true;
|
||||
bool ping_enabled = true;
|
||||
|
||||
void StopImpl(bool from_error);
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -185,7 +185,7 @@ void Swapchain::SetPresentMode() {
|
||||
}
|
||||
// If vsync is enabled attempt to use mailbox mode in case the user wants to speedup/slowdown
|
||||
// the game. If mailbox is not available use immediate and warn about it.
|
||||
if (use_vsync && Settings::values.frame_limit.GetValue() > 100) {
|
||||
if (use_vsync && Settings::GetFrameLimit() > 100) {
|
||||
present_mode = has_mailbox ? vk::PresentModeKHR::eMailbox : vk::PresentModeKHR::eImmediate;
|
||||
if (!has_mailbox) {
|
||||
LOG_WARNING(
|
||||
|
Loading…
x
Reference in New Issue
Block a user