diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt index 5d523be67c..c51aeb871e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -24,6 +27,6 @@ object SettingsFile { fun loadCustomConfig(game: Game) { val fileName = FileUtil.getFilename(Uri.parse(game.path)) - NativeConfig.initializePerGameConfig(game.programId, fileName) + NativeConfig.initializePerGameConfig(game.applicationId, fileName) } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt index 96632b4606..36e0668cee 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/AddonsFragment.kt @@ -189,7 +189,7 @@ class AddonsFragment : Fragment() { fragmentManager = parentFragmentManager, addonViewModel = addonViewModel, documents = documents, - programId = args.game.programId + programId = args.game.applicationId ) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 016ac57356..8b859c96f5 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt @@ -347,7 +347,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { } try { if (GpuDriverHelper.isAdrenoGpu()) { - val programIdHex = game!!.programIdHex + val programIdHex = game!!.applicationIdHex if (NativeFreedrenoConfig.loadPerGameConfigWithGlobalFallback(programIdHex)) { Log.info("[EmulationFragment] Loaded per-game Freedreno config for $programIdHex") } else { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/FreedrenoSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/FreedrenoSettingsFragment.kt index 2eb77690ca..2e3ed5ed7b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/FreedrenoSettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/FreedrenoSettingsFragment.kt @@ -59,7 +59,7 @@ class FreedrenoSettingsFragment : Fragment() { NativeFreedrenoConfig.initializeFreedrenoConfig() if (isPerGameConfig) { - NativeFreedrenoConfig.loadPerGameConfig(game!!.programIdHex) + NativeFreedrenoConfig.loadPerGameConfig(game!!.applicationIdHex) } else { NativeFreedrenoConfig.reloadFreedrenoConfig() } @@ -157,7 +157,7 @@ class FreedrenoSettingsFragment : Fragment() { binding.buttonSave.setOnClickListener { if (isPerGameConfig) { - NativeFreedrenoConfig.savePerGameConfig(game!!.programIdHex) + NativeFreedrenoConfig.savePerGameConfig(game!!.applicationIdHex) showSnackbar(getString(R.string.freedreno_per_game_saved)) } else { NativeFreedrenoConfig.saveFreedrenoConfig() diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt index a2d6563e50..dcd790abe1 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/GamePropertiesFragment.kt @@ -455,7 +455,7 @@ class GamePropertiesFragment : Fragment() { val shaderCacheDir = File( DirectoryInitialization.userDirectory + - "/cache/shader/" + args.game.settingsName.lowercase() + "/cache/shader/" + args.game.shaderCacheName.lowercase() ) if (shaderCacheDir.exists()) { add( @@ -600,7 +600,7 @@ class GamePropertiesFragment : Fragment() { val files = cacheSaveDir.listFiles() var savesFolderFile: File? = null if (files != null) { - val savesFolderName = args.game.programIdHex + val savesFolderName = args.game.applicationIdHex for (file in files) { if (file.isDirectory && file.name == savesFolderName) { savesFolderFile = file diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt index 6510c069e3..bb76361fa9 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/InstallableFragment.kt @@ -232,7 +232,7 @@ class InstallableFragment : Fragment() { fragmentManager = parentFragmentManager, addonViewModel = addonViewModel, documents = documents, - programId = addonViewModel.game?.programId + programId = addonViewModel.game?.applicationId ) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt index 2331630c4e..9aaa052d4e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt @@ -142,10 +142,11 @@ class AddonViewModel : ViewModel() { } fun onDeleteAddon(patch: Patch) { + val currentGame = game ?: return when (PatchType.from(patch.type)) { - PatchType.Update -> NativeLibrary.removeUpdate(patch.programId) - PatchType.DLC -> NativeLibrary.removeDLC(patch.programId) - PatchType.Mod -> NativeLibrary.removeMod(patch.programId, patch.name) + PatchType.Update -> NativeLibrary.removeUpdate(currentGame.programId) + PatchType.DLC -> NativeLibrary.removeDLC(currentGame.applicationId) + PatchType.Mod -> NativeLibrary.removeMod(currentGame.programId, patch.name) } refreshAddons(force = true) } @@ -165,7 +166,7 @@ class AddonViewModel : ViewModel() { } NativeConfig.setDisabledAddons( - currentGame.programId, + currentGame.applicationId, currentList.mapNotNull { if (it.enabled) { null @@ -199,6 +200,6 @@ class AddonViewModel : ViewModel() { } private fun gameKey(game: Game): String { - return "${game.programId}|${game.path}" + return "${game.applicationId}|${game.path}" } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt index 41f03c2488..14361a426e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt @@ -150,7 +150,7 @@ class DriverViewModel : ViewModel() { ?: return@withContext val shaderDir = File( externalFilesDir.absolutePath + - "/shader/" + game.settingsName.lowercase() + "/shader/" + game.shaderCacheName.lowercase() ) if (shaderDir.exists()) { shaderDir.deleteRecursively() diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt index 799708dfa7..8252c82c6b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2023 yuzu Emulator Project @@ -35,19 +35,26 @@ class Game( val keyAddedToLibraryTime get() = "${path}_AddedToLibraryTime" val keyLastPlayedTime get() = "${path}_LastPlayed" + private val programIdLong: Long + get() = programId.toLongOrNull() ?: 0L + + private val applicationIdLong: Long + get() = programIdLong and -8192L + + val applicationId: String + get() = applicationIdLong.toString() + val settingsName: String get() { - val programIdLong = programId.toLong() - return if (programIdLong == 0L) { + return if (applicationIdLong == 0L) { FileUtil.getFilename(Uri.parse(path)) } else { - "0" + programIdLong.toString(16).uppercase() + "0" + applicationIdLong.toString(16).uppercase() } } val programIdHex: String get() { - val programIdLong = programId.toLong() return if (programIdLong == 0L) { "0" } else { @@ -55,16 +62,32 @@ class Game( } } + val shaderCacheName: String + get() = if (programIdLong == 0L) { + FileUtil.getFilename(Uri.parse(path)) + } else { + "0" + programIdLong.toString(16).uppercase() + } + + val applicationIdHex: String + get() { + return if (applicationIdLong == 0L) { + "0" + } else { + "0" + applicationIdLong.toString(16).uppercase() + } + } + val saveZipName: String get() = "$title ${YuzuApplication.appContext.getString(R.string.save_data).lowercase()} - ${ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")) }.zip" val saveDir: String - get() = NativeConfig.getSaveDir() + NativeLibrary.getSavePath(programId) + get() = NativeConfig.getSaveDir() + NativeLibrary.getSavePath(applicationId) val addonDir: String - get() = DirectoryInitialization.userDirectory + "/load/" + programIdHex + "/" + get() = DirectoryInitialization.userDirectory + "/load/" + applicationIdHex + "/" val launchIntent: Intent get() = Intent(YuzuApplication.appContext, EmulationActivity::class.java).apply { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CustomSettingsHandler.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CustomSettingsHandler.kt index 377313d0aa..f8a58bfd9b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CustomSettingsHandler.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/CustomSettingsHandler.kt @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2023 yuzu Emulator Project @@ -65,7 +65,7 @@ object CustomSettingsHandler { // Initialize per-game config try { val fileName = FileUtil.getFilename(Uri.parse(game.path)) - NativeConfig.initializePerGameConfig(game.programId, fileName) + NativeConfig.initializePerGameConfig(game.applicationId, fileName) Log.info("[CustomSettingsHandler] Successfully applied custom settings") return game } catch (e: Exception) { @@ -333,20 +333,20 @@ object CustomSettingsHandler { */ fun findGameByTitleId(titleId: String, context: Context): Game? { Log.info("[CustomSettingsHandler] Searching for game with title ID: $titleId") - // Convert hex title ID to decimal for comparison with programId - val programIdDecimal = try { - titleId.toLong(16).toString() + // Convert the program ID to the application ID used by per-game settings. + val applicationIdLong = try { + titleId.toLong(16) and -8192L } catch (e: NumberFormatException) { Log.error("[CustomSettingsHandler] Invalid title ID format: $titleId") return null } + val applicationIdDecimal = applicationIdLong.toString() // Expected hex format with "0" prefix - val expectedHex = "0${titleId.uppercase()}" + val expectedHex = "0${applicationIdLong.toString(16).uppercase()}" // First check cached games for fast lookup GameHelper.cachedGameList.find { game -> - game.programId == programIdDecimal || - game.programIdHex.equals(expectedHex, ignoreCase = true) + game.applicationId == applicationIdDecimal || game.applicationIdHex.equals(expectedHex, ignoreCase = true) }?.let { foundGame -> Log.info("[CustomSettingsHandler] Found game in cache: ${foundGame.title}") return foundGame @@ -355,8 +355,7 @@ object CustomSettingsHandler { Log.info("[CustomSettingsHandler] Game not in cache, scanning full library...") val allGames = GameHelper.getGames() val foundGame = allGames.find { game -> - game.programId == programIdDecimal || - game.programIdHex.equals(expectedHex, ignoreCase = true) + game.applicationId == applicationIdDecimal || game.applicationIdHex.equals(expectedHex, ignoreCase = true) } if (foundGame != null) { Log.info("[CustomSettingsHandler] Found game: ${foundGame.title} at ${foundGame.path}") diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt index 411b764e36..3948fdc498 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt @@ -170,12 +170,12 @@ object GameHelper { val game = getGame(it.uri, true, false) if (game != null) { games.add(game) - if (game.programId != "0") { - gamesByProgramId[game.programId] = game + if (game.applicationId != "0") { + gamesByProgramId[game.applicationId] = game } } else if (mountedContainer) { GameMetadata.getProgramId(filePath).toLongOrNull()?.let { programId -> - gamesByProgramId[(programId and 0x800L.inv()).toString()] + gamesByProgramId[(programId and -8192L).toString()] }?.let { existingGame -> NativeLibrary.getPatchesForFile(existingGame.path, existingGame.programId) existingGame.version = GameMetadata.getVersion( diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index e8e9f0a14b..1e75a69973 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -788,7 +788,7 @@ int Java_org_yuzu_yuzu_1emu_NativeLibrary_installFileToNand(JNIEnv* env, jobject jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* env, jobject jobj, jstring jprogramId, jstring jupdatePath) { - u64 program_id = EmulationSession::GetProgramId(env, jprogramId); + const u64 program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId)); std::string updatePath = Common::Android::GetJString(env, jupdatePath); std::shared_ptr nsp = std::make_shared( EmulationSession::GetInstance().System().GetFilesystem()->OpenFile( @@ -796,7 +796,7 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_doesUpdateMatchProgram(JNIEnv* en for (const auto& item : nsp->GetNCAs()) { for (const auto& nca_details : item.second) { if (nca_details.second->GetName().ends_with(".cnmt.nca")) { - auto update_id = nca_details.second->GetTitleId() & ~0xFFFULL; + const auto update_id = FileSys::GetBaseTitleID(nca_details.second->GetTitleId()); if (update_id == program_id) { return true; } @@ -1491,7 +1491,7 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_firmwareVersion(JNIEnv* env, jclas } jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_gameRequiresFirmware(JNIEnv* env, jclass clazz, jstring jprogramId) { - auto program_id = EmulationSession::GetProgramId(env, jprogramId); + const auto program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId)); return FirmwareManager::GameRequiresFirmware(program_id); } @@ -1575,20 +1575,21 @@ jobjectArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getPatchesForFile(JNIEnv* env void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeUpdate(JNIEnv* env, jobject jobj, jstring jprogramId) { - auto program_id = EmulationSession::GetProgramId(env, jprogramId); + const auto program_id = EmulationSession::GetProgramId(env, jprogramId); ContentManager::RemoveUpdate(EmulationSession::GetInstance().System().GetFileSystemController(), program_id); } void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeDLC(JNIEnv* env, jobject jobj, jstring jprogramId) { - auto program_id = EmulationSession::GetProgramId(env, jprogramId); + const auto program_id = FileSys::GetBaseTitleID( + EmulationSession::GetProgramId(env, jprogramId)); ContentManager::RemoveAllDLC(EmulationSession::GetInstance().System(), program_id); } void Java_org_yuzu_yuzu_1emu_NativeLibrary_removeMod(JNIEnv* env, jobject jobj, jstring jprogramId, jstring jname) { - auto program_id = EmulationSession::GetProgramId(env, jprogramId); + const auto program_id = EmulationSession::GetProgramId(env, jprogramId); ContentManager::RemoveMod(EmulationSession::GetInstance().System().GetFileSystemController(), program_id, Common::Android::GetJString(env, jname)); } @@ -1635,7 +1636,7 @@ jint Java_org_yuzu_yuzu_1emu_NativeLibrary_verifyGameContents(JNIEnv* env, jobje jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getSavePath(JNIEnv* env, jobject jobj, jstring jprogramId) { - auto program_id = EmulationSession::GetProgramId(env, jprogramId); + const auto program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId)); if (program_id == 0) { return Common::Android::ToJString(env, ""); } diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp index 647fdbb997..7054d74202 100644 --- a/src/android/app/src/main/jni/native_config.cpp +++ b/src/android/app/src/main/jni/native_config.cpp @@ -12,6 +12,7 @@ #include "common/fs/path_util.h" #include "common/logging.h" #include "common/settings.h" +#include "core/file_sys/common_funcs.h" #include "frontend_common/config.h" #include "frontend_common/settings_generator.h" #include "native.h" @@ -56,7 +57,7 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveGlobalConfig(JNIEnv* env, jo void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializePerGameConfig(JNIEnv* env, jobject obj, jstring jprogramId, jstring jfileName) { - auto program_id = EmulationSession::GetProgramId(env, jprogramId); + const auto program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId)); auto file_name = Common::Android::GetJString(env, jfileName); const auto config_file_name = program_id == 0 ? file_name : fmt::format("{:016X}", program_id); per_game_config = @@ -322,7 +323,7 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_addGameDir(JNIEnv* env, jobject jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getDisabledAddons(JNIEnv* env, jobject obj, jstring jprogramId) { - auto program_id = EmulationSession::GetProgramId(env, jprogramId); + const auto program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId)); auto& disabledAddons = Settings::values.disabled_addons[program_id]; jobjectArray jdisabledAddonsArray = env->NewObjectArray(disabledAddons.size(), Common::Android::GetStringClass(), @@ -337,7 +338,7 @@ jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getDisabledAddons(JNIEnv void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setDisabledAddons(JNIEnv* env, jobject obj, jstring jprogramId, jobjectArray jdisabledAddons) { - auto program_id = EmulationSession::GetProgramId(env, jprogramId); + const auto program_id = FileSys::GetBaseTitleID(EmulationSession::GetProgramId(env, jprogramId)); Settings::values.disabled_addons[program_id].clear(); std::vector disabled_addons; const int size = env->GetArrayLength(jdisabledAddons); diff --git a/src/core/core.cpp b/src/core/core.cpp index 054af8dc4a..0e3ef429a4 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -17,6 +17,7 @@ #include "common/string_util.h" #include "core/arm/exclusive_monitor.h" #include "core/core.h" +#include "core/file_sys/common_funcs.h" #include "launch_timestamp_cache.h" #include "core/core_timing.h" @@ -396,7 +397,7 @@ struct System::Impl { LOG_ERROR(Core, "Failed to find program id for ROM"); } - GameSettings::LoadOverrides(program_id, gpu_core->Renderer()); + GameSettings::LoadOverrides(FileSys::GetBaseTitleID(program_id), gpu_core->Renderer()); if (auto room_member = Network::GetRoomMember().lock()) { Network::GameInfo game_info; game_info.name = name; diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 6ae4679054..36e761d98a 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -161,7 +161,7 @@ std::string GetUpdateVersionStringFromSlot(const ContentProvider* provider, u64 PatchManager::PatchManager(u64 title_id_, const Service::FileSystem::FileSystemController& fs_controller_, const ContentProvider& content_provider_) - : title_id{title_id_}, fs_controller{fs_controller_}, content_provider{content_provider_} {} + : title_id{title_id_}, application_id{GetBaseTitleID(title_id_)}, fs_controller{fs_controller_}, content_provider{content_provider_} {} PatchManager::~PatchManager() = default; @@ -169,13 +169,41 @@ u64 PatchManager::GetTitleID() const { return title_id; } +u64 PatchManager::GetUpdateTitleIDForContent() const { + const auto program_update_id = GetUpdateTitleID(title_id); + if (program_update_id == GetUpdateTitleID(application_id) || content_provider.HasEntry(program_update_id, ContentRecordType::Program)) { + return program_update_id; + } + return GetUpdateTitleID(application_id); +} + +std::vector PatchManager::GetModificationLoadRoots() const { + std::vector roots; + roots.push_back(fs_controller.GetModificationLoadRoot(title_id)); + if (application_id != title_id) { + roots.push_back(fs_controller.GetModificationLoadRoot(application_id)); + } + std::erase(roots, nullptr); + return roots; +} + +std::vector PatchManager::GetSDMCModificationLoadRoots() const { + std::vector roots; + roots.push_back(fs_controller.GetSDMCModificationLoadRoot(title_id)); + if (application_id != title_id) { + roots.push_back(fs_controller.GetSDMCModificationLoadRoot(application_id)); + } + std::erase(roots, nullptr); + return roots; +} + VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id); if (exefs == nullptr) return exefs; - const auto& disabled = Settings::values.disabled_addons[title_id]; + const auto& disabled = Settings::values.disabled_addons[application_id]; bool update_disabled = true; std::optional enabled_version; @@ -183,7 +211,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { bool checked_manual = false; const auto* content_union = static_cast(&content_provider); - const auto update_tid = GetUpdateTitleID(title_id); + const auto update_tid = GetUpdateTitleIDForContent(); if (content_union) { // First, check ExternalContentProvider @@ -303,17 +331,21 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { } // LayeredExeFS - const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); - const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); + const auto load_dirs = GetModificationLoadRoots(); + const auto sdmc_load_dirs = GetSDMCModificationLoadRoots(); - std::vector patch_dirs = {sdmc_load_dir}; - if (load_dir != nullptr) { + std::vector patch_dirs; + for (const auto& sdmc_load_dir : sdmc_load_dirs) { + patch_dirs.push_back(sdmc_load_dir); + } + for (const auto& load_dir : load_dirs) { const auto load_patch_dirs = load_dir->GetSubdirectories(); patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end()); } - std::sort(patch_dirs.begin(), patch_dirs.end(), - [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); + std::stable_sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) { + return l->GetName() < r->GetName(); + }); std::vector layers; layers.reserve(patch_dirs.size() + 1); @@ -347,7 +379,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { std::vector PatchManager::CollectPatches(const std::vector& patch_dirs, const std::string& build_id) const { - const auto& disabled = Settings::values.disabled_addons[title_id]; + const auto& disabled = Settings::values.disabled_addons[application_id]; const auto nso_build_id = fmt::format("{:0<64}", build_id); std::vector out; @@ -412,15 +444,20 @@ std::vector PatchManager::PatchNSO(const std::vector& nso, const std::st LOG_INFO(Loader, "Patching NSO for name={}, build_id={}", name, build_id); - const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); - if (load_dir == nullptr) { + const auto load_dirs = GetModificationLoadRoots(); + if (load_dirs.empty()) { LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); return nso; } - auto patch_dirs = load_dir->GetSubdirectories(); - std::sort(patch_dirs.begin(), patch_dirs.end(), - [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); + std::vector patch_dirs; + for (const auto& load_dir : load_dirs) { + const auto load_patch_dirs = load_dir->GetSubdirectories(); + patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end()); + } + std::stable_sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) { + return l->GetName() < r->GetName(); + }); const auto patches = CollectPatches(patch_dirs, build_id); auto out = nso; @@ -455,29 +492,39 @@ bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name) LOG_INFO(Loader, "Querying NSO patch existence for build_id={}, name={}", build_id, name); - const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); - if (load_dir == nullptr) { + const auto load_dirs = GetModificationLoadRoots(); + if (load_dirs.empty()) { LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); return false; } - auto patch_dirs = load_dir->GetSubdirectories(); - std::sort(patch_dirs.begin(), patch_dirs.end(), - [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); + std::vector patch_dirs; + for (const auto& load_dir : load_dirs) { + const auto load_patch_dirs = load_dir->GetSubdirectories(); + patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end()); + } + std::stable_sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) { + return l->GetName() < r->GetName(); + }); return !CollectPatches(patch_dirs, build_id).empty(); } std::vector PatchManager::CreateCheatList(const BuildID& build_id_) const { - const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); - if (load_dir == nullptr) { + const auto load_dirs = GetModificationLoadRoots(); + if (load_dirs.empty()) { LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); return {}; } - const auto& disabled = Settings::values.disabled_addons[title_id]; - auto patch_dirs = load_dir->GetSubdirectories(); - std::sort(patch_dirs.begin(), patch_dirs.end(), [](auto const& l, auto const& r) { return l->GetName() < r->GetName(); }); + const auto& disabled = Settings::values.disabled_addons[application_id]; + std::vector patch_dirs; + for (const auto& load_dir : load_dirs) { + const auto load_patch_dirs = load_dir->GetSubdirectories(); + patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end()); + } + std::stable_sort(patch_dirs.begin(), patch_dirs.end(), + [](auto const& l, auto const& r) { return l->GetName() < r->GetName(); }); // / / cheats / .txt std::vector out; @@ -493,39 +540,52 @@ std::vector PatchManager::CreateCheatList(const BuildI } // Uncareless user-friendly loading of patches (must start with 'cheat_') // / .txt - for (auto const& f : load_dir->GetFiles()) { - auto const name = f->GetName(); - if (name.starts_with("cheat_") && std::find(disabled.cbegin(), disabled.cend(), name) == disabled.cend()) { - std::vector data(f->GetSize()); - if (f->Read(data.data(), data.size()) == data.size()) { - const Core::Memory::TextCheatParser parser; - auto const res = parser.Parse(std::string_view(reinterpret_cast(data.data()), data.size())); - std::copy(res.begin(), res.end(), std::back_inserter(out)); - } else { - LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}", title_id); + for (const auto& load_dir : load_dirs) { + for (auto const& f : load_dir->GetFiles()) { + auto const name = f->GetName(); + if (name.starts_with("cheat_") && std::find(disabled.cbegin(), disabled.cend(), name) == disabled.cend()) { + std::vector data(f->GetSize()); + if (f->Read(data.data(), data.size()) == data.size()) { + const Core::Memory::TextCheatParser parser; + auto const res = parser.Parse(std::string_view(reinterpret_cast(data.data()), data.size())); + std::copy(res.begin(), res.end(), std::back_inserter(out)); + } else { + LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}", title_id); + } } } } return out; } -static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type, +static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, u64 application_id, ContentRecordType type, const Service::FileSystem::FileSystemController& fs_controller) { - const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); - const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); + std::vector load_dirs{fs_controller.GetModificationLoadRoot(title_id)}; + std::vector sdmc_load_dirs{fs_controller.GetSDMCModificationLoadRoot(title_id)}; + if (application_id != title_id) { + load_dirs.push_back(fs_controller.GetModificationLoadRoot(application_id)); + sdmc_load_dirs.push_back(fs_controller.GetSDMCModificationLoadRoot(application_id)); + } + std::erase(load_dirs, nullptr); + std::erase(sdmc_load_dirs, nullptr); if ((type != ContentRecordType::Program && type != ContentRecordType::Data && type != ContentRecordType::HtmlDocument) || - (load_dir == nullptr && sdmc_load_dir == nullptr)) { + (load_dirs.empty() && sdmc_load_dirs.empty())) { return; } - const auto& disabled = Settings::values.disabled_addons[title_id]; - std::vector patch_dirs = load_dir->GetSubdirectories(); - if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) { - patch_dirs.push_back(sdmc_load_dir); + const auto& disabled = Settings::values.disabled_addons[application_id]; + std::vector patch_dirs; + for (const auto& load_dir : load_dirs) { + const auto load_patch_dirs = load_dir->GetSubdirectories(); + patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end()); } - std::sort(patch_dirs.begin(), patch_dirs.end(), - [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); + if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) { + patch_dirs.insert(patch_dirs.end(), sdmc_load_dirs.begin(), sdmc_load_dirs.end()); + } + std::stable_sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) { + return l->GetName() < r->GetName(); + }); std::vector layers; std::vector layers_ext; @@ -597,8 +657,8 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs auto romfs = base_romfs; // Game Updates - const auto update_tid = GetUpdateTitleID(title_id); - const auto& disabled = Settings::values.disabled_addons[title_id]; + const auto update_tid = GetUpdateTitleIDForContent(); + const auto& disabled = Settings::values.disabled_addons[application_id]; bool update_disabled = true; std::optional enabled_version; @@ -705,7 +765,7 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs // LayeredFS if (apply_layeredfs) { - ApplyLayeredFS(romfs, title_id, type, fs_controller); + ApplyLayeredFS(romfs, title_id, application_id, type, fs_controller); } return romfs; @@ -717,10 +777,10 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { } std::vector out; - const auto& disabled = Settings::values.disabled_addons[title_id]; + const auto& disabled = Settings::values.disabled_addons[application_id]; // Game Updates - const auto update_tid = GetUpdateTitleID(title_id); + const auto update_tid = GetUpdateTitleIDForContent(); std::vector external_update_patches; @@ -869,7 +929,7 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { .version = "", .type = PatchType::Update, .program_id = title_id, - .title_id = title_id, + .title_id = update_tid, .source = PatchSource::Unknown, .numeric_version = 0}; @@ -895,8 +955,7 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { } // General Mods (LayeredFS and IPS) - const auto mod_dir = fs_controller.GetModificationLoadRoot(title_id); - if (mod_dir != nullptr) { + for (const auto& mod_dir : GetModificationLoadRoots()) { for (auto const& f : mod_dir->GetFiles()) if (auto const name = f->GetName(); name.starts_with("cheat_")) { auto const mod_disabled = std::find(disabled.begin(), disabled.end(), name) != disabled.end(); @@ -963,8 +1022,7 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { } // SDMC mod directory (RomFS LayeredFS) - const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); - if (sdmc_mod_dir != nullptr) { + for (const auto& sdmc_mod_dir : GetSDMCModificationLoadRoots()) { std::string types; if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "exefs"))) AppendCommaIfNotEmpty(types, "LayeredExeFS"); @@ -999,10 +1057,10 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { dlc_match.reserve(dlc_entries_with_origin.size()); for (const auto& [slot, entry] : dlc_entries_with_origin) { const auto base_tid = GetBaseTitleID(entry.title_id); - const bool matches_base = base_tid == title_id; + const bool matches_base = base_tid == application_id; if (!matches_base) { LOG_DEBUG(Loader, "DLC {:016X} base {:016X} doesn't match title {:016X}", - entry.title_id, base_tid, title_id); + entry.title_id, base_tid, application_id); continue; } @@ -1077,16 +1135,22 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { } std::optional PatchManager::GetGameVersion() const { - const auto update_tid = GetUpdateTitleID(title_id); + const auto update_tid = GetUpdateTitleIDForContent(); if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) { return content_provider.GetEntryVersion(update_tid); } - return content_provider.GetEntryVersion(title_id); + if (const auto version = content_provider.GetEntryVersion(title_id); version.has_value()) { + return version; + } + return content_provider.GetEntryVersion(application_id); } PatchManager::Metadata PatchManager::GetControlMetadata() const { - const auto base_control_nca = content_provider.GetEntry(title_id, ContentRecordType::Control); + auto base_control_nca = content_provider.GetEntry(title_id, ContentRecordType::Control); + if (base_control_nca == nullptr && application_id != title_id) { + base_control_nca = content_provider.GetEntry(application_id, ContentRecordType::Control); + } if (base_control_nca == nullptr) { return {}; } @@ -1162,8 +1226,14 @@ PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const { auto metadata = pm.GetControlMetadata(); if (metadata.first != nullptr) return metadata; - const FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(application_id), system.GetFileSystemController(), system.GetContentProvider()}; - return pm_update.GetControlMetadata(); + const auto update_id = FileSys::GetUpdateTitleID(application_id); + const auto application_update_id = FileSys::GetUpdateTitleID(GetBaseTitleID(application_id)); + const FileSys::PatchManager pm_update{update_id, system.GetFileSystemController(), system.GetContentProvider()}; + metadata = pm_update.GetControlMetadata(); + if (metadata.first != nullptr || update_id == application_update_id) + return metadata; + const FileSys::PatchManager pm_application_update{application_update_id, system.GetFileSystemController(), system.GetContentProvider()}; + return pm_application_update.GetControlMetadata(); } } // namespace FileSys diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 5d71dbcf20..2328a4daf8 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -10,6 +10,7 @@ #include #include #include +#include #include "common/common_types.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/vfs/vfs_types.h" @@ -109,10 +110,14 @@ public: [[nodiscard]] static PatchManager::Metadata GetMetadataFromBaseOrUpdate(Core::System& system, u64 application_id) noexcept; private: + [[nodiscard]] u64 GetUpdateTitleIDForContent() const; + [[nodiscard]] std::vector GetModificationLoadRoots() const; + [[nodiscard]] std::vector GetSDMCModificationLoadRoots() const; [[nodiscard]] std::vector CollectPatches(const std::vector& patch_dirs, const std::string& build_id) const; u64 title_id; + u64 application_id; const Service::FileSystem::FileSystemController& fs_controller; const ContentProvider& content_provider; }; diff --git a/src/core/hle/service/am/process_creation.cpp b/src/core/hle/service/am/process_creation.cpp index 60154fc301..450ad88866 100644 --- a/src/core/hle/service/am/process_creation.cpp +++ b/src/core/hle/service/am/process_creation.cpp @@ -6,6 +6,7 @@ #include #include "core/core.h" +#include "core/file_sys/common_funcs.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/patch_manager.h" @@ -104,8 +105,16 @@ std::unique_ptr CreateApplicationProcess(std::vector& out_control, // TODO(DarkLordZach): When FSController/Game Card Support is added, if // current_process_game_card use correct StorageId - launch.base_game_storage_id = GetStorageIdForFrontendSlot(storage.GetSlotForEntry(launch.title_id, FileSys::ContentRecordType::Program)); - launch.update_storage_id = GetStorageIdForFrontendSlot(storage.GetSlotForEntry(FileSys::GetUpdateTitleID(launch.title_id), FileSys::ContentRecordType::Program)); + auto base_slot = storage.GetSlotForEntry(launch.title_id, FileSys::ContentRecordType::Program); + if (!base_slot) { + base_slot = storage.GetSlotForEntry(FileSys::GetBaseTitleID(launch.title_id), FileSys::ContentRecordType::Program); + } + launch.base_game_storage_id = GetStorageIdForFrontendSlot(base_slot); + auto update_slot = storage.GetSlotForEntry(FileSys::GetUpdateTitleID(launch.title_id), FileSys::ContentRecordType::Program); + if (!update_slot) { + update_slot = storage.GetSlotForEntry(FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(launch.title_id)), FileSys::ContentRecordType::Program); + } + launch.update_storage_id = GetStorageIdForFrontendSlot(update_slot); system.GetARPManager().Register(launch.title_id, launch, out_control); return process; diff --git a/src/core/hle/service/am/service/application_functions.cpp b/src/core/hle/service/am/service/application_functions.cpp index 215d204098..7d164f4637 100644 --- a/src/core/hle/service/am/service/application_functions.cpp +++ b/src/core/hle/service/am/service/application_functions.cpp @@ -158,7 +158,7 @@ Result IApplicationFunctions::EnsureSaveData(Out out_size, Common::UUID use LOG_INFO(Service_AM, "called, uid={}", user_id.FormattedString()); FileSys::SaveDataAttribute attribute{}; - attribute.program_id = m_applet->program_id; + attribute.program_id = FileSys::GetBaseTitleID(m_applet->program_id); attribute.user_id = user_id.AsU128(); attribute.type = FileSys::SaveDataType::Account; @@ -238,7 +238,7 @@ Result IApplicationFunctions::ExtendSaveData(Out out_required_size, FileSys static_cast(type), user_id.FormattedString(), normal_size, journal_size); system.GetFileSystemController().OpenSaveDataController()->WriteSaveDataSize( - type, m_applet->program_id, user_id.AsU128(), {normal_size, journal_size}); + type, FileSys::GetBaseTitleID(m_applet->program_id), user_id.AsU128(), {normal_size, journal_size}); // The following value is used to indicate the amount of space remaining on failure // due to running out of space. Since we always succeed, this should be 0. @@ -252,7 +252,7 @@ Result IApplicationFunctions::GetSaveDataSize(Out out_normal_size, Out LOG_DEBUG(Service_AM, "called with type={} user_id={}", type, user_id.FormattedString()); const auto size = system.GetFileSystemController().OpenSaveDataController()->ReadSaveDataSize( - type, m_applet->program_id, user_id.AsU128()); + type, FileSys::GetBaseTitleID(m_applet->program_id), user_id.AsU128()); *out_normal_size = size.normal; *out_journal_size = size.journal; diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index d5984f560b..40c2719934 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -14,6 +14,7 @@ #include "core/core.h" #include "core/file_sys/bis_factory.h" #include "core/file_sys/card_image.h" +#include "core/file_sys/common_funcs.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/errors.h" #include "core/file_sys/patch_manager.h" @@ -339,7 +340,7 @@ Result FileSystemController::RegisterProcess( registrations.emplace(process_id, Registration{ .program_id = program_id, .romfs_factory = std::move(romfs_factory), - .save_data_factory = CreateSaveDataFactory(program_id), + .save_data_factory = CreateSaveDataFactory(FileSys::GetBaseTitleID(program_id)), }); LOG_DEBUG(Service_FS, "Registered for process {}", process_id); diff --git a/src/core/hle/service/filesystem/fsp/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp/fsp_srv.cpp index fdbd50ed7a..e6761f8544 100644 --- a/src/core/hle/service/filesystem/fsp/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp/fsp_srv.cpp @@ -18,6 +18,7 @@ #include "common/settings.h" #include "common/string_util.h" #include "core/core.h" +#include "core/file_sys/common_funcs.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/errors.h" #include "core/file_sys/fs_directory.h" @@ -313,7 +314,7 @@ Result FSP_SRV::OpenSaveDataFileSystemBySystemSaveDataId(OutInterface ResolveIndexedProgram(Core::System& system, u64 pr return IndexedProgram{std::move(update), target_id, true}; } + const auto application_update_id = + FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(target_id)); + if (application_update_id != update_id) { + if (auto update = provider.GetEntryRaw(application_update_id, FileSys::ContentRecordType::Program)) { + LOG_INFO(Loader, "Program index {} has no base program, loading it from application update {:016X}", program_index, application_update_id); + return IndexedProgram{std::move(update), target_id, true}; + } + } + LOG_WARNING(Loader, "No program NCA for {:016X} (index {}), falling back to the container", target_id, program_index); return std::nullopt; diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp index 2f9cd68e9c..22f0482768 100644 --- a/src/core/loader/nca.cpp +++ b/src/core/loader/nca.cpp @@ -11,6 +11,7 @@ #include "common/hex_util.h" #include "common/scope_exit.h" #include "core/core.h" +#include "core/file_sys/common_funcs.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/nca_metadata.h" @@ -76,8 +77,13 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S LOG_INFO(Loader, "No ExeFS found in NCA, looking for ExeFS from update"); const auto& installed = system.GetContentProvider(); - const auto update_nca = installed.GetEntry(FileSys::GetUpdateTitleID(nca->GetTitleId()), - FileSys::ContentRecordType::Program); + const auto program_update_id = FileSys::GetUpdateTitleID(nca->GetTitleId()); + auto update_nca = installed.GetEntry(program_update_id, FileSys::ContentRecordType::Program); + if (update_nca == nullptr) { + update_nca = installed.GetEntry( + FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(nca->GetTitleId())), + FileSys::ContentRecordType::Program); + } if (update_nca) { exefs = update_nca->GetExeFS(); diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index cfc908b4ef..e13780673a 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp @@ -186,8 +186,13 @@ ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& out_file) { return ResultStatus::ErrorNoPackedUpdate; } - const auto read = nsp->GetNCAFile(FileSys::GetUpdateTitleID(nsp->GetProgramTitleID()), - FileSys::ContentRecordType::Program); + const auto program_update_id = FileSys::GetUpdateTitleID(nsp->GetProgramTitleID()); + auto read = nsp->GetNCAFile(program_update_id, FileSys::ContentRecordType::Program); + if (read == nullptr) { + read = nsp->GetNCAFile( + FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(nsp->GetProgramTitleID())), + FileSys::ContentRecordType::Program); + } if (read == nullptr) { return ResultStatus::ErrorNoPackedUpdate; diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index 983184a226..46ba7ba936 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -9,6 +9,7 @@ #include "common/common_types.h" #include "core/core.h" #include "core/file_sys/card_image.h" +#include "core/file_sys/common_funcs.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" @@ -137,8 +138,13 @@ ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& out_file) { return ResultStatus::ErrorXCIMissingProgramNCA; } - const auto read = xci->GetSecurePartitionNSP()->GetNCAFile( - FileSys::GetUpdateTitleID(program_id), FileSys::ContentRecordType::Program); + const auto program_update_id = FileSys::GetUpdateTitleID(program_id); + auto read = xci->GetSecurePartitionNSP()->GetNCAFile(program_update_id, FileSys::ContentRecordType::Program); + if (read == nullptr) { + read = xci->GetSecurePartitionNSP()->GetNCAFile( + FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(program_id)), + FileSys::ContentRecordType::Program); + } if (read == nullptr) { return ResultStatus::ErrorNoPackedUpdate; } diff --git a/src/frontend_common/content_manager.h b/src/frontend_common/content_manager.h index 4f3112e3fb..4a6fab144b 100644 --- a/src/frontend_common/content_manager.h +++ b/src/frontend_common/content_manager.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2024 yuzu Emulator Project @@ -56,13 +56,14 @@ inline bool RemoveDLC(const Service::FileSystem::FileSystemController& fs_contro */ inline size_t RemoveAllDLC(Core::System& system, const u64 program_id) { size_t count{}; + const auto application_id = FileSys::GetBaseTitleID(program_id); const auto& fs_controller = system.GetFileSystemController(); const auto dlc_entries = system.GetContentProvider().ListEntriesFilter( FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); std::vector program_dlc_entries; for (const auto& entry : dlc_entries) { - if (FileSys::GetBaseTitleID(entry.title_id) == program_id) { + if (FileSys::GetBaseTitleID(entry.title_id) == application_id) { program_dlc_entries.push_back(entry.title_id); } } @@ -83,9 +84,17 @@ inline size_t RemoveAllDLC(Core::System& system, const u64 program_id) { */ inline bool RemoveUpdate(const Service::FileSystem::FileSystemController& fs_controller, const u64 program_id) { - const auto update_id = program_id | 0x800; - return fs_controller.GetUserNANDContents()->RemoveExistingEntry(update_id) || - fs_controller.GetSDMCContents()->RemoveExistingEntry(update_id); + const auto remove_update = [&fs_controller](u64 update_id) { + return fs_controller.GetUserNANDContents()->RemoveExistingEntry(update_id) || + fs_controller.GetSDMCContents()->RemoveExistingEntry(update_id); + }; + const auto update_id = FileSys::GetUpdateTitleID(program_id); + const auto application_update_id = + FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(program_id)); + if (update_id != application_update_id && remove_update(update_id)) { + return true; + } + return remove_update(application_update_id); } /** @@ -111,15 +120,26 @@ inline bool RemoveBaseContent(const Service::FileSystem::FileSystemController& f inline bool RemoveMod(const Service::FileSystem::FileSystemController& fs_controller, const u64 program_id, const std::string& mod_name) { // Check general Mods (LayeredFS and IPS) - const auto mod_dir = fs_controller.GetModificationLoadRoot(program_id); - if (mod_dir != nullptr) { - return mod_dir->DeleteSubdirectoryRecursive(mod_name); + const auto remove_from_root = [&mod_name](const auto& root) { + return root != nullptr && root->DeleteSubdirectoryRecursive(mod_name); + }; + if (remove_from_root(fs_controller.GetModificationLoadRoot(program_id))) { + return true; + } + if (FileSys::GetBaseTitleID(program_id) != program_id && + remove_from_root( + fs_controller.GetModificationLoadRoot(FileSys::GetBaseTitleID(program_id)))) { + return true; } // Check SDMC mod directory (RomFS LayeredFS) - const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(program_id); - if (sdmc_mod_dir != nullptr) { - return sdmc_mod_dir->DeleteSubdirectoryRecursive(mod_name); + if (remove_from_root(fs_controller.GetSDMCModificationLoadRoot(program_id))) { + return true; + } + if (FileSys::GetBaseTitleID(program_id) != program_id && + remove_from_root( + fs_controller.GetSDMCModificationLoadRoot(FileSys::GetBaseTitleID(program_id)))) { + return true; } return false; diff --git a/src/frontend_common/mod_manager.cpp b/src/frontend_common/mod_manager.cpp index b1adaebf91..1de3b2eee8 100644 --- a/src/frontend_common/mod_manager.cpp +++ b/src/frontend_common/mod_manager.cpp @@ -7,6 +7,7 @@ #include "common/fs/fs.h" #include "common/fs/fs_types.h" #include "common/logging.h" +#include "core/file_sys/common_funcs.h" #include "frontend_common/data_manager.h" #include "mod_manager.h" @@ -40,7 +41,7 @@ std::vector GetModFolder(const std::string& root) { } ModInstallResult InstallMod(const std::filesystem::path& path, const u64 program_id, const bool copy) { - const auto program_id_string = fmt::format("{:016X}", program_id); + const auto program_id_string = fmt::format("{:016X}", FileSys::GetBaseTitleID(program_id)); const auto mod_name = path.filename(); const auto mod_dir = DataManager::GetDataDir(DataManager::DataDir::Mods) / program_id_string / mod_name; diff --git a/src/qt_common/util/game.cpp b/src/qt_common/util/game.cpp index ddbe979765..d6d9f09ef4 100644 --- a/src/qt_common/util/game.cpp +++ b/src/qt_common/util/game.cpp @@ -5,6 +5,7 @@ #include "common/fs/fs.h" #include "common/fs/path_util.h" +#include "core/file_sys/common_funcs.h" #include "core/file_sys/savedata_factory.h" #include "core/hle/service/am/am_types.h" #include "frontend_common/content_manager.h" @@ -305,6 +306,7 @@ void RemoveAllTransferableShaderCaches(u64 program_id) { } void RemoveCustomConfiguration(u64 program_id, const std::string& game_path) { + program_id = FileSys::GetBaseTitleID(program_id); const auto file_path = std::filesystem::path(Common::FS::ToU8String(game_path)); const auto config_file_name = program_id == 0 ? Common::FS::PathToUTF8String(file_path.filename()).append(".ini") diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp index 6f530eb0ab..2ca025a146 100644 --- a/src/yuzu/configuration/configure_per_game.cpp +++ b/src/yuzu/configuration/configure_per_game.cpp @@ -24,6 +24,7 @@ #include "common/settings_input.h" #include "configuration/shared_widget.h" #include "core/core.h" +#include "core/file_sys/common_funcs.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/file_sys/xts_archive.h" @@ -50,7 +51,7 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::string& file_name, std::vector& vk_device_records, Core::System& system_) - : QDialog(parent), ui(std::make_unique()), title_id{title_id_}, + : QDialog(parent), ui(std::make_unique()), title_id{FileSys::GetBaseTitleID(title_id_)}, system{system_}, builder{std::make_unique(this, !system_.IsPoweredOn())}, tab_group{std::make_shared>()} { diff --git a/src/yuzu/configuration/configure_per_game_addons.cpp b/src/yuzu/configuration/configure_per_game_addons.cpp index bc21c70a7c..129958a8d9 100644 --- a/src/yuzu/configuration/configure_per_game_addons.cpp +++ b/src/yuzu/configuration/configure_per_game_addons.cpp @@ -24,6 +24,7 @@ #include "common/fs/path_util.h" #include "configuration/addon/mod_select_dialog.h" #include "core/core.h" +#include "core/file_sys/common_funcs.h" #include "core/file_sys/patch_manager.h" #include "core/loader/loader.h" #include "frontend_common/mod_manager.h" @@ -137,7 +138,7 @@ void ConfigurePerGameAddons::LoadFromFile(FileSys::VirtualFile file_) { } void ConfigurePerGameAddons::SetTitleId(u64 id) { - this->title_id = id; + this->title_id = FileSys::GetBaseTitleID(id); } void ConfigurePerGameAddons::InstallMods(const QStringList& mods) { diff --git a/src/yuzu/main_window.cpp b/src/yuzu/main_window.cpp index 934879ade5..d750c2443e 100644 --- a/src/yuzu/main_window.cpp +++ b/src/yuzu/main_window.cpp @@ -1922,7 +1922,7 @@ void MainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletPa std::filesystem::path{Common::U16StringFromBuffer(filename.utf16(), filename.size())}; const auto config_file_name = title_id == 0 ? Common::FS::PathToUTF8String(file_path.filename()) - : fmt::format("{:016X}", title_id); + : fmt::format("{:016X}", FileSys::GetBaseTitleID(title_id)); QtConfig per_game_config(config_file_name, Config::ConfigType::PerGameConfig); QtCommon::system->HIDCore().ReloadInputDevices(); QtCommon::system->ApplySettings(); @@ -2544,9 +2544,11 @@ void MainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pat } const FileSys::NCA update_nca{packed_update_raw, nullptr}; + const auto selected_update_id = FileSys::GetUpdateTitleID(title_id); + const auto application_update_id = FileSys::GetUpdateTitleID(FileSys::GetBaseTitleID(title_id)); if (type != FileSys::ContentRecordType::Program || update_nca.GetStatus() != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS || - update_nca.GetTitleId() != FileSys::GetUpdateTitleID(title_id)) { + (update_nca.GetTitleId() != selected_update_id && update_nca.GetTitleId() != application_update_id)) { packed_update_raw = {}; } @@ -4421,6 +4423,7 @@ void MainWindow::SetFPSSuffix() { bool MainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id, u64* selected_title_id, u8* selected_content_record_type) { + program_id = FileSys::GetBaseTitleID(program_id); using ContentInfo = std::tuple; boost::container::flat_set available_title_ids;