From 5083c90aad41013f8b7b7ec7c5f3669174eea339 Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Thu, 13 Mar 2025 00:24:35 +0100 Subject: [PATCH] Add enable required LLE modules for online features option --- .../java/org/citra/citra_emu/NativeLibrary.kt | 12 +- .../features/settings/model/BooleanSetting.kt | 4 +- .../settings/ui/SettingsFragmentPresenter.kt | 9 + .../citra/citra_emu/utils/DocumentsTree.kt | 9 +- src/android/app/src/main/jni/config.cpp | 3 +- src/android/app/src/main/jni/default_ini.h | 4 + .../app/src/main/res/values/strings.xml | 3 +- src/citra_qt/configuration/config.cpp | 2 + .../configuration/configure_system.cpp | 12 + src/citra_qt/configuration/configure_system.h | 1 + .../configuration/configure_system.ui | 74 +++-- src/citra_sdl/config.cpp | 1 + src/common/hacks/hack_list.cpp | 40 ++- src/common/hacks/hack_list.h | 3 +- src/common/hacks/hack_manager.h | 24 +- src/common/settings.h | 4 +- src/core/core.cpp | 4 +- src/core/hle/service/am/am.cpp | 310 +++++++++++++----- src/core/hle/service/am/am.h | 10 + src/core/hle/service/service.cpp | 97 +++--- src/core/hle/service/service.h | 3 +- 21 files changed, 448 insertions(+), 181 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt index f176f6b87..c8b3d8fee 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt @@ -567,7 +567,11 @@ object NativeLibrary { @JvmStatic fun createDir(directory: String, directoryName: String): Boolean = if (FileUtil.isNativePath(directory)) { - CitraApplication.documentsTree.createDir(directory, directoryName) + try { + CitraApplication.documentsTree.createDir(directory, directoryName) + } catch (e: Exception) { + false + } } else { FileUtil.createDir(directory, directoryName) != null } @@ -641,7 +645,11 @@ object NativeLibrary { @JvmStatic fun renameFile(path: String, destinationFilename: String): Boolean = if (FileUtil.isNativePath(path)) { - CitraApplication.documentsTree.renameFile(path, destinationFilename) + try { + CitraApplication.documentsTree.renameFile(path, destinationFilename) + } catch (e: Exception) { + false + } } else { FileUtil.renameFile(path, destinationFilename) } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt index 793f028f7..25d32b1ee 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/BooleanSetting.kt @@ -17,7 +17,8 @@ enum class BooleanSetting( INSTANT_DEBUG_LOG("instant_debug_log", Settings.SECTION_DEBUG, false), CUSTOM_LAYOUT("custom_layout",Settings.SECTION_LAYOUT,false), DELAY_START_LLE_MODULES("delay_start_for_lle_modules", Settings.SECTION_DEBUG, true), - DETERMINISTIC_ASYNC_OPERATIONS("deterministic_async_operations", Settings.SECTION_DEBUG, false); + DETERMINISTIC_ASYNC_OPERATIONS("deterministic_async_operations", Settings.SECTION_DEBUG, false), + REQUIRED_ONLINE_LLE_MODULES("enable_required_online_lle_modules", Settings.SECTION_SYSTEM, false); override var boolean: Boolean = defaultValue @@ -41,6 +42,7 @@ enum class BooleanSetting( ASYNC_SHADERS, DELAY_START_LLE_MODULES, DETERMINISTIC_ASYNC_OPERATIONS, + REQUIRED_ONLINE_LLE_MODULES, ) fun from(key: String): BooleanSetting? = diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt index 3dde86f0e..c78b38738 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -1303,6 +1303,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) IntSetting.LLE_APPLETS.defaultValue ) ) + add( + SwitchSetting( + BooleanSetting.REQUIRED_ONLINE_LLE_MODULES, + R.string.enable_required_online_lle_modules, + R.string.enable_required_online_lle_modules_desc, + BooleanSetting.REQUIRED_ONLINE_LLE_MODULES.key, + BooleanSetting.REQUIRED_ONLINE_LLE_MODULES.defaultValue + ) + ) add( SliderSetting( IntSetting.CPU_CLOCK_SPEED, diff --git a/src/android/app/src/main/java/org/citra/citra_emu/utils/DocumentsTree.kt b/src/android/app/src/main/java/org/citra/citra_emu/utils/DocumentsTree.kt index 4071bd1a9..5314f0171 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/utils/DocumentsTree.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/utils/DocumentsTree.kt @@ -1,4 +1,4 @@ -// Copyright Citra Emulator Project / Lime3DS Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -161,8 +161,8 @@ class DocumentsTree { val node = resolvePath(filepath) ?: return false try { val filename = URLDecoder.decode(destinationFilename, FileUtil.DECODE_METHOD) - DocumentsContract.renameDocument(context.contentResolver, node.uri!!, filename) - node.rename(filename) + val newUri = DocumentsContract.renameDocument(context.contentResolver, node.uri!!, filename) + node.rename(filename, newUri) return true } catch (e: Exception) { error("[DocumentsTree]: Cannot rename file, error: " + e.message) @@ -255,10 +255,11 @@ class DocumentsTree { } @Synchronized - fun rename(name: String) { + fun rename(name: String, uri: Uri?) { parent ?: return parent!!.removeChild(this) this.name = name + this.uri = uri parent!!.addChild(this) } diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index 19e422324..3fd3e4f0f 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.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. @@ -235,6 +235,7 @@ void Config::ReadValues() { // System ReadSetting("System", Settings::values.is_new_3ds); ReadSetting("System", Settings::values.lle_applets); + ReadSetting("System", Settings::values.enable_required_online_lle_modules); ReadSetting("System", Settings::values.region_value); ReadSetting("System", Settings::values.init_clock); { diff --git a/src/android/app/src/main/jni/default_ini.h b/src/android/app/src/main/jni/default_ini.h index b60dde668..33ae4803b 100644 --- a/src/android/app/src/main/jni/default_ini.h +++ b/src/android/app/src/main/jni/default_ini.h @@ -329,6 +329,10 @@ is_new_3ds = # 0 (default): No, 1: Yes lle_applets = +# Whether to enable LLE modules for online play +# 0 (default): No, 1: Yes +enable_required_online_lle_modules = + # The system region that Citra will use during emulation # -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan region_value = diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index eb108e1b6..5e49a3dcc 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -778,5 +778,6 @@ Delays the start of the app when LLE modules are enabled. Deterministic Async Operations Makes async operations deterministic for debugging. Enabling this may cause freezes. - + Enable required LLE modules for online features (if installed) + Enables the LLE modules needed for online multiplayer, eShop access, etc. diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 5e261dca3..22c7811da 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -725,6 +725,7 @@ void QtConfig::ReadSystemValues() { ReadGlobalSetting(Settings::values.is_new_3ds); ReadGlobalSetting(Settings::values.lle_applets); + ReadGlobalSetting(Settings::values.enable_required_online_lle_modules); ReadGlobalSetting(Settings::values.region_value); if (global) { @@ -1248,6 +1249,7 @@ void QtConfig::SaveSystemValues() { WriteGlobalSetting(Settings::values.is_new_3ds); WriteGlobalSetting(Settings::values.lle_applets); + WriteGlobalSetting(Settings::values.enable_required_online_lle_modules); WriteGlobalSetting(Settings::values.region_value); if (global) { diff --git a/src/citra_qt/configuration/configure_system.cpp b/src/citra_qt/configuration/configure_system.cpp index 920e1ce70..005ebe394 100644 --- a/src/citra_qt/configuration/configure_system.cpp +++ b/src/citra_qt/configuration/configure_system.cpp @@ -319,6 +319,8 @@ void ConfigureSystem::SetConfiguration() { ui->toggle_new_3ds->setChecked(Settings::values.is_new_3ds.GetValue()); ui->toggle_lle_applets->setChecked(Settings::values.lle_applets.GetValue()); + ui->enable_required_online_lle_modules->setChecked( + Settings::values.enable_required_online_lle_modules.GetValue()); ui->plugin_loader->setChecked(Settings::values.plugin_loader_enabled.GetValue()); ui->allow_plugin_loader->setChecked(Settings::values.allow_plugin_loader.GetValue()); } @@ -429,6 +431,9 @@ void ConfigureSystem::ApplyConfiguration() { is_new_3ds); ConfigurationShared::ApplyPerGameSetting(&Settings::values.lle_applets, ui->toggle_lle_applets, lle_applets); + ConfigurationShared::ApplyPerGameSetting( + &Settings::values.enable_required_online_lle_modules, + ui->enable_required_online_lle_modules, required_online_lle_modules); Settings::values.init_clock = static_cast(ui->combo_init_clock->currentIndex()); @@ -451,6 +456,8 @@ void ConfigureSystem::ApplyConfiguration() { Settings::values.init_time_offset = time_offset_days + time_offset_time; Settings::values.is_new_3ds = ui->toggle_new_3ds->isChecked(); Settings::values.lle_applets = ui->toggle_lle_applets->isChecked(); + Settings::values.enable_required_online_lle_modules = + ui->enable_required_online_lle_modules->isChecked(); Settings::values.plugin_loader_enabled.SetValue(ui->plugin_loader->isChecked()); Settings::values.allow_plugin_loader.SetValue(ui->allow_plugin_loader->isChecked()); @@ -605,6 +612,8 @@ void ConfigureSystem::SetupPerGameUI() { if (Settings::IsConfiguringGlobal()) { ui->toggle_new_3ds->setEnabled(Settings::values.is_new_3ds.UsingGlobal()); ui->toggle_lle_applets->setEnabled(Settings::values.lle_applets.UsingGlobal()); + ui->enable_required_online_lle_modules->setEnabled( + Settings::values.enable_required_online_lle_modules.UsingGlobal()); return; } @@ -649,4 +658,7 @@ void ConfigureSystem::SetupPerGameUI() { is_new_3ds); ConfigurationShared::SetColoredTristate(ui->toggle_lle_applets, Settings::values.lle_applets, lle_applets); + ConfigurationShared::SetColoredTristate(ui->enable_required_online_lle_modules, + Settings::values.enable_required_online_lle_modules, + required_online_lle_modules); } diff --git a/src/citra_qt/configuration/configure_system.h b/src/citra_qt/configuration/configure_system.h index f4b721527..086b84a22 100644 --- a/src/citra_qt/configuration/configure_system.h +++ b/src/citra_qt/configuration/configure_system.h @@ -63,6 +63,7 @@ private: Core::System& system; ConfigurationShared::CheckState is_new_3ds; ConfigurationShared::CheckState lle_applets; + ConfigurationShared::CheckState required_online_lle_modules; bool enabled = false; std::shared_ptr cfg; diff --git a/src/citra_qt/configuration/configure_system.ui b/src/citra_qt/configuration/configure_system.ui index ff4739245..5c015305a 100644 --- a/src/citra_qt/configuration/configure_system.ui +++ b/src/citra_qt/configuration/configure_system.ui @@ -78,7 +78,17 @@ - + + + + Enable required LLE modules for online features (if installed) + + + Enables the LLE modules needed for online multiplayer, eShop access, etc. + + + + @@ -91,21 +101,21 @@ - + Username - + Birthday - + @@ -176,14 +186,14 @@ - + Language - + Note: this can be overridden when region setting is auto-select @@ -250,14 +260,14 @@ - + Sound output mode - + @@ -276,24 +286,24 @@ - + Country - + - + Clock - + @@ -307,28 +317,28 @@ - + Startup time - + yyyy-MM-ddTHH:mm:ss - + Offset time - + @@ -352,14 +362,14 @@ - + Initial System Ticks - + @@ -373,14 +383,14 @@ - + Initial System Ticks Override - + @@ -393,21 +403,21 @@ - + Play Coins - + 300 - + <html><head/><body><p>Number of steps per hour reported by the pedometer. Range from 0 to 65,535.</p></body></html> @@ -417,28 +427,28 @@ - + 9999 - + Run System Setup when Home Menu is launched - + Console ID: - + @@ -454,14 +464,14 @@ - + MAC: - + @@ -477,21 +487,21 @@ - + 3GX Plugin Loader: - + Enable 3GX plugin loader - + Allow applications to change plugin loader state diff --git a/src/citra_sdl/config.cpp b/src/citra_sdl/config.cpp index 3db076fab..a55b96eed 100644 --- a/src/citra_sdl/config.cpp +++ b/src/citra_sdl/config.cpp @@ -224,6 +224,7 @@ void SdlConfig::ReadValues() { // System ReadSetting("System", Settings::values.is_new_3ds); ReadSetting("System", Settings::values.lle_applets); + ReadSetting("System", Settings::values.enable_required_online_lle_modules); ReadSetting("System", Settings::values.region_value); ReadSetting("System", Settings::values.init_clock); { diff --git a/src/common/hacks/hack_list.cpp b/src/common/hacks/hack_list.cpp index be043ed47..b438a2cb3 100644 --- a/src/common/hacks/hack_list.cpp +++ b/src/common/hacks/hack_list.cpp @@ -1,4 +1,4 @@ -// Copyright 2024 Azahar Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -70,5 +70,43 @@ HackManager hack_manager = { }, }}, + {HackType::ONLINE_LLE_REQUIRED, + HackEntry{ + .mode = HackAllowMode::FORCE, + .affected_title_ids = + { + // eShop + 0x0004001000020900, // JAP + 0x0004001000021900, // USA + 0x0004001000022900, // EUR + 0x0004001000027900, // KOR + 0x0004001000028900, // TWN + + // System Settings + 0x0004001000020000, // JAP + 0x0004001000021000, // USA + 0x0004001000022000, // EUR + 0x0004001000026000, // CHN + 0x0004001000027000, // KOR + 0x0004001000028000, // TWN + + // Nintendo Network ID Settings + 0x000400100002BF00, // JAP + 0x000400100002C000, // USA + 0x000400100002C100, // EUR + + // System Settings + 0x0004003000008202, // JAP + 0x0004003000008F02, // USA + 0x0004003000009802, // EUR + 0x000400300000A102, // CHN + 0x000400300000A902, // KOR + 0x000400300000B102, // TWN + + // Pretendo Network's Nimbus + 0x000400000D40D200, + }, + }}, + }}; } \ No newline at end of file diff --git a/src/common/hacks/hack_list.h b/src/common/hacks/hack_list.h index 55986a617..1a21a0575 100644 --- a/src/common/hacks/hack_list.h +++ b/src/common/hacks/hack_list.h @@ -1,4 +1,4 @@ -// Copyright 2024 Azahar Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -12,6 +12,7 @@ enum class HackType : int { RIGHT_EYE_DISABLE, ACCURATE_MULTIPLICATION, DECRYPTION_AUTHORIZED, + ONLINE_LLE_REQUIRED, }; class UserHackData {}; diff --git a/src/common/hacks/hack_manager.h b/src/common/hacks/hack_manager.h index fba859247..f13d0580f 100644 --- a/src/common/hacks/hack_manager.h +++ b/src/common/hacks/hack_manager.h @@ -1,4 +1,4 @@ -// Copyright 2024 Azahar Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -32,6 +32,28 @@ struct HackManager { return (hack != nullptr) ? hack->mode : default_mode; } + /** + * Overrides the provided boolean setting depending on the hack type for the title ID + * If there is no hack, or the hack is set to allow, the setting value is returned + * If the hack disallows, false is returned. + * If the hack forces, true is returned. + */ + bool OverrideBooleanSetting(const HackType& type, u64 title_id, bool setting_value) { + const HackEntry* hack = GetHack(type, title_id); + if (hack == nullptr) + return setting_value; + switch (hack->mode) { + case HackAllowMode::DISALLOW: + return false; + case HackAllowMode::FORCE: + return true; + case HackAllowMode::ALLOW: + default: + break; + } + return setting_value; + } + std::multimap entries; }; diff --git a/src/common/settings.h b/src/common/settings.h index b3351085c..21f10e62e 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -450,8 +450,10 @@ struct Values { Setting use_cpu_jit{true, "use_cpu_jit"}; SwitchableSetting cpu_clock_percentage{100, 5, 400, "cpu_clock_percentage"}; SwitchableSetting is_new_3ds{true, "is_new_3ds"}; - SwitchableSetting lle_applets{false, "lle_applets"}; + SwitchableSetting lle_applets{true, "lle_applets"}; SwitchableSetting deterministic_async_operations{false, "deterministic_async_operations"}; + SwitchableSetting enable_required_online_lle_modules{ + false, "enable_required_online_lle_modules"}; // Data Storage Setting use_virtual_sd{true, "use_virtual_sd"}; diff --git a/src/core/core.cpp b/src/core/core.cpp index 31579acac..b017dc413 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -508,8 +508,10 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, service_manager = std::make_unique(*this); archive_manager = std::make_unique(*this); + u64 loading_title_id = 0; + app_loader->ReadProgramId(loading_title_id); HW::AES::InitKeys(); - Service::Init(*this, lle_modules, !app_loader->DoingInitialSetup()); + Service::Init(*this, loading_title_id, lle_modules, !app_loader->DoingInitialSetup()); GDBStub::DeferStart(); if (!registered_image_interface) { diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 5a3098f8d..8c82b9265 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -861,11 +861,6 @@ Result TicketFile::Commit() { ticket_id = ticket.GetTicketID(); const auto ticket_path = GetTicketPath(ticket.GetTitleID(), ticket.GetTicketID()); - // Create ticket folder if it does not exist - std::string ticket_folder; - Common::SplitPath(ticket_path, &ticket_folder, nullptr, nullptr); - FileUtil::CreateFullPath(ticket_folder); - // Save ticket if (ticket.Save(ticket_path) != Loader::ResultStatus::Success) { LOG_ERROR(Service_AM, "Failed to install ticket provided to TicketFile."); @@ -1182,6 +1177,13 @@ std::string GetMediaTitlePath(Service::FS::MediaType media_type) { } void Module::ScanForTickets() { + scan_tickets_future = std::async([this]() { + std::scoped_lock lock(am_lists_mutex); + ScanForTicketsImpl(); + }); +} + +void Module::ScanForTicketsImpl() { am_ticket_list.clear(); LOG_DEBUG(Service_AM, "Starting ticket scan"); @@ -1210,6 +1212,13 @@ void Module::ScanForTickets() { } void Module::ScanForTitles(Service::FS::MediaType media_type) { + scan_titles_future = std::async([this, media_type]() { + std::scoped_lock lock(am_lists_mutex); + ScanForTitlesImpl(media_type); + }); +} + +void Module::ScanForTitlesImpl(Service::FS::MediaType media_type) { am_title_list[static_cast(media_type)].clear(); LOG_DEBUG(Service_AM, "Starting title scan for media_type={}", static_cast(media_type)); @@ -1245,9 +1254,12 @@ void Module::ScanForTitles(Service::FS::MediaType media_type) { } void Module::ScanForAllTitles() { - ScanForTickets(); - ScanForTitles(Service::FS::MediaType::NAND); - ScanForTitles(Service::FS::MediaType::SDMC); + scan_all_future = std::async([this]() { + std::scoped_lock lock(am_lists_mutex); + ScanForTicketsImpl(); + ScanForTitlesImpl(Service::FS::MediaType::NAND); + ScanForTitlesImpl(Service::FS::MediaType::SDMC); + }); } Module::Interface::Interface(std::shared_ptr am, const char* name, u32 max_session) @@ -1307,7 +1319,7 @@ void Module::Interface::GetNumPrograms(Kernel::HLERequestContext& ctx) { }, true); } else { - + std::scoped_lock lock(am->am_lists_mutex); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(ResultSuccess); rb.Push(static_cast(am->am_title_list[media_type].size())); @@ -1648,6 +1660,7 @@ void Module::Interface::GetProgramList(Kernel::HLERequestContext& ctx) { return; } + std::scoped_lock lock(am->am_lists_mutex); u32 media_count = static_cast(am->am_title_list[media_type].size()); u32 copied = std::min(media_count, count); @@ -1660,8 +1673,8 @@ void Module::Interface::GetProgramList(Kernel::HLERequestContext& ctx) { } Result GetTitleInfoFromList(std::span title_id_list, Service::FS::MediaType media_type, - Kernel::MappedBuffer& title_info_out) { - std::size_t write_offset = 0; + std::vector& title_info_out) { + title_info_out.reserve(title_id_list.size()); for (u32 i = 0; i < title_id_list.size(); i++) { std::string tmd_path = GetTitleMetadataPath(media_type, title_id_list[i]); @@ -1682,8 +1695,7 @@ Result GetTitleInfoFromList(std::span title_id_list, Service::FS::Med } 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); + title_info_out.push_back(title_info); } return ResultSuccess; @@ -1773,41 +1785,64 @@ void Module::Interface::GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool true); } else { - auto& title_id_list_buffer = rp.PopMappedBuffer(); - auto& title_info_out = rp.PopMappedBuffer(); + struct AsyncData { + Service::FS::MediaType media_type; + std::vector title_id_list; - std::vector title_id_list(title_count); - title_id_list_buffer.Read(title_id_list.data(), 0, title_count * sizeof(u64)); + Result res{0}; + std::vector out; + Kernel::MappedBuffer* title_id_list_buffer; + Kernel::MappedBuffer* title_info_out; + }; + auto async_data = std::make_shared(); + async_data->media_type = media_type; + async_data->title_id_list.resize(title_count); + async_data->title_id_list_buffer = &rp.PopMappedBuffer(); + async_data->title_id_list_buffer->Read(async_data->title_id_list.data(), 0, + title_count * sizeof(u64)); + async_data->title_info_out = &rp.PopMappedBuffer(); - // 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 importing context is present and not - // committed. If that's the case, return not found - Result result = ResultSuccess; - 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); + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + // 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 importing + // context is present and not committed. If that's the case, return not found + for (auto tid : async_data->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); + async_data->res = + Result(ErrorDescription::NotFound, ErrorModule::AM, + ErrorSummary::InvalidState, ErrorLevel::Permanent); + } + } } - } - } - if (result.IsSuccess()) - result = GetTitleInfoFromList(title_id_list, media_type, title_info_out); - - IPC::RequestBuilder rb = rp.MakeBuilder(1, ignore_platform ? 0 : 4); - rb.Push(result); - if (!ignore_platform) { - rb.PushMappedBuffer(title_id_list_buffer); - rb.PushMappedBuffer(title_info_out); - } + if (async_data->res.IsSuccess()) { + async_data->res = GetTitleInfoFromList(async_data->title_id_list, + async_data->media_type, async_data->out); + } + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + if (async_data->res.IsSuccess()) { + async_data->title_info_out->Write(async_data->out.data(), 0, + async_data->out.size() * sizeof(TitleInfo)); + } + IPC::RequestBuilder rb(ctx, 1, 4); + rb.Push(async_data->res); + rb.PushMappedBuffer(*async_data->title_id_list_buffer); + rb.PushMappedBuffer(*async_data->title_info_out); + }, + true); } } @@ -1954,32 +1989,74 @@ void Module::Interface::GetDLCTitleInfos(Kernel::HLERequestContext& ctx) { }, true); } else { - auto& title_id_list_buffer = rp.PopMappedBuffer(); - auto& title_info_out = rp.PopMappedBuffer(); + struct AsyncData { + Service::FS::MediaType media_type; + std::vector title_id_list; - std::vector title_id_list(title_count); - title_id_list_buffer.Read(title_id_list.data(), 0, title_count * sizeof(u64)); + Result res{0}; + std::vector out; + Kernel::MappedBuffer* title_id_list_buffer; + Kernel::MappedBuffer* title_info_out; + }; + auto async_data = std::make_shared(); + async_data->media_type = media_type; + async_data->title_id_list.resize(title_count); + async_data->title_id_list_buffer = &rp.PopMappedBuffer(); + async_data->title_id_list_buffer->Read(async_data->title_id_list.data(), 0, + title_count * sizeof(u64)); + async_data->title_info_out = &rp.PopMappedBuffer(); - Result result = ResultSuccess; + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + // Validate that DLC TIDs were passed in + for (u32 i = 0; i < async_data->title_id_list.size(); i++) { + u32 tid_high = static_cast(async_data->title_id_list[i] >> 32); + if (tid_high != TID_HIGH_DLC) { + async_data->res = Result(ErrCodes::InvalidTIDInList, ErrorModule::AM, + ErrorSummary::InvalidArgument, ErrorLevel::Usage); + break; + } + } - // Validate that DLC TIDs were passed in - for (u32 i = 0; i < title_count; i++) { - u32 tid_high = static_cast(title_id_list[i] >> 32); - if (tid_high != TID_HIGH_DLC) { - result = Result(ErrCodes::InvalidTIDInList, ErrorModule::AM, - ErrorSummary::InvalidArgument, ErrorLevel::Usage); - break; - } - } + // 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 importing + // context is present and not committed. If that's the case, return not found + for (auto tid : async_data->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); + async_data->res = + Result(ErrorDescription::NotFound, ErrorModule::AM, + ErrorSummary::InvalidState, ErrorLevel::Permanent); + } + } + } - if (result.IsSuccess()) { - result = GetTitleInfoFromList(title_id_list, media_type, title_info_out); - } - - IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); - rb.Push(result); - rb.PushMappedBuffer(title_id_list_buffer); - rb.PushMappedBuffer(title_info_out); + if (async_data->res.IsSuccess()) { + async_data->res = GetTitleInfoFromList(async_data->title_id_list, + async_data->media_type, async_data->out); + } + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + if (async_data->res.IsSuccess()) { + async_data->title_info_out->Write(async_data->out.data(), 0, + async_data->out.size() * sizeof(TitleInfo)); + } + IPC::RequestBuilder rb(ctx, 1, 4); + rb.Push(async_data->res); + rb.PushMappedBuffer(*async_data->title_id_list_buffer); + rb.PushMappedBuffer(*async_data->title_info_out); + }, + true); } } @@ -2059,32 +2136,74 @@ void Module::Interface::GetPatchTitleInfos(Kernel::HLERequestContext& ctx) { }, true); } else { - auto& title_id_list_buffer = rp.PopMappedBuffer(); - auto& title_info_out = rp.PopMappedBuffer(); + struct AsyncData { + Service::FS::MediaType media_type; + std::vector title_id_list; - std::vector title_id_list(title_count); - title_id_list_buffer.Read(title_id_list.data(), 0, title_count * sizeof(u64)); + Result res{0}; + std::vector out; + Kernel::MappedBuffer* title_id_list_buffer; + Kernel::MappedBuffer* title_info_out; + }; + auto async_data = std::make_shared(); + async_data->media_type = media_type; + async_data->title_id_list.resize(title_count); + async_data->title_id_list_buffer = &rp.PopMappedBuffer(); + async_data->title_id_list_buffer->Read(async_data->title_id_list.data(), 0, + title_count * sizeof(u64)); + async_data->title_info_out = &rp.PopMappedBuffer(); - Result result = ResultSuccess; + ctx.RunAsync( + [this, async_data](Kernel::HLERequestContext& ctx) { + // Validate that update TIDs were passed in + for (u32 i = 0; i < async_data->title_id_list.size(); i++) { + u32 tid_high = static_cast(async_data->title_id_list[i] >> 32); + if (tid_high != TID_HIGH_UPDATE) { + async_data->res = Result(ErrCodes::InvalidTIDInList, ErrorModule::AM, + ErrorSummary::InvalidArgument, ErrorLevel::Usage); + break; + } + } - // Validate that update TIDs were passed in - for (u32 i = 0; i < title_count; i++) { - u32 tid_high = static_cast(title_id_list[i] >> 32); - if (tid_high != TID_HIGH_UPDATE) { - result = Result(ErrCodes::InvalidTIDInList, ErrorModule::AM, - ErrorSummary::InvalidArgument, ErrorLevel::Usage); - break; - } - } + // 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 importing + // context is present and not committed. If that's the case, return not found + for (auto tid : async_data->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); + async_data->res = + Result(ErrorDescription::NotFound, ErrorModule::AM, + ErrorSummary::InvalidState, ErrorLevel::Permanent); + } + } + } - if (result.IsSuccess()) { - result = GetTitleInfoFromList(title_id_list, media_type, title_info_out); - } - - IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); - rb.Push(result); - rb.PushMappedBuffer(title_id_list_buffer); - rb.PushMappedBuffer(title_info_out); + if (async_data->res.IsSuccess()) { + async_data->res = GetTitleInfoFromList(async_data->title_id_list, + async_data->media_type, async_data->out); + } + return 0; + }, + [async_data](Kernel::HLERequestContext& ctx) { + if (async_data->res.IsSuccess()) { + async_data->title_info_out->Write(async_data->out.data(), 0, + async_data->out.size() * sizeof(TitleInfo)); + } + IPC::RequestBuilder rb(ctx, 1, 4); + rb.Push(async_data->res); + rb.PushMappedBuffer(*async_data->title_id_list_buffer); + rb.PushMappedBuffer(*async_data->title_info_out); + }, + true); } } @@ -2266,6 +2385,7 @@ void Module::Interface::DeleteTicket(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "title_id={:016X}", title_id); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + std::scoped_lock lock(am->am_lists_mutex); auto range = am->am_ticket_list.equal_range(title_id); if (range.first == range.second) { rb.Push(Result(ErrorDescription::AlreadyDone, ErrorModule::AM, ErrorSummary::Success, @@ -2288,6 +2408,7 @@ void Module::Interface::GetNumTickets(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_AM, ""); + std::scoped_lock lock(am->am_lists_mutex); u32 ticket_count = static_cast(am->am_ticket_list.size()); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); @@ -2304,6 +2425,7 @@ void Module::Interface::GetTicketList(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "ticket_list_count={}, ticket_index={}", ticket_list_count, ticket_index); u32 tickets_written = 0; + std::scoped_lock lock(am->am_lists_mutex); auto it = am->am_ticket_list.begin(); std::advance(it, std::min(static_cast(ticket_index), am->am_ticket_list.size())); @@ -2631,6 +2753,7 @@ void Module::Interface::GetPersonalizedTicketInfoList(Kernel::HLERequestContext& LOG_DEBUG(Service_AM, "(STUBBED) called, ticket_count={}", ticket_count); u32 written = 0; + std::scoped_lock lock(am->am_lists_mutex); for (auto it = am->am_ticket_list.begin(); it != am->am_ticket_list.end() && written < ticket_count; it++) { u64 title_id = it->first; @@ -3285,6 +3408,7 @@ void Module::Interface::EndImportTicket(Kernel::HLERequestContext& ctx) { auto ticket_file = GetFileBackendFromSession(ticket); if (ticket_file.Succeeded()) { rb.Push(ticket_file.Unwrap()->Commit()); + std::scoped_lock lock(am->am_lists_mutex); am->am_ticket_list.insert(std::make_pair(ticket_file.Unwrap()->GetTitleID(), ticket_file.Unwrap()->GetTicketID())); } else { @@ -3308,6 +3432,7 @@ void Module::Interface::BeginImportTitle(Kernel::HLERequestContext& ctx) { am->importing_title = std::make_shared(Core::System::GetInstance(), title_id, media_type); + std::scoped_lock lock(am->am_lists_mutex); auto entries = am->am_ticket_list.find(title_id); if (entries == am->am_ticket_list.end()) { // Ticket is not installed @@ -3770,11 +3895,11 @@ void Module::Interface::Sign(Kernel::HLERequestContext& ctx) { template void Module::serialize(Archive& ar, const unsigned int) { + std::scoped_lock lock(am_lists_mutex); DEBUG_SERIALIZATION_POINT; ar & cia_installing; ar & force_old_device_id; ar & force_new_device_id; - ar & am_title_list; ar & system_updater_mutex; } SERIALIZE_IMPL(Module) @@ -3815,6 +3940,7 @@ void Module::Interface::DeleteTicketId(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "title_id={:016X} ticket_id={}", title_id, ticket_id); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + std::scoped_lock lock(am->am_lists_mutex); auto range = am->am_ticket_list.equal_range(title_id); auto it = range.first; for (; it != range.second; it++) { @@ -3842,6 +3968,7 @@ void Module::Interface::GetNumTicketIds(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "title_id={:016X}", title_id); + std::scoped_lock lock(am->am_lists_mutex); auto range = am->am_ticket_list.equal_range(title_id); u32 count = static_cast(std::distance(range.first, range.second)); @@ -3861,6 +3988,7 @@ void Module::Interface::GetTicketIdList(Kernel::HLERequestContext& ctx) { auto out_buf = rp.PopMappedBuffer(); u32 index = 0; + std::scoped_lock lock(am->am_lists_mutex); for (auto [it, rangeEnd] = am->am_ticket_list.equal_range(title_id); it != rangeEnd && index < list_count; index++, it++) { u64 ticket_id = it->second; @@ -3878,6 +4006,7 @@ void Module::Interface::GetNumTicketsOfProgram(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "title_id={:016X}", title_id); + std::scoped_lock lock(am->am_lists_mutex); auto range = am->am_ticket_list.equal_range(title_id); u32 count = static_cast(std::distance(range.first, range.second)); @@ -3895,6 +4024,7 @@ void Module::Interface::ListTicketInfos(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "(STUBBED) called, ticket_count={}", ticket_count); + std::scoped_lock lock(am->am_lists_mutex); auto range = am->am_ticket_list.equal_range(title_id); auto it = range.first; std::advance(it, std::min(static_cast(skip), @@ -3943,6 +4073,7 @@ void Module::Interface::ExportTicketWrapped(Kernel::HLERequestContext& ctx) { return; } + std::scoped_lock lock(am->am_lists_mutex); auto range = am->am_ticket_list.equal_range(title_id); auto it = range.first; for (; it != range.second; it++) @@ -4006,6 +4137,7 @@ void Module::Interface::ExportTicketWrapped(Kernel::HLERequestContext& ctx) { } Module::Module(Core::System& _system) : system(_system) { + FileUtil::CreateFullPath(GetTicketDirectory()); ScanForAllTitles(); system_updater_mutex = system.Kernel().CreateMutex(false, "AM::SystemUpdaterMutex"); } diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index e3fcbacf0..fa1e9d622 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -6,7 +6,9 @@ #include #include +#include #include +#include #include #include #include @@ -1022,12 +1024,16 @@ public: private: void ScanForTickets(); + void ScanForTicketsImpl(); + /** * Scans the for titles in a storage medium for listing. * @param media_type the storage medium to scan */ void ScanForTitles(Service::FS::MediaType media_type); + void ScanForTitlesImpl(Service::FS::MediaType media_type); + /** * Scans all storage mediums for titles for listing. */ @@ -1037,6 +1043,10 @@ private: bool cia_installing = false; bool force_old_device_id = false; bool force_new_device_id = false; + std::future scan_tickets_future; + std::future scan_titles_future; + std::future scan_all_future; + std::mutex am_lists_mutex; std::array, 3> am_title_list; std::multimap am_ticket_list; std::shared_ptr system_updater_mutex; diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 025f566dc..b574b574a 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -5,6 +5,7 @@ #include #include #include "common/assert.h" +#include "common/hacks/hack_manager.h" #include "common/logging/log.h" #include "core/core.h" #include "core/hle/ipc.h" @@ -59,53 +60,54 @@ namespace Service { const std::array service_module_map{ - {{"FS", 0x00040130'00001102, FS::InstallInterfaces}, - {"PM", 0x00040130'00001202, PM::InstallInterfaces}, - {"LDR", 0x00040130'00003702, LDR::InstallInterfaces}, - {"PXI", 0x00040130'00001402, PXI::InstallInterfaces}, + {{"FS", 0x00040130'00001102, FS::InstallInterfaces, false}, + {"PM", 0x00040130'00001202, PM::InstallInterfaces, false}, + {"LDR", 0x00040130'00003702, LDR::InstallInterfaces, false}, + {"PXI", 0x00040130'00001402, PXI::InstallInterfaces, false}, - {"ERR", 0x00040030'00008A02, ERR::InstallInterfaces}, - {"AC", 0x00040130'00002402, AC::InstallInterfaces}, - {"ACT", 0x00040130'00003802, ACT::InstallInterfaces}, - {"AM", 0x00040130'00001502, AM::InstallInterfaces}, - {"BOSS", 0x00040130'00003402, BOSS::InstallInterfaces}, + {"ERR", 0x00040030'00008A02, ERR::InstallInterfaces, false}, + {"AC", 0x00040130'00002402, AC::InstallInterfaces, false}, + {"ACT", 0x00040130'00003802, ACT::InstallInterfaces, true}, + {"AM", 0x00040130'00001502, AM::InstallInterfaces, false}, + {"BOSS", 0x00040130'00003402, BOSS::InstallInterfaces, false}, {"CAM", 0x00040130'00001602, [](Core::System& system) { CAM::InstallInterfaces(system); Y2R::InstallInterfaces(system); - }}, - {"CECD", 0x00040130'00002602, CECD::InstallInterfaces}, - {"CFG", 0x00040130'00001702, CFG::InstallInterfaces}, - {"DLP", 0x00040130'00002802, DLP::InstallInterfaces}, - {"DSP", 0x00040130'00001A02, DSP::InstallInterfaces}, - {"FRD", 0x00040130'00003202, FRD::InstallInterfaces}, - {"GSP", 0x00040130'00001C02, GSP::InstallInterfaces}, - {"HID", 0x00040130'00001D02, HID::InstallInterfaces}, - {"IR", 0x00040130'00003302, IR::InstallInterfaces}, - {"MIC", 0x00040130'00002002, MIC::InstallInterfaces}, - {"MVD", 0x00040130'20004102, MVD::InstallInterfaces}, - {"NDM", 0x00040130'00002B02, NDM::InstallInterfaces}, - {"NEWS", 0x00040130'00003502, NEWS::InstallInterfaces}, - {"NFC", 0x00040130'00004002, NFC::InstallInterfaces}, - {"NIM", 0x00040130'00002C02, NIM::InstallInterfaces}, - {"NS", 0x00040130'00008002, APT::InstallInterfaces}, - {"NWM", 0x00040130'00002D02, NWM::InstallInterfaces}, - {"PTM", 0x00040130'00002202, PTM::InstallInterfaces}, - {"QTM", 0x00040130'00004202, QTM::InstallInterfaces}, - {"CSND", 0x00040130'00002702, CSND::InstallInterfaces}, - {"HTTP", 0x00040130'00002902, HTTP::InstallInterfaces}, - {"SOC", 0x00040130'00002E02, SOC::InstallInterfaces}, - {"SSL", 0x00040130'00002F02, SSL::InstallInterfaces}, - {"PS", 0x00040130'00003102, PS::InstallInterfaces}, - {"PLGLDR", 0x00040130'00006902, PLGLDR::InstallInterfaces}, + }, + false}, + {"CECD", 0x00040130'00002602, CECD::InstallInterfaces, false}, + {"CFG", 0x00040130'00001702, CFG::InstallInterfaces, false}, + {"DLP", 0x00040130'00002802, DLP::InstallInterfaces, false}, + {"DSP", 0x00040130'00001A02, DSP::InstallInterfaces, false}, + {"FRD", 0x00040130'00003202, FRD::InstallInterfaces, true}, + {"GSP", 0x00040130'00001C02, GSP::InstallInterfaces, false}, + {"HID", 0x00040130'00001D02, HID::InstallInterfaces, false}, + {"IR", 0x00040130'00003302, IR::InstallInterfaces, false}, + {"MIC", 0x00040130'00002002, MIC::InstallInterfaces, false}, + {"MVD", 0x00040130'20004102, MVD::InstallInterfaces, false}, + {"NDM", 0x00040130'00002B02, NDM::InstallInterfaces, false}, + {"NEWS", 0x00040130'00003502, NEWS::InstallInterfaces, false}, + {"NFC", 0x00040130'00004002, NFC::InstallInterfaces, false}, + {"NIM", 0x00040130'00002C02, NIM::InstallInterfaces, true}, + {"NS", 0x00040130'00008002, APT::InstallInterfaces, false}, + {"NWM", 0x00040130'00002D02, NWM::InstallInterfaces, false}, + {"PTM", 0x00040130'00002202, PTM::InstallInterfaces, false}, + {"QTM", 0x00040130'00004202, QTM::InstallInterfaces, false}, + {"CSND", 0x00040130'00002702, CSND::InstallInterfaces, false}, + {"HTTP", 0x00040130'00002902, HTTP::InstallInterfaces, false}, + {"SOC", 0x00040130'00002E02, SOC::InstallInterfaces, false}, + {"SSL", 0x00040130'00002F02, SSL::InstallInterfaces, false}, + {"PS", 0x00040130'00003102, PS::InstallInterfaces, false}, + {"PLGLDR", 0x00040130'00006902, PLGLDR::InstallInterfaces, false}, + {"MCU", 0x00040130'00001F02, MCU::InstallInterfaces, false}, // no HLE implementation - {"CDC", 0x00040130'00001802, nullptr}, - {"GPIO", 0x00040130'00001B02, nullptr}, - {"I2C", 0x00040130'00001E02, nullptr}, - {"MCU", 0x00040130'00001F02, MCU::InstallInterfaces}, - {"MP", 0x00040130'00002A02, nullptr}, - {"PDN", 0x00040130'00002102, nullptr}, - {"SPI", 0x00040130'00002302, nullptr}}}; + {"CDC", 0x00040130'00001802, nullptr, false}, + {"GPIO", 0x00040130'00001B02, nullptr, false}, + {"I2C", 0x00040130'00001E02, nullptr, false}, + {"MP", 0x00040130'00002A02, nullptr, false}, + {"PDN", 0x00040130'00002102, nullptr, false}, + {"SPI", 0x00040130'00002302, nullptr, false}}}; /** * Creates a function string for logging, complete with the name (or header code, depending @@ -195,8 +197,13 @@ std::string ServiceFrameworkBase::GetFunctionName(IPC::Header header) const { return itr->second.name; } -static bool AttemptLLE(const ServiceModuleInfo& service_module) { - if (!Settings::values.lle_modules.at(service_module.name)) +static bool AttemptLLE(const ServiceModuleInfo& service_module, u64 loading_titleid) { + const bool enable_recommended_lle_modules = Common::Hacks::hack_manager.OverrideBooleanSetting( + Common::Hacks::HackType::ONLINE_LLE_REQUIRED, loading_titleid, + Settings::values.enable_required_online_lle_modules.GetValue()); + + if (!Settings::values.lle_modules.at(service_module.name) && + (!enable_recommended_lle_modules || !service_module.is_online_recommended)) return false; std::unique_ptr loader = Loader::GetLoader(AM::GetTitleContentPath(FS::MediaType::NAND, service_module.title_id)); @@ -220,7 +227,7 @@ static bool AttemptLLE(const ServiceModuleInfo& service_module) { } /// Initialize ServiceManager -void Init(Core::System& core, std::vector& lle_modules, bool allow_lle) { +void Init(Core::System& core, u64 loading_titleid, std::vector& lle_modules, bool allow_lle) { SM::ServiceManager::InstallInterfaces(core); core.Kernel().SetAppMainThreadExtendedSleep(false); bool lle_module_present = false; @@ -237,7 +244,7 @@ void Init(Core::System& core, std::vector& lle_modules, bool allow_lle) { const bool has_lle = allow_lle && core.GetSaveStateStatus() != Core::System::SaveStateStatus::LOADING && - AttemptLLE(service_module); + AttemptLLE(service_module, loading_titleid); if (has_lle) { lle_modules.push_back(service_module.title_id); } diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index d61633cf6..fbe368c2c 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -184,12 +184,13 @@ private: }; /// Initialize ServiceManager -void Init(Core::System& system, std::vector& lle_modules, bool allow_lle); +void Init(Core::System& system, u64 loading_titleid, std::vector& lle_modules, bool allow_lle); struct ServiceModuleInfo { std::string name; u64 title_id; std::function init_function; + bool is_online_recommended; }; extern const std::array service_module_map;