diff options
Diffstat (limited to 'src/Ryujinx.HLE/HOS')
-rw-r--r-- | src/Ryujinx.HLE/HOS/ModLoader.cs | 176 |
1 files changed, 124 insertions, 52 deletions
diff --git a/src/Ryujinx.HLE/HOS/ModLoader.cs b/src/Ryujinx.HLE/HOS/ModLoader.cs index 834bc059..669c775b 100644 --- a/src/Ryujinx.HLE/HOS/ModLoader.cs +++ b/src/Ryujinx.HLE/HOS/ModLoader.cs @@ -7,6 +7,7 @@ using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.RomFs; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.Loaders.Executables; using Ryujinx.HLE.Loaders.Mods; @@ -37,15 +38,19 @@ namespace Ryujinx.HLE.HOS private const string AmsNroPatchDir = "nro_patches"; private const string AmsKipPatchDir = "kip_patches"; + private static readonly ModMetadataJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + public readonly struct Mod<T> where T : FileSystemInfo { public readonly string Name; public readonly T Path; + public readonly bool Enabled; - public Mod(string name, T path) + public Mod(string name, T path, bool enabled) { Name = name; Path = path; + Enabled = enabled; } } @@ -67,7 +72,7 @@ namespace Ryujinx.HLE.HOS } } - // Title dependent mods + // Application dependent mods public class ModCache { public List<Mod<FileInfo>> RomfsContainers { get; } @@ -88,7 +93,7 @@ namespace Ryujinx.HLE.HOS } } - // Title independent mods + // Application independent mods private class PatchCache { public List<Mod<DirectoryInfo>> NsoPatches { get; } @@ -107,7 +112,7 @@ namespace Ryujinx.HLE.HOS } } - private readonly Dictionary<ulong, ModCache> _appMods; // key is TitleId + private readonly Dictionary<ulong, ModCache> _appMods; // key is ApplicationId private PatchCache _patches; private static readonly EnumerationOptions _dirEnumOptions; @@ -153,26 +158,52 @@ namespace Ryujinx.HLE.HOS return modsDir.FullName; } - private static DirectoryInfo FindTitleDir(DirectoryInfo contentsDir, string titleId) - => contentsDir.EnumerateDirectories(titleId, _dirEnumOptions).FirstOrDefault(); + private static DirectoryInfo FindApplicationDir(DirectoryInfo contentsDir, string applicationId) + => contentsDir.EnumerateDirectories(applicationId, _dirEnumOptions).FirstOrDefault(); - private static void AddModsFromDirectory(ModCache mods, DirectoryInfo dir, string titleId) + private static void AddModsFromDirectory(ModCache mods, DirectoryInfo dir, ModMetadata modMetadata) { System.Text.StringBuilder types = new(); foreach (var modDir in dir.EnumerateDirectories()) { types.Clear(); - Mod<DirectoryInfo> mod = new("", null); + Mod<DirectoryInfo> mod = new("", null, true); if (StrEquals(RomfsDir, modDir.Name)) { - mods.RomfsDirs.Add(mod = new Mod<DirectoryInfo>(dir.Name, modDir)); + bool enabled; + + try + { + var modData = modMetadata.Mods.Find(x => modDir.FullName.Contains(x.Path)); + enabled = modData.Enabled; + } + catch + { + // Mod is not in the list yet. New mods should be enabled by default. + enabled = true; + } + + mods.RomfsDirs.Add(mod = new Mod<DirectoryInfo>(dir.Name, modDir, enabled)); types.Append('R'); } else if (StrEquals(ExefsDir, modDir.Name)) { - mods.ExefsDirs.Add(mod = new Mod<DirectoryInfo>(dir.Name, modDir)); + bool enabled; + + try + { + var modData = modMetadata.Mods.Find(x => modDir.FullName.Contains(x.Path)); + enabled = modData.Enabled; + } + catch + { + // Mod is not in the list yet. New mods should be enabled by default. + enabled = true; + } + + mods.ExefsDirs.Add(mod = new Mod<DirectoryInfo>(dir.Name, modDir, enabled)); types.Append('E'); } else if (StrEquals(CheatDir, modDir.Name)) @@ -181,7 +212,7 @@ namespace Ryujinx.HLE.HOS } else { - AddModsFromDirectory(mods, modDir, titleId); + AddModsFromDirectory(mods, modDir, modMetadata); } if (types.Length > 0) @@ -191,18 +222,18 @@ namespace Ryujinx.HLE.HOS } } - public static string GetTitleDir(string modsBasePath, string titleId) + public static string GetApplicationDir(string modsBasePath, string applicationId) { var contentsDir = new DirectoryInfo(Path.Combine(modsBasePath, AmsContentsDir)); - var titleModsPath = FindTitleDir(contentsDir, titleId); + var applicationModsPath = FindApplicationDir(contentsDir, applicationId); - if (titleModsPath == null) + if (applicationModsPath == null) { - Logger.Info?.Print(LogClass.ModLoader, $"Creating mods directory for Title {titleId.ToUpper()}"); - titleModsPath = contentsDir.CreateSubdirectory(titleId); + Logger.Info?.Print(LogClass.ModLoader, $"Creating mods directory for Application {applicationId.ToUpper()}"); + applicationModsPath = contentsDir.CreateSubdirectory(applicationId); } - return titleModsPath.FullName; + return applicationModsPath.FullName; } // Static Query Methods @@ -238,47 +269,68 @@ namespace Ryujinx.HLE.HOS foreach (var modDir in patchDir.EnumerateDirectories()) { - patches.Add(new Mod<DirectoryInfo>(modDir.Name, modDir)); + patches.Add(new Mod<DirectoryInfo>(modDir.Name, modDir, true)); Logger.Info?.Print(LogClass.ModLoader, $"Found {type} patch '{modDir.Name}'"); } } - private static void QueryTitleDir(ModCache mods, DirectoryInfo titleDir) + private static void QueryApplicationDir(ModCache mods, DirectoryInfo applicationDir, ulong applicationId) { - if (!titleDir.Exists) + if (!applicationDir.Exists) { return; } - var fsFile = new FileInfo(Path.Combine(titleDir.FullName, RomfsContainer)); + string modJsonPath = Path.Combine(AppDataManager.GamesDirPath, applicationId.ToString("x16"), "mods.json"); + ModMetadata modMetadata = new(); + + if (File.Exists(modJsonPath)) + { + try + { + modMetadata = JsonHelper.DeserializeFromFile(modJsonPath, _serializerContext.ModMetadata); + } + catch + { + Logger.Warning?.Print(LogClass.ModLoader, $"Failed to deserialize mod data for {applicationId:X16} at {modJsonPath}"); + } + } + + var fsFile = new FileInfo(Path.Combine(applicationDir.FullName, RomfsContainer)); if (fsFile.Exists) { - mods.RomfsContainers.Add(new Mod<FileInfo>($"<{titleDir.Name} RomFs>", fsFile)); + var modData = modMetadata.Mods.Find(x => fsFile.FullName.Contains(x.Path)); + var enabled = modData == null || modData.Enabled; + + mods.RomfsContainers.Add(new Mod<FileInfo>($"<{applicationDir.Name} RomFs>", fsFile, enabled)); } - fsFile = new FileInfo(Path.Combine(titleDir.FullName, ExefsContainer)); + fsFile = new FileInfo(Path.Combine(applicationDir.FullName, ExefsContainer)); if (fsFile.Exists) { - mods.ExefsContainers.Add(new Mod<FileInfo>($"<{titleDir.Name} ExeFs>", fsFile)); + var modData = modMetadata.Mods.Find(x => fsFile.FullName.Contains(x.Path)); + var enabled = modData == null || modData.Enabled; + + mods.ExefsContainers.Add(new Mod<FileInfo>($"<{applicationDir.Name} ExeFs>", fsFile, enabled)); } - AddModsFromDirectory(mods, titleDir, titleDir.Name); + AddModsFromDirectory(mods, applicationDir, modMetadata); } - public static void QueryContentsDir(ModCache mods, DirectoryInfo contentsDir, ulong titleId) + public static void QueryContentsDir(ModCache mods, DirectoryInfo contentsDir, ulong applicationId) { if (!contentsDir.Exists) { return; } - Logger.Info?.Print(LogClass.ModLoader, $"Searching mods for {((titleId & 0x1000) != 0 ? "DLC" : "Title")} {titleId:X16}"); + Logger.Info?.Print(LogClass.ModLoader, $"Searching mods for {((applicationId & 0x1000) != 0 ? "DLC" : "Application")} {applicationId:X16}"); - var titleDir = FindTitleDir(contentsDir, $"{titleId:x16}"); + var applicationDir = FindApplicationDir(contentsDir, $"{applicationId:x16}"); - if (titleDir != null) + if (applicationDir != null) { - QueryTitleDir(mods, titleDir); + QueryApplicationDir(mods, applicationDir, applicationId); } } @@ -387,9 +439,9 @@ namespace Ryujinx.HLE.HOS { if (IsContentsDir(searchDir.Name)) { - foreach ((ulong titleId, ModCache cache) in modCaches) + foreach ((ulong applicationId, ModCache cache) in modCaches) { - QueryContentsDir(cache, searchDir, titleId); + QueryContentsDir(cache, searchDir, applicationId); } return true; @@ -410,7 +462,7 @@ namespace Ryujinx.HLE.HOS if (!searchDir.Exists) { Logger.Warning?.Print(LogClass.ModLoader, $"Mod Search Dir '{searchDir.FullName}' doesn't exist"); - continue; + return; } if (!TryQuery(searchDir, patches, modCaches)) @@ -425,21 +477,21 @@ namespace Ryujinx.HLE.HOS patches.Initialized = true; } - public void CollectMods(IEnumerable<ulong> titles, params string[] searchDirPaths) + public void CollectMods(IEnumerable<ulong> applications, params string[] searchDirPaths) { Clear(); - foreach (ulong titleId in titles) + foreach (ulong applicationId in applications) { - _appMods[titleId] = new ModCache(); + _appMods[applicationId] = new ModCache(); } CollectMods(_appMods, _patches, searchDirPaths); } - internal IStorage ApplyRomFsMods(ulong titleId, IStorage baseStorage) + internal IStorage ApplyRomFsMods(ulong applicationId, IStorage baseStorage) { - if (!_appMods.TryGetValue(titleId, out ModCache mods) || mods.RomfsDirs.Count + mods.RomfsContainers.Count == 0) + if (!_appMods.TryGetValue(applicationId, out ModCache mods) || mods.RomfsDirs.Count + mods.RomfsContainers.Count == 0) { return baseStorage; } @@ -448,11 +500,16 @@ namespace Ryujinx.HLE.HOS var builder = new RomFsBuilder(); int count = 0; - Logger.Info?.Print(LogClass.ModLoader, $"Applying RomFS mods for Title {titleId:X16}"); + Logger.Info?.Print(LogClass.ModLoader, $"Applying RomFS mods for Application {applicationId:X16}"); // Prioritize loose files first foreach (var mod in mods.RomfsDirs) { + if (!mod.Enabled) + { + continue; + } + using (IFileSystem fs = new LocalFileSystem(mod.Path.FullName)) { AddFiles(fs, mod.Name, fileSet, builder); @@ -463,7 +520,12 @@ namespace Ryujinx.HLE.HOS // Then files inside images foreach (var mod in mods.RomfsContainers) { - Logger.Info?.Print(LogClass.ModLoader, $"Found 'romfs.bin' for Title {titleId:X16}"); + if (!mod.Enabled) + { + continue; + } + + Logger.Info?.Print(LogClass.ModLoader, $"Found 'romfs.bin' for Application {applicationId:X16}"); using (IFileSystem fs = new RomFsFileSystem(mod.Path.OpenRead().AsStorage())) { AddFiles(fs, mod.Name, fileSet, builder); @@ -519,9 +581,9 @@ namespace Ryujinx.HLE.HOS } } - internal bool ReplaceExefsPartition(ulong titleId, ref IFileSystem exefs) + internal bool ReplaceExefsPartition(ulong applicationId, ref IFileSystem exefs) { - if (!_appMods.TryGetValue(titleId, out ModCache mods) || mods.ExefsContainers.Count == 0) + if (!_appMods.TryGetValue(applicationId, out ModCache mods) || mods.ExefsContainers.Count == 0) { return false; } @@ -549,7 +611,7 @@ namespace Ryujinx.HLE.HOS public bool Modified => (Stubs.Data | Replaces.Data) != 0; } - internal ModLoadResult ApplyExefsMods(ulong titleId, NsoExecutable[] nsos) + internal ModLoadResult ApplyExefsMods(ulong applicationId, NsoExecutable[] nsos) { ModLoadResult modLoadResult = new() { @@ -557,7 +619,7 @@ namespace Ryujinx.HLE.HOS Replaces = new BitVector32(), }; - if (!_appMods.TryGetValue(titleId, out ModCache mods) || mods.ExefsDirs.Count == 0) + if (!_appMods.TryGetValue(applicationId, out ModCache mods) || mods.ExefsDirs.Count == 0) { return modLoadResult; } @@ -571,6 +633,11 @@ namespace Ryujinx.HLE.HOS foreach (var mod in exeMods) { + if (!mod.Enabled) + { + continue; + } + for (int i = 0; i < ProcessConst.ExeFsPrefixes.Length; ++i) { var nsoName = ProcessConst.ExeFsPrefixes[i]; @@ -637,11 +704,11 @@ namespace Ryujinx.HLE.HOS ApplyProgramPatches(nroPatches, 0, nro); } - internal bool ApplyNsoPatches(ulong titleId, params IExecutable[] programs) + internal bool ApplyNsoPatches(ulong applicationId, params IExecutable[] programs) { IEnumerable<Mod<DirectoryInfo>> nsoMods = _patches.NsoPatches; - if (_appMods.TryGetValue(titleId, out ModCache mods)) + if (_appMods.TryGetValue(applicationId, out ModCache mods)) { nsoMods = nsoMods.Concat(mods.ExefsDirs); } @@ -651,7 +718,7 @@ namespace Ryujinx.HLE.HOS return ApplyProgramPatches(nsoMods, 0x100, programs); } - internal void LoadCheats(ulong titleId, ProcessTamperInfo tamperInfo, TamperMachine tamperMachine) + internal void LoadCheats(ulong applicationId, ProcessTamperInfo tamperInfo, TamperMachine tamperMachine) { if (tamperInfo?.BuildIds == null || tamperInfo.CodeAddresses == null) { @@ -660,9 +727,9 @@ namespace Ryujinx.HLE.HOS return; } - Logger.Info?.Print(LogClass.ModLoader, $"Build ids found for title {titleId:X16}:\n {String.Join("\n ", tamperInfo.BuildIds)}"); + Logger.Info?.Print(LogClass.ModLoader, $"Build ids found for application {applicationId:X16}:\n {String.Join("\n ", tamperInfo.BuildIds)}"); - if (!_appMods.TryGetValue(titleId, out ModCache mods) || mods.Cheats.Count == 0) + if (!_appMods.TryGetValue(applicationId, out ModCache mods) || mods.Cheats.Count == 0) { return; } @@ -687,12 +754,12 @@ namespace Ryujinx.HLE.HOS tamperMachine.InstallAtmosphereCheat(cheat.Name, cheatId, cheat.Instructions, tamperInfo, exeAddress); } - EnableCheats(titleId, tamperMachine); + EnableCheats(applicationId, tamperMachine); } - internal static void EnableCheats(ulong titleId, TamperMachine tamperMachine) + internal static void EnableCheats(ulong applicationId, TamperMachine tamperMachine) { - var contentDirectory = FindTitleDir(new DirectoryInfo(Path.Combine(GetModsBasePath(), AmsContentsDir)), $"{titleId:x16}"); + var contentDirectory = FindApplicationDir(new DirectoryInfo(Path.Combine(GetModsBasePath(), AmsContentsDir)), $"{applicationId:x16}"); string enabledCheatsPath = Path.Combine(contentDirectory.FullName, CheatDir, "enabled.txt"); if (File.Exists(enabledCheatsPath)) @@ -724,6 +791,11 @@ namespace Ryujinx.HLE.HOS // Collect patches foreach (var mod in mods) { + if (!mod.Enabled) + { + continue; + } + var patchDir = mod.Path; foreach (var patchFile in patchDir.EnumerateFiles()) { |