Ryujinx/src/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs
Mythrax 21c84439b3
nacp: add support for zlib-compressed title blocks
Introduced with TLoZ BotW 1.9.0, a compression flag determines whether the first 0x3000 bytes of the NACP title block contain a zlib-compressed blob that decompresses to 0x6000 bytes with up to 32 language entries. Added Polish and Thai language support (indexes 16/17), NacpHelper decompression utility, and updated all title-reading call sites to use resolved entries.
2026-02-21 16:02:28 +10:00

151 lines
5.4 KiB
C#

using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Loader;
using LibHac.Ns;
using LibHac.Tools.FsSystem;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Gpu;
using Ryujinx.HLE.Loaders.Executables;
using Ryujinx.HLE.Utilities;
using Ryujinx.Memory;
using System.Linq;
using static Ryujinx.HLE.HOS.ModLoader;
namespace Ryujinx.HLE.Loaders.Processes.Extensions
{
static class FileSystemExtensions
{
public static MetaLoader GetNpdm(this IFileSystem fileSystem)
{
MetaLoader metaLoader = new();
if (fileSystem == null || !fileSystem.FileExists(ProcessConst.MainNpdmPath))
{
Logger.Warning?.Print(LogClass.Loader, "NPDM file not found, using default values!");
metaLoader.LoadDefault();
}
else
{
metaLoader.LoadFromFile(fileSystem);
}
return metaLoader;
}
public static ProcessResult Load(this IFileSystem exeFs, Switch device, BlitStruct<ApplicationControlProperty> nacpData, MetaLoader metaLoader, byte programIndex, bool isHomebrew = false)
{
ulong programId = metaLoader.ProgramId;
// Replace the whole ExeFs partition by the modded one.
if (device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(programId, ref exeFs))
{
metaLoader = null;
}
// Reload the MetaLoader in case of ExeFs partition replacement.
metaLoader ??= exeFs.GetNpdm();
NsoExecutable[] nsoExecutables = new NsoExecutable[ProcessConst.ExeFsPrefixes.Length];
for (int i = 0; i < nsoExecutables.Length; i++)
{
string name = ProcessConst.ExeFsPrefixes[i];
if (!exeFs.FileExists($"/{name}"))
{
continue; // File doesn't exist, skip.
}
Logger.Info?.Print(LogClass.Loader, $"Loading {name}...");
using UniqueRef<IFile> nsoFile = new();
exeFs.OpenFile(ref nsoFile.Ref, $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
nsoExecutables[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name);
}
// ExeFs file replacements.
ModLoadResult modLoadResult = device.Configuration.VirtualFileSystem.ModLoader.ApplyExefsMods(programId, nsoExecutables);
// Take the Npdm from mods if present.
if (modLoadResult.Npdm != null)
{
metaLoader = modLoadResult.Npdm;
}
// Collect the Nsos, ignoring ones that aren't used.
nsoExecutables = nsoExecutables.Where(x => x != null).ToArray();
// Apply Nsos patches.
device.Configuration.VirtualFileSystem.ModLoader.ApplyNsoPatches(programId, nsoExecutables);
string programName = string.Empty;
if (!isHomebrew && programId > 0x010000000000FFFF)
{
int langIdx = (int)device.System.State.DesiredTitleLanguage;
var decompressedTitles = NacpHelper.IsCompressed(in nacpData.Value)
? NacpHelper.DecompressTitleEntries(in nacpData.Value) : null;
int titleCount = decompressedTitles?.Length ?? 16;
if (langIdx < titleCount)
{
programName = decompressedTitles != null
? decompressedTitles[langIdx].NameString.ToString()
: nacpData.Value.Title[langIdx].NameString.ToString();
}
if (string.IsNullOrWhiteSpace(programName))
{
for (int i = 0; i < titleCount; i++)
{
bool empty = decompressedTitles != null
? decompressedTitles[i].Name[0] == 0
: nacpData.Value.Title[i].Name[0] == 0;
if (!empty)
{
programName = decompressedTitles != null
? decompressedTitles[i].NameString.ToString()
: nacpData.Value.Title[i].NameString.ToString();
break;
}
}
}
}
// Initialize GPU.
GraphicsConfig.TitleId = programId.ToString("X16");
device.Gpu.HostInitalized.Set();
if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible))
{
device.Configuration.MemoryManagerMode = MemoryManagerMode.SoftwarePageTable;
}
ProcessResult processResult = ProcessLoaderHelper.LoadNsos(
device,
device.System.KernelContext,
metaLoader,
nacpData,
device.System.EnablePtc,
modLoadResult.Hash,
true,
programName,
programId,
programIndex,
null,
nsoExecutables);
// TODO: This should be stored using ProcessId instead.
device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(programId);
return processResult;
}
}
}