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 static LibHac.Fs.ApplicationSaveDataManagement;
using static Ryujinx.HLE.HOS.ModLoader;
using ApplicationId = LibHac.Ncm.ApplicationId;
namespace Ryujinx.HLE.HOS
@ -30,7 +31,22 @@ namespace Ryujinx.HLE.HOS
public class ApplicationLoader
{
// 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 ContentManager _contentManager;
@ -463,37 +479,48 @@ namespace Ryujinx.HLE.HOS
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;
continue; // file doesn't exist, skip
}
Logger.Info?.Print(LogClass.Loader, $"Loading {file.Name}...");
Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
codeFs.OpenFile(out IFile nsoFile, file.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
codeFs.OpenFile(out IFile nsoFile, $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
NsoExecutable nso = new NsoExecutable(nsoFile.AsStorage(), file.Name);
nsos.Add(nso);
}
nsos[i] = new NsoExecutable(nsoFile.AsStorage(), name);
}
// 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);
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.");
}
@ -501,7 +528,7 @@ namespace Ryujinx.HLE.HOS
Graphics.Gpu.GraphicsConfig.TitleId = TitleIdText;
_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);
}

View File

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