diff --git a/src/citra_qt/citra_qt.cpp b/src/citra_qt/citra_qt.cpp index 797034387..b0148ba27 100644 --- a/src/citra_qt/citra_qt.cpp +++ b/src/citra_qt/citra_qt.cpp @@ -489,7 +489,7 @@ void GMainWindow::InitializeWidgets() { artic_traffic_label = new QLabel(); artic_traffic_label->setToolTip( - tr("Current Artic Base traffic speed. Higher values indicate bigger transfer loads.")); + tr("Current Artic traffic speed. Higher values indicate bigger transfer loads.")); emu_speed_label = new QLabel(); emu_speed_label->setToolTip(tr("Current emulation speed. Values higher or lower than 100% " @@ -982,6 +982,7 @@ void GMainWindow::ConnectMenuEvents() { connect_menu(ui->action_Load_File, &GMainWindow::OnMenuLoadFile); connect_menu(ui->action_Install_CIA, &GMainWindow::OnMenuInstallCIA); connect_menu(ui->action_Connect_Artic, &GMainWindow::OnMenuConnectArticBase); + connect_menu(ui->action_Setup_System_Files, &GMainWindow::OnMenuSetUpSystemFiles); for (u32 region = 0; region < Core::NUM_SYSTEM_TITLE_REGIONS; region++) { connect_menu(ui->menu_Boot_Home_Menu->actions().at(region), [this, region] { OnMenuBootHomeMenu(region); }); @@ -1367,9 +1368,9 @@ bool GMainWindow::LoadROM(const QString& filename) { case Core::System::ResultStatus::ErrorArticDisconnected: QMessageBox::critical( - this, tr("Artic Base Server"), + this, tr("Artic Server"), tr(fmt::format( - "An error has occurred whilst communicating with the Artic Base Server.\n{}", + "An error has occurred whilst communicating with the Artic Server.\n{}", system.GetStatusDetails()) .c_str())); break; @@ -1401,7 +1402,9 @@ void GMainWindow::BootGame(const QString& filename) { ShutdownGame(); } - const bool is_artic = filename.startsWith(QString::fromStdString("articbase://")); + const bool is_artic = filename.startsWith(QString::fromStdString("articbase:/")) || + filename.startsWith(QString::fromStdString("articinio:/")) || + filename.startsWith(QString::fromStdString("articinin:/")); if (!is_artic && filename.endsWith(QStringLiteral(".cia"))) { const auto answer = QMessageBox::question( @@ -1444,7 +1447,7 @@ void GMainWindow::BootGame(const QString& filename) { QtConfig per_game_config(config_file_name, QtConfig::ConfigType::PerGameConfig); } - // Artic Base Server cannot accept a client multiple times, so multiple loaders are not + // Artic Server cannot accept a client multiple times, so multiple loaders are not // possible. Instead register the app loader early and do not create it again on system load. if (!loader->SupportsMultipleInstancesForSameFile()) { system.RegisterAppLoaderEarly(loader); @@ -2192,6 +2195,92 @@ void GMainWindow::OnMenuLoadFile() { BootGame(filename); } +void GMainWindow::OnMenuSetUpSystemFiles() { + QDialog dialog(this); + dialog.setWindowTitle(tr("Set Up System Files")); + + QVBoxLayout layout(&dialog); + + QLabel label_description( + tr("

Azahar needs files from a real console to be able to use some of its features.
" + "You can get such files with the Azahar " + "Artic Setup Tool
Notes:


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