diff options
Diffstat (limited to 'Ryujinx.HLE')
28 files changed, 1180 insertions, 1109 deletions
diff --git a/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs b/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs index 3f94ce61..1b3968ea 100644 --- a/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs +++ b/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs @@ -20,7 +20,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; - using Path = System.IO.Path; using RightsId = LibHac.Fs.RightsId; @@ -146,6 +145,7 @@ namespace Ryujinx.HLE.FileSystem return $"{basePath}:/{fileName}"; } + return null; } @@ -191,7 +191,7 @@ namespace Ryujinx.HLE.FileSystem fsServerClient = horizon.CreatePrivilegedHorizonClient(); var fsServer = new FileSystemServer(fsServerClient); - RandomDataGenerator randomGenerator = buffer => Random.Shared.NextBytes(buffer); + RandomDataGenerator randomGenerator = Random.Shared.NextBytes; DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet, fsServer, randomGenerator); @@ -264,7 +264,7 @@ namespace Ryujinx.HLE.FileSystem if (result.IsSuccess()) { - Ticket ticket = new Ticket(ticketFile.Get.AsStream()); + Ticket ticket = new(ticketFile.Get.AsStream()); var titleKey = ticket.GetTitleKey(KeySet); if (titleKey != null) diff --git a/Ryujinx.HLE/HOS/ApplicationLoader.cs b/Ryujinx.HLE/HOS/ApplicationLoader.cs deleted file mode 100644 index 82bd9b31..00000000 --- a/Ryujinx.HLE/HOS/ApplicationLoader.cs +++ /dev/null @@ -1,908 +0,0 @@ -using LibHac; -using LibHac.Account; -using LibHac.Common; -using LibHac.Fs; -using LibHac.Fs.Fsa; -using LibHac.Fs.Shim; -using LibHac.FsSystem; -using LibHac.Loader; -using LibHac.Ncm; -using LibHac.Ns; -using LibHac.Tools.Fs; -using LibHac.Tools.FsSystem; -using LibHac.Tools.FsSystem.NcaUtils; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.Logging; -using Ryujinx.Cpu; -using Ryujinx.HLE.FileSystem; -using Ryujinx.HLE.Loaders.Executables; -using Ryujinx.Memory; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using static Ryujinx.HLE.HOS.ModLoader; -using ApplicationId = LibHac.Ncm.ApplicationId; -using Path = System.IO.Path; - -namespace Ryujinx.HLE.HOS -{ - using JsonHelper = Common.Utilities.JsonHelper; - - public class ApplicationLoader - { - // Binaries from exefs are loaded into mem in this order. Do not change. - internal static readonly string[] ExeFsPrefixes = - { - "rtld", - "main", - "subsdk0", - "subsdk1", - "subsdk2", - "subsdk3", - "subsdk4", - "subsdk5", - "subsdk6", - "subsdk7", - "subsdk8", - "subsdk9", - "sdk" - }; - - private readonly Switch _device; - private string _titleName; - private string _displayVersion; - private BlitStruct<ApplicationControlProperty> _controlData; - - public BlitStruct<ApplicationControlProperty> ControlData => _controlData; - public string TitleName => _titleName; - public string DisplayVersion => _displayVersion; - - public ulong TitleId { get; private set; } - public bool TitleIs64Bit { get; private set; } - - public string TitleIdText => TitleId.ToString("x16"); - - public IDiskCacheLoadState DiskCacheLoadState { get; private set; } - - public ApplicationLoader(Switch device) - { - _device = device; - _controlData = new BlitStruct<ApplicationControlProperty>(1); - } - - public void LoadCart(string exeFsDir, string romFsFile = null) - { - LocalFileSystem codeFs = new LocalFileSystem(exeFsDir); - - MetaLoader metaData = ReadNpdm(codeFs); - - _device.Configuration.VirtualFileSystem.ModLoader.CollectMods( - new[] { TitleId }, - _device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(), - _device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath()); - - if (TitleId != 0) - { - EnsureSaveData(new ApplicationId(TitleId)); - } - - ulong pid = LoadExeFs(codeFs, string.Empty, metaData); - - if (romFsFile != null) - { - _device.Configuration.VirtualFileSystem.LoadRomFs(pid, romFsFile); - } - } - - public static (Nca main, Nca patch, Nca control) GetGameData(VirtualFileSystem fileSystem, PartitionFileSystem pfs, int programIndex) - { - Nca mainNca = null; - Nca patchNca = null; - Nca controlNca = null; - - fileSystem.ImportTickets(pfs); - - foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) - { - using var ncaFile = new UniqueRef<IFile>(); - - pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - - Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage()); - - int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF); - - if (ncaProgramIndex != programIndex) - { - continue; - } - - if (nca.Header.ContentType == NcaContentType.Program) - { - int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); - - if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection()) - { - patchNca = nca; - } - else - { - mainNca = nca; - } - } - else if (nca.Header.ContentType == NcaContentType.Control) - { - controlNca = nca; - } - } - - return (mainNca, patchNca, controlNca); - } - - public static (Nca patch, Nca control) GetGameUpdateDataFromPartition(VirtualFileSystem fileSystem, PartitionFileSystem pfs, string titleId, int programIndex) - { - Nca patchNca = null; - Nca controlNca = null; - - fileSystem.ImportTickets(pfs); - - foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) - { - using var ncaFile = new UniqueRef<IFile>(); - - pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - - Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage()); - - int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF); - - if (ncaProgramIndex != programIndex) - { - continue; - } - - if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleId) - { - break; - } - - if (nca.Header.ContentType == NcaContentType.Program) - { - patchNca = nca; - } - else if (nca.Header.ContentType == NcaContentType.Control) - { - controlNca = nca; - } - } - - return (patchNca, controlNca); - } - - public static (Nca patch, Nca control) GetGameUpdateData(VirtualFileSystem fileSystem, string titleId, int programIndex, out string updatePath) - { - updatePath = null; - - if (ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase)) - { - // Clear the program index part. - titleIdBase &= 0xFFFFFFFFFFFFFFF0; - - // Load update informations if existing. - string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json"); - - if (File.Exists(titleUpdateMetadataPath)) - { - updatePath = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(titleUpdateMetadataPath).Selected; - - if (File.Exists(updatePath)) - { - FileStream file = new FileStream(updatePath, FileMode.Open, FileAccess.Read); - PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage()); - - return GetGameUpdateDataFromPartition(fileSystem, nsp, titleIdBase.ToString("x16"), programIndex); - } - } - } - - return (null, null); - } - - public void LoadXci(string xciFile) - { - FileStream file = new FileStream(xciFile, FileMode.Open, FileAccess.Read); - Xci xci = new Xci(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage()); - - if (!xci.HasPartition(XciPartitionType.Secure)) - { - Logger.Error?.Print(LogClass.Loader, "Unable to load XCI: Could not find XCI secure partition"); - - return; - } - - PartitionFileSystem securePartition = xci.OpenPartition(XciPartitionType.Secure); - - Nca mainNca; - Nca patchNca; - Nca controlNca; - - try - { - (mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, securePartition, _device.Configuration.UserChannelPersistence.Index); - - RegisterProgramMapInfo(securePartition).ThrowIfFailure(); - } - catch (Exception e) - { - Logger.Error?.Print(LogClass.Loader, $"Unable to load XCI: {e.Message}"); - - return; - } - - if (mainNca == null) - { - Logger.Error?.Print(LogClass.Loader, "Unable to load XCI: Could not find Main NCA"); - - return; - } - - _device.Configuration.ContentManager.LoadEntries(_device); - _device.Configuration.ContentManager.ClearAocData(); - _device.Configuration.ContentManager.AddAocData(securePartition, xciFile, mainNca.Header.TitleId, _device.Configuration.FsIntegrityCheckLevel); - - LoadNca(mainNca, patchNca, controlNca); - } - - public void LoadNsp(string nspFile) - { - FileStream file = new FileStream(nspFile, FileMode.Open, FileAccess.Read); - PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage()); - - Nca mainNca; - Nca patchNca; - Nca controlNca; - - try - { - (mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, nsp, _device.Configuration.UserChannelPersistence.Index); - - RegisterProgramMapInfo(nsp).ThrowIfFailure(); - } - catch (Exception e) - { - Logger.Error?.Print(LogClass.Loader, $"Unable to load NSP: {e.Message}"); - - return; - } - - if (mainNca != null) - { - _device.Configuration.ContentManager.ClearAocData(); - _device.Configuration.ContentManager.AddAocData(nsp, nspFile, mainNca.Header.TitleId, _device.Configuration.FsIntegrityCheckLevel); - - LoadNca(mainNca, patchNca, controlNca); - - return; - } - - // This is not a normal NSP, it's actually a ExeFS as a NSP - LoadExeFs(nsp, null, isHomebrew: true); - } - - public void LoadNca(string ncaFile) - { - FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read); - Nca nca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false)); - - LoadNca(nca, null, null); - } - - public void LoadServiceNca(string ncaFile) - { - FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read); - Nca mainNca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false)); - - if (mainNca.Header.ContentType != NcaContentType.Program) - { - Logger.Error?.Print(LogClass.Loader, "Selected NCA is not a \"Program\" NCA"); - - return; - } - - IFileSystem codeFs = null; - - if (mainNca.CanOpenSection(NcaSectionType.Code)) - { - codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, _device.System.FsIntegrityCheckLevel); - } - - if (codeFs == null) - { - Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA"); - - return; - } - - using var npdmFile = new UniqueRef<IFile>(); - - Result result = codeFs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read); - - MetaLoader metaData; - - npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure(); - - var npdmBuffer = new byte[fileSize]; - npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure(); - - metaData = new MetaLoader(); - metaData.Load(npdmBuffer).ThrowIfFailure(); - - NsoExecutable[] nsos = new NsoExecutable[ExeFsPrefixes.Length]; - - for (int i = 0; i < nsos.Length; i++) - { - string name = ExeFsPrefixes[i]; - - if (!codeFs.FileExists($"/{name}")) - { - continue; // File doesn't exist, skip. - } - - Logger.Info?.Print(LogClass.Loader, $"Loading {name}..."); - - using var nsoFile = new UniqueRef<IFile>(); - - codeFs.OpenFile(ref nsoFile.Ref, $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure(); - - nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name); - } - - // Collect the nsos, ignoring ones that aren't used. - NsoExecutable[] programs = nsos.Where(x => x != null).ToArray(); - - string displayVersion = _device.System.ContentManager.GetCurrentFirmwareVersion().VersionString; - bool usePtc = _device.System.EnablePtc; - - metaData.GetNpdm(out Npdm npdm).ThrowIfFailure(); - ProgramInfo programInfo = new ProgramInfo(in npdm, displayVersion, usePtc, allowCodeMemoryForJit: false); - ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: programs); - - string titleIdText = npdm.Aci.ProgramId.Value.ToString("x16"); - bool titleIs64Bit = (npdm.Meta.Flags & 1) != 0; - - string programName = Encoding.ASCII.GetString(npdm.Meta.ProgramName).TrimEnd('\0'); - - Logger.Info?.Print(LogClass.Loader, $"Service Loaded: {programName} [{titleIdText}] [{(titleIs64Bit ? "64-bit" : "32-bit")}]"); - } - - private void LoadNca(Nca mainNca, Nca patchNca, Nca controlNca) - { - if (mainNca.Header.ContentType != NcaContentType.Program) - { - Logger.Error?.Print(LogClass.Loader, "Selected NCA is not a \"Program\" NCA"); - - return; - } - - IStorage dataStorage = null; - IFileSystem codeFs = null; - - (Nca updatePatchNca, Nca updateControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), _device.Configuration.UserChannelPersistence.Index, out _); - - if (updatePatchNca != null) - { - patchNca = updatePatchNca; - } - - if (updateControlNca != null) - { - controlNca = updateControlNca; - } - - // Load program 0 control NCA as we are going to need it for display version. - (_, Nca updateProgram0ControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _); - - // Load Aoc - string titleAocMetadataPath = Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "dlc.json"); - - if (File.Exists(titleAocMetadataPath)) - { - List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(titleAocMetadataPath); - - foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList) - { - foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList) - { - if (File.Exists(downloadableContentContainer.ContainerPath) && downloadableContentNca.Enabled) - { - _device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, downloadableContentContainer.ContainerPath, downloadableContentNca.FullPath); - } - else - { - Logger.Warning?.Print(LogClass.Application, $"Cannot find AddOnContent file {downloadableContentContainer.ContainerPath}. It may have been moved or renamed."); - } - } - } - } - - if (patchNca == null) - { - if (mainNca.CanOpenSection(NcaSectionType.Data)) - { - dataStorage = mainNca.OpenStorage(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); - } - - if (mainNca.CanOpenSection(NcaSectionType.Code)) - { - codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, _device.System.FsIntegrityCheckLevel); - } - } - else - { - if (patchNca.CanOpenSection(NcaSectionType.Data)) - { - dataStorage = mainNca.OpenStorageWithPatch(patchNca, NcaSectionType.Data, _device.System.FsIntegrityCheckLevel); - } - - if (patchNca.CanOpenSection(NcaSectionType.Code)) - { - codeFs = mainNca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, _device.System.FsIntegrityCheckLevel); - } - } - - if (codeFs == null) - { - Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA"); - - return; - } - - MetaLoader metaData = ReadNpdm(codeFs); - - _device.Configuration.VirtualFileSystem.ModLoader.CollectMods( - _device.Configuration.ContentManager.GetAocTitleIds().Prepend(TitleId), - _device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(), - _device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath()); - - string displayVersion = string.Empty; - - if (controlNca != null) - { - ReadControlData(_device, controlNca, ref _controlData, ref _titleName, ref displayVersion); - } - else - { - ControlData.ByteSpan.Clear(); - } - - // NOTE: Nintendo doesn't guarantee that the display version will be updated on sub programs when updating a multi program application. - // BODY: As such, to avoid PTC cache confusion, we only trust the the program 0 display version when launching a sub program. - if (updateProgram0ControlNca != null && _device.Configuration.UserChannelPersistence.Index != 0) - { - string dummyTitleName = ""; - BlitStruct<ApplicationControlProperty> dummyControl = new BlitStruct<ApplicationControlProperty>(1); - - ReadControlData(_device, updateProgram0ControlNca, ref dummyControl, ref dummyTitleName, ref displayVersion); - } - - _displayVersion = displayVersion; - - ulong pid = LoadExeFs(codeFs, displayVersion, metaData); - - if (dataStorage == null) - { - Logger.Warning?.Print(LogClass.Loader, "No RomFS found in NCA"); - } - else - { - IStorage newStorage = _device.Configuration.VirtualFileSystem.ModLoader.ApplyRomFsMods(TitleId, dataStorage); - - _device.Configuration.VirtualFileSystem.SetRomFs(pid, newStorage.AsStream(FileAccess.Read)); - } - - // Don't create save data for system programs. - if (TitleId != 0 && (TitleId < SystemProgramId.Start.Value || TitleId > SystemAppletId.End.Value)) - { - // Multi-program applications can technically use any program ID for the main program, but in practice they always use 0 in the low nibble. - // We'll know if this changes in the future because stuff will get errors when trying to mount the correct save. - EnsureSaveData(new ApplicationId(TitleId & ~0xFul)); - } - - Logger.Info?.Print(LogClass.Loader, $"Application Loaded: {TitleName} v{DisplayVersion} [{TitleIdText}] [{(TitleIs64Bit ? "64-bit" : "32-bit")}]"); - } - - // Sets TitleId, so be sure to call before using it - private MetaLoader ReadNpdm(IFileSystem fs) - { - using var npdmFile = new UniqueRef<IFile>(); - - Result result = fs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read); - - MetaLoader metaData; - - if (ResultFs.PathNotFound.Includes(result)) - { - Logger.Warning?.Print(LogClass.Loader, "NPDM file not found, using default values!"); - - metaData = GetDefaultNpdm(); - } - else - { - npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure(); - - var npdmBuffer = new byte[fileSize]; - npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure(); - - metaData = new MetaLoader(); - metaData.Load(npdmBuffer).ThrowIfFailure(); - } - - metaData.GetNpdm(out var npdm).ThrowIfFailure(); - - TitleId = npdm.Aci.ProgramId.Value; - TitleIs64Bit = (npdm.Meta.Flags & 1) != 0; - _device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId); - - return metaData; - } - - private static void ReadControlData(Switch device, Nca controlNca, ref BlitStruct<ApplicationControlProperty> controlData, ref string titleName, ref string displayVersion) - { - using var controlFile = new UniqueRef<IFile>(); - - IFileSystem controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, device.System.FsIntegrityCheckLevel); - Result result = controlFs.OpenFile(ref controlFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read); - - if (result.IsSuccess()) - { - result = controlFile.Get.Read(out long bytesRead, 0, controlData.ByteSpan, ReadOption.None); - - if (result.IsSuccess() && bytesRead == controlData.ByteSpan.Length) - { - titleName = controlData.Value.Title[(int)device.System.State.DesiredTitleLanguage].NameString.ToString(); - - if (string.IsNullOrWhiteSpace(titleName)) - { - titleName = controlData.Value.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString(); - } - - displayVersion = controlData.Value.DisplayVersionString.ToString(); - } - } - else - { - controlData.ByteSpan.Clear(); - } - } - - private ulong LoadExeFs(IFileSystem codeFs, string displayVersion, MetaLoader metaData = null, bool isHomebrew = false) - { - if (_device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(TitleId, ref codeFs)) - { - metaData = null; // TODO: Check if we should retain old npdm. - } - - metaData ??= ReadNpdm(codeFs); - - NsoExecutable[] nsos = new NsoExecutable[ExeFsPrefixes.Length]; - - for (int i = 0; i < nsos.Length; i++) - { - string name = ExeFsPrefixes[i]; - - if (!codeFs.FileExists($"/{name}")) - { - continue; // File doesn't exist, skip. - } - - Logger.Info?.Print(LogClass.Loader, $"Loading {name}..."); - - using var nsoFile = new UniqueRef<IFile>(); - - codeFs.OpenFile(ref nsoFile.Ref, $"/{name}".ToU8Span(), OpenMode.Read).ThrowIfFailure(); - - nsos[i] = new NsoExecutable(nsoFile.Release().AsStorage(), name); - } - - // ExeFs file replacements. - ModLoadResult modLoadResult = _device.Configuration.VirtualFileSystem.ModLoader.ApplyExefsMods(TitleId, nsos); - - // Collect the nsos, ignoring ones that aren't used. - NsoExecutable[] programs = nsos.Where(x => x != null).ToArray(); - - // Take the npdm from mods if present. - if (modLoadResult.Npdm != null) - { - metaData = modLoadResult.Npdm; - } - - _device.Configuration.VirtualFileSystem.ModLoader.ApplyNsoPatches(TitleId, programs); - - _device.Configuration.ContentManager.LoadEntries(_device); - - bool usePtc = _device.System.EnablePtc; - - // Don't use PPTC if ExeFs files have been replaced. - usePtc &= !modLoadResult.Modified; - - if (_device.System.EnablePtc && !usePtc) - { - Logger.Warning?.Print(LogClass.Ptc, $"Detected unsupported ExeFs modifications. PPTC disabled."); - } - - Graphics.Gpu.GraphicsConfig.TitleId = TitleIdText; - _device.Gpu.HostInitalized.Set(); - - MemoryManagerMode memoryManagerMode = _device.Configuration.MemoryManagerMode; - - if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible)) - { - memoryManagerMode = MemoryManagerMode.SoftwarePageTable; - } - - // We allow it for nx-hbloader because it can be used to launch homebrew. - bool allowCodeMemoryForJit = TitleId == 0x010000000000100DUL || isHomebrew; - - metaData.GetNpdm(out Npdm npdm).ThrowIfFailure(); - ProgramInfo programInfo = new ProgramInfo(in npdm, displayVersion, usePtc, allowCodeMemoryForJit); - ProgramLoadResult result = ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: programs); - - DiskCacheLoadState = result.DiskCacheLoadState; - - _device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, result.TamperInfo, _device.TamperMachine); - - return result.ProcessId; - } - - public void LoadProgram(string filePath) - { - MetaLoader metaData = GetDefaultNpdm(); - metaData.GetNpdm(out Npdm npdm).ThrowIfFailure(); - ProgramInfo programInfo = new ProgramInfo(in npdm, string.Empty, diskCacheEnabled: false, allowCodeMemoryForJit: true); - - bool isNro = Path.GetExtension(filePath).ToLower() == ".nro"; - - IExecutable executable; - Stream romfsStream = null; - - if (isNro) - { - FileStream input = new FileStream(filePath, FileMode.Open); - NroExecutable obj = new NroExecutable(input.AsStorage()); - - executable = obj; - - // Homebrew NRO can actually have some data after the actual NRO. - if (input.Length > obj.FileSize) - { - input.Position = obj.FileSize; - - BinaryReader reader = new BinaryReader(input); - - uint asetMagic = reader.ReadUInt32(); - if (asetMagic == 0x54455341) - { - uint asetVersion = reader.ReadUInt32(); - if (asetVersion == 0) - { - ulong iconOffset = reader.ReadUInt64(); - ulong iconSize = reader.ReadUInt64(); - - ulong nacpOffset = reader.ReadUInt64(); - ulong nacpSize = reader.ReadUInt64(); - - ulong romfsOffset = reader.ReadUInt64(); - ulong romfsSize = reader.ReadUInt64(); - - if (romfsSize != 0) - { - romfsStream = new HomebrewRomFsStream(input, obj.FileSize + (long)romfsOffset); - } - - if (nacpSize != 0) - { - input.Seek(obj.FileSize + (long)nacpOffset, SeekOrigin.Begin); - - reader.Read(ControlData.ByteSpan); - - ref ApplicationControlProperty nacp = ref ControlData.Value; - - programInfo.Name = nacp.Title[(int)_device.System.State.DesiredTitleLanguage].NameString.ToString(); - - if (string.IsNullOrWhiteSpace(programInfo.Name)) - { - programInfo.Name = nacp.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString(); - } - - if (nacp.PresenceGroupId != 0) - { - programInfo.ProgramId = nacp.PresenceGroupId; - } - else if (nacp.SaveDataOwnerId != 0) - { - programInfo.ProgramId = nacp.SaveDataOwnerId; - } - else if (nacp.AddOnContentBaseId != 0) - { - programInfo.ProgramId = nacp.AddOnContentBaseId - 0x1000; - } - else - { - programInfo.ProgramId = 0000000000000000; - } - } - } - else - { - Logger.Warning?.Print(LogClass.Loader, $"Unsupported ASET header version found \"{asetVersion}\""); - } - } - } - } - else - { - executable = new NsoExecutable(new LocalStorage(filePath, FileAccess.Read), Path.GetFileNameWithoutExtension(filePath)); - } - - _device.Configuration.ContentManager.LoadEntries(_device); - - _titleName = programInfo.Name; - TitleId = programInfo.ProgramId; - TitleIs64Bit = (npdm.Meta.Flags & 1) != 0; - _device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId); - - // Explicitly null titleid to disable the shader cache. - Graphics.Gpu.GraphicsConfig.TitleId = null; - _device.Gpu.HostInitalized.Set(); - - ProgramLoadResult result = ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: executable); - - if (romfsStream != null) - { - _device.Configuration.VirtualFileSystem.SetRomFs(result.ProcessId, romfsStream); - } - - DiskCacheLoadState = result.DiskCacheLoadState; - - _device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, result.TamperInfo, _device.TamperMachine); - } - - private MetaLoader GetDefaultNpdm() - { - Assembly asm = Assembly.GetCallingAssembly(); - - using (Stream npdmStream = asm.GetManifestResourceStream("Ryujinx.HLE.Homebrew.npdm")) - { - var npdmBuffer = new byte[npdmStream.Length]; - npdmStream.Read(npdmBuffer); - - var metaLoader = new MetaLoader(); - metaLoader.Load(npdmBuffer).ThrowIfFailure(); - - return metaLoader; - } - } - - private static (ulong applicationId, int programCount) GetMultiProgramInfo(VirtualFileSystem fileSystem, PartitionFileSystem pfs) - { - ulong mainProgramId = 0; - Span<bool> hasIndex = stackalloc bool[0x10]; - - fileSystem.ImportTickets(pfs); - - foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) - { - using var ncaFile = new UniqueRef<IFile>(); - - pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); - - Nca nca = new Nca(fileSystem.KeySet, ncaFile.Release().AsStorage()); - - if (nca.Header.ContentType != NcaContentType.Program) - { - continue; - } - - int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); - - if (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection()) - { - continue; - } - - ulong currentProgramId = nca.Header.TitleId; - ulong currentMainProgramId = currentProgramId & ~0xFFFul; - - if (mainProgramId == 0 && currentMainProgramId != 0) - { - mainProgramId = currentMainProgramId; - } - - if (mainProgramId != currentMainProgramId) - { - // As far as I know there aren't any multi-application game cards containing multi-program applications, - // so because multi-application game cards are the only way we should run into multiple applications - // we'll just return that there's a single program. - return (mainProgramId, 1); - } - - hasIndex[(int)(currentProgramId & 0xF)] = true; - } - - int programCount = 0; - - for (int i = 0; i < hasIndex.Length && hasIndex[i]; i++) - { - programCount++; - } - - return (mainProgramId, programCount); - } - - private Result RegisterProgramMapInfo(PartitionFileSystem pfs) - { - (ulong applicationId, int programCount) = GetMultiProgramInfo(_device.Configuration.VirtualFileSystem, pfs); - - if (programCount <= 0) - return Result.Success; - - Span<ProgramIndexMapInfo> mapInfo = stackalloc ProgramIndexMapInfo[0x10]; - - for (int i = 0; i < programCount; i++) - { - mapInfo[i].ProgramId = new ProgramId(applicationId + (uint)i); - mapInfo[i].MainProgramId = new ApplicationId(applicationId); - mapInfo[i].ProgramIndex = (byte)i; - } - - return _device.System.LibHacHorizonManager.NsClient.Fs.RegisterProgramIndexMapInfo(mapInfo.Slice(0, programCount)); - } - - private Result EnsureSaveData(ApplicationId applicationId) - { - Logger.Info?.Print(LogClass.Application, "Ensuring required savedata exists."); - - Uid user = _device.System.AccountManager.LastOpenedUser.UserId.ToLibHacUid(); - - ref ApplicationControlProperty control = ref ControlData.Value; - - if (LibHac.Common.Utilities.IsZeros(ControlData.ByteSpan)) - { - // If the current application doesn't have a loaded control property, create a dummy one - // and set the savedata sizes so a user savedata will be created. - control = ref new BlitStruct<ApplicationControlProperty>(1).Value; - - // The set sizes don't actually matter as long as they're non-zero because we use directory savedata. - control.UserAccountSaveDataSize = 0x4000; - control.UserAccountSaveDataJournalSize = 0x4000; - control.SaveDataOwnerId = applicationId.Value; - - Logger.Warning?.Print(LogClass.Application, - "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); - } - - HorizonClient hos = _device.System.LibHacHorizonManager.RyujinxClient; - Result resultCode = hos.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, in control); - - if (resultCode.IsFailure()) - { - Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {resultCode.ToStringWithName()}"); - - return resultCode; - } - - resultCode = hos.Fs.EnsureApplicationSaveData(out _, applicationId, in control, in user); - - if (resultCode.IsFailure()) - { - Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {resultCode.ToStringWithName()}"); - } - - return resultCode; - } - } -} diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index 2b77a7c2..1639532e 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -35,6 +35,7 @@ using Ryujinx.HLE.HOS.Services.SurfaceFlinger; using Ryujinx.HLE.HOS.Services.Time.Clock; using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.Loaders.Executables; +using Ryujinx.HLE.Loaders.Processes; using Ryujinx.Horizon; using System; using System.Collections.Generic; @@ -358,11 +359,11 @@ namespace Ryujinx.HLE.HOS } } - public void LoadKip(string kipPath) + public bool LoadKip(string kipPath) { using var kipFile = new SharedRef<IStorage>(new LocalStorage(kipPath, FileAccess.Read)); - ProgramLoader.LoadKip(KernelContext, new KipExecutable(in kipFile)); + return ProcessLoaderHelper.LoadKip(KernelContext, new KipExecutable(in kipFile)); } public void ChangeDockedModeState(bool newState) diff --git a/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs b/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs index 556703cf..4cf67172 100644 --- a/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs +++ b/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Kernel.Process { - internal class ProcessTamperInfo + class ProcessTamperInfo { public KProcess Process { get; } public IEnumerable<string> BuildIds { get; } diff --git a/Ryujinx.HLE/HOS/ModLoader.cs b/Ryujinx.HLE/HOS/ModLoader.cs index a6dc9013..16512541 100644 --- a/Ryujinx.HLE/HOS/ModLoader.cs +++ b/Ryujinx.HLE/HOS/ModLoader.cs @@ -10,6 +10,7 @@ using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.Loaders.Executables; using Ryujinx.HLE.Loaders.Mods; +using Ryujinx.HLE.Loaders.Processes; using System; using System.Collections.Generic; using System.Collections.Specialized; @@ -547,7 +548,7 @@ namespace Ryujinx.HLE.HOS return modLoadResult; } - if (nsos.Length != ApplicationLoader.ExeFsPrefixes.Length) + if (nsos.Length != ProcessConst.ExeFsPrefixes.Length) { throw new ArgumentOutOfRangeException("NSO Count is incorrect"); } @@ -556,9 +557,9 @@ namespace Ryujinx.HLE.HOS foreach (var mod in exeMods) { - for (int i = 0; i < ApplicationLoader.ExeFsPrefixes.Length; ++i) + for (int i = 0; i < ProcessConst.ExeFsPrefixes.Length; ++i) { - var nsoName = ApplicationLoader.ExeFsPrefixes[i]; + var nsoName = ProcessConst.ExeFsPrefixes[i]; FileInfo nsoFile = new FileInfo(Path.Combine(mod.Path.FullName, nsoName)); if (nsoFile.Exists) @@ -596,7 +597,7 @@ namespace Ryujinx.HLE.HOS } } - for (int i = ApplicationLoader.ExeFsPrefixes.Length - 1; i >= 0; --i) + for (int i = ProcessConst.ExeFsPrefixes.Length - 1; i >= 0; --i) { if (modLoadResult.Stubs[1 << i] && !modLoadResult.Replaces[1 << i]) // Prioritizes replacements over stubs { diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs index 1b412d74..413bedce 100644 --- a/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs +++ b/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs @@ -190,7 +190,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc // TODO: Account actually calls nn::arp::detail::IReader::GetApplicationControlProperty() with the current Pid and store the result (NACP file) internally. // But since we use LibHac and we load one Application at a time, it's not necessary. - context.ResponseData.Write((byte)context.Device.Application.ControlData.Value.UserAccountSwitchLock); + context.ResponseData.Write((byte)context.Device.Processes.ActiveApplication.ApplicationControlProperties.UserAccountSwitchLock); Logger.Stub?.PrintStub(LogClass.ServiceAcc); diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs index 00081e1b..72049714 100644 --- a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs @@ -9,7 +9,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib public ILibraryAppletSelfAccessor(ServiceCtx context) { - if (context.Device.Application.TitleId == 0x0100000000001009) + if (context.Device.Processes.ActiveApplication.ProgramId == 0x0100000000001009) { // Create MiiEdit data. _appletStandalone = new AppletStandalone() @@ -25,7 +25,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib } else { - throw new NotImplementedException($"{context.Device.Application.TitleId} applet is not implemented."); + throw new NotImplementedException($"{context.Device.Processes.ActiveApplication.ProgramId} applet is not implemented."); } } diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs index f8f88a1c..924f5429 100644 --- a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs +++ b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs @@ -115,28 +115,12 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati Uid userId = context.RequestData.ReadStruct<AccountUid>().ToLibHacUid(); // Mask out the low nibble of the program ID to get the application ID - ApplicationId applicationId = new ApplicationId(context.Device.Application.TitleId & ~0xFul); + ApplicationId applicationId = new ApplicationId(context.Device.Processes.ActiveApplication.ProgramId & ~0xFul); - BlitStruct<ApplicationControlProperty> controlHolder = context.Device.Application.ControlData; - - ref ApplicationControlProperty control = ref controlHolder.Value; - - if (LibHac.Common.Utilities.IsZeros(controlHolder.ByteSpan)) - { - // If the current application doesn't have a loaded control property, create a dummy one - // and set the savedata sizes so a user savedata will be created. - control = ref new BlitStruct<ApplicationControlProperty>(1).Value; - - // The set sizes don't actually matter as long as they're non-zero because we use directory savedata. - control.UserAccountSaveDataSize = 0x4000; - control.UserAccountSaveDataJournalSize = 0x4000; - - Logger.Warning?.Print(LogClass.ServiceAm, - "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); - } + ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties; LibHac.HorizonClient hos = context.Device.System.LibHacHorizonManager.AmClient; - LibHac.Result result = hos.Fs.EnsureApplicationSaveData(out long requiredSize, applicationId, in control, in userId); + LibHac.Result result = hos.Fs.EnsureApplicationSaveData(out long requiredSize, applicationId, in nacp, in userId); context.ResponseData.Write(requiredSize); @@ -153,7 +137,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati // TODO: When above calls are implemented, switch to using ns:am long desiredLanguageCode = context.Device.System.State.DesiredLanguageCode; - int supportedLanguages = (int)context.Device.Application.ControlData.Value.SupportedLanguageFlag; + int supportedLanguages = (int)context.Device.Processes.ActiveApplication.ApplicationControlProperties.SupportedLanguageFlag; int firstSupported = BitOperations.TrailingZeroCount(supportedLanguages); if (firstSupported > (int)TitleLanguage.BrazilianPortuguese) @@ -196,7 +180,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati public ResultCode GetDisplayVersion(ServiceCtx context) { // If an NACP isn't found, the buffer will be all '\0' which seems to be the correct implementation. - context.ResponseData.Write(context.Device.Application.ControlData.Value.DisplayVersion); + context.ResponseData.Write(context.Device.Processes.ActiveApplication.ApplicationControlProperties.DisplayVersion); return ResultCode.Success; } @@ -251,13 +235,12 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati long journalSize = context.RequestData.ReadInt64(); // Mask out the low nibble of the program ID to get the application ID - ApplicationId applicationId = new ApplicationId(context.Device.Application.TitleId & ~0xFul); + ApplicationId applicationId = new ApplicationId(context.Device.Processes.ActiveApplication.ProgramId & ~0xFul); - BlitStruct<ApplicationControlProperty> controlHolder = context.Device.Application.ControlData; + ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties; LibHac.Result result = _horizon.Fs.CreateApplicationCacheStorage(out long requiredSize, - out CacheStorageTargetMedia storageTarget, applicationId, in controlHolder.Value, index, saveSize, - journalSize); + out CacheStorageTargetMedia storageTarget, applicationId, in nacp, index, saveSize, journalSize); if (result.IsFailure()) return (ResultCode)result.Value; @@ -677,7 +660,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati throw new InvalidSystemResourceException($"JIT (010000000000003B) system title not found! The JIT will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx#requirements for more information)"); } - context.Device.Application.LoadServiceNca(filePath); + context.Device.LoadNca(filePath); // FIXME: Most likely not how this should be done? while (!context.Device.System.SmRegistry.IsServiceRegistered("jit:u")) diff --git a/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs b/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs index c985092b..3e4eca0a 100644 --- a/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs +++ b/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs @@ -33,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp return new ApplicationLaunchProperty { - TitleId = context.Device.Application.TitleId, + TitleId = context.Device.Processes.ActiveApplication.ProgramId, Version = 0x00, BaseGameStorageId = (byte)StorageId.BuiltInSystem, UpdateGameStorageId = (byte)StorageId.None diff --git a/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs b/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs index 1789122e..e0c65f44 100644 --- a/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs +++ b/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs @@ -31,7 +31,7 @@ namespace Ryujinx.HLE.HOS.Services.Caps byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray(); - ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Application.TitleId, out ApplicationAlbumEntry applicationAlbumEntry); + ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry); context.ResponseData.WriteStruct(applicationAlbumEntry); @@ -60,7 +60,7 @@ namespace Ryujinx.HLE.HOS.Services.Caps byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray(); - ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Application.TitleId, out ApplicationAlbumEntry applicationAlbumEntry); + ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry); context.ResponseData.WriteStruct(applicationAlbumEntry); @@ -88,7 +88,7 @@ namespace Ryujinx.HLE.HOS.Services.Caps byte[] screenshotData = context.Memory.GetSpan(screenshotDataPosition, (int)screenshotDataSize, true).ToArray(); - ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Application.TitleId, out ApplicationAlbumEntry applicationAlbumEntry); + ResultCode resultCode = context.Device.System.CaptureManager.SaveScreenShot(screenshotData, appletResourceUserId, context.Device.Processes.ActiveApplication.ProgramId, out ApplicationAlbumEntry applicationAlbumEntry); context.ResponseData.WriteStruct(applicationAlbumEntry); diff --git a/Ryujinx.HLE/HOS/Services/Fatal/IService.cs b/Ryujinx.HLE/HOS/Services/Fatal/IService.cs index 6d663a4d..c884e880 100644 --- a/Ryujinx.HLE/HOS/Services/Fatal/IService.cs +++ b/Ryujinx.HLE/HOS/Services/Fatal/IService.cs @@ -55,7 +55,7 @@ namespace Ryujinx.HLE.HOS.Services.Fatal errorReport.AppendLine(); errorReport.AppendLine("ErrorReport log:"); - errorReport.AppendLine($"\tTitleId: {context.Device.Application.TitleId:x16}"); + errorReport.AppendLine($"\tTitleId: {context.Device.Processes.ActiveApplication.ProgramIdText}"); errorReport.AppendLine($"\tPid: {pid}"); errorReport.AppendLine($"\tResultCode: {((int)resultCode & 0x1FF) + 2000}-{((int)resultCode >> 9) & 0x3FFF:d4}"); errorReport.AppendLine($"\tFatalPolicy: {fatalPolicy}"); @@ -64,7 +64,7 @@ namespace Ryujinx.HLE.HOS.Services.Fatal { errorReport.AppendLine("CPU Context:"); - if (context.Device.Application.TitleIs64Bit) + if (context.Device.Processes.ActiveApplication.Is64Bit) { CpuContext64 cpuContext64 = MemoryMarshal.Cast<byte, CpuContext64>(cpuContext)[0]; diff --git a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs index 17a33b79..4317c8f6 100644 --- a/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs +++ b/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs @@ -334,7 +334,7 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator } // TODO: Call nn::arp::GetApplicationControlProperty here when implemented. - ApplicationControlProperty controlProperty = context.Device.Application.ControlData.Value; + ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties; /* diff --git a/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs b/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs index 37143a5a..1b63f362 100644 --- a/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs +++ b/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs @@ -808,7 +808,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs { byte programIndex = context.RequestData.ReadByte(); - if ((context.Device.Application.TitleId & 0xf) != programIndex) + if ((context.Device.Processes.ActiveApplication.ProgramId & 0xf) != programIndex) { throw new NotImplementedException($"Accessing storage from other programs is not supported (program index = {programIndex})."); } diff --git a/Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs b/Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs index 0d552003..b8f9e3b9 100644 --- a/Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs +++ b/Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs @@ -48,7 +48,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. - return CountAddOnContentImpl(context, context.Device.Application.TitleId); + return CountAddOnContentImpl(context, context.Device.Processes.ActiveApplication.ProgramId); } [CommandHipc(3)] @@ -59,7 +59,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. - return ListAddContentImpl(context, context.Device.Application.TitleId); + return ListAddContentImpl(context, context.Device.Processes.ActiveApplication.ProgramId); } [CommandHipc(4)] // 1.0.0-6.2.0 @@ -79,7 +79,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. - return GetAddOnContentBaseIdImpl(context, context.Device.Application.TitleId); + return GetAddOnContentBaseIdImpl(context, context.Device.Processes.ActiveApplication.ProgramId); } [CommandHipc(6)] // 1.0.0-6.2.0 @@ -99,7 +99,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. - return PrepareAddOnContentImpl(context, context.Device.Application.TitleId); + return PrepareAddOnContentImpl(context, context.Device.Processes.ActiveApplication.ProgramId); } [CommandHipc(8)] // 4.0.0+ @@ -128,7 +128,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc // NOTE: Service call arp:r GetApplicationLaunchProperty to get TitleId using the PId. // TODO: Found where stored value is used. - ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, context.Device.Application.TitleId); + ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, context.Device.Processes.ActiveApplication.ProgramId); if (resultCode != ResultCode.Success) { @@ -294,7 +294,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc // NOTE: Service calls arp:r GetApplicationControlProperty to get AddOnContentBaseId using TitleId, // If the call fails, it returns ResultCode.InvalidPid. - _addOnContentBaseId = context.Device.Application.ControlData.Value.AddOnContentBaseId; + _addOnContentBaseId = context.Device.Processes.ActiveApplication.ApplicationControlProperties.AddOnContentBaseId; if (_addOnContentBaseId == 0) { @@ -308,7 +308,7 @@ namespace Ryujinx.HLE.HOS.Services.Ns.Aoc { uint index = context.RequestData.ReadUInt32(); - ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, context.Device.Application.TitleId); + ResultCode resultCode = GetAddOnContentBaseIdFromTitleId(context, context.Device.Processes.ActiveApplication.ProgramId); if (resultCode != ResultCode.Success) { diff --git a/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs b/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs index d3a89178..249343d7 100644 --- a/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs +++ b/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs @@ -1,4 +1,8 @@ -namespace Ryujinx.HLE.HOS.Services.Ns +using LibHac.Ns; +using Ryujinx.Common.Utilities; +using System; + +namespace Ryujinx.HLE.HOS.Services.Ns { [Service("ns:am")] class IApplicationManagerInterface : IpcService @@ -14,9 +18,9 @@ ulong position = context.Request.ReceiveBuff[0].Position; - byte[] nacpData = context.Device.Application.ControlData.ByteSpan.ToArray(); + ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties; - context.Memory.Write(position, nacpData); + context.Memory.Write(position, SpanHelpers.AsByteSpan(ref nacp).ToArray()); return ResultCode.Success; } diff --git a/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs b/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs index 3b6965d0..8f6acc1c 100644 --- a/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs +++ b/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs @@ -1,4 +1,7 @@ -namespace Ryujinx.HLE.HOS.Services.Ns +using LibHac.Common; +using LibHac.Ns; + +namespace Ryujinx.HLE.HOS.Services.Ns { class IReadOnlyApplicationControlDataInterface : IpcService { @@ -13,9 +16,9 @@ ulong position = context.Request.ReceiveBuff[0].Position; - byte[] nacpData = context.Device.Application.ControlData.ByteSpan.ToArray(); + ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties; - context.Memory.Write(position, nacpData); + context.Memory.Write(position, SpanHelpers.AsByteSpan(ref nacp).ToArray()); return ResultCode.Success; } diff --git a/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs b/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs index e0017808..02964749 100644 --- a/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs +++ b/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs @@ -56,8 +56,8 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory _titleId = titleId; // TODO: Call nn::arp::GetApplicationControlProperty here when implemented, if it return ResultCode.Success we assign fields. - _ratingAge = Array.ConvertAll(context.Device.Application.ControlData.Value.RatingAge.ItemsRo.ToArray(), Convert.ToInt32); - _parentalControlFlag = context.Device.Application.ControlData.Value.ParentalControlFlag; + _ratingAge = Array.ConvertAll(context.Device.Processes.ActiveApplication.ApplicationControlProperties.RatingAge.ItemsRo.ToArray(), Convert.ToInt32); + _parentalControlFlag = context.Device.Processes.ActiveApplication.ApplicationControlProperties.ParentalControlFlag; } } diff --git a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs index 1d6cc118..52a07d46 100644 --- a/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs +++ b/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService { @@ -16,8 +15,6 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService internal static ResultCode GetPlayStatistics(ServiceCtx context, bool byUserId = false) { - ref readonly var controlProperty = ref context.Device.Application.ControlData.Value; - ulong inputPosition = context.Request.SendBuff[0].Position; ulong inputSize = context.Request.SendBuff[0].Size; @@ -34,7 +31,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService } } - PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)controlProperty.PlayLogQueryCapability; + PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)context.Device.Processes.ActiveApplication.ApplicationControlProperties.PlayLogQueryCapability; List<ulong> titleIds = new List<ulong>(); @@ -48,7 +45,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService // Check if input title ids are in the whitelist. foreach (ulong titleId in titleIds) { - if (!controlProperty.PlayLogQueryableApplicationId.ItemsRo.Contains(titleId)) + if (!context.Device.Processes.ActiveApplication.ApplicationControlProperties.PlayLogQueryableApplicationId.ItemsRo.Contains(titleId)) { return (ResultCode)Am.ResultCode.ObjectInvalid; } diff --git a/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs b/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs new file mode 100644 index 00000000..58759ddb --- /dev/null +++ b/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs @@ -0,0 +1,133 @@ +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.HLE.Loaders.Executables; +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, bool isHomebrew = false) + { + ulong programId = metaLoader.GetProgramId(); + + // 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 var nsoFile = new UniqueRef<IFile>(); + + 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); + + // Don't use PTC if ExeFS files have been replaced. + bool enablePtc = device.System.EnablePtc && !modLoadResult.Modified; + if (!enablePtc) + { + Logger.Warning?.Print(LogClass.Ptc, $"Detected unsupported ExeFs modifications. PTC disabled."); + } + + // We allow it for nx-hbloader because it can be used to launch homebrew. + bool allowCodeMemoryForJit = programId == 0x010000000000100DUL || isHomebrew; + + string programName = ""; + + if (!isHomebrew && programId > 0x010000000000FFFF) + { + programName = nacpData.Value.Title[(int)device.System.State.DesiredTitleLanguage].NameString.ToString(); + + if (string.IsNullOrWhiteSpace(programName)) + { + programName = nacpData.Value.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString(); + } + } + + // Initialize GPU. + Graphics.Gpu.GraphicsConfig.TitleId = $"{programId: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.Value, + enablePtc, + allowCodeMemoryForJit, + programName, + metaLoader.GetProgramId(), + null, + nsoExecutables); + + // TODO: This should be stored using ProcessId instead. + device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(metaLoader.GetProgramId()); + + return processResult; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs b/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs new file mode 100644 index 00000000..28d90785 --- /dev/null +++ b/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs @@ -0,0 +1,39 @@ +using LibHac.Common; +using LibHac.FsSystem; +using LibHac.Loader; +using LibHac.Ns; +using Ryujinx.HLE.Loaders.Processes.Extensions; +using ApplicationId = LibHac.Ncm.ApplicationId; + +namespace Ryujinx.HLE.Loaders.Processes +{ + static class LocalFileSystemExtensions + { + public static ProcessResult Load(this LocalFileSystem exeFs, Switch device, string romFsPath = "") + { + MetaLoader metaLoader = exeFs.GetNpdm(); + var nacpData = new BlitStruct<ApplicationControlProperty>(1); + ulong programId = metaLoader.GetProgramId(); + + device.Configuration.VirtualFileSystem.ModLoader.CollectMods( + new[] { programId }, + device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(), + device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath()); + + if (programId != 0) + { + ProcessLoaderHelper.EnsureSaveData(device, new ApplicationId(programId), nacpData); + } + + ProcessResult processResult = exeFs.Load(device, nacpData, metaLoader); + + // Load RomFS. + if (!string.IsNullOrEmpty(romFsPath)) + { + device.Configuration.VirtualFileSystem.LoadRomFs(processResult.ProcessId, romFsPath); + } + + return processResult; + } + } +} diff --git a/Ryujinx.HLE/Loaders/Processes/Extensions/MetaLoaderExtensions.cs b/Ryujinx.HLE/Loaders/Processes/Extensions/MetaLoaderExtensions.cs new file mode 100644 index 00000000..c639ee52 --- /dev/null +++ b/Ryujinx.HLE/Loaders/Processes/Extensions/MetaLoaderExtensions.cs @@ -0,0 +1,61 @@ +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.Loader; +using LibHac.Util; +using Ryujinx.Common; +using System; + +namespace Ryujinx.HLE.Loaders.Processes.Extensions +{ + public static class MetaLoaderExtensions + { + public static ulong GetProgramId(this MetaLoader metaLoader) + { + metaLoader.GetNpdm(out var npdm).ThrowIfFailure(); + + return npdm.Aci.ProgramId.Value; + } + + public static string GetProgramName(this MetaLoader metaLoader) + { + metaLoader.GetNpdm(out var npdm).ThrowIfFailure(); + + return StringUtils.Utf8ZToString(npdm.Meta.ProgramName); + } + + public static bool IsProgram64Bit(this MetaLoader metaLoader) + { + metaLoader.GetNpdm(out var npdm).ThrowIfFailure(); + + return (npdm.Meta.Flags & 1) != 0; + } + + public static void LoadDefault(this MetaLoader metaLoader) + { + byte[] npdmBuffer = EmbeddedResources.Read("Ryujinx.HLE/Homebrew.npdm"); + + metaLoader.Load(npdmBuffer).ThrowIfFailure(); + } + + public static void LoadFromFile(this MetaLoader metaLoader, IFileSystem fileSystem, string path = "") + { + if (string.IsNullOrEmpty(path)) + { + path = ProcessConst.MainNpdmPath; + } + + using var npdmFile = new UniqueRef<IFile>(); + + fileSystem.OpenFile(ref npdmFile.Ref, path.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + npdmFile.Get.GetSize(out long fileSize).ThrowIfFailure(); + + Span<byte> npdmBuffer = new byte[fileSize]; + + npdmFile.Get.Read(out _, 0, npdmBuffer).ThrowIfFailure(); + + metaLoader.Load(npdmBuffer).ThrowIfFailure(); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs b/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs new file mode 100644 index 00000000..473f374d --- /dev/null +++ b/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs @@ -0,0 +1,175 @@ +using LibHac; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.Loader; +using LibHac.Ncm; +using LibHac.Ns; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common.Logging; +using System.IO; +using System.Linq; +using ApplicationId = LibHac.Ncm.ApplicationId; + +namespace Ryujinx.HLE.Loaders.Processes.Extensions +{ + static class NcaExtensions + { + public static ProcessResult Load(this Nca nca, Switch device, Nca patchNca, Nca controlNca) + { + // Extract RomFs and ExeFs from NCA. + IStorage romFs = nca.GetRomFs(device, patchNca); + IFileSystem exeFs = nca.GetExeFs(device, patchNca); + + if (exeFs == null) + { + Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA"); + + return ProcessResult.Failed; + } + + // Load Npdm file. + MetaLoader metaLoader = exeFs.GetNpdm(); + + // Collecting mods related to AocTitleIds and ProgramId. + device.Configuration.VirtualFileSystem.ModLoader.CollectMods( + device.Configuration.ContentManager.GetAocTitleIds().Prepend(metaLoader.GetProgramId()), + device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(), + device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath()); + + // Load Nacp file. + var nacpData = new BlitStruct<ApplicationControlProperty>(1); + + if (controlNca != null) + { + nacpData = controlNca.GetNacp(device); + } + + /* TODO: Rework this since it's wrong and doesn't work as it takes the DisplayVersion from a "potential" inexistant update. + + // Load program 0 control NCA as we are going to need it for display version. + (_, Nca updateProgram0ControlNca) = GetGameUpdateData(_device.Configuration.VirtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _); + + // NOTE: Nintendo doesn't guarantee that the display version will be updated on sub programs when updating a multi program application. + // As such, to avoid PTC cache confusion, we only trust the program 0 display version when launching a sub program. + if (updateProgram0ControlNca != null && _device.Configuration.UserChannelPersistence.Index != 0) + { + nacpData.Value.DisplayVersion = updateProgram0ControlNca.GetNacp(_device).Value.DisplayVersion; + } + + */ + + ProcessResult processResult = exeFs.Load(device, nacpData, metaLoader); + + // Load RomFS. + if (romFs == null) + { + Logger.Warning?.Print(LogClass.Loader, "No RomFS found in NCA"); + } + else + { + romFs = device.Configuration.VirtualFileSystem.ModLoader.ApplyRomFsMods(processResult.ProgramId, romFs); + + device.Configuration.VirtualFileSystem.SetRomFs(processResult.ProcessId, romFs.AsStream(FileAccess.Read)); + } + + // Don't create save data for system programs. + if (processResult.ProgramId != 0 && (processResult.ProgramId < SystemProgramId.Start.Value || processResult.ProgramId > SystemAppletId.End.Value)) + { + // Multi-program applications can technically use any program ID for the main program, but in practice they always use 0 in the low nibble. + // We'll know if this changes in the future because applications will get errors when trying to mount the correct save. + ProcessLoaderHelper.EnsureSaveData(device, new ApplicationId(processResult.ProgramId & ~0xFul), nacpData); + } + + return processResult; + } + + public static int GetProgramIndex(this Nca nca) + { + return (int)(nca.Header.TitleId & 0xF); + } + + public static bool IsProgram(this Nca nca) + { + return nca.Header.ContentType == NcaContentType.Program; + } + + public static bool IsPatch(this Nca nca) + { + int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program); + + return nca.IsProgram() && nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection(); + } + + public static bool IsControl(this Nca nca) + { + return nca.Header.ContentType == NcaContentType.Control; + } + + public static IFileSystem GetExeFs(this Nca nca, Switch device, Nca patchNca = null) + { + IFileSystem exeFs = null; + + if (patchNca == null) + { + if (nca.CanOpenSection(NcaSectionType.Code)) + { + exeFs = nca.OpenFileSystem(NcaSectionType.Code, device.System.FsIntegrityCheckLevel); + } + } + else + { + if (patchNca.CanOpenSection(NcaSectionType.Code)) + { + exeFs = nca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, device.System.FsIntegrityCheckLevel); + } + } + + return exeFs; + } + + public static IStorage GetRomFs(this Nca nca, Switch device, Nca patchNca = null) + { + IStorage romFs = null; + + if (patchNca == null) + { + if (nca.CanOpenSection(NcaSectionType.Data)) + { + romFs = nca.OpenStorage(NcaSectionType.Data, device.System.FsIntegrityCheckLevel); + } + } + else + { + if (patchNca.CanOpenSection(NcaSectionType.Data)) + { + romFs = nca.OpenStorageWithPatch(patchNca, NcaSectionType.Data, device.System.FsIntegrityCheckLevel); + } + } + + return romFs; + } + + public static BlitStruct<ApplicationControlProperty> GetNacp(this Nca controlNca, Switch device) + { + var nacpData = new BlitStruct<ApplicationControlProperty>(1); + + using var controlFile = new UniqueRef<IFile>(); + + Result result = controlNca.OpenFileSystem(NcaSectionType.Data, device.System.FsIntegrityCheckLevel) + .OpenFile(ref controlFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read); + + if (result.IsSuccess()) + { + result = controlFile.Get.Read(out long bytesRead, 0, nacpData.ByteSpan, ReadOption.None); + } + else + { + nacpData.ByteSpan.Clear(); + } + + return nacpData; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs b/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs new file mode 100644 index 00000000..5147f5c3 --- /dev/null +++ b/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs @@ -0,0 +1,177 @@ +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; + +namespace Ryujinx.HLE.Loaders.Processes.Extensions +{ + public static class PartitionFileSystemExtensions + { + internal static (bool, ProcessResult) TryLoad(this PartitionFileSystem partitionFileSystem, Switch device, string path, out string errorMessage) + { + errorMessage = null; + + // Load required NCAs. + Nca mainNca = null; + Nca patchNca = null; + Nca controlNca = null; + + try + { + device.Configuration.VirtualFileSystem.ImportTickets(partitionFileSystem); + + // TODO: To support multi-games container, this should use CNMT NCA instead. + foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca")) + { + Nca nca = partitionFileSystem.GetNca(device, fileEntry.FullPath); + + if (nca.GetProgramIndex() != device.Configuration.UserChannelPersistence.Index) + { + continue; + } + + if (nca.IsPatch()) + { + patchNca = nca; + } + else if (nca.IsProgram()) + { + mainNca = nca; + } + else if (nca.IsControl()) + { + controlNca = nca; + } + } + + ProcessLoaderHelper.RegisterProgramMapInfo(device, partitionFileSystem).ThrowIfFailure(); + } + catch (Exception ex) + { + errorMessage = $"Unable to load: {ex.Message}"; + + return (false, ProcessResult.Failed); + } + + if (mainNca != null) + { + if (mainNca.Header.ContentType != NcaContentType.Program) + { + errorMessage = "Selected NCA file is not a \"Program\" NCA"; + + return (false, ProcessResult.Failed); + } + + // Load Update NCAs. + Nca updatePatchNca = null; + Nca updateControlNca = null; + + if (ulong.TryParse(mainNca.Header.TitleId.ToString("x16"), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase)) + { + // Clear the program index part. + titleIdBase &= ~0xFUL; + + // Load update information if exists. + string titleUpdateMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json"); + if (File.Exists(titleUpdateMetadataPath)) + { + string updatePath = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(titleUpdateMetadataPath).Selected; + if (File.Exists(updatePath)) + { + PartitionFileSystem updatePartitionFileSystem = new(new FileStream(updatePath, FileMode.Open, FileAccess.Read).AsStorage()); + + device.Configuration.VirtualFileSystem.ImportTickets(updatePartitionFileSystem); + + // TODO: This should use CNMT NCA instead. + foreach (DirectoryEntryEx fileEntry in updatePartitionFileSystem.EnumerateEntries("/", "*.nca")) + { + Nca nca = updatePartitionFileSystem.GetNca(device, fileEntry.FullPath); + + if (nca.GetProgramIndex() != device.Configuration.UserChannelPersistence.Index) + { + continue; + } + + if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleIdBase.ToString("x16")) + { + break; + } + + if (nca.IsProgram()) + { + updatePatchNca = nca; + } + else if (nca.IsControl()) + { + updateControlNca = nca; + } + } + } + } + } + + if (updatePatchNca != null) + { + patchNca = updatePatchNca; + } + + if (updateControlNca != null) + { + controlNca = updateControlNca; + } + + // Load contained DownloadableContents. + // TODO: If we want to support multi-processes in future, we shouldn't clear AddOnContent data here. + device.Configuration.ContentManager.ClearAocData(); + device.Configuration.ContentManager.AddAocData(partitionFileSystem, path, mainNca.Header.TitleId, device.Configuration.FsIntegrityCheckLevel); + + // Load DownloadableContents. + string addOnContentMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "dlc.json"); + if (File.Exists(addOnContentMetadataPath)) + { + List<DownloadableContentContainer> dlcContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(addOnContentMetadataPath); + + foreach (DownloadableContentContainer downloadableContentContainer in dlcContainerList) + { + foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList) + { + if (File.Exists(downloadableContentContainer.ContainerPath) && downloadableContentNca.Enabled) + { + device.Configuration.ContentManager.AddAocItem(downloadableContentNca.TitleId, downloadableContentContainer.ContainerPath, downloadableContentNca.FullPath); + } + else + { + Logger.Warning?.Print(LogClass.Application, $"Cannot find AddOnContent file {downloadableContentContainer.ContainerPath}. It may have been moved or renamed."); + } + } + } + } + + return (true, mainNca.Load(device, patchNca, controlNca)); + } + + errorMessage = "Unable to load: Could not find Main NCA"; + + return (false, ProcessResult.Failed); + } + + public static Nca GetNca(this IFileSystem fileSystem, Switch device, string path) + { + using var ncaFile = new UniqueRef<IFile>(); + + fileSystem.OpenFile(ref ncaFile.Ref, path.ToU8Span(), OpenMode.Read).ThrowIfFailure(); + + return new Nca(device.Configuration.VirtualFileSystem.KeySet, ncaFile.Release().AsStorage()); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Processes/ProcessConst.cs b/Ryujinx.HLE/Loaders/Processes/ProcessConst.cs new file mode 100644 index 00000000..42ae2e89 --- /dev/null +++ b/Ryujinx.HLE/Loaders/Processes/ProcessConst.cs @@ -0,0 +1,33 @@ +namespace Ryujinx.HLE.Loaders.Processes +{ + static class ProcessConst + { + // Binaries from exefs are loaded into mem in this order. Do not change. + public static readonly string[] ExeFsPrefixes = + { + "rtld", + "main", + "subsdk0", + "subsdk1", + "subsdk2", + "subsdk3", + "subsdk4", + "subsdk5", + "subsdk6", + "subsdk7", + "subsdk8", + "subsdk9", + "sdk" + }; + + public static readonly string MainNpdmPath = "/main.npdm"; + + public const int NroAsetMagic = ('A' << 0) | ('S' << 8) | ('E' << 16) | ('T' << 24); + + public const bool AslrEnabled = true; + + public const int NsoArgsHeaderSize = 8; + public const int NsoArgsDataSize = 0x9000; + public const int NsoArgsTotalSize = NsoArgsHeaderSize + NsoArgsDataSize; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs b/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs new file mode 100644 index 00000000..785db0e5 --- /dev/null +++ b/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs @@ -0,0 +1,244 @@ +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Ns; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Loaders.Executables; +using Ryujinx.HLE.Loaders.Processes.Extensions; +using System.Collections.Concurrent; +using System.IO; +using System.Linq; +using Path = System.IO.Path; + +namespace Ryujinx.HLE.Loaders.Processes +{ + public class ProcessLoader + { + private readonly Switch _device; + + private readonly ConcurrentDictionary<ulong, ProcessResult> _processesByPid; + + private ulong _latestPid; + + public ProcessResult ActiveApplication => _processesByPid[_latestPid]; + + public ProcessLoader(Switch device) + { + _device = device; + _processesByPid = new ConcurrentDictionary<ulong, ProcessResult>(); + } + + public bool LoadXci(string path) + { + FileStream stream = new(path, FileMode.Open, FileAccess.Read); + Xci xci = new(_device.Configuration.VirtualFileSystem.KeySet, stream.AsStorage()); + + if (!xci.HasPartition(XciPartitionType.Secure)) + { + Logger.Error?.Print(LogClass.Loader, "Unable to load XCI: Could not find XCI Secure partition"); + + return false; + } + + (bool success, ProcessResult processResult) = xci.OpenPartition(XciPartitionType.Secure).TryLoad(_device, path, out string errorMessage); + + if (!success) + { + Logger.Error?.Print(LogClass.Loader, errorMessage, nameof(PartitionFileSystemExtensions.TryLoad)); + + return false; + } + + if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult)) + { + if (processResult.Start(_device)) + { + _latestPid = processResult.ProcessId; + + return true; + } + } + + return false; + } + + public bool LoadNsp(string path) + { + FileStream file = new(path, FileMode.Open, FileAccess.Read); + PartitionFileSystem partitionFileSystem = new(file.AsStorage()); + + (bool success, ProcessResult processResult) = partitionFileSystem.TryLoad(_device, path, out string errorMessage); + + if (processResult.ProcessId == 0) + { + // This is not a normal NSP, it's actually a ExeFS as a NSP + processResult = partitionFileSystem.Load(_device, new BlitStruct<ApplicationControlProperty>(1), partitionFileSystem.GetNpdm(), true); + } + + if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult)) + { + if (processResult.Start(_device)) + { + _latestPid = processResult.ProcessId; + + return true; + } + } + + if (!success) + { + Logger.Error?.Print(LogClass.Loader, errorMessage, nameof(PartitionFileSystemExtensions.TryLoad)); + } + + return false; + } + + public bool LoadNca(string path) + { + FileStream file = new(path, FileMode.Open, FileAccess.Read); + Nca nca = new(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false)); + + ProcessResult processResult = nca.Load(_device, null, null); + + if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult)) + { + if (processResult.Start(_device)) + { + // NOTE: Check if process is SystemApplicationId or ApplicationId + if (processResult.ProgramId > 0x01000000000007FF) + { + _latestPid = processResult.ProcessId; + } + + return true; + } + } + + return false; + } + + public bool LoadUnpackedNca(string exeFsDirPath, string romFsPath = null) + { + ProcessResult processResult = new LocalFileSystem(exeFsDirPath).Load(_device, romFsPath); + + if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult)) + { + if (processResult.Start(_device)) + { + _latestPid = processResult.ProcessId; + + return true; + } + } + + return false; + } + + public bool LoadNxo(string path) + { + var nacpData = new BlitStruct<ApplicationControlProperty>(1); + IFileSystem dummyExeFs = null; + Stream romfsStream = null; + + string programName = ""; + ulong programId = 0000000000000000; + + // Load executable. + IExecutable executable; + + if (Path.GetExtension(path).ToLower() == ".nro") + { + FileStream input = new(path, FileMode.Open); + NroExecutable nro = new(input.AsStorage()); + + executable = nro; + + // Open RomFS if exists. + IStorage romFsStorage = nro.OpenNroAssetSection(LibHac.Tools.Ro.NroAssetType.RomFs, false); + romFsStorage.GetSize(out long romFsSize).ThrowIfFailure(); + if (romFsSize != 0) + { + romfsStream = romFsStorage.AsStream(); + } + + // Load Nacp if exists. + IStorage nacpStorage = nro.OpenNroAssetSection(LibHac.Tools.Ro.NroAssetType.Nacp, false); + nacpStorage.GetSize(out long nacpSize).ThrowIfFailure(); + if (nacpSize != 0) + { + nacpStorage.Read(0, nacpData.ByteSpan); + + programName = nacpData.Value.Title[(int)_device.System.State.DesiredTitleLanguage].NameString.ToString(); + + if (string.IsNullOrWhiteSpace(programName)) + { + programName = nacpData.Value.Title.ItemsRo.ToArray().FirstOrDefault(x => x.Name[0] != 0).NameString.ToString(); + } + + if (nacpData.Value.PresenceGroupId != 0) + { + programId = nacpData.Value.PresenceGroupId; + } + else if (nacpData.Value.SaveDataOwnerId != 0) + { + programId = nacpData.Value.SaveDataOwnerId; + } + else if (nacpData.Value.AddOnContentBaseId != 0) + { + programId = nacpData.Value.AddOnContentBaseId - 0x1000; + } + } + + // TODO: Add icon maybe ? + } + else + { + programName = System.IO.Path.GetFileNameWithoutExtension(path); + + executable = new NsoExecutable(new LocalStorage(path, FileAccess.Read), programName); + } + + // Explicitly null TitleId to disable the shader cache. + Graphics.Gpu.GraphicsConfig.TitleId = null; + _device.Gpu.HostInitalized.Set(); + + ProcessResult processResult = ProcessLoaderHelper.LoadNsos(_device, + _device.System.KernelContext, + dummyExeFs.GetNpdm(), + nacpData.Value, + diskCacheEnabled: false, + allowCodeMemoryForJit: true, + programName, + programId, + null, + executable); + + // Make sure the process id is valid. + if (processResult.ProcessId != 0) + { + // Load RomFS. + if (romfsStream != null) + { + _device.Configuration.VirtualFileSystem.SetRomFs(processResult.ProcessId, romfsStream); + } + + // Start process. + if (_processesByPid.TryAdd(processResult.ProcessId, processResult)) + { + if (processResult.Start(_device)) + { + _latestPid = processResult.ProcessId; + + return true; + } + } + } + + return false; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/ProgramLoader.cs b/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs index 4ebcb7e7..7176445e 100644 --- a/Ryujinx.HLE/HOS/ProgramLoader.cs +++ b/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs @@ -1,69 +1,132 @@ +using LibHac.Account; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Shim; +using LibHac.FsSystem; using LibHac.Loader; using LibHac.Ncm; -using LibHac.Util; +using LibHac.Ns; +using LibHac.Tools.Fs; +using LibHac.Tools.FsSystem; +using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common; using Ryujinx.Common.Logging; -using Ryujinx.Cpu; +using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.Loaders.Executables; +using Ryujinx.HLE.Loaders.Processes.Extensions; using Ryujinx.Horizon.Common; using System; using System.Linq; using System.Runtime.InteropServices; -using Npdm = LibHac.Loader.Npdm; +using ApplicationId = LibHac.Ncm.ApplicationId; -namespace Ryujinx.HLE.HOS +namespace Ryujinx.HLE.Loaders.Processes { - struct ProgramInfo + static class ProcessLoaderHelper { - public string Name; - public ulong ProgramId; - public readonly string TitleIdText; - public readonly string DisplayVersion; - public readonly bool DiskCacheEnabled; - public readonly bool AllowCodeMemoryForJit; - - public ProgramInfo(in Npdm npdm, string displayVersion, bool diskCacheEnabled, bool allowCodeMemoryForJit) + public static LibHac.Result RegisterProgramMapInfo(Switch device, PartitionFileSystem partitionFileSystem) { - ulong programId = npdm.Aci.ProgramId.Value; - - Name = StringUtils.Utf8ZToString(npdm.Meta.ProgramName); - ProgramId = programId; - TitleIdText = programId.ToString("x16"); - DisplayVersion = displayVersion; - DiskCacheEnabled = diskCacheEnabled; - AllowCodeMemoryForJit = allowCodeMemoryForJit; - } - } + ulong applicationId = 0; + int programCount = 0; - struct ProgramLoadResult - { - public static ProgramLoadResult Failed => new ProgramLoadResult(false, null, null, 0); + Span<bool> hasIndex = stackalloc bool[0x10]; - public readonly bool Success; - public readonly ProcessTamperInfo TamperInfo; - public readonly IDiskCacheLoadState DiskCacheLoadState; - public readonly ulong ProcessId; + foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca")) + { + Nca nca = partitionFileSystem.GetNca(device, fileEntry.FullPath); - public ProgramLoadResult(bool success, ProcessTamperInfo tamperInfo, IDiskCacheLoadState diskCacheLoadState, ulong pid) - { - Success = success; - TamperInfo = tamperInfo; - DiskCacheLoadState = diskCacheLoadState; - ProcessId = pid; + if (!nca.IsProgram() && nca.IsPatch()) + { + continue; + } + + ulong currentProgramId = nca.Header.TitleId; + ulong currentMainProgramId = currentProgramId & ~0xFFFul; + + if (applicationId == 0 && currentMainProgramId != 0) + { + applicationId = currentMainProgramId; + } + + if (applicationId != currentMainProgramId) + { + // Currently there aren't any known multi-application game cards containing multi-program applications, + // so because multi-application game cards are the only way we could run into multiple applications + // we'll just return that there's a single program. + programCount = 1; + + break; + } + + hasIndex[(int)(currentProgramId & 0xF)] = true; + } + + if (programCount == 0) + { + for (int i = 0; i < hasIndex.Length && hasIndex[i]; i++) + { + programCount++; + } + } + + if (programCount <= 0) + { + return LibHac.Result.Success; + } + + Span<ProgramIndexMapInfo> mapInfo = stackalloc ProgramIndexMapInfo[0x10]; + + for (int i = 0; i < programCount; i++) + { + mapInfo[i].ProgramId = new ProgramId(applicationId + (uint)i); + mapInfo[i].MainProgramId = new ApplicationId(applicationId); + mapInfo[i].ProgramIndex = (byte)i; + } + + return device.System.LibHacHorizonManager.NsClient.Fs.RegisterProgramIndexMapInfo(mapInfo[..programCount]); } - } - static class ProgramLoader - { - private const bool AslrEnabled = true; + public static LibHac.Result EnsureSaveData(Switch device, ApplicationId applicationId, BlitStruct<ApplicationControlProperty> applicationControlProperty) + { + Logger.Info?.Print(LogClass.Application, "Ensuring required savedata exists."); - private const int ArgsHeaderSize = 8; - private const int ArgsDataSize = 0x9000; - private const int ArgsTotalSize = ArgsHeaderSize + ArgsDataSize; + ref ApplicationControlProperty control = ref applicationControlProperty.Value; + + if (LibHac.Common.Utilities.IsZeros(applicationControlProperty.ByteSpan)) + { + // If the current application doesn't have a loaded control property, create a dummy one and set the savedata sizes so a user savedata will be created. + control = ref new BlitStruct<ApplicationControlProperty>(1).Value; + + // The set sizes don't actually matter as long as they're non-zero because we use directory savedata. + control.UserAccountSaveDataSize = 0x4000; + control.UserAccountSaveDataJournalSize = 0x4000; + control.SaveDataOwnerId = applicationId.Value; + + Logger.Warning?.Print(LogClass.Application, "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); + } + + LibHac.Result resultCode = device.System.LibHacHorizonManager.RyujinxClient.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, in control); + if (resultCode.IsFailure()) + { + Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {resultCode.ToStringWithName()}"); + + return resultCode; + } + + Uid userId = device.System.AccountManager.LastOpenedUser.UserId.ToLibHacUid(); + + resultCode = device.System.LibHacHorizonManager.RyujinxClient.Fs.EnsureApplicationSaveData(out _, applicationId, in control, in userId); + if (resultCode.IsFailure()) + { + Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {resultCode.ToStringWithName()}"); + } + + return resultCode; + } public static bool LoadKip(KernelContext context, KipExecutable kip) { @@ -74,17 +137,14 @@ namespace Ryujinx.HLE.HOS endOffset = kip.BssOffset + kip.BssSize; } - uint codeSize = BitUtils.AlignUp<uint>(kip.TextOffset + endOffset, KPageTableBase.PageSize); - - int codePagesCount = (int)(codeSize / KPageTableBase.PageSize); - + uint codeSize = BitUtils.AlignUp<uint>(kip.TextOffset + endOffset, KPageTableBase.PageSize); + int codePagesCount = (int)(codeSize / KPageTableBase.PageSize); ulong codeBaseAddress = kip.Is64BitAddressSpace ? 0x8000000UL : 0x200000UL; - - ulong codeAddress = codeBaseAddress + kip.TextOffset; + ulong codeAddress = codeBaseAddress + kip.TextOffset; ProcessCreationFlags flags = 0; - if (AslrEnabled) + if (ProcessConst.AslrEnabled) { // TODO: Randomization. @@ -101,24 +161,11 @@ namespace Ryujinx.HLE.HOS flags |= ProcessCreationFlags.Is64Bit; } - ProcessCreationInfo creationInfo = new ProcessCreationInfo( - kip.Name, - kip.Version, - kip.ProgramId, - codeAddress, - codePagesCount, - flags, - 0, - 0); - - MemoryRegion memoryRegion = kip.UsesSecureMemory - ? MemoryRegion.Service - : MemoryRegion.Application; - - KMemoryRegionManager region = context.MemoryManager.MemoryRegions[(int)memoryRegion]; + ProcessCreationInfo creationInfo = new(kip.Name, kip.Version, kip.ProgramId, codeAddress, codePagesCount, flags, 0, 0); + MemoryRegion memoryRegion = kip.UsesSecureMemory ? MemoryRegion.Service : MemoryRegion.Application; + KMemoryRegionManager region = context.MemoryManager.MemoryRegions[(int)memoryRegion]; Result result = region.AllocatePages(out KPageList pageList, (ulong)codePagesCount); - if (result != Result.Success) { Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); @@ -126,7 +173,7 @@ namespace Ryujinx.HLE.HOS return false; } - KProcess process = new KProcess(context); + KProcess process = new(context); var processContextFactory = new ArmProcessContextFactory( context.Device.System.TickSource, @@ -137,14 +184,7 @@ namespace Ryujinx.HLE.HOS codeAddress, codeSize); - result = process.InitializeKip( - creationInfo, - kip.Capabilities, - pageList, - context.ResourceLimit, - memoryRegion, - processContextFactory); - + result = process.InitializeKip(creationInfo, kip.Capabilities, pageList, context.ResourceLimit, memoryRegion, processContextFactory); if (result != Result.Success) { Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); @@ -153,7 +193,6 @@ namespace Ryujinx.HLE.HOS } result = LoadIntoMemory(process, kip, codeBaseAddress); - if (result != Result.Success) { Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); @@ -164,7 +203,6 @@ namespace Ryujinx.HLE.HOS process.DefaultCpuCore = kip.IdealCoreId; result = process.Start(kip.Priority, (ulong)kip.StackSize); - if (result != Result.Success) { Logger.Error?.Print(LogClass.Loader, $"Process start returned error \"{result}\"."); @@ -177,20 +215,27 @@ namespace Ryujinx.HLE.HOS return true; } - public static ProgramLoadResult LoadNsos( + public static ProcessResult LoadNsos( + Switch device, KernelContext context, - MetaLoader metaData, - ProgramInfo programInfo, + MetaLoader metaLoader, + ApplicationControlProperty applicationControlProperties, + bool diskCacheEnabled, + bool allowCodeMemoryForJit, + string name, + ulong programId, byte[] arguments = null, params IExecutable[] executables) { context.Device.System.ServiceTable.WaitServicesReady(); - LibHac.Result rc = metaData.GetNpdm(out var npdm); + LibHac.Result resultCode = metaLoader.GetNpdm(out var npdm); - if (rc.IsFailure()) + if (resultCode.IsFailure()) { - return ProgramLoadResult.Failed; + Logger.Error?.Print(LogClass.Loader, $"Process initialization failed getting npdm. Result Code {resultCode.ToStringWithName()}"); + + return ProcessResult.Failed; } ref readonly var meta = ref npdm.Meta; @@ -202,10 +247,10 @@ namespace Ryujinx.HLE.HOS var buildIds = executables.Select(e => (e switch { - NsoExecutable nso => BitConverter.ToString(nso.BuildId.ItemsRo.ToArray()), - NroExecutable nro => BitConverter.ToString(nro.Header.BuildId), + NsoExecutable nso => Convert.ToHexString(nso.BuildId.ItemsRo.ToArray()), + NroExecutable nro => Convert.ToHexString(nro.Header.BuildId), _ => "" - }).Replace("-", "").ToUpper()); + }).ToUpper()); ulong[] nsoBase = new ulong[executables.Length]; @@ -214,7 +259,7 @@ namespace Ryujinx.HLE.HOS IExecutable nso = executables[index]; uint textEnd = nso.TextOffset + (uint)nso.Text.Length; - uint roEnd = nso.RoOffset + (uint)nso.Ro.Length; + uint roEnd = nso.RoOffset + (uint)nso.Ro.Length; uint dataEnd = nso.DataOffset + (uint)nso.Data.Length + nso.BssSize; uint nsoSize = textEnd; @@ -239,31 +284,30 @@ namespace Ryujinx.HLE.HOS { argsStart = codeSize; - argsSize = (uint)BitUtils.AlignDown(arguments.Length * 2 + ArgsTotalSize - 1, KPageTableBase.PageSize); + argsSize = (uint)BitUtils.AlignDown(arguments.Length * 2 + ProcessConst.NsoArgsTotalSize - 1, KPageTableBase.PageSize); codeSize += argsSize; } } - int codePagesCount = (int)(codeSize / KPageTableBase.PageSize); - + int codePagesCount = (int)(codeSize / KPageTableBase.PageSize); int personalMmHeapPagesCount = (int)(meta.SystemResourceSize / KPageTableBase.PageSize); - ProcessCreationInfo creationInfo = new ProcessCreationInfo( - programInfo.Name, + ProcessCreationInfo creationInfo = new( + name, (int)meta.Version, - programInfo.ProgramId, + programId, codeStart, codePagesCount, (ProcessCreationFlags)meta.Flags | ProcessCreationFlags.IsApplication, 0, personalMmHeapPagesCount); - context.Device.System.LibHacHorizonManager.InitializeApplicationClient(new ProgramId(programInfo.ProgramId), in npdm); + context.Device.System.LibHacHorizonManager.InitializeApplicationClient(new ProgramId(programId), in npdm); Result result; - KResourceLimit resourceLimit = new KResourceLimit(context); + KResourceLimit resourceLimit = new(context); long applicationRgSize = (long)context.MemoryManager.MemoryRegions[(int)MemoryRegion.Application].Size; @@ -293,26 +337,26 @@ namespace Ryujinx.HLE.HOS { Logger.Error?.Print(LogClass.Loader, $"Process initialization failed setting resource limit values."); - return ProgramLoadResult.Failed; + return ProcessResult.Failed; } - KProcess process = new KProcess(context, programInfo.AllowCodeMemoryForJit); - - MemoryRegion memoryRegion = (MemoryRegion)((npdm.Acid.Flags >> 2) & 0xf); + KProcess process = new(context, allowCodeMemoryForJit); + // NOTE: This field doesn't exists one firmware pre-5.0.0, a workaround have to be found. + MemoryRegion memoryRegion = (MemoryRegion)(npdm.Acid.Flags >> 2 & 0xf); if (memoryRegion > MemoryRegion.NvServices) { Logger.Error?.Print(LogClass.Loader, $"Process initialization failed due to invalid ACID flags."); - return ProgramLoadResult.Failed; + return ProcessResult.Failed; } var processContextFactory = new ArmProcessContextFactory( context.Device.System.TickSource, context.Device.Gpu, - programInfo.TitleIdText, - programInfo.DisplayVersion, - programInfo.DiskCacheEnabled, + $"{programId:x16}", + applicationControlProperties.DisplayVersionString.ToString(), + diskCacheEnabled, codeStart, codeSize); @@ -327,7 +371,7 @@ namespace Ryujinx.HLE.HOS { Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); - return ProgramLoadResult.Failed; + return ProcessResult.Failed; } for (int index = 0; index < executables.Length; index++) @@ -335,32 +379,22 @@ namespace Ryujinx.HLE.HOS Logger.Info?.Print(LogClass.Loader, $"Loading image {index} at 0x{nsoBase[index]:x16}..."); result = LoadIntoMemory(process, executables[index], nsoBase[index]); - if (result != Result.Success) { Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\"."); - return ProgramLoadResult.Failed; + return ProcessResult.Failed; } } process.DefaultCpuCore = meta.DefaultCpuId; - result = process.Start(meta.MainThreadPriority, meta.MainThreadStackSize); - - if (result != Result.Success) - { - Logger.Error?.Print(LogClass.Loader, $"Process start returned error \"{result}\"."); - - return ProgramLoadResult.Failed; - } - context.Processes.TryAdd(process.Pid, process); // Keep the build ids because the tamper machine uses them to know which process to associate a // tamper to and also keep the starting address of each executable inside a process because some // memory modifications are relative to this address. - ProcessTamperInfo tamperInfo = new ProcessTamperInfo( + ProcessTamperInfo tamperInfo = new( process, buildIds, nsoBase, @@ -368,10 +402,13 @@ namespace Ryujinx.HLE.HOS process.MemoryManager.AliasRegionStart, process.MemoryManager.CodeRegionStart); - return new ProgramLoadResult(true, tamperInfo, processContextFactory.DiskCacheLoadState, process.Pid); + // Once everything is loaded, we can load cheats. + device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(programId, tamperInfo, device.TamperMachine); + + return new ProcessResult(metaLoader, applicationControlProperties, diskCacheEnabled, allowCodeMemoryForJit, processContextFactory.DiskCacheLoadState, process.Pid, meta.MainThreadPriority, meta.MainThreadStackSize); } - private static Result LoadIntoMemory(KProcess process, IExecutable image, ulong baseAddress) + public static Result LoadIntoMemory(KProcess process, IExecutable image, ulong baseAddress) { ulong textStart = baseAddress + image.TextOffset; ulong roStart = baseAddress + image.RoOffset; @@ -404,14 +441,12 @@ namespace Ryujinx.HLE.HOS } Result result = SetProcessMemoryPermission(textStart, (ulong)image.Text.Length, KMemoryPermission.ReadAndExecute); - if (result != Result.Success) { return result; } result = SetProcessMemoryPermission(roStart, (ulong)image.Ro.Length, KMemoryPermission.Read); - if (result != Result.Success) { return result; diff --git a/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs b/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs new file mode 100644 index 00000000..6bbeee1b --- /dev/null +++ b/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs @@ -0,0 +1,92 @@ +using LibHac.Loader; +using LibHac.Ns; +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.Loaders.Processes.Extensions; +using Ryujinx.Horizon.Common; + +namespace Ryujinx.HLE.Loaders.Processes +{ + public struct ProcessResult + { + public static ProcessResult Failed => new(null, new ApplicationControlProperty(), false, false, null, 0, 0, 0); + + private readonly byte _mainThreadPriority; + private readonly uint _mainThreadStackSize; + + public readonly IDiskCacheLoadState DiskCacheLoadState; + + public readonly MetaLoader MetaLoader; + public readonly ApplicationControlProperty ApplicationControlProperties; + + public readonly ulong ProcessId; + public string Name; + public ulong ProgramId; + public readonly string ProgramIdText; + public readonly bool Is64Bit; + public readonly bool DiskCacheEnabled; + public readonly bool AllowCodeMemoryForJit; + + public ProcessResult( + MetaLoader metaLoader, + ApplicationControlProperty applicationControlProperties, + bool diskCacheEnabled, + bool allowCodeMemoryForJit, + IDiskCacheLoadState diskCacheLoadState, + ulong pid, + byte mainThreadPriority, + uint mainThreadStackSize) + { + _mainThreadPriority = mainThreadPriority; + _mainThreadStackSize = mainThreadStackSize; + + DiskCacheLoadState = diskCacheLoadState; + ProcessId = pid; + + MetaLoader = metaLoader; + ApplicationControlProperties = applicationControlProperties; + + if (metaLoader is not null) + { + ulong programId = metaLoader.GetProgramId(); + + Name = metaLoader.GetProgramName(); + ProgramId = programId; + ProgramIdText = $"{programId:x16}"; + Is64Bit = metaLoader.IsProgram64Bit(); + } + + DiskCacheEnabled = diskCacheEnabled; + AllowCodeMemoryForJit = allowCodeMemoryForJit; + } + + public bool Start(Switch device) + { + device.Configuration.ContentManager.LoadEntries(device); + + Result result = device.System.KernelContext.Processes[ProcessId].Start(_mainThreadPriority, _mainThreadStackSize); + if (result != Result.Success) + { + Logger.Error?.Print(LogClass.Loader, $"Process start returned error \"{result}\"."); + + return false; + } + + // TODO: LibHac npdm currently doesn't support version field. + string version; + + if (ProgramId > 0x0100000000007FFF) + { + version = ApplicationControlProperties.DisplayVersionString.ToString(); + } + else + { + version = device.System.ContentManager.GetCurrentFirmwareVersion().VersionString; + } + + Logger.Info?.Print(LogClass.Loader, $"Application Loaded: {Name} v{version} [{ProgramIdText}] [{(Is64Bit ? "64-bit" : "32-bit")}]"); + + return true; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs index 61e5e572..62d14a54 100644 --- a/Ryujinx.HLE/Switch.cs +++ b/Ryujinx.HLE/Switch.cs @@ -6,6 +6,7 @@ using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS.Services.Apm; using Ryujinx.HLE.HOS.Services.Hid; +using Ryujinx.HLE.Loaders.Processes; using Ryujinx.HLE.Ui; using Ryujinx.Memory; using System; @@ -20,7 +21,7 @@ namespace Ryujinx.HLE public GpuContext Gpu { get; } public VirtualFileSystem FileSystem { get; } public HOS.Horizon System { get; } - public ApplicationLoader Application { get; } + public ProcessLoader Processes { get; } public PerformanceStatistics Statistics { get; } public Hid Hid { get; } public TamperMachine TamperMachine { get; } @@ -50,7 +51,7 @@ namespace Ryujinx.HLE System = new HOS.Horizon(this); Statistics = new PerformanceStatistics(); Hid = new Hid(this, System.HidStorage); - Application = new ApplicationLoader(this); + Processes = new ProcessLoader(this); TamperMachine = new TamperMachine(); System.State.SetLanguage(Configuration.SystemLanguage); @@ -64,29 +65,29 @@ namespace Ryujinx.HLE System.GlobalAccessLogMode = Configuration.FsGlobalAccessLogMode; } - public void LoadCart(string exeFsDir, string romFsFile = null) + public bool LoadCart(string exeFsDir, string romFsFile = null) { - Application.LoadCart(exeFsDir, romFsFile); + return Processes.LoadUnpackedNca(exeFsDir, romFsFile); } - public void LoadXci(string xciFile) + public bool LoadXci(string xciFile) { - Application.LoadXci(xciFile); + return Processes.LoadXci(xciFile); } - public void LoadNca(string ncaFile) + public bool LoadNca(string ncaFile) { - Application.LoadNca(ncaFile); + return Processes.LoadNca(ncaFile); } - public void LoadNsp(string nspFile) + public bool LoadNsp(string nspFile) { - Application.LoadNsp(nspFile); + return Processes.LoadNsp(nspFile); } - public void LoadProgram(string fileName) + public bool LoadProgram(string fileName) { - Application.LoadProgram(fileName); + return Processes.LoadNxo(fileName); } public bool WaitFifo() @@ -123,7 +124,7 @@ namespace Ryujinx.HLE public void EnableCheats() { - FileSystem.ModLoader.EnableCheats(Application.TitleId, TamperMachine); + FileSystem.ModLoader.EnableCheats(Processes.ActiveApplication.ProgramId, TamperMachine); } public bool IsAudioMuted() @@ -152,4 +153,4 @@ namespace Ryujinx.HLE } } } -}
\ No newline at end of file +} |
