Add enable required LLE modules for online features option

This commit is contained in:
PabloMK7 2025-03-13 00:24:35 +01:00
parent 42d77cd720
commit 5083c90aad
21 changed files with 448 additions and 181 deletions

View File

@ -567,7 +567,11 @@ object NativeLibrary {
@JvmStatic @JvmStatic
fun createDir(directory: String, directoryName: String): Boolean = fun createDir(directory: String, directoryName: String): Boolean =
if (FileUtil.isNativePath(directory)) { if (FileUtil.isNativePath(directory)) {
CitraApplication.documentsTree.createDir(directory, directoryName) try {
CitraApplication.documentsTree.createDir(directory, directoryName)
} catch (e: Exception) {
false
}
} else { } else {
FileUtil.createDir(directory, directoryName) != null FileUtil.createDir(directory, directoryName) != null
} }
@ -641,7 +645,11 @@ object NativeLibrary {
@JvmStatic @JvmStatic
fun renameFile(path: String, destinationFilename: String): Boolean = fun renameFile(path: String, destinationFilename: String): Boolean =
if (FileUtil.isNativePath(path)) { if (FileUtil.isNativePath(path)) {
CitraApplication.documentsTree.renameFile(path, destinationFilename) try {
CitraApplication.documentsTree.renameFile(path, destinationFilename)
} catch (e: Exception) {
false
}
} else { } else {
FileUtil.renameFile(path, destinationFilename) FileUtil.renameFile(path, destinationFilename)
} }

View File

@ -17,7 +17,8 @@ enum class BooleanSetting(
INSTANT_DEBUG_LOG("instant_debug_log", Settings.SECTION_DEBUG, false), INSTANT_DEBUG_LOG("instant_debug_log", Settings.SECTION_DEBUG, false),
CUSTOM_LAYOUT("custom_layout",Settings.SECTION_LAYOUT,false), CUSTOM_LAYOUT("custom_layout",Settings.SECTION_LAYOUT,false),
DELAY_START_LLE_MODULES("delay_start_for_lle_modules", Settings.SECTION_DEBUG, true), 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 override var boolean: Boolean = defaultValue
@ -41,6 +42,7 @@ enum class BooleanSetting(
ASYNC_SHADERS, ASYNC_SHADERS,
DELAY_START_LLE_MODULES, DELAY_START_LLE_MODULES,
DETERMINISTIC_ASYNC_OPERATIONS, DETERMINISTIC_ASYNC_OPERATIONS,
REQUIRED_ONLINE_LLE_MODULES,
) )
fun from(key: String): BooleanSetting? = fun from(key: String): BooleanSetting? =

View File

@ -1303,6 +1303,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
IntSetting.LLE_APPLETS.defaultValue 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( add(
SliderSetting( SliderSetting(
IntSetting.CPU_CLOCK_SPEED, IntSetting.CPU_CLOCK_SPEED,

View File

@ -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 // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -161,8 +161,8 @@ class DocumentsTree {
val node = resolvePath(filepath) ?: return false val node = resolvePath(filepath) ?: return false
try { try {
val filename = URLDecoder.decode(destinationFilename, FileUtil.DECODE_METHOD) val filename = URLDecoder.decode(destinationFilename, FileUtil.DECODE_METHOD)
DocumentsContract.renameDocument(context.contentResolver, node.uri!!, filename) val newUri = DocumentsContract.renameDocument(context.contentResolver, node.uri!!, filename)
node.rename(filename) node.rename(filename, newUri)
return true return true
} catch (e: Exception) { } catch (e: Exception) {
error("[DocumentsTree]: Cannot rename file, error: " + e.message) error("[DocumentsTree]: Cannot rename file, error: " + e.message)
@ -255,10 +255,11 @@ class DocumentsTree {
} }
@Synchronized @Synchronized
fun rename(name: String) { fun rename(name: String, uri: Uri?) {
parent ?: return parent ?: return
parent!!.removeChild(this) parent!!.removeChild(this)
this.name = name this.name = name
this.uri = uri
parent!!.addChild(this) parent!!.addChild(this)
} }

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -235,6 +235,7 @@ void Config::ReadValues() {
// System // System
ReadSetting("System", Settings::values.is_new_3ds); ReadSetting("System", Settings::values.is_new_3ds);
ReadSetting("System", Settings::values.lle_applets); 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.region_value);
ReadSetting("System", Settings::values.init_clock); ReadSetting("System", Settings::values.init_clock);
{ {

View File

@ -329,6 +329,10 @@ is_new_3ds =
# 0 (default): No, 1: Yes # 0 (default): No, 1: Yes
lle_applets = 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 # 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 # -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan
region_value = region_value =

View File

@ -778,5 +778,6 @@
<string name="delay_start_lle_modules_description">Delays the start of the app when LLE modules are enabled.</string> <string name="delay_start_lle_modules_description">Delays the start of the app when LLE modules are enabled.</string>
<string name="deterministic_async_operations">Deterministic Async Operations</string> <string name="deterministic_async_operations">Deterministic Async Operations</string>
<string name="deterministic_async_operations_description">Makes async operations deterministic for debugging. Enabling this may cause freezes.</string> <string name="deterministic_async_operations_description">Makes async operations deterministic for debugging. Enabling this may cause freezes.</string>
<string name="enable_required_online_lle_modules">Enable required LLE modules for online features (if installed)</string>
<string name="enable_required_online_lle_modules_desc">Enables the LLE modules needed for online multiplayer, eShop access, etc.</string>
</resources> </resources>

View File

@ -725,6 +725,7 @@ void QtConfig::ReadSystemValues() {
ReadGlobalSetting(Settings::values.is_new_3ds); ReadGlobalSetting(Settings::values.is_new_3ds);
ReadGlobalSetting(Settings::values.lle_applets); ReadGlobalSetting(Settings::values.lle_applets);
ReadGlobalSetting(Settings::values.enable_required_online_lle_modules);
ReadGlobalSetting(Settings::values.region_value); ReadGlobalSetting(Settings::values.region_value);
if (global) { if (global) {
@ -1248,6 +1249,7 @@ void QtConfig::SaveSystemValues() {
WriteGlobalSetting(Settings::values.is_new_3ds); WriteGlobalSetting(Settings::values.is_new_3ds);
WriteGlobalSetting(Settings::values.lle_applets); WriteGlobalSetting(Settings::values.lle_applets);
WriteGlobalSetting(Settings::values.enable_required_online_lle_modules);
WriteGlobalSetting(Settings::values.region_value); WriteGlobalSetting(Settings::values.region_value);
if (global) { if (global) {

View File

@ -319,6 +319,8 @@ void ConfigureSystem::SetConfiguration() {
ui->toggle_new_3ds->setChecked(Settings::values.is_new_3ds.GetValue()); ui->toggle_new_3ds->setChecked(Settings::values.is_new_3ds.GetValue());
ui->toggle_lle_applets->setChecked(Settings::values.lle_applets.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->plugin_loader->setChecked(Settings::values.plugin_loader_enabled.GetValue());
ui->allow_plugin_loader->setChecked(Settings::values.allow_plugin_loader.GetValue()); ui->allow_plugin_loader->setChecked(Settings::values.allow_plugin_loader.GetValue());
} }
@ -429,6 +431,9 @@ void ConfigureSystem::ApplyConfiguration() {
is_new_3ds); is_new_3ds);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.lle_applets, ConfigurationShared::ApplyPerGameSetting(&Settings::values.lle_applets,
ui->toggle_lle_applets, 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 = Settings::values.init_clock =
static_cast<Settings::InitClock>(ui->combo_init_clock->currentIndex()); static_cast<Settings::InitClock>(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.init_time_offset = time_offset_days + time_offset_time;
Settings::values.is_new_3ds = ui->toggle_new_3ds->isChecked(); Settings::values.is_new_3ds = ui->toggle_new_3ds->isChecked();
Settings::values.lle_applets = ui->toggle_lle_applets->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.plugin_loader_enabled.SetValue(ui->plugin_loader->isChecked());
Settings::values.allow_plugin_loader.SetValue(ui->allow_plugin_loader->isChecked()); Settings::values.allow_plugin_loader.SetValue(ui->allow_plugin_loader->isChecked());
@ -605,6 +612,8 @@ void ConfigureSystem::SetupPerGameUI() {
if (Settings::IsConfiguringGlobal()) { if (Settings::IsConfiguringGlobal()) {
ui->toggle_new_3ds->setEnabled(Settings::values.is_new_3ds.UsingGlobal()); ui->toggle_new_3ds->setEnabled(Settings::values.is_new_3ds.UsingGlobal());
ui->toggle_lle_applets->setEnabled(Settings::values.lle_applets.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; return;
} }
@ -649,4 +658,7 @@ void ConfigureSystem::SetupPerGameUI() {
is_new_3ds); is_new_3ds);
ConfigurationShared::SetColoredTristate(ui->toggle_lle_applets, Settings::values.lle_applets, ConfigurationShared::SetColoredTristate(ui->toggle_lle_applets, Settings::values.lle_applets,
lle_applets); lle_applets);
ConfigurationShared::SetColoredTristate(ui->enable_required_online_lle_modules,
Settings::values.enable_required_online_lle_modules,
required_online_lle_modules);
} }

View File

@ -63,6 +63,7 @@ private:
Core::System& system; Core::System& system;
ConfigurationShared::CheckState is_new_3ds; ConfigurationShared::CheckState is_new_3ds;
ConfigurationShared::CheckState lle_applets; ConfigurationShared::CheckState lle_applets;
ConfigurationShared::CheckState required_online_lle_modules;
bool enabled = false; bool enabled = false;
std::shared_ptr<Service::CFG::Module> cfg; std::shared_ptr<Service::CFG::Module> cfg;

View File

@ -78,7 +78,17 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="3" column="0">
<widget class="QCheckBox" name="enable_required_online_lle_modules">
<property name="text">
<string>Enable required LLE modules for online features (if installed)</string>
</property>
<property name="toolTip">
<string>Enables the LLE modules needed for online multiplayer, eShop access, etc.</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="edit_username"> <widget class="QLineEdit" name="edit_username">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed"> <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
@ -91,21 +101,21 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0"> <item row="4" column="0">
<widget class="QLabel" name="label_username"> <widget class="QLabel" name="label_username">
<property name="text"> <property name="text">
<string>Username</string> <string>Username</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0"> <item row="5" column="0">
<widget class="QLabel" name="label_birthday"> <widget class="QLabel" name="label_birthday">
<property name="text"> <property name="text">
<string>Birthday</string> <string>Birthday</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1"> <item row="5" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_birthday2"> <layout class="QHBoxLayout" name="horizontalLayout_birthday2">
<item> <item>
<widget class="QComboBox" name="combo_birthmonth"> <widget class="QComboBox" name="combo_birthmonth">
@ -176,14 +186,14 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="5" column="0"> <item row="6" column="0">
<widget class="QLabel" name="label_language"> <widget class="QLabel" name="label_language">
<property name="text"> <property name="text">
<string>Language</string> <string>Language</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="1"> <item row="6" column="1">
<widget class="QComboBox" name="combo_language"> <widget class="QComboBox" name="combo_language">
<property name="toolTip"> <property name="toolTip">
<string>Note: this can be overridden when region setting is auto-select</string> <string>Note: this can be overridden when region setting is auto-select</string>
@ -250,14 +260,14 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="6" column="0"> <item row="7" column="0">
<widget class="QLabel" name="label_sound"> <widget class="QLabel" name="label_sound">
<property name="text"> <property name="text">
<string>Sound output mode</string> <string>Sound output mode</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="1"> <item row="7" column="1">
<widget class="QComboBox" name="combo_sound"> <widget class="QComboBox" name="combo_sound">
<item> <item>
<property name="text"> <property name="text">
@ -276,24 +286,24 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="7" column="0"> <item row="8" column="0">
<widget class="QLabel" name="label_country"> <widget class="QLabel" name="label_country">
<property name="text"> <property name="text">
<string>Country</string> <string>Country</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="1"> <item row="8" column="1">
<widget class="QComboBox" name="combo_country"/> <widget class="QComboBox" name="combo_country"/>
</item> </item>
<item row="8" column="0"> <item row="9" column="0">
<widget class="QLabel" name="label_init_clock"> <widget class="QLabel" name="label_init_clock">
<property name="text"> <property name="text">
<string>Clock</string> <string>Clock</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="8" column="1"> <item row="9" column="1">
<widget class="QComboBox" name="combo_init_clock"> <widget class="QComboBox" name="combo_init_clock">
<item> <item>
<property name="text"> <property name="text">
@ -307,28 +317,28 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="9" column="0"> <item row="10" column="0">
<widget class="QLabel" name="label_init_time"> <widget class="QLabel" name="label_init_time">
<property name="text"> <property name="text">
<string>Startup time</string> <string>Startup time</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="9" column="1"> <item row="10" column="1">
<widget class="QDateTimeEdit" name="edit_init_time"> <widget class="QDateTimeEdit" name="edit_init_time">
<property name="displayFormat"> <property name="displayFormat">
<string>yyyy-MM-ddTHH:mm:ss</string> <string>yyyy-MM-ddTHH:mm:ss</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="9" column="0"> <item row="11" column="0">
<widget class="QLabel" name="label_init_time_offset"> <widget class="QLabel" name="label_init_time_offset">
<property name="text"> <property name="text">
<string>Offset time</string> <string>Offset time</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="9" column="1"> <item row="11" column="1">
<layout class="QGridLayout" name="edit_init_time_offset_grid"> <layout class="QGridLayout" name="edit_init_time_offset_grid">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QSpinBox" name="edit_init_time_offset_days"> <widget class="QSpinBox" name="edit_init_time_offset_days">
@ -352,14 +362,14 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="10" column="0"> <item row="12" column="0">
<widget class="QLabel" name="label_init_ticks_type"> <widget class="QLabel" name="label_init_ticks_type">
<property name="text"> <property name="text">
<string>Initial System Ticks</string> <string>Initial System Ticks</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="10" column="1"> <item row="12" column="1">
<widget class="QComboBox" name="combo_init_ticks_type"> <widget class="QComboBox" name="combo_init_ticks_type">
<item> <item>
<property name="text"> <property name="text">
@ -373,14 +383,14 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="11" column="0"> <item row="13" column="0">
<widget class="QLabel" name="label_init_ticks_value"> <widget class="QLabel" name="label_init_ticks_value">
<property name="text"> <property name="text">
<string>Initial System Ticks Override</string> <string>Initial System Ticks Override</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="11" column="1"> <item row="13" column="1">
<widget class="QLineEdit" name="edit_init_ticks_value"> <widget class="QLineEdit" name="edit_init_ticks_value">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed"> <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
@ -393,21 +403,21 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="12" column="0"> <item row="14" column="0">
<widget class="QLabel" name="label_play_coins"> <widget class="QLabel" name="label_play_coins">
<property name="text"> <property name="text">
<string>Play Coins</string> <string>Play Coins</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="12" column="1"> <item row="14" column="1">
<widget class="QSpinBox" name="spinBox_play_coins"> <widget class="QSpinBox" name="spinBox_play_coins">
<property name="maximum"> <property name="maximum">
<number>300</number> <number>300</number>
</property> </property>
</widget> </widget>
</item> </item>
<item row="13" column="0"> <item row="15" column="0">
<widget class="QLabel" name="label_steps_per_hour"> <widget class="QLabel" name="label_steps_per_hour">
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Number of steps per hour reported by the pedometer. Range from 0 to 65,535.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Number of steps per hour reported by the pedometer. Range from 0 to 65,535.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@ -417,28 +427,28 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="13" column="1"> <item row="15" column="1">
<widget class="QSpinBox" name="spinBox_steps_per_hour"> <widget class="QSpinBox" name="spinBox_steps_per_hour">
<property name="maximum"> <property name="maximum">
<number>9999</number> <number>9999</number>
</property> </property>
</widget> </widget>
</item> </item>
<item row="14" column="1"> <item row="16" column="1">
<widget class="QCheckBox" name="toggle_system_setup"> <widget class="QCheckBox" name="toggle_system_setup">
<property name="text"> <property name="text">
<string>Run System Setup when Home Menu is launched</string> <string>Run System Setup when Home Menu is launched</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="15" column="0"> <item row="17" column="0">
<widget class="QLabel" name="label_console_id"> <widget class="QLabel" name="label_console_id">
<property name="text"> <property name="text">
<string>Console ID:</string> <string>Console ID:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="15" column="1"> <item row="17" column="1">
<widget class="QPushButton" name="button_regenerate_console_id"> <widget class="QPushButton" name="button_regenerate_console_id">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"> <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
@ -454,14 +464,14 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="16" column="0"> <item row="18" column="0">
<widget class="QLabel" name="label_mac"> <widget class="QLabel" name="label_mac">
<property name="text"> <property name="text">
<string>MAC:</string> <string>MAC:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="16" column="1"> <item row="18" column="1">
<widget class="QPushButton" name="button_regenerate_mac"> <widget class="QPushButton" name="button_regenerate_mac">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"> <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
@ -477,21 +487,21 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="17" column="0"> <item row="19" column="0">
<widget class="QLabel" name="label_plugin_loader"> <widget class="QLabel" name="label_plugin_loader">
<property name="text"> <property name="text">
<string>3GX Plugin Loader:</string> <string>3GX Plugin Loader:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="17" column="1"> <item row="19" column="1">
<widget class="QCheckBox" name="plugin_loader"> <widget class="QCheckBox" name="plugin_loader">
<property name="text"> <property name="text">
<string>Enable 3GX plugin loader</string> <string>Enable 3GX plugin loader</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="18" column="1"> <item row="20" column="1">
<widget class="QCheckBox" name="allow_plugin_loader"> <widget class="QCheckBox" name="allow_plugin_loader">
<property name="text"> <property name="text">
<string>Allow applications to change plugin loader state</string> <string>Allow applications to change plugin loader state</string>

View File

@ -224,6 +224,7 @@ void SdlConfig::ReadValues() {
// System // System
ReadSetting("System", Settings::values.is_new_3ds); ReadSetting("System", Settings::values.is_new_3ds);
ReadSetting("System", Settings::values.lle_applets); 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.region_value);
ReadSetting("System", Settings::values.init_clock); ReadSetting("System", Settings::values.init_clock);
{ {

View File

@ -1,4 +1,4 @@
// Copyright 2024 Azahar Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -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,
},
}},
}}; }};
} }

View File

@ -1,4 +1,4 @@
// Copyright 2024 Azahar Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -12,6 +12,7 @@ enum class HackType : int {
RIGHT_EYE_DISABLE, RIGHT_EYE_DISABLE,
ACCURATE_MULTIPLICATION, ACCURATE_MULTIPLICATION,
DECRYPTION_AUTHORIZED, DECRYPTION_AUTHORIZED,
ONLINE_LLE_REQUIRED,
}; };
class UserHackData {}; class UserHackData {};

View File

@ -1,4 +1,4 @@
// Copyright 2024 Azahar Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -32,6 +32,28 @@ struct HackManager {
return (hack != nullptr) ? hack->mode : default_mode; 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<HackType, HackEntry> entries; std::multimap<HackType, HackEntry> entries;
}; };

View File

@ -450,8 +450,10 @@ struct Values {
Setting<bool> use_cpu_jit{true, "use_cpu_jit"}; Setting<bool> use_cpu_jit{true, "use_cpu_jit"};
SwitchableSetting<s32, true> cpu_clock_percentage{100, 5, 400, "cpu_clock_percentage"}; SwitchableSetting<s32, true> cpu_clock_percentage{100, 5, 400, "cpu_clock_percentage"};
SwitchableSetting<bool> is_new_3ds{true, "is_new_3ds"}; SwitchableSetting<bool> is_new_3ds{true, "is_new_3ds"};
SwitchableSetting<bool> lle_applets{false, "lle_applets"}; SwitchableSetting<bool> lle_applets{true, "lle_applets"};
SwitchableSetting<bool> deterministic_async_operations{false, "deterministic_async_operations"}; SwitchableSetting<bool> deterministic_async_operations{false, "deterministic_async_operations"};
SwitchableSetting<bool> enable_required_online_lle_modules{
false, "enable_required_online_lle_modules"};
// Data Storage // Data Storage
Setting<bool> use_virtual_sd{true, "use_virtual_sd"}; Setting<bool> use_virtual_sd{true, "use_virtual_sd"};

View File

@ -508,8 +508,10 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window,
service_manager = std::make_unique<Service::SM::ServiceManager>(*this); service_manager = std::make_unique<Service::SM::ServiceManager>(*this);
archive_manager = std::make_unique<Service::FS::ArchiveManager>(*this); archive_manager = std::make_unique<Service::FS::ArchiveManager>(*this);
u64 loading_title_id = 0;
app_loader->ReadProgramId(loading_title_id);
HW::AES::InitKeys(); HW::AES::InitKeys();
Service::Init(*this, lle_modules, !app_loader->DoingInitialSetup()); Service::Init(*this, loading_title_id, lle_modules, !app_loader->DoingInitialSetup());
GDBStub::DeferStart(); GDBStub::DeferStart();
if (!registered_image_interface) { if (!registered_image_interface) {

View File

@ -861,11 +861,6 @@ Result TicketFile::Commit() {
ticket_id = ticket.GetTicketID(); ticket_id = ticket.GetTicketID();
const auto ticket_path = GetTicketPath(ticket.GetTitleID(), 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 // Save ticket
if (ticket.Save(ticket_path) != Loader::ResultStatus::Success) { if (ticket.Save(ticket_path) != Loader::ResultStatus::Success) {
LOG_ERROR(Service_AM, "Failed to install ticket provided to TicketFile."); 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() { void Module::ScanForTickets() {
scan_tickets_future = std::async([this]() {
std::scoped_lock lock(am_lists_mutex);
ScanForTicketsImpl();
});
}
void Module::ScanForTicketsImpl() {
am_ticket_list.clear(); am_ticket_list.clear();
LOG_DEBUG(Service_AM, "Starting ticket scan"); LOG_DEBUG(Service_AM, "Starting ticket scan");
@ -1210,6 +1212,13 @@ void Module::ScanForTickets() {
} }
void Module::ScanForTitles(Service::FS::MediaType media_type) { 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<u32>(media_type)].clear(); am_title_list[static_cast<u32>(media_type)].clear();
LOG_DEBUG(Service_AM, "Starting title scan for media_type={}", static_cast<int>(media_type)); LOG_DEBUG(Service_AM, "Starting title scan for media_type={}", static_cast<int>(media_type));
@ -1245,9 +1254,12 @@ void Module::ScanForTitles(Service::FS::MediaType media_type) {
} }
void Module::ScanForAllTitles() { void Module::ScanForAllTitles() {
ScanForTickets(); scan_all_future = std::async([this]() {
ScanForTitles(Service::FS::MediaType::NAND); std::scoped_lock lock(am_lists_mutex);
ScanForTitles(Service::FS::MediaType::SDMC); ScanForTicketsImpl();
ScanForTitlesImpl(Service::FS::MediaType::NAND);
ScanForTitlesImpl(Service::FS::MediaType::SDMC);
});
} }
Module::Interface::Interface(std::shared_ptr<Module> am, const char* name, u32 max_session) Module::Interface::Interface(std::shared_ptr<Module> am, const char* name, u32 max_session)
@ -1307,7 +1319,7 @@ void Module::Interface::GetNumPrograms(Kernel::HLERequestContext& ctx) {
}, },
true); true);
} else { } else {
std::scoped_lock lock(am->am_lists_mutex);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
rb.Push<u32>(static_cast<u32>(am->am_title_list[media_type].size())); rb.Push<u32>(static_cast<u32>(am->am_title_list[media_type].size()));
@ -1648,6 +1660,7 @@ void Module::Interface::GetProgramList(Kernel::HLERequestContext& ctx) {
return; return;
} }
std::scoped_lock lock(am->am_lists_mutex);
u32 media_count = static_cast<u32>(am->am_title_list[media_type].size()); u32 media_count = static_cast<u32>(am->am_title_list[media_type].size());
u32 copied = std::min(media_count, count); u32 copied = std::min(media_count, count);
@ -1660,8 +1673,8 @@ void Module::Interface::GetProgramList(Kernel::HLERequestContext& ctx) {
} }
Result GetTitleInfoFromList(std::span<const u64> title_id_list, Service::FS::MediaType media_type, Result GetTitleInfoFromList(std::span<const u64> title_id_list, Service::FS::MediaType media_type,
Kernel::MappedBuffer& title_info_out) { std::vector<TitleInfo>& title_info_out) {
std::size_t write_offset = 0; title_info_out.reserve(title_id_list.size());
for (u32 i = 0; i < title_id_list.size(); i++) { for (u32 i = 0; i < title_id_list.size(); i++) {
std::string tmd_path = GetTitleMetadataPath(media_type, title_id_list[i]); std::string tmd_path = GetTitleMetadataPath(media_type, title_id_list[i]);
@ -1682,8 +1695,7 @@ Result GetTitleInfoFromList(std::span<const u64> title_id_list, Service::FS::Med
} }
LOG_DEBUG(Service_AM, "found title_id={:016X} version={:04X}", title_id_list[i], LOG_DEBUG(Service_AM, "found title_id={:016X} version={:04X}", title_id_list[i],
title_info.version); title_info.version);
title_info_out.Write(&title_info, write_offset, sizeof(TitleInfo)); title_info_out.push_back(title_info);
write_offset += sizeof(TitleInfo);
} }
return ResultSuccess; return ResultSuccess;
@ -1773,41 +1785,64 @@ void Module::Interface::GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool
true); true);
} else { } else {
auto& title_id_list_buffer = rp.PopMappedBuffer(); struct AsyncData {
auto& title_info_out = rp.PopMappedBuffer(); Service::FS::MediaType media_type;
std::vector<u64> title_id_list;
std::vector<u64> title_id_list(title_count); Result res{0};
title_id_list_buffer.Read(title_id_list.data(), 0, title_count * sizeof(u64)); std::vector<TitleInfo> out;
Kernel::MappedBuffer* title_id_list_buffer;
Kernel::MappedBuffer* title_info_out;
};
auto async_data = std::make_shared<AsyncData>();
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. ctx.RunAsync(
// Normally, since the program wouldn't be commited, getting the title info returns not [this, async_data](Kernel::HLERequestContext& ctx) {
// found. However, since GetTitleInfoFromList does not care if the program was commited and // nim checks if the current importing title already exists during installation.
// only checks for the tmd, it will detect the title and return information while it // Normally, since the program wouldn't be commited, getting the title info returns
// shouldn't. To prevent this, we check if the importing context is present and not // not found. However, since GetTitleInfoFromList does not care if the program was
// committed. If that's the case, return not found // commited and only checks for the tmd, it will detect the title and return
Result result = ResultSuccess; // information while it shouldn't. To prevent this, we check if the importing
for (auto tid : title_id_list) { // context is present and not committed. If that's the case, return not found
for (auto& import_ctx : am->import_title_contexts) { for (auto tid : async_data->title_id_list) {
if (import_ctx.first == tid && for (auto& import_ctx : am->import_title_contexts) {
(import_ctx.second.state == ImportTitleContextState::WAITING_FOR_IMPORT || if (import_ctx.first == tid &&
import_ctx.second.state == ImportTitleContextState::WAITING_FOR_COMMIT || (import_ctx.second.state ==
import_ctx.second.state == ImportTitleContextState::RESUMABLE)) { ImportTitleContextState::WAITING_FOR_IMPORT ||
LOG_DEBUG(Service_AM, "title pending commit title_id={:016X}", tid); import_ctx.second.state ==
result = Result(ErrorDescription::NotFound, ErrorModule::AM, ImportTitleContextState::WAITING_FOR_COMMIT ||
ErrorSummary::InvalidState, ErrorLevel::Permanent); 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()) if (async_data->res.IsSuccess()) {
result = GetTitleInfoFromList(title_id_list, media_type, title_info_out); async_data->res = GetTitleInfoFromList(async_data->title_id_list,
async_data->media_type, async_data->out);
IPC::RequestBuilder rb = rp.MakeBuilder(1, ignore_platform ? 0 : 4); }
rb.Push(result); return 0;
if (!ignore_platform) { },
rb.PushMappedBuffer(title_id_list_buffer); [async_data](Kernel::HLERequestContext& ctx) {
rb.PushMappedBuffer(title_info_out); 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); true);
} else { } else {
auto& title_id_list_buffer = rp.PopMappedBuffer(); struct AsyncData {
auto& title_info_out = rp.PopMappedBuffer(); Service::FS::MediaType media_type;
std::vector<u64> title_id_list;
std::vector<u64> title_id_list(title_count); Result res{0};
title_id_list_buffer.Read(title_id_list.data(), 0, title_count * sizeof(u64)); std::vector<TitleInfo> out;
Kernel::MappedBuffer* title_id_list_buffer;
Kernel::MappedBuffer* title_info_out;
};
auto async_data = std::make_shared<AsyncData>();
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<u32>(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 // nim checks if the current importing title already exists during installation.
for (u32 i = 0; i < title_count; i++) { // Normally, since the program wouldn't be commited, getting the title info returns
u32 tid_high = static_cast<u32>(title_id_list[i] >> 32); // not found. However, since GetTitleInfoFromList does not care if the program was
if (tid_high != TID_HIGH_DLC) { // commited and only checks for the tmd, it will detect the title and return
result = Result(ErrCodes::InvalidTIDInList, ErrorModule::AM, // information while it shouldn't. To prevent this, we check if the importing
ErrorSummary::InvalidArgument, ErrorLevel::Usage); // context is present and not committed. If that's the case, return not found
break; 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()) { if (async_data->res.IsSuccess()) {
result = GetTitleInfoFromList(title_id_list, media_type, title_info_out); async_data->res = GetTitleInfoFromList(async_data->title_id_list,
} async_data->media_type, async_data->out);
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); return 0;
rb.Push(result); },
rb.PushMappedBuffer(title_id_list_buffer); [async_data](Kernel::HLERequestContext& ctx) {
rb.PushMappedBuffer(title_info_out); 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); true);
} else { } else {
auto& title_id_list_buffer = rp.PopMappedBuffer(); struct AsyncData {
auto& title_info_out = rp.PopMappedBuffer(); Service::FS::MediaType media_type;
std::vector<u64> title_id_list;
std::vector<u64> title_id_list(title_count); Result res{0};
title_id_list_buffer.Read(title_id_list.data(), 0, title_count * sizeof(u64)); std::vector<TitleInfo> out;
Kernel::MappedBuffer* title_id_list_buffer;
Kernel::MappedBuffer* title_info_out;
};
auto async_data = std::make_shared<AsyncData>();
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<u32>(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 // nim checks if the current importing title already exists during installation.
for (u32 i = 0; i < title_count; i++) { // Normally, since the program wouldn't be commited, getting the title info returns
u32 tid_high = static_cast<u32>(title_id_list[i] >> 32); // not found. However, since GetTitleInfoFromList does not care if the program was
if (tid_high != TID_HIGH_UPDATE) { // commited and only checks for the tmd, it will detect the title and return
result = Result(ErrCodes::InvalidTIDInList, ErrorModule::AM, // information while it shouldn't. To prevent this, we check if the importing
ErrorSummary::InvalidArgument, ErrorLevel::Usage); // context is present and not committed. If that's the case, return not found
break; 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()) { if (async_data->res.IsSuccess()) {
result = GetTitleInfoFromList(title_id_list, media_type, title_info_out); async_data->res = GetTitleInfoFromList(async_data->title_id_list,
} async_data->media_type, async_data->out);
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); return 0;
rb.Push(result); },
rb.PushMappedBuffer(title_id_list_buffer); [async_data](Kernel::HLERequestContext& ctx) {
rb.PushMappedBuffer(title_info_out); 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); LOG_DEBUG(Service_AM, "title_id={:016X}", title_id);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); 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 range = am->am_ticket_list.equal_range(title_id);
if (range.first == range.second) { if (range.first == range.second) {
rb.Push(Result(ErrorDescription::AlreadyDone, ErrorModule::AM, ErrorSummary::Success, rb.Push(Result(ErrorDescription::AlreadyDone, ErrorModule::AM, ErrorSummary::Success,
@ -2288,6 +2408,7 @@ void Module::Interface::GetNumTickets(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, ""); LOG_DEBUG(Service_AM, "");
std::scoped_lock lock(am->am_lists_mutex);
u32 ticket_count = static_cast<u32>(am->am_ticket_list.size()); u32 ticket_count = static_cast<u32>(am->am_ticket_list.size());
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); 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); LOG_DEBUG(Service_AM, "ticket_list_count={}, ticket_index={}", ticket_list_count, ticket_index);
u32 tickets_written = 0; u32 tickets_written = 0;
std::scoped_lock lock(am->am_lists_mutex);
auto it = am->am_ticket_list.begin(); auto it = am->am_ticket_list.begin();
std::advance(it, std::min(static_cast<size_t>(ticket_index), am->am_ticket_list.size())); std::advance(it, std::min(static_cast<size_t>(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); LOG_DEBUG(Service_AM, "(STUBBED) called, ticket_count={}", ticket_count);
u32 written = 0; u32 written = 0;
std::scoped_lock lock(am->am_lists_mutex);
for (auto it = am->am_ticket_list.begin(); for (auto it = am->am_ticket_list.begin();
it != am->am_ticket_list.end() && written < ticket_count; it++) { it != am->am_ticket_list.end() && written < ticket_count; it++) {
u64 title_id = it->first; u64 title_id = it->first;
@ -3285,6 +3408,7 @@ void Module::Interface::EndImportTicket(Kernel::HLERequestContext& ctx) {
auto ticket_file = GetFileBackendFromSession<TicketFile>(ticket); auto ticket_file = GetFileBackendFromSession<TicketFile>(ticket);
if (ticket_file.Succeeded()) { if (ticket_file.Succeeded()) {
rb.Push(ticket_file.Unwrap()->Commit()); 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(), am->am_ticket_list.insert(std::make_pair(ticket_file.Unwrap()->GetTitleID(),
ticket_file.Unwrap()->GetTicketID())); ticket_file.Unwrap()->GetTicketID()));
} else { } else {
@ -3308,6 +3432,7 @@ void Module::Interface::BeginImportTitle(Kernel::HLERequestContext& ctx) {
am->importing_title = am->importing_title =
std::make_shared<CurrentImportingTitle>(Core::System::GetInstance(), title_id, media_type); std::make_shared<CurrentImportingTitle>(Core::System::GetInstance(), title_id, media_type);
std::scoped_lock lock(am->am_lists_mutex);
auto entries = am->am_ticket_list.find(title_id); auto entries = am->am_ticket_list.find(title_id);
if (entries == am->am_ticket_list.end()) { if (entries == am->am_ticket_list.end()) {
// Ticket is not installed // Ticket is not installed
@ -3770,11 +3895,11 @@ void Module::Interface::Sign(Kernel::HLERequestContext& ctx) {
template <class Archive> template <class Archive>
void Module::serialize(Archive& ar, const unsigned int) { void Module::serialize(Archive& ar, const unsigned int) {
std::scoped_lock lock(am_lists_mutex);
DEBUG_SERIALIZATION_POINT; DEBUG_SERIALIZATION_POINT;
ar & cia_installing; ar & cia_installing;
ar & force_old_device_id; ar & force_old_device_id;
ar & force_new_device_id; ar & force_new_device_id;
ar & am_title_list;
ar & system_updater_mutex; ar & system_updater_mutex;
} }
SERIALIZE_IMPL(Module) 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); LOG_DEBUG(Service_AM, "title_id={:016X} ticket_id={}", title_id, ticket_id);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); 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 range = am->am_ticket_list.equal_range(title_id);
auto it = range.first; auto it = range.first;
for (; it != range.second; it++) { 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); 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); auto range = am->am_ticket_list.equal_range(title_id);
u32 count = static_cast<u32>(std::distance(range.first, range.second)); u32 count = static_cast<u32>(std::distance(range.first, range.second));
@ -3861,6 +3988,7 @@ void Module::Interface::GetTicketIdList(Kernel::HLERequestContext& ctx) {
auto out_buf = rp.PopMappedBuffer(); auto out_buf = rp.PopMappedBuffer();
u32 index = 0; u32 index = 0;
std::scoped_lock lock(am->am_lists_mutex);
for (auto [it, rangeEnd] = am->am_ticket_list.equal_range(title_id); for (auto [it, rangeEnd] = am->am_ticket_list.equal_range(title_id);
it != rangeEnd && index < list_count; index++, it++) { it != rangeEnd && index < list_count; index++, it++) {
u64 ticket_id = it->second; 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); 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); auto range = am->am_ticket_list.equal_range(title_id);
u32 count = static_cast<u32>(std::distance(range.first, range.second)); u32 count = static_cast<u32>(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); 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 range = am->am_ticket_list.equal_range(title_id);
auto it = range.first; auto it = range.first;
std::advance(it, std::min(static_cast<size_t>(skip), std::advance(it, std::min(static_cast<size_t>(skip),
@ -3943,6 +4073,7 @@ void Module::Interface::ExportTicketWrapped(Kernel::HLERequestContext& ctx) {
return; return;
} }
std::scoped_lock lock(am->am_lists_mutex);
auto range = am->am_ticket_list.equal_range(title_id); auto range = am->am_ticket_list.equal_range(title_id);
auto it = range.first; auto it = range.first;
for (; it != range.second; it++) for (; it != range.second; it++)
@ -4006,6 +4137,7 @@ void Module::Interface::ExportTicketWrapped(Kernel::HLERequestContext& ctx) {
} }
Module::Module(Core::System& _system) : system(_system) { Module::Module(Core::System& _system) : system(_system) {
FileUtil::CreateFullPath(GetTicketDirectory());
ScanForAllTitles(); ScanForAllTitles();
system_updater_mutex = system.Kernel().CreateMutex(false, "AM::SystemUpdaterMutex"); system_updater_mutex = system.Kernel().CreateMutex(false, "AM::SystemUpdaterMutex");
} }

View File

@ -6,7 +6,9 @@
#include <array> #include <array>
#include <functional> #include <functional>
#include <future>
#include <memory> #include <memory>
#include <mutex>
#include <string> #include <string>
#include <vector> #include <vector>
#include <boost/serialization/array.hpp> #include <boost/serialization/array.hpp>
@ -1022,12 +1024,16 @@ public:
private: private:
void ScanForTickets(); void ScanForTickets();
void ScanForTicketsImpl();
/** /**
* Scans the for titles in a storage medium for listing. * Scans the for titles in a storage medium for listing.
* @param media_type the storage medium to scan * @param media_type the storage medium to scan
*/ */
void ScanForTitles(Service::FS::MediaType media_type); void ScanForTitles(Service::FS::MediaType media_type);
void ScanForTitlesImpl(Service::FS::MediaType media_type);
/** /**
* Scans all storage mediums for titles for listing. * Scans all storage mediums for titles for listing.
*/ */
@ -1037,6 +1043,10 @@ private:
bool cia_installing = false; bool cia_installing = false;
bool force_old_device_id = false; bool force_old_device_id = false;
bool force_new_device_id = false; bool force_new_device_id = false;
std::future<void> scan_tickets_future;
std::future<void> scan_titles_future;
std::future<void> scan_all_future;
std::mutex am_lists_mutex;
std::array<std::vector<u64_le>, 3> am_title_list; std::array<std::vector<u64_le>, 3> am_title_list;
std::multimap<u64, u64> am_ticket_list; std::multimap<u64, u64> am_ticket_list;
std::shared_ptr<Kernel::Mutex> system_updater_mutex; std::shared_ptr<Kernel::Mutex> system_updater_mutex;

View File

@ -5,6 +5,7 @@
#include <algorithm> #include <algorithm>
#include <fmt/format.h> #include <fmt/format.h>
#include "common/assert.h" #include "common/assert.h"
#include "common/hacks/hack_manager.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/core.h" #include "core/core.h"
#include "core/hle/ipc.h" #include "core/hle/ipc.h"
@ -59,53 +60,54 @@
namespace Service { namespace Service {
const std::array<ServiceModuleInfo, 41> service_module_map{ const std::array<ServiceModuleInfo, 41> service_module_map{
{{"FS", 0x00040130'00001102, FS::InstallInterfaces}, {{"FS", 0x00040130'00001102, FS::InstallInterfaces, false},
{"PM", 0x00040130'00001202, PM::InstallInterfaces}, {"PM", 0x00040130'00001202, PM::InstallInterfaces, false},
{"LDR", 0x00040130'00003702, LDR::InstallInterfaces}, {"LDR", 0x00040130'00003702, LDR::InstallInterfaces, false},
{"PXI", 0x00040130'00001402, PXI::InstallInterfaces}, {"PXI", 0x00040130'00001402, PXI::InstallInterfaces, false},
{"ERR", 0x00040030'00008A02, ERR::InstallInterfaces}, {"ERR", 0x00040030'00008A02, ERR::InstallInterfaces, false},
{"AC", 0x00040130'00002402, AC::InstallInterfaces}, {"AC", 0x00040130'00002402, AC::InstallInterfaces, false},
{"ACT", 0x00040130'00003802, ACT::InstallInterfaces}, {"ACT", 0x00040130'00003802, ACT::InstallInterfaces, true},
{"AM", 0x00040130'00001502, AM::InstallInterfaces}, {"AM", 0x00040130'00001502, AM::InstallInterfaces, false},
{"BOSS", 0x00040130'00003402, BOSS::InstallInterfaces}, {"BOSS", 0x00040130'00003402, BOSS::InstallInterfaces, false},
{"CAM", 0x00040130'00001602, {"CAM", 0x00040130'00001602,
[](Core::System& system) { [](Core::System& system) {
CAM::InstallInterfaces(system); CAM::InstallInterfaces(system);
Y2R::InstallInterfaces(system); Y2R::InstallInterfaces(system);
}}, },
{"CECD", 0x00040130'00002602, CECD::InstallInterfaces}, false},
{"CFG", 0x00040130'00001702, CFG::InstallInterfaces}, {"CECD", 0x00040130'00002602, CECD::InstallInterfaces, false},
{"DLP", 0x00040130'00002802, DLP::InstallInterfaces}, {"CFG", 0x00040130'00001702, CFG::InstallInterfaces, false},
{"DSP", 0x00040130'00001A02, DSP::InstallInterfaces}, {"DLP", 0x00040130'00002802, DLP::InstallInterfaces, false},
{"FRD", 0x00040130'00003202, FRD::InstallInterfaces}, {"DSP", 0x00040130'00001A02, DSP::InstallInterfaces, false},
{"GSP", 0x00040130'00001C02, GSP::InstallInterfaces}, {"FRD", 0x00040130'00003202, FRD::InstallInterfaces, true},
{"HID", 0x00040130'00001D02, HID::InstallInterfaces}, {"GSP", 0x00040130'00001C02, GSP::InstallInterfaces, false},
{"IR", 0x00040130'00003302, IR::InstallInterfaces}, {"HID", 0x00040130'00001D02, HID::InstallInterfaces, false},
{"MIC", 0x00040130'00002002, MIC::InstallInterfaces}, {"IR", 0x00040130'00003302, IR::InstallInterfaces, false},
{"MVD", 0x00040130'20004102, MVD::InstallInterfaces}, {"MIC", 0x00040130'00002002, MIC::InstallInterfaces, false},
{"NDM", 0x00040130'00002B02, NDM::InstallInterfaces}, {"MVD", 0x00040130'20004102, MVD::InstallInterfaces, false},
{"NEWS", 0x00040130'00003502, NEWS::InstallInterfaces}, {"NDM", 0x00040130'00002B02, NDM::InstallInterfaces, false},
{"NFC", 0x00040130'00004002, NFC::InstallInterfaces}, {"NEWS", 0x00040130'00003502, NEWS::InstallInterfaces, false},
{"NIM", 0x00040130'00002C02, NIM::InstallInterfaces}, {"NFC", 0x00040130'00004002, NFC::InstallInterfaces, false},
{"NS", 0x00040130'00008002, APT::InstallInterfaces}, {"NIM", 0x00040130'00002C02, NIM::InstallInterfaces, true},
{"NWM", 0x00040130'00002D02, NWM::InstallInterfaces}, {"NS", 0x00040130'00008002, APT::InstallInterfaces, false},
{"PTM", 0x00040130'00002202, PTM::InstallInterfaces}, {"NWM", 0x00040130'00002D02, NWM::InstallInterfaces, false},
{"QTM", 0x00040130'00004202, QTM::InstallInterfaces}, {"PTM", 0x00040130'00002202, PTM::InstallInterfaces, false},
{"CSND", 0x00040130'00002702, CSND::InstallInterfaces}, {"QTM", 0x00040130'00004202, QTM::InstallInterfaces, false},
{"HTTP", 0x00040130'00002902, HTTP::InstallInterfaces}, {"CSND", 0x00040130'00002702, CSND::InstallInterfaces, false},
{"SOC", 0x00040130'00002E02, SOC::InstallInterfaces}, {"HTTP", 0x00040130'00002902, HTTP::InstallInterfaces, false},
{"SSL", 0x00040130'00002F02, SSL::InstallInterfaces}, {"SOC", 0x00040130'00002E02, SOC::InstallInterfaces, false},
{"PS", 0x00040130'00003102, PS::InstallInterfaces}, {"SSL", 0x00040130'00002F02, SSL::InstallInterfaces, false},
{"PLGLDR", 0x00040130'00006902, PLGLDR::InstallInterfaces}, {"PS", 0x00040130'00003102, PS::InstallInterfaces, false},
{"PLGLDR", 0x00040130'00006902, PLGLDR::InstallInterfaces, false},
{"MCU", 0x00040130'00001F02, MCU::InstallInterfaces, false},
// no HLE implementation // no HLE implementation
{"CDC", 0x00040130'00001802, nullptr}, {"CDC", 0x00040130'00001802, nullptr, false},
{"GPIO", 0x00040130'00001B02, nullptr}, {"GPIO", 0x00040130'00001B02, nullptr, false},
{"I2C", 0x00040130'00001E02, nullptr}, {"I2C", 0x00040130'00001E02, nullptr, false},
{"MCU", 0x00040130'00001F02, MCU::InstallInterfaces}, {"MP", 0x00040130'00002A02, nullptr, false},
{"MP", 0x00040130'00002A02, nullptr}, {"PDN", 0x00040130'00002102, nullptr, false},
{"PDN", 0x00040130'00002102, nullptr}, {"SPI", 0x00040130'00002302, nullptr, false}}};
{"SPI", 0x00040130'00002302, nullptr}}};
/** /**
* Creates a function string for logging, complete with the name (or header code, depending * 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; return itr->second.name;
} }
static bool AttemptLLE(const ServiceModuleInfo& service_module) { static bool AttemptLLE(const ServiceModuleInfo& service_module, u64 loading_titleid) {
if (!Settings::values.lle_modules.at(service_module.name)) 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; return false;
std::unique_ptr<Loader::AppLoader> loader = std::unique_ptr<Loader::AppLoader> loader =
Loader::GetLoader(AM::GetTitleContentPath(FS::MediaType::NAND, service_module.title_id)); Loader::GetLoader(AM::GetTitleContentPath(FS::MediaType::NAND, service_module.title_id));
@ -220,7 +227,7 @@ static bool AttemptLLE(const ServiceModuleInfo& service_module) {
} }
/// Initialize ServiceManager /// Initialize ServiceManager
void Init(Core::System& core, std::vector<u64>& lle_modules, bool allow_lle) { void Init(Core::System& core, u64 loading_titleid, std::vector<u64>& lle_modules, bool allow_lle) {
SM::ServiceManager::InstallInterfaces(core); SM::ServiceManager::InstallInterfaces(core);
core.Kernel().SetAppMainThreadExtendedSleep(false); core.Kernel().SetAppMainThreadExtendedSleep(false);
bool lle_module_present = false; bool lle_module_present = false;
@ -237,7 +244,7 @@ void Init(Core::System& core, std::vector<u64>& lle_modules, bool allow_lle) {
const bool has_lle = allow_lle && const bool has_lle = allow_lle &&
core.GetSaveStateStatus() != Core::System::SaveStateStatus::LOADING && core.GetSaveStateStatus() != Core::System::SaveStateStatus::LOADING &&
AttemptLLE(service_module); AttemptLLE(service_module, loading_titleid);
if (has_lle) { if (has_lle) {
lle_modules.push_back(service_module.title_id); lle_modules.push_back(service_module.title_id);
} }

View File

@ -184,12 +184,13 @@ private:
}; };
/// Initialize ServiceManager /// Initialize ServiceManager
void Init(Core::System& system, std::vector<u64>& lle_modules, bool allow_lle); void Init(Core::System& system, u64 loading_titleid, std::vector<u64>& lle_modules, bool allow_lle);
struct ServiceModuleInfo { struct ServiceModuleInfo {
std::string name; std::string name;
u64 title_id; u64 title_id;
std::function<void(Core::System&)> init_function; std::function<void(Core::System&)> init_function;
bool is_online_recommended;
}; };
extern const std::array<ServiceModuleInfo, 41> service_module_map; extern const std::array<ServiceModuleInfo, 41> service_module_map;