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;