aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.HLE/HOS/ApplicationLoader.cs
diff options
context:
space:
mode:
authormageven <62494521+mageven@users.noreply.github.com>2020-07-09 10:01:15 +0530
committerGitHub <noreply@github.com>2020-07-09 14:31:15 +1000
commit189c0c9c726b3a700272831cd5cf10b2fc817cc2 (patch)
tree724e5d808c061917ae686e1e557c504fff3cd24f /Ryujinx.HLE/HOS/ApplicationLoader.cs
parentc050994995268494d46a6cac1d4ffa931effa0f6 (diff)
Implement modding support (#1249)
* Implement Modding Support * Executables: Rewrite to use contiguous mem and Spans * Reorder ExeFs, Npdm, ControlData and SaveData calls After discussion with gdkchan, it was decided it's best to call LoadExeFs after all other loads are done as it starts the guest process. * Build RomFs manually instead of Layering FS Layered FS approach has considerable latency when building the final romfs. So, we manually replace files in a single romfs instance. * Add RomFs modding via storage file * Fix and cleanup MemPatch * Add dynamically loaded NRO patching * Support exefs file replacement * Rewrite ModLoader to use mods-search architecture * Disable PPTC when exefs patches are detected Disable PPTC on exefs replacements too * Rewrite ModLoader, again * Increased maintainability and matches Atmosphere closely * Creates base mods structure if it doesn't exist * Add Exefs partition replacement * IPSwitch: Fix nsobid parsing * Move mod logs to new LogClass * Allow custom suffixes to title dirs again * Address nits * Add a per-App "Open Mods Directory" context menu item Creates the path if not present. * Normalize tooltips verbiage * Use LocalStorage and remove unused namespaces
Diffstat (limited to 'Ryujinx.HLE/HOS/ApplicationLoader.cs')
-rw-r--r--Ryujinx.HLE/HOS/ApplicationLoader.cs116
1 files changed, 74 insertions, 42 deletions
diff --git a/Ryujinx.HLE/HOS/ApplicationLoader.cs b/Ryujinx.HLE/HOS/ApplicationLoader.cs
index bc7016bd..994d0f25 100644
--- a/Ryujinx.HLE/HOS/ApplicationLoader.cs
+++ b/Ryujinx.HLE/HOS/ApplicationLoader.cs
@@ -43,7 +43,8 @@ namespace Ryujinx.HLE.HOS
public bool EnablePtc => _device.System.EnablePtc;
- public IntegrityCheckLevel FsIntegrityCheckLevel => _device.System.FsIntegrityCheckLevel;
+ // Binaries from exefs are loaded into mem in this order. Do not change.
+ private static readonly string[] ExeFsPrefixes = { "rtld", "main", "subsdk*", "sdk" };
public ApplicationLoader(Switch device, VirtualFileSystem fileSystem, ContentManager contentManager)
{
@@ -52,6 +53,9 @@ namespace Ryujinx.HLE.HOS
_fileSystem = fileSystem;
ControlData = new BlitStruct<ApplicationControlProperty>(1);
+
+ // Clear Mods cache
+ _fileSystem.ModLoader.Clear();
}
public void LoadCart(string exeFsDir, string romFsFile = null)
@@ -63,12 +67,14 @@ namespace Ryujinx.HLE.HOS
LocalFileSystem codeFs = new LocalFileSystem(exeFsDir);
- LoadExeFs(codeFs, out _);
+ Npdm metaData = ReadNpdm(codeFs);
if (TitleId != 0)
{
EnsureSaveData(new TitleId(TitleId));
}
+
+ LoadExeFs(codeFs, metaData);
}
private (Nca main, Nca patch, Nca control) GetGameData(PartitionFileSystem pfs)
@@ -191,7 +197,7 @@ namespace Ryujinx.HLE.HOS
}
// This is not a normal NSP, it's actually a ExeFS as a NSP
- LoadExeFs(nsp, out _);
+ LoadExeFs(nsp);
}
public void LoadNca(string ncaFile)
@@ -272,24 +278,24 @@ namespace Ryujinx.HLE.HOS
{
if (mainNca.CanOpenSection(NcaSectionType.Data))
{
- dataStorage = mainNca.OpenStorage(NcaSectionType.Data, FsIntegrityCheckLevel);
+ dataStorage = mainNca.OpenStorage(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
}
if (mainNca.CanOpenSection(NcaSectionType.Code))
{
- codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, FsIntegrityCheckLevel);
+ codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, _device.System.FsIntegrityCheckLevel);
}
}
else
{
if (patchNca.CanOpenSection(NcaSectionType.Data))
{
- dataStorage = mainNca.OpenStorageWithPatch(patchNca, NcaSectionType.Data, FsIntegrityCheckLevel);
+ dataStorage = mainNca.OpenStorageWithPatch(patchNca, NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
}
if (patchNca.CanOpenSection(NcaSectionType.Code))
{
- codeFs = mainNca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, FsIntegrityCheckLevel);
+ codeFs = mainNca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, _device.System.FsIntegrityCheckLevel);
}
}
@@ -300,37 +306,65 @@ namespace Ryujinx.HLE.HOS
return;
}
- if (dataStorage == null)
+ Npdm metaData = ReadNpdm(codeFs);
+
+ _fileSystem.ModLoader.CollectMods(TitleId, _fileSystem.GetBaseModsPath());
+
+ if (controlNca != null)
{
- Logger.PrintWarning(LogClass.Loader, "No RomFS found in NCA");
+ ReadControlData(controlNca);
}
else
{
- _fileSystem.SetRomFs(dataStorage.AsStream(FileAccess.Read));
+ ControlData.ByteSpan.Clear();
}
- if (controlNca != null)
+ if (dataStorage == null)
{
- ReadControlData(controlNca);
+ Logger.PrintWarning(LogClass.Loader, "No RomFS found in NCA");
}
else
{
- ControlData.ByteSpan.Clear();
+ IStorage newStorage = _fileSystem.ModLoader.ApplyRomFsMods(TitleId, dataStorage);
+ _fileSystem.SetRomFs(newStorage.AsStream(FileAccess.Read));
}
- LoadExeFs(codeFs, out _);
-
if (TitleId != 0)
{
EnsureSaveData(new TitleId(TitleId));
}
+ LoadExeFs(codeFs, metaData);
+
Logger.PrintInfo(LogClass.Loader, $"Application Loaded: {TitleName} v{DisplayVersion} [{TitleIdText}] [{(TitleIs64Bit ? "64-bit" : "32-bit")}]");
}
- public void ReadControlData(Nca controlNca)
+ // Sets TitleId, so be sure to call before using it
+ private Npdm ReadNpdm(IFileSystem fs)
+ {
+ Result result = fs.OpenFile(out IFile npdmFile, "/main.npdm".ToU8Span(), OpenMode.Read);
+ Npdm metaData;
+
+ if (ResultFs.PathNotFound.Includes(result))
+ {
+ Logger.PrintWarning(LogClass.Loader, "NPDM file not found, using default values!");
+
+ metaData = GetDefaultNpdm();
+ }
+ else
+ {
+ metaData = new Npdm(npdmFile.AsStream());
+ }
+
+ TitleId = metaData.Aci0.TitleId;
+ TitleIs64Bit = metaData.Is64Bit;
+
+ return metaData;
+ }
+
+ private void ReadControlData(Nca controlNca)
{
- IFileSystem controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, FsIntegrityCheckLevel);
+ IFileSystem controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
Result result = controlFs.OpenFile(out IFile controlFile, "/control.nacp".ToU8Span(), OpenMode.Read);
@@ -358,26 +392,20 @@ namespace Ryujinx.HLE.HOS
}
}
- private void LoadExeFs(IFileSystem codeFs, out Npdm metaData)
+ private void LoadExeFs(IFileSystem codeFs, Npdm metaData = null)
{
- Result result = codeFs.OpenFile(out IFile npdmFile, "/main.npdm".ToU8Span(), OpenMode.Read);
-
- if (ResultFs.PathNotFound.Includes(result))
+ if (_fileSystem.ModLoader.ReplaceExefsPartition(TitleId, ref codeFs))
{
- Logger.PrintWarning(LogClass.Loader, "NPDM file not found, using default values!");
-
- metaData = GetDefaultNpdm();
- }
- else
- {
- metaData = new Npdm(npdmFile.AsStream());
+ metaData = null; //TODO: Check if we should retain old npdm
}
- List<IExecutable> nsos = new List<IExecutable>();
+ metaData ??= ReadNpdm(codeFs);
+
+ List<NsoExecutable> nsos = new List<NsoExecutable>();
- void LoadNso(string filename)
+ foreach (string exePrefix in ExeFsPrefixes) // Load binaries with standard prefixes
{
- foreach (DirectoryEntryEx file in codeFs.EnumerateEntries("/", $"{filename}*"))
+ foreach (DirectoryEntryEx file in codeFs.EnumerateEntries("/", exePrefix))
{
if (Path.GetExtension(file.Name) != string.Empty)
{
@@ -388,25 +416,29 @@ namespace Ryujinx.HLE.HOS
codeFs.OpenFile(out IFile nsoFile, file.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
- NsoExecutable nso = new NsoExecutable(nsoFile.AsStorage());
+ NsoExecutable nso = new NsoExecutable(nsoFile.AsStorage(), file.Name);
nsos.Add(nso);
}
}
- TitleId = metaData.Aci0.TitleId;
- TitleIs64Bit = metaData.Is64Bit;
+ // ExeFs file replacements
+ bool modified = _fileSystem.ModLoader.ApplyExefsMods(TitleId, nsos);
+
+ var programs = nsos.ToArray();
- LoadNso("rtld");
- LoadNso("main");
- LoadNso("subsdk");
- LoadNso("sdk");
+ modified |= _fileSystem.ModLoader.ApplyNsoPatches(TitleId, programs);
_contentManager.LoadEntries(_device);
- Ptc.Initialize(TitleIdText, DisplayVersion, EnablePtc);
+ if (EnablePtc && modified)
+ {
+ Logger.PrintWarning(LogClass.Ptc, $"Detected exefs modifications. PPTC disabled.");
+ }
+
+ Ptc.Initialize(TitleIdText, DisplayVersion, EnablePtc && !modified);
- ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, executables: nsos.ToArray());
+ ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, executables: programs);
}
public void LoadProgram(string filePath)
@@ -420,7 +452,7 @@ namespace Ryujinx.HLE.HOS
if (isNro)
{
FileStream input = new FileStream(filePath, FileMode.Open);
- NroExecutable obj = new NroExecutable(input);
+ NroExecutable obj = new NroExecutable(input.AsStorage());
executable = obj;
// homebrew NRO can actually have some data after the actual NRO
@@ -493,7 +525,7 @@ namespace Ryujinx.HLE.HOS
}
else
{
- executable = new NsoExecutable(new LocalStorage(filePath, FileAccess.Read));
+ executable = new NsoExecutable(new LocalStorage(filePath, FileAccess.Read), Path.GetFileNameWithoutExtension(filePath));
}
_contentManager.LoadEntries(_device);