Add the ability to add individual files exefs with mod loader (#1766)

Co-authored-by: Ac_K <Acoustik666@gmail.com>
This commit is contained in:
Somebody Whoisbored 2020-12-29 12:54:32 -07:00 committed by GitHub
parent 9a808fe484
commit fb0db32338
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 92 additions and 43 deletions

View file

@ -21,6 +21,7 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using static LibHac.Fs.ApplicationSaveDataManagement; using static LibHac.Fs.ApplicationSaveDataManagement;
using static Ryujinx.HLE.HOS.ModLoader;
using ApplicationId = LibHac.Ncm.ApplicationId; using ApplicationId = LibHac.Ncm.ApplicationId;
namespace Ryujinx.HLE.HOS namespace Ryujinx.HLE.HOS
@ -30,7 +31,22 @@ namespace Ryujinx.HLE.HOS
public class ApplicationLoader public class ApplicationLoader
{ {
// Binaries from exefs are loaded into mem in this order. Do not change. // Binaries from exefs are loaded into mem in this order. Do not change.
private static readonly string[] ExeFsPrefixes = { "rtld", "main", "subsdk*", "sdk" }; internal static readonly string[] ExeFsPrefixes =
{
"rtld",
"main",
"subsdk0",
"subsdk1",
"subsdk2",
"subsdk3",
"subsdk4",
"subsdk5",
"subsdk6",
"subsdk7",
"subsdk8",
"subsdk9",
"sdk"
};
private readonly Switch _device; private readonly Switch _device;
private readonly ContentManager _contentManager; private readonly ContentManager _contentManager;
@ -463,37 +479,48 @@ namespace Ryujinx.HLE.HOS
metaData ??= ReadNpdm(codeFs); metaData ??= ReadNpdm(codeFs);
List<NsoExecutable> nsos = new List<NsoExecutable>(); NsoExecutable[] nsos = new NsoExecutable[ExeFsPrefixes.Length];
foreach (string exePrefix in ExeFsPrefixes) // Load binaries with standard prefixes for(int i = 0; i < nsos.Length; i++)
{ {
foreach (DirectoryEntryEx file in codeFs.EnumerateEntries("/", exePrefix)) string name = ExeFsPrefixes[i];
if (!codeFs.FileExists(name))
{ {
if (Path.GetExtension(file.Name) != string.Empty) continue; // file doesn't exist, skip
{
continue;
}
Logger.Info?.Print(LogClass.Loader, $"Loading {file.Name}...");
codeFs.OpenFile(out IFile nsoFile, file.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
NsoExecutable nso = new NsoExecutable(nsoFile.AsStorage(), file.Name);
nsos.Add(nso);
} }
Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
codeFs.OpenFile(out IFile nsoFile, $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
nsos[i] = new NsoExecutable(nsoFile.AsStorage(), name);
} }
// ExeFs file replacements // ExeFs file replacements
bool modified = _fileSystem.ModLoader.ApplyExefsMods(TitleId, nsos); ModLoadResult modLoadResult = _fileSystem.ModLoader.ApplyExefsMods(TitleId, nsos);
NsoExecutable[] programs = nsos.ToArray(); // collect the nsos, ignoring ones that aren't used
NsoExecutable[] programs = nsos.Where(x => x != null).ToArray();
modified |= _fileSystem.ModLoader.ApplyNsoPatches(TitleId, programs); // take the npdm from mods if present
if (modLoadResult.Npdm != null)
{
metaData = modLoadResult.Npdm;
}
bool hasPatches = _fileSystem.ModLoader.ApplyNsoPatches(TitleId, programs);
_contentManager.LoadEntries(_device); _contentManager.LoadEntries(_device);
if (_device.System.EnablePtc && modified) bool usePtc = _device.System.EnablePtc;
// don't use PTC if exefs files have been replaced
usePtc &= !modLoadResult.Modified;
// don't use PTC if exefs files have been patched
usePtc &= !hasPatches;
if (_device.System.EnablePtc && !usePtc)
{ {
Logger.Warning?.Print(LogClass.Ptc, $"Detected exefs modifications. PPTC disabled."); Logger.Warning?.Print(LogClass.Ptc, $"Detected exefs modifications. PPTC disabled.");
} }
@ -501,7 +528,7 @@ namespace Ryujinx.HLE.HOS
Graphics.Gpu.GraphicsConfig.TitleId = TitleIdText; Graphics.Gpu.GraphicsConfig.TitleId = TitleIdText;
_device.Gpu.HostInitalized.Set(); _device.Gpu.HostInitalized.Set();
Ptc.Initialize(TitleIdText, DisplayVersion, _device.System.EnablePtc && !modified); Ptc.Initialize(TitleIdText, DisplayVersion, usePtc);
ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, executables: programs); ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, executables: programs);
} }

View file

@ -12,6 +12,7 @@ using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq; using System.Linq;
using System.IO; using System.IO;
using Ryujinx.HLE.Loaders.Npdm;
namespace Ryujinx.HLE.HOS namespace Ryujinx.HLE.HOS
{ {
@ -381,66 +382,87 @@ namespace Ryujinx.HLE.HOS
return true; return true;
} }
internal bool ApplyExefsMods(ulong titleId, List<NsoExecutable> nsos) public struct ModLoadResult
{ {
public BitVector32 Stubs;
public BitVector32 Replaces;
public Npdm Npdm;
public bool Modified => (Stubs.Data | Replaces.Data) != 0;
}
internal ModLoadResult ApplyExefsMods(ulong titleId, NsoExecutable[] nsos)
{
ModLoadResult modLoadResult = new ModLoadResult
{
Stubs = new BitVector32(),
Replaces = new BitVector32()
};
if (!AppMods.TryGetValue(titleId, out ModCache mods) || mods.ExefsDirs.Count == 0) if (!AppMods.TryGetValue(titleId, out ModCache mods) || mods.ExefsDirs.Count == 0)
{ {
return false; return modLoadResult;
} }
bool replaced = false;
if (nsos.Count > 32) if (nsos.Length != ApplicationLoader.ExeFsPrefixes.Length)
{ {
throw new ArgumentOutOfRangeException("NSO Count is more than 32"); throw new ArgumentOutOfRangeException("NSO Count is incorrect");
} }
var exeMods = mods.ExefsDirs; var exeMods = mods.ExefsDirs;
BitVector32 stubs = new BitVector32();
BitVector32 repls = new BitVector32();
foreach (var mod in exeMods) foreach (var mod in exeMods)
{ {
for (int i = 0; i < nsos.Count; ++i) for (int i = 0; i < ApplicationLoader.ExeFsPrefixes.Length; ++i)
{ {
var nso = nsos[i]; var nsoName = ApplicationLoader.ExeFsPrefixes[i];
var nsoName = nso.Name;
FileInfo nsoFile = new FileInfo(Path.Combine(mod.Path.FullName, nsoName)); FileInfo nsoFile = new FileInfo(Path.Combine(mod.Path.FullName, nsoName));
if (nsoFile.Exists) if (nsoFile.Exists)
{ {
if (repls[1 << i]) if (modLoadResult.Replaces[1 << i])
{ {
Logger.Warning?.Print(LogClass.ModLoader, $"Multiple replacements to '{nsoName}'"); Logger.Warning?.Print(LogClass.ModLoader, $"Multiple replacements to '{nsoName}'");
continue; continue;
} }
repls[1 << i] = true; modLoadResult.Replaces[1 << i] = true;
nsos[i] = new NsoExecutable(nsoFile.OpenRead().AsStorage(), nsoName); nsos[i] = new NsoExecutable(nsoFile.OpenRead().AsStorage(), nsoName);
Logger.Info?.Print(LogClass.ModLoader, $"NSO '{nsoName}' replaced"); Logger.Info?.Print(LogClass.ModLoader, $"NSO '{nsoName}' replaced");
}
replaced = true; modLoadResult.Stubs[1 << i] |= File.Exists(Path.Combine(mod.Path.FullName, nsoName + StubExtension));
}
FileInfo npdmFile = new FileInfo(Path.Combine(mod.Path.FullName, "main.npdm"));
if(npdmFile.Exists)
{
if(modLoadResult.Npdm != null)
{
Logger.Warning?.Print(LogClass.ModLoader, "Multiple replacements to 'main.npdm'");
continue; continue;
} }
stubs[1 << i] |= File.Exists(Path.Combine(mod.Path.FullName, nsoName + StubExtension)); modLoadResult.Npdm = new Npdm(npdmFile.OpenRead());
Logger.Info?.Print(LogClass.ModLoader, $"main.npdm replaced");
} }
} }
for (int i = nsos.Count - 1; i >= 0; --i) for (int i = ApplicationLoader.ExeFsPrefixes.Length - 1; i >= 0; --i)
{ {
if (stubs[1 << i] && !repls[1 << i]) // Prioritizes replacements over stubs if (modLoadResult.Stubs[1 << i] && !modLoadResult.Replaces[1 << i]) // Prioritizes replacements over stubs
{ {
Logger.Info?.Print(LogClass.ModLoader, $" NSO '{nsos[i].Name}' stubbed"); Logger.Info?.Print(LogClass.ModLoader, $" NSO '{nsos[i].Name}' stubbed");
nsos.RemoveAt(i); nsos[i] = null;
replaced = true;
} }
} }
return replaced; return modLoadResult;
} }
internal void ApplyNroPatches(NroExecutable nro) internal void ApplyNroPatches(NroExecutable nro)