Refactor out Application details from Horizon (#1236)
* Initial Application refactor * Misc typo and access modifier fixes * Clean unused namespaces * Address gdkchan's comments * Move ticket reading to common method * Change IParentalControlService to use ApplicationLoader.ControlData
This commit is contained in:
parent
0ff00bd6d3
commit
ba4830293e
@ -580,7 +580,7 @@ namespace Ryujinx.HLE.FileSystem.Content
|
|||||||
|
|
||||||
SystemVersion VerifyAndGetVersionZip(ZipArchive archive)
|
SystemVersion VerifyAndGetVersionZip(ZipArchive archive)
|
||||||
{
|
{
|
||||||
IntegrityCheckLevel integrityCheckLevel = Switch.GetIntigrityCheckLevel();
|
IntegrityCheckLevel integrityCheckLevel = Switch.GetIntegrityCheckLevel();
|
||||||
|
|
||||||
SystemVersion systemVersion = null;
|
SystemVersion systemVersion = null;
|
||||||
|
|
||||||
@ -743,7 +743,7 @@ namespace Ryujinx.HLE.FileSystem.Content
|
|||||||
|
|
||||||
SystemVersion VerifyAndGetVersion(IFileSystem filesystem)
|
SystemVersion VerifyAndGetVersion(IFileSystem filesystem)
|
||||||
{
|
{
|
||||||
IntegrityCheckLevel integrityCheckLevel = Switch.GetIntigrityCheckLevel();
|
IntegrityCheckLevel integrityCheckLevel = Switch.GetIntegrityCheckLevel();
|
||||||
|
|
||||||
SystemVersion systemVersion = null;
|
SystemVersion systemVersion = null;
|
||||||
|
|
||||||
@ -874,7 +874,7 @@ namespace Ryujinx.HLE.FileSystem.Content
|
|||||||
|
|
||||||
public SystemVersion GetCurrentFirmwareVersion()
|
public SystemVersion GetCurrentFirmwareVersion()
|
||||||
{
|
{
|
||||||
IntegrityCheckLevel integrityCheckLevel = Switch.GetIntigrityCheckLevel();
|
IntegrityCheckLevel integrityCheckLevel = Switch.GetIntegrityCheckLevel();
|
||||||
|
|
||||||
LoadEntries();
|
LoadEntries();
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
using LibHac;
|
using LibHac;
|
||||||
|
using LibHac.Common;
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.FsService;
|
using LibHac.FsService;
|
||||||
using LibHac.FsSystem;
|
using LibHac.FsSystem;
|
||||||
|
using LibHac.Spl;
|
||||||
using Ryujinx.HLE.FileSystem.Content;
|
using Ryujinx.HLE.FileSystem.Content;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
using System;
|
using System;
|
||||||
@ -261,6 +263,21 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
KeySet = ExternalKeyReader.ReadKeyFile(keyFile, titleKeyFile, consoleKeyFile);
|
KeySet = ExternalKeyReader.ReadKeyFile(keyFile, titleKeyFile, consoleKeyFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ImportTickets(IFileSystem fs)
|
||||||
|
{
|
||||||
|
foreach (DirectoryEntryEx ticketEntry in fs.EnumerateEntries("/", "*.tik"))
|
||||||
|
{
|
||||||
|
Result result = fs.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read);
|
||||||
|
|
||||||
|
if (result.IsSuccess())
|
||||||
|
{
|
||||||
|
Ticket ticket = new Ticket(ticketFile.AsStream());
|
||||||
|
|
||||||
|
KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(KeySet)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Unload()
|
public void Unload()
|
||||||
{
|
{
|
||||||
RomFs?.Dispose();
|
RomFs?.Dispose();
|
||||||
@ -283,7 +300,7 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
{
|
{
|
||||||
if (_isInitialized)
|
if (_isInitialized)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"VirtualFileSystem can only be instanciated once!");
|
throw new InvalidOperationException($"VirtualFileSystem can only be instantiated once!");
|
||||||
}
|
}
|
||||||
|
|
||||||
_isInitialized = true;
|
_isInitialized = true;
|
||||||
|
537
Ryujinx.HLE/HOS/ApplicationLoader.cs
Normal file
537
Ryujinx.HLE/HOS/ApplicationLoader.cs
Normal file
@ -0,0 +1,537 @@
|
|||||||
|
using LibHac;
|
||||||
|
using LibHac.Account;
|
||||||
|
using LibHac.Common;
|
||||||
|
using LibHac.Fs;
|
||||||
|
using LibHac.FsSystem;
|
||||||
|
using LibHac.FsSystem.NcaUtils;
|
||||||
|
using LibHac.Ncm;
|
||||||
|
using LibHac.Ns;
|
||||||
|
using LibHac.Spl;
|
||||||
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.HLE.FileSystem.Content;
|
||||||
|
using Ryujinx.HLE.Loaders.Executables;
|
||||||
|
using Ryujinx.HLE.Loaders.Npdm;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using static LibHac.Fs.ApplicationSaveDataManagement;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS
|
||||||
|
{
|
||||||
|
using JsonHelper = Common.Utilities.JsonHelper;
|
||||||
|
|
||||||
|
public class ApplicationLoader
|
||||||
|
{
|
||||||
|
private readonly Switch _device;
|
||||||
|
private readonly ContentManager _contentManager;
|
||||||
|
private readonly VirtualFileSystem _fileSystem;
|
||||||
|
|
||||||
|
public IntegrityCheckLevel FsIntegrityCheckLevel => _device.System.FsIntegrityCheckLevel;
|
||||||
|
|
||||||
|
public ulong TitleId { get; private set; }
|
||||||
|
public string TitleIdText => TitleId.ToString("x16");
|
||||||
|
public string TitleName { get; private set; }
|
||||||
|
|
||||||
|
public string TitleVersionString { get; private set; }
|
||||||
|
|
||||||
|
public bool TitleIs64Bit { get; private set; }
|
||||||
|
|
||||||
|
public BlitStruct<ApplicationControlProperty> ControlData { get; set; }
|
||||||
|
|
||||||
|
public ApplicationLoader(Switch device, VirtualFileSystem fileSystem, ContentManager contentManager)
|
||||||
|
{
|
||||||
|
_device = device;
|
||||||
|
_contentManager = contentManager;
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
|
||||||
|
ControlData = new BlitStruct<ApplicationControlProperty>(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadCart(string exeFsDir, string romFsFile = null)
|
||||||
|
{
|
||||||
|
if (romFsFile != null)
|
||||||
|
{
|
||||||
|
_fileSystem.LoadRomFs(romFsFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalFileSystem codeFs = new LocalFileSystem(exeFsDir);
|
||||||
|
|
||||||
|
LoadExeFs(codeFs, out _);
|
||||||
|
|
||||||
|
if (TitleId != 0)
|
||||||
|
{
|
||||||
|
EnsureSaveData(new TitleId(TitleId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private (Nca Main, Nca Patch, Nca Control) GetGameData(PartitionFileSystem pfs)
|
||||||
|
{
|
||||||
|
Nca mainNca = null;
|
||||||
|
Nca patchNca = null;
|
||||||
|
Nca controlNca = null;
|
||||||
|
|
||||||
|
_fileSystem.ImportTickets(pfs);
|
||||||
|
|
||||||
|
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||||
|
{
|
||||||
|
pfs.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
Nca nca = new Nca(_fileSystem.KeySet, ncaFile.AsStorage());
|
||||||
|
|
||||||
|
if (nca.Header.ContentType == NcaContentType.Program)
|
||||||
|
{
|
||||||
|
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||||
|
|
||||||
|
if (nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
||||||
|
{
|
||||||
|
patchNca = nca;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mainNca = nca;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (nca.Header.ContentType == NcaContentType.Control)
|
||||||
|
{
|
||||||
|
controlNca = nca;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (mainNca, patchNca, controlNca);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadXci(string xciFile)
|
||||||
|
{
|
||||||
|
FileStream file = new FileStream(xciFile, FileMode.Open, FileAccess.Read);
|
||||||
|
|
||||||
|
Xci xci = new Xci(_fileSystem.KeySet, file.AsStorage());
|
||||||
|
|
||||||
|
if (!xci.HasPartition(XciPartitionType.Secure))
|
||||||
|
{
|
||||||
|
Logger.PrintError(LogClass.Loader, "Unable to load XCI: Could not find XCI secure partition");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PartitionFileSystem securePartition = xci.OpenPartition(XciPartitionType.Secure);
|
||||||
|
|
||||||
|
Nca mainNca = null;
|
||||||
|
Nca patchNca = null;
|
||||||
|
Nca controlNca = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
(mainNca, patchNca, controlNca) = GetGameData(securePartition);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.PrintError(LogClass.Loader, $"Unable to load XCI: {e.Message}");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainNca == null)
|
||||||
|
{
|
||||||
|
Logger.PrintError(LogClass.Loader, "Unable to load XCI: Could not find Main NCA");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_contentManager.LoadEntries(_device);
|
||||||
|
|
||||||
|
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 = null;
|
||||||
|
Nca patchNca = null;
|
||||||
|
Nca controlNca = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
(mainNca, patchNca, controlNca) = GetGameData(nsp);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.PrintError(LogClass.Loader, $"Unable to load NSP: {e.Message}");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainNca == null)
|
||||||
|
{
|
||||||
|
Logger.PrintError(LogClass.Loader, "Unable to load NSP: Could not find Main NCA");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainNca != null)
|
||||||
|
{
|
||||||
|
LoadNca(mainNca, patchNca, controlNca);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is not a normal NSP, it's actually a ExeFS as a NSP
|
||||||
|
LoadExeFs(nsp, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadNca(string ncaFile)
|
||||||
|
{
|
||||||
|
FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read);
|
||||||
|
|
||||||
|
Nca nca = new Nca(_fileSystem.KeySet, file.AsStorage(false));
|
||||||
|
|
||||||
|
LoadNca(nca, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadNca(Nca mainNca, Nca patchNca, Nca controlNca)
|
||||||
|
{
|
||||||
|
if (mainNca.Header.ContentType != NcaContentType.Program)
|
||||||
|
{
|
||||||
|
Logger.PrintError(LogClass.Loader, "Selected NCA is not a \"Program\" NCA");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IStorage dataStorage = null;
|
||||||
|
IFileSystem codeFs = null;
|
||||||
|
|
||||||
|
string titleUpdateMetadataPath = System.IO.Path.Combine(_fileSystem.GetBasePath(), "games", mainNca.Header.TitleId.ToString("x16"), "updates.json");
|
||||||
|
|
||||||
|
if (File.Exists(titleUpdateMetadataPath))
|
||||||
|
{
|
||||||
|
string 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());
|
||||||
|
|
||||||
|
_fileSystem.ImportTickets(nsp);
|
||||||
|
|
||||||
|
foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
|
||||||
|
{
|
||||||
|
nsp.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
Nca nca = new Nca(_fileSystem.KeySet, ncaFile.AsStorage());
|
||||||
|
|
||||||
|
if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != mainNca.Header.TitleId.ToString("x16"))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nca.Header.ContentType == NcaContentType.Program)
|
||||||
|
{
|
||||||
|
patchNca = nca;
|
||||||
|
}
|
||||||
|
else if (nca.Header.ContentType == NcaContentType.Control)
|
||||||
|
{
|
||||||
|
controlNca = nca;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (patchNca == null)
|
||||||
|
{
|
||||||
|
if (mainNca.CanOpenSection(NcaSectionType.Data))
|
||||||
|
{
|
||||||
|
dataStorage = mainNca.OpenStorage(NcaSectionType.Data, FsIntegrityCheckLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainNca.CanOpenSection(NcaSectionType.Code))
|
||||||
|
{
|
||||||
|
codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, FsIntegrityCheckLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (patchNca.CanOpenSection(NcaSectionType.Data))
|
||||||
|
{
|
||||||
|
dataStorage = mainNca.OpenStorageWithPatch(patchNca, NcaSectionType.Data, FsIntegrityCheckLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (patchNca.CanOpenSection(NcaSectionType.Code))
|
||||||
|
{
|
||||||
|
codeFs = mainNca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, FsIntegrityCheckLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codeFs == null)
|
||||||
|
{
|
||||||
|
Logger.PrintError(LogClass.Loader, "No ExeFS found in NCA");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataStorage == null)
|
||||||
|
{
|
||||||
|
Logger.PrintWarning(LogClass.Loader, "No RomFS found in NCA");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_fileSystem.SetRomFs(dataStorage.AsStream(FileAccess.Read));
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadExeFs(codeFs, out Npdm metaData);
|
||||||
|
|
||||||
|
TitleId = metaData.Aci0.TitleId;
|
||||||
|
TitleIs64Bit = metaData.Is64Bit;
|
||||||
|
|
||||||
|
if (controlNca != null)
|
||||||
|
{
|
||||||
|
ReadControlData(controlNca);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ControlData.ByteSpan.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TitleId != 0)
|
||||||
|
{
|
||||||
|
EnsureSaveData(new TitleId(TitleId));
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.PrintInfo(LogClass.Loader, $"Application Loaded: {TitleName} v{TitleVersionString} [{TitleIdText}] [{(TitleIs64Bit ? "64-bit" : "32-bit")}]");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReadControlData(Nca controlNca)
|
||||||
|
{
|
||||||
|
IFileSystem controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, FsIntegrityCheckLevel);
|
||||||
|
|
||||||
|
Result result = controlFs.OpenFile(out IFile controlFile, "/control.nacp".ToU8Span(), OpenMode.Read);
|
||||||
|
|
||||||
|
if (result.IsSuccess())
|
||||||
|
{
|
||||||
|
result = controlFile.Read(out long bytesRead, 0, ControlData.ByteSpan, ReadOption.None);
|
||||||
|
|
||||||
|
if (result.IsSuccess() && bytesRead == ControlData.ByteSpan.Length)
|
||||||
|
{
|
||||||
|
TitleName = ControlData.Value
|
||||||
|
.Titles[(int)_device.System.State.DesiredTitleLanguage].Name.ToString();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(TitleName))
|
||||||
|
{
|
||||||
|
TitleName = ControlData.Value.Titles.ToArray()
|
||||||
|
.FirstOrDefault(x => x.Name[0] != 0).Name.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
TitleVersionString = ControlData.Value.DisplayVersion.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ControlData.ByteSpan.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadExeFs(IFileSystem codeFs, out Npdm metaData)
|
||||||
|
{
|
||||||
|
Result result = codeFs.OpenFile(out IFile npdmFile, "/main.npdm".ToU8Span(), OpenMode.Read);
|
||||||
|
|
||||||
|
if (ResultFs.PathNotFound.Includes(result))
|
||||||
|
{
|
||||||
|
Logger.PrintWarning(LogClass.Loader, "NPDM file not found, using default values!");
|
||||||
|
|
||||||
|
metaData = GetDefaultNpdm();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
metaData = new Npdm(npdmFile.AsStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
List<IExecutable> nsos = new List<IExecutable>();
|
||||||
|
|
||||||
|
void LoadNso(string filename)
|
||||||
|
{
|
||||||
|
foreach (DirectoryEntryEx file in codeFs.EnumerateEntries("/", $"{filename}*"))
|
||||||
|
{
|
||||||
|
if (Path.GetExtension(file.Name) != string.Empty)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.PrintInfo(LogClass.Loader, $"Loading {file.Name}...");
|
||||||
|
|
||||||
|
codeFs.OpenFile(out IFile nsoFile, file.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
NsoExecutable nso = new NsoExecutable(nsoFile.AsStorage());
|
||||||
|
|
||||||
|
nsos.Add(nso);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TitleId = metaData.Aci0.TitleId;
|
||||||
|
TitleIs64Bit = metaData.Is64Bit;
|
||||||
|
|
||||||
|
LoadNso("rtld");
|
||||||
|
LoadNso("main");
|
||||||
|
LoadNso("subsdk");
|
||||||
|
LoadNso("sdk");
|
||||||
|
|
||||||
|
_contentManager.LoadEntries(_device);
|
||||||
|
|
||||||
|
ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, executables: nsos.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadProgram(string filePath)
|
||||||
|
{
|
||||||
|
Npdm metaData = GetDefaultNpdm();
|
||||||
|
|
||||||
|
bool isNro = Path.GetExtension(filePath).ToLower() == ".nro";
|
||||||
|
|
||||||
|
IExecutable nro;
|
||||||
|
|
||||||
|
if (isNro)
|
||||||
|
{
|
||||||
|
FileStream input = new FileStream(filePath, FileMode.Open);
|
||||||
|
NroExecutable obj = new NroExecutable(input);
|
||||||
|
nro = 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)
|
||||||
|
{
|
||||||
|
_fileSystem.SetRomFs(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;
|
||||||
|
|
||||||
|
metaData.TitleName = nacp.Titles[(int)_device.System.State.DesiredTitleLanguage].Name.ToString();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(metaData.TitleName))
|
||||||
|
{
|
||||||
|
metaData.TitleName = nacp.Titles.ToArray().FirstOrDefault(x => x.Name[0] != 0).Name.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nacp.PresenceGroupId != 0)
|
||||||
|
{
|
||||||
|
metaData.Aci0.TitleId = nacp.PresenceGroupId;
|
||||||
|
}
|
||||||
|
else if (nacp.SaveDataOwnerId.Value != 0)
|
||||||
|
{
|
||||||
|
metaData.Aci0.TitleId = nacp.SaveDataOwnerId.Value;
|
||||||
|
}
|
||||||
|
else if (nacp.AddOnContentBaseId != 0)
|
||||||
|
{
|
||||||
|
metaData.Aci0.TitleId = nacp.AddOnContentBaseId - 0x1000;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
metaData.Aci0.TitleId = 0000000000000000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.PrintWarning(LogClass.Loader, $"Unsupported ASET header version found \"{asetVersion}\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nro = new NsoExecutable(new LocalStorage(filePath, FileAccess.Read));
|
||||||
|
}
|
||||||
|
|
||||||
|
_contentManager.LoadEntries(_device);
|
||||||
|
|
||||||
|
TitleName = metaData.TitleName;
|
||||||
|
TitleId = metaData.Aci0.TitleId;
|
||||||
|
TitleIs64Bit = metaData.Is64Bit;
|
||||||
|
|
||||||
|
ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, executables: nro);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Npdm GetDefaultNpdm()
|
||||||
|
{
|
||||||
|
Assembly asm = Assembly.GetCallingAssembly();
|
||||||
|
|
||||||
|
using (Stream npdmStream = asm.GetManifestResourceStream("Ryujinx.HLE.Homebrew.npdm"))
|
||||||
|
{
|
||||||
|
return new Npdm(npdmStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result EnsureSaveData(TitleId titleId)
|
||||||
|
{
|
||||||
|
Logger.PrintInfo(LogClass.Application, "Ensuring required savedata exists.");
|
||||||
|
|
||||||
|
Uid user = _device.System.State.Account.LastOpenedUser.UserId.ToLibHacUid();
|
||||||
|
|
||||||
|
ref ApplicationControlProperty control = ref ControlData.Value;
|
||||||
|
|
||||||
|
if (Util.IsEmpty(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;
|
||||||
|
|
||||||
|
Logger.PrintWarning(LogClass.Application,
|
||||||
|
"No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
|
||||||
|
}
|
||||||
|
|
||||||
|
FileSystemClient fs = _fileSystem.FsClient;
|
||||||
|
|
||||||
|
Result rc = fs.EnsureApplicationCacheStorage(out _, titleId, ref control);
|
||||||
|
|
||||||
|
if (rc.IsFailure())
|
||||||
|
{
|
||||||
|
Logger.PrintError(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {rc.ToStringWithName()}");
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = EnsureApplicationSaveData(fs, out _, titleId, ref control, ref user);
|
||||||
|
|
||||||
|
if (rc.IsFailure())
|
||||||
|
{
|
||||||
|
Logger.PrintError(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {rc.ToStringWithName()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,8 @@
|
|||||||
using LibHac;
|
using LibHac;
|
||||||
using LibHac.Account;
|
|
||||||
using LibHac.Bcat;
|
using LibHac.Bcat;
|
||||||
using LibHac.Common;
|
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.FsSystem;
|
using LibHac.FsSystem;
|
||||||
using LibHac.FsSystem.NcaUtils;
|
|
||||||
using LibHac.Ncm;
|
|
||||||
using LibHac.Ns;
|
|
||||||
using LibHac.Spl;
|
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Configuration;
|
|
||||||
using Ryujinx.Common.Logging;
|
|
||||||
using Ryujinx.Configuration;
|
using Ryujinx.Configuration;
|
||||||
using Ryujinx.HLE.FileSystem.Content;
|
using Ryujinx.HLE.FileSystem.Content;
|
||||||
using Ryujinx.HLE.HOS.Font;
|
using Ryujinx.HLE.HOS.Font;
|
||||||
@ -30,20 +22,14 @@ using Ryujinx.HLE.HOS.Services.SurfaceFlinger;
|
|||||||
using Ryujinx.HLE.HOS.Services.Time.Clock;
|
using Ryujinx.HLE.HOS.Services.Time.Clock;
|
||||||
using Ryujinx.HLE.HOS.SystemState;
|
using Ryujinx.HLE.HOS.SystemState;
|
||||||
using Ryujinx.HLE.Loaders.Executables;
|
using Ryujinx.HLE.Loaders.Executables;
|
||||||
using Ryujinx.HLE.Loaders.Npdm;
|
|
||||||
using Ryujinx.HLE.Utilities;
|
using Ryujinx.HLE.Utilities;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
using static LibHac.Fs.ApplicationSaveDataManagement;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS
|
namespace Ryujinx.HLE.HOS
|
||||||
{
|
{
|
||||||
using TimeServiceManager = Services.Time.TimeManager;
|
using TimeServiceManager = Services.Time.TimeManager;
|
||||||
using JsonHelper = Common.Utilities.JsonHelper;
|
|
||||||
|
|
||||||
public class Horizon : IDisposable
|
public class Horizon : IDisposable
|
||||||
{
|
{
|
||||||
@ -80,16 +66,6 @@ namespace Ryujinx.HLE.HOS
|
|||||||
#pragma warning restore CS0649
|
#pragma warning restore CS0649
|
||||||
private bool _isDisposed;
|
private bool _isDisposed;
|
||||||
|
|
||||||
public BlitStruct<ApplicationControlProperty> ControlData { get; set; }
|
|
||||||
|
|
||||||
public string TitleName { get; private set; }
|
|
||||||
|
|
||||||
public ulong TitleId { get; private set; }
|
|
||||||
public string TitleIdText => TitleId.ToString("x16");
|
|
||||||
|
|
||||||
public string TitleVersionString { get; private set; }
|
|
||||||
|
|
||||||
public bool TitleIs64Bit { get; private set; }
|
|
||||||
|
|
||||||
public IntegrityCheckLevel FsIntegrityCheckLevel { get; set; }
|
public IntegrityCheckLevel FsIntegrityCheckLevel { get; set; }
|
||||||
|
|
||||||
@ -104,8 +80,6 @@ namespace Ryujinx.HLE.HOS
|
|||||||
|
|
||||||
public Horizon(Switch device, ContentManager contentManager)
|
public Horizon(Switch device, ContentManager contentManager)
|
||||||
{
|
{
|
||||||
ControlData = new BlitStruct<ApplicationControlProperty>(1);
|
|
||||||
|
|
||||||
KernelContext = new KernelContext(device, device.Memory);
|
KernelContext = new KernelContext(device, device.Memory);
|
||||||
|
|
||||||
Device = device;
|
Device = device;
|
||||||
@ -207,6 +181,13 @@ namespace Ryujinx.HLE.HOS
|
|||||||
InitLibHacHorizon();
|
InitLibHacHorizon();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void LoadKip(string kipFile)
|
||||||
|
{
|
||||||
|
using IStorage fs = new LocalStorage(kipFile, FileAccess.Read);
|
||||||
|
|
||||||
|
ProgramLoader.LoadKip(KernelContext, new KipExecutable(fs));
|
||||||
|
}
|
||||||
|
|
||||||
private void InitLibHacHorizon()
|
private void InitLibHacHorizon()
|
||||||
{
|
{
|
||||||
LibHac.Horizon horizon = new LibHac.Horizon(null, Device.FileSystem.FsServer);
|
LibHac.Horizon horizon = new LibHac.Horizon(null, Device.FileSystem.FsServer);
|
||||||
@ -233,537 +214,6 @@ namespace Ryujinx.HLE.HOS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadCart(string exeFsDir, string romFsFile = null)
|
|
||||||
{
|
|
||||||
if (romFsFile != null)
|
|
||||||
{
|
|
||||||
Device.FileSystem.LoadRomFs(romFsFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
LocalFileSystem codeFs = new LocalFileSystem(exeFsDir);
|
|
||||||
|
|
||||||
LoadExeFs(codeFs, out _);
|
|
||||||
|
|
||||||
if (TitleId != 0)
|
|
||||||
{
|
|
||||||
EnsureSaveData(new TitleId(TitleId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadXci(string xciFile)
|
|
||||||
{
|
|
||||||
FileStream file = new FileStream(xciFile, FileMode.Open, FileAccess.Read);
|
|
||||||
|
|
||||||
Xci xci = new Xci(KeySet, file.AsStorage());
|
|
||||||
|
|
||||||
(Nca mainNca, Nca patchNca, Nca controlNca) = GetXciGameData(xci);
|
|
||||||
|
|
||||||
if (mainNca == null)
|
|
||||||
{
|
|
||||||
Logger.PrintError(LogClass.Loader, "Unable to load XCI");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentManager.LoadEntries(Device);
|
|
||||||
|
|
||||||
LoadNca(mainNca, patchNca, controlNca);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadKip(string kipFile)
|
|
||||||
{
|
|
||||||
using (IStorage fs = new LocalStorage(kipFile, FileAccess.Read))
|
|
||||||
{
|
|
||||||
ProgramLoader.LoadKip(KernelContext, new KipExecutable(fs));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private (Nca Main, Nca patch, Nca Control) GetXciGameData(Xci xci)
|
|
||||||
{
|
|
||||||
if (!xci.HasPartition(XciPartitionType.Secure))
|
|
||||||
{
|
|
||||||
throw new InvalidDataException("Could not find XCI secure partition");
|
|
||||||
}
|
|
||||||
|
|
||||||
Nca mainNca = null;
|
|
||||||
Nca patchNca = null;
|
|
||||||
Nca controlNca = null;
|
|
||||||
|
|
||||||
XciPartition securePartition = xci.OpenPartition(XciPartitionType.Secure);
|
|
||||||
|
|
||||||
foreach (DirectoryEntryEx ticketEntry in securePartition.EnumerateEntries("/", "*.tik"))
|
|
||||||
{
|
|
||||||
Result result = securePartition.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read);
|
|
||||||
|
|
||||||
if (result.IsSuccess())
|
|
||||||
{
|
|
||||||
Ticket ticket = new Ticket(ticketFile.AsStream());
|
|
||||||
|
|
||||||
KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(KeySet)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (DirectoryEntryEx fileEntry in securePartition.EnumerateEntries("/", "*.nca"))
|
|
||||||
{
|
|
||||||
Result result = securePartition.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read);
|
|
||||||
if (result.IsFailure())
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Nca nca = new Nca(KeySet, ncaFile.AsStorage());
|
|
||||||
|
|
||||||
if (nca.Header.ContentType == NcaContentType.Program)
|
|
||||||
{
|
|
||||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
|
||||||
|
|
||||||
if (nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
|
||||||
{
|
|
||||||
patchNca = nca;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mainNca = nca;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (nca.Header.ContentType == NcaContentType.Control)
|
|
||||||
{
|
|
||||||
controlNca = nca;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mainNca == null)
|
|
||||||
{
|
|
||||||
Logger.PrintError(LogClass.Loader, "Could not find an Application NCA in the provided XCI file");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (controlNca != null)
|
|
||||||
{
|
|
||||||
ReadControlData(controlNca);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ControlData.ByteSpan.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (mainNca, patchNca, controlNca);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ReadControlData(Nca controlNca)
|
|
||||||
{
|
|
||||||
IFileSystem controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, FsIntegrityCheckLevel);
|
|
||||||
|
|
||||||
Result result = controlFs.OpenFile(out IFile controlFile, "/control.nacp".ToU8Span(), OpenMode.Read);
|
|
||||||
|
|
||||||
if (result.IsSuccess())
|
|
||||||
{
|
|
||||||
result = controlFile.Read(out long bytesRead, 0, ControlData.ByteSpan, ReadOption.None);
|
|
||||||
|
|
||||||
if (result.IsSuccess() && bytesRead == ControlData.ByteSpan.Length)
|
|
||||||
{
|
|
||||||
TitleName = ControlData.Value
|
|
||||||
.Titles[(int) State.DesiredTitleLanguage].Name.ToString();
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(TitleName))
|
|
||||||
{
|
|
||||||
TitleName = ControlData.Value.Titles.ToArray()
|
|
||||||
.FirstOrDefault(x => x.Name[0] != 0).Name.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
TitleVersionString = ControlData.Value.DisplayVersion.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ControlData.ByteSpan.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadNca(string ncaFile)
|
|
||||||
{
|
|
||||||
FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read);
|
|
||||||
|
|
||||||
Nca nca = new Nca(KeySet, file.AsStorage(false));
|
|
||||||
|
|
||||||
LoadNca(nca, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadNsp(string nspFile)
|
|
||||||
{
|
|
||||||
FileStream file = new FileStream(nspFile, FileMode.Open, FileAccess.Read);
|
|
||||||
|
|
||||||
PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
|
|
||||||
|
|
||||||
foreach (DirectoryEntryEx ticketEntry in nsp.EnumerateEntries("/", "*.tik"))
|
|
||||||
{
|
|
||||||
Result result = nsp.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read);
|
|
||||||
|
|
||||||
if (result.IsSuccess())
|
|
||||||
{
|
|
||||||
Ticket ticket = new Ticket(ticketFile.AsStream());
|
|
||||||
|
|
||||||
KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(KeySet)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Nca mainNca = null;
|
|
||||||
Nca patchNca = null;
|
|
||||||
Nca controlNca = null;
|
|
||||||
|
|
||||||
foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
|
|
||||||
{
|
|
||||||
nsp.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
|
||||||
|
|
||||||
Nca nca = new Nca(KeySet, ncaFile.AsStorage());
|
|
||||||
|
|
||||||
if (nca.Header.ContentType == NcaContentType.Program)
|
|
||||||
{
|
|
||||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
|
||||||
|
|
||||||
if (nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
|
||||||
{
|
|
||||||
patchNca = nca;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mainNca = nca;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (nca.Header.ContentType == NcaContentType.Control)
|
|
||||||
{
|
|
||||||
controlNca = nca;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mainNca != null)
|
|
||||||
{
|
|
||||||
LoadNca(mainNca, patchNca, controlNca);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is not a normal NSP, it's actually a ExeFS as a NSP
|
|
||||||
LoadExeFs(nsp, out _);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadNca(Nca mainNca, Nca patchNca, Nca controlNca)
|
|
||||||
{
|
|
||||||
if (mainNca.Header.ContentType != NcaContentType.Program)
|
|
||||||
{
|
|
||||||
Logger.PrintError(LogClass.Loader, "Selected NCA is not a \"Program\" NCA");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IStorage dataStorage = null;
|
|
||||||
IFileSystem codeFs = null;
|
|
||||||
|
|
||||||
string titleUpdateMetadataPath = System.IO.Path.Combine(Device.FileSystem.GetBasePath(), "games", mainNca.Header.TitleId.ToString("x16"), "updates.json");
|
|
||||||
|
|
||||||
if (File.Exists(titleUpdateMetadataPath))
|
|
||||||
{
|
|
||||||
string 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());
|
|
||||||
|
|
||||||
foreach (DirectoryEntryEx ticketEntry in nsp.EnumerateEntries("/", "*.tik"))
|
|
||||||
{
|
|
||||||
Result result = nsp.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read);
|
|
||||||
|
|
||||||
if (result.IsSuccess())
|
|
||||||
{
|
|
||||||
Ticket ticket = new Ticket(ticketFile.AsStream());
|
|
||||||
|
|
||||||
KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(KeySet)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
|
|
||||||
{
|
|
||||||
nsp.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
|
||||||
|
|
||||||
Nca nca = new Nca(KeySet, ncaFile.AsStorage());
|
|
||||||
|
|
||||||
if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != mainNca.Header.TitleId.ToString("x16"))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nca.Header.ContentType == NcaContentType.Program)
|
|
||||||
{
|
|
||||||
patchNca = nca;
|
|
||||||
}
|
|
||||||
else if (nca.Header.ContentType == NcaContentType.Control)
|
|
||||||
{
|
|
||||||
controlNca = nca;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (patchNca == null)
|
|
||||||
{
|
|
||||||
if (mainNca.CanOpenSection(NcaSectionType.Data))
|
|
||||||
{
|
|
||||||
dataStorage = mainNca.OpenStorage(NcaSectionType.Data, FsIntegrityCheckLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mainNca.CanOpenSection(NcaSectionType.Code))
|
|
||||||
{
|
|
||||||
codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, FsIntegrityCheckLevel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (patchNca.CanOpenSection(NcaSectionType.Data))
|
|
||||||
{
|
|
||||||
dataStorage = mainNca.OpenStorageWithPatch(patchNca, NcaSectionType.Data, FsIntegrityCheckLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (patchNca.CanOpenSection(NcaSectionType.Code))
|
|
||||||
{
|
|
||||||
codeFs = mainNca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, FsIntegrityCheckLevel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (codeFs == null)
|
|
||||||
{
|
|
||||||
Logger.PrintError(LogClass.Loader, "No ExeFS found in NCA");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dataStorage == null)
|
|
||||||
{
|
|
||||||
Logger.PrintWarning(LogClass.Loader, "No RomFS found in NCA");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Device.FileSystem.SetRomFs(dataStorage.AsStream(FileAccess.Read));
|
|
||||||
}
|
|
||||||
|
|
||||||
LoadExeFs(codeFs, out Npdm metaData);
|
|
||||||
|
|
||||||
TitleId = metaData.Aci0.TitleId;
|
|
||||||
TitleIs64Bit = metaData.Is64Bit;
|
|
||||||
|
|
||||||
if (controlNca != null)
|
|
||||||
{
|
|
||||||
ReadControlData(controlNca);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ControlData.ByteSpan.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TitleId != 0)
|
|
||||||
{
|
|
||||||
EnsureSaveData(new TitleId(TitleId));
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.PrintInfo(LogClass.Loader, $"Application Loaded: {TitleName} v{TitleVersionString} [{TitleIdText}] [{(TitleIs64Bit ? "64-bit" : "32-bit")}]");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadExeFs(IFileSystem codeFs, out Npdm metaData)
|
|
||||||
{
|
|
||||||
Result result = codeFs.OpenFile(out IFile npdmFile, "/main.npdm".ToU8Span(), OpenMode.Read);
|
|
||||||
|
|
||||||
if (ResultFs.PathNotFound.Includes(result))
|
|
||||||
{
|
|
||||||
Logger.PrintWarning(LogClass.Loader, "NPDM file not found, using default values!");
|
|
||||||
|
|
||||||
metaData = GetDefaultNpdm();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
metaData = new Npdm(npdmFile.AsStream());
|
|
||||||
}
|
|
||||||
|
|
||||||
List<IExecutable> staticObjects = new List<IExecutable>();
|
|
||||||
|
|
||||||
void LoadNso(string filename)
|
|
||||||
{
|
|
||||||
foreach (DirectoryEntryEx file in codeFs.EnumerateEntries("/", $"{filename}*"))
|
|
||||||
{
|
|
||||||
if (Path.GetExtension(file.Name) != string.Empty)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.PrintInfo(LogClass.Loader, $"Loading {file.Name}...");
|
|
||||||
|
|
||||||
codeFs.OpenFile(out IFile nsoFile, file.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
|
||||||
|
|
||||||
NsoExecutable staticObject = new NsoExecutable(nsoFile.AsStorage());
|
|
||||||
|
|
||||||
staticObjects.Add(staticObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TitleId = metaData.Aci0.TitleId;
|
|
||||||
TitleIs64Bit = metaData.Is64Bit;
|
|
||||||
|
|
||||||
LoadNso("rtld");
|
|
||||||
LoadNso("main");
|
|
||||||
LoadNso("subsdk");
|
|
||||||
LoadNso("sdk");
|
|
||||||
|
|
||||||
ContentManager.LoadEntries(Device);
|
|
||||||
|
|
||||||
ProgramLoader.LoadNsos(KernelContext, metaData, staticObjects.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadProgram(string filePath)
|
|
||||||
{
|
|
||||||
Npdm metaData = GetDefaultNpdm();
|
|
||||||
|
|
||||||
bool isNro = Path.GetExtension(filePath).ToLower() == ".nro";
|
|
||||||
|
|
||||||
|
|
||||||
IExecutable staticObject;
|
|
||||||
|
|
||||||
if (isNro)
|
|
||||||
{
|
|
||||||
FileStream input = new FileStream(filePath, FileMode.Open);
|
|
||||||
NroExecutable obj = new NroExecutable(input);
|
|
||||||
staticObject = 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)
|
|
||||||
{
|
|
||||||
Device.FileSystem.SetRomFs(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;
|
|
||||||
|
|
||||||
metaData.TitleName = nacp.Titles[(int)State.DesiredTitleLanguage].Name.ToString();
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(metaData.TitleName))
|
|
||||||
{
|
|
||||||
metaData.TitleName = nacp.Titles.ToArray().FirstOrDefault(x => x.Name[0] != 0).Name.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nacp.PresenceGroupId != 0)
|
|
||||||
{
|
|
||||||
metaData.Aci0.TitleId = nacp.PresenceGroupId;
|
|
||||||
}
|
|
||||||
else if (nacp.SaveDataOwnerId.Value != 0)
|
|
||||||
{
|
|
||||||
metaData.Aci0.TitleId = nacp.SaveDataOwnerId.Value;
|
|
||||||
}
|
|
||||||
else if (nacp.AddOnContentBaseId != 0)
|
|
||||||
{
|
|
||||||
metaData.Aci0.TitleId = nacp.AddOnContentBaseId - 0x1000;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
metaData.Aci0.TitleId = 0000000000000000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.PrintWarning(LogClass.Loader, $"Unsupported ASET header version found \"{asetVersion}\"");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
staticObject = new NsoExecutable(new LocalStorage(filePath, FileAccess.Read));
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentManager.LoadEntries(Device);
|
|
||||||
|
|
||||||
TitleName = metaData.TitleName;
|
|
||||||
TitleId = metaData.Aci0.TitleId;
|
|
||||||
TitleIs64Bit = metaData.Is64Bit;
|
|
||||||
|
|
||||||
ProgramLoader.LoadNsos(KernelContext, metaData, new IExecutable[] { staticObject });
|
|
||||||
}
|
|
||||||
|
|
||||||
private Npdm GetDefaultNpdm()
|
|
||||||
{
|
|
||||||
Assembly asm = Assembly.GetCallingAssembly();
|
|
||||||
|
|
||||||
using (Stream npdmStream = asm.GetManifestResourceStream("Ryujinx.HLE.Homebrew.npdm"))
|
|
||||||
{
|
|
||||||
return new Npdm(npdmStream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Result EnsureSaveData(TitleId titleId)
|
|
||||||
{
|
|
||||||
Logger.PrintInfo(LogClass.Application, "Ensuring required savedata exists.");
|
|
||||||
|
|
||||||
Uid user = State.Account.LastOpenedUser.UserId.ToLibHacUid();
|
|
||||||
|
|
||||||
ref ApplicationControlProperty control = ref ControlData.Value;
|
|
||||||
|
|
||||||
if (LibHac.Util.IsEmpty(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;
|
|
||||||
|
|
||||||
Logger.PrintWarning(LogClass.Application,
|
|
||||||
"No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
|
|
||||||
}
|
|
||||||
|
|
||||||
FileSystemClient fs = Device.FileSystem.FsClient;
|
|
||||||
|
|
||||||
Result rc = fs.EnsureApplicationCacheStorage(out _, titleId, ref control);
|
|
||||||
|
|
||||||
if (rc.IsFailure())
|
|
||||||
{
|
|
||||||
Logger.PrintError(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {rc.ToStringWithName()}");
|
|
||||||
}
|
|
||||||
|
|
||||||
rc = EnsureApplicationSaveData(fs, out _, titleId, ref control, ref user);
|
|
||||||
|
|
||||||
if (rc.IsFailure())
|
|
||||||
{
|
|
||||||
Logger.PrintError(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {rc.ToStringWithName()}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SignalDisplayResolutionChange()
|
public void SignalDisplayResolutionChange()
|
||||||
{
|
{
|
||||||
DisplayResolutionChangeEvent.ReadableEvent.Signal();
|
DisplayResolutionChangeEvent.ReadableEvent.Signal();
|
||||||
|
@ -121,21 +121,21 @@ namespace Ryujinx.HLE.HOS
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static bool LoadNsos(
|
public static bool LoadNsos(
|
||||||
KernelContext context,
|
KernelContext context,
|
||||||
Npdm metaData,
|
Npdm metaData,
|
||||||
IExecutable[] nsos,
|
byte[] arguments = null,
|
||||||
byte[] arguments = null)
|
params IExecutable[] executables)
|
||||||
{
|
{
|
||||||
ulong argsStart = 0;
|
ulong argsStart = 0;
|
||||||
int argsSize = 0;
|
int argsSize = 0;
|
||||||
ulong codeStart = metaData.Is64Bit ? 0x8000000UL : 0x200000UL;
|
ulong codeStart = metaData.Is64Bit ? 0x8000000UL : 0x200000UL;
|
||||||
int codeSize = 0;
|
int codeSize = 0;
|
||||||
|
|
||||||
ulong[] nsoBase = new ulong[nsos.Length];
|
ulong[] nsoBase = new ulong[executables.Length];
|
||||||
|
|
||||||
for (int index = 0; index < nsos.Length; index++)
|
for (int index = 0; index < executables.Length; index++)
|
||||||
{
|
{
|
||||||
IExecutable staticObject = nsos[index];
|
IExecutable staticObject = executables[index];
|
||||||
|
|
||||||
int textEnd = staticObject.TextOffset + staticObject.Text.Length;
|
int textEnd = staticObject.TextOffset + staticObject.Text.Length;
|
||||||
int roEnd = staticObject.RoOffset + staticObject.Ro.Length;
|
int roEnd = staticObject.RoOffset + staticObject.Ro.Length;
|
||||||
@ -226,11 +226,11 @@ namespace Ryujinx.HLE.HOS
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int index = 0; index < nsos.Length; index++)
|
for (int index = 0; index < executables.Length; index++)
|
||||||
{
|
{
|
||||||
Logger.PrintInfo(LogClass.Loader, $"Loading image {index} at 0x{nsoBase[index]:x16}...");
|
Logger.PrintInfo(LogClass.Loader, $"Loading image {index} at 0x{nsoBase[index]:x16}...");
|
||||||
|
|
||||||
result = LoadIntoMemory(process, nsos[index], nsoBase[index]);
|
result = LoadIntoMemory(process, executables[index], nsoBase[index]);
|
||||||
|
|
||||||
if (result != KernelResult.Success)
|
if (result != KernelResult.Success)
|
||||||
{
|
{
|
||||||
|
@ -289,7 +289,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
|||||||
// Account actually calls nn::arp::detail::IReader::GetApplicationControlProperty() with the current PID and store the result (NACP File) internally.
|
// 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.
|
// But since we use LibHac and we load one Application at a time, it's not necessary.
|
||||||
|
|
||||||
context.ResponseData.Write(context.Device.System.ControlData.Value.UserAccountSwitchLock);
|
context.ResponseData.Write(context.Device.Application.ControlData.Value.UserAccountSwitchLock);
|
||||||
|
|
||||||
Logger.PrintStub(LogClass.ServiceAcc);
|
Logger.PrintStub(LogClass.ServiceAcc);
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
|||||||
Uid userId = context.RequestData.ReadStruct<AccountUid>().ToLibHacUid();
|
Uid userId = context.RequestData.ReadStruct<AccountUid>().ToLibHacUid();
|
||||||
TitleId titleId = new TitleId(context.Process.TitleId);
|
TitleId titleId = new TitleId(context.Process.TitleId);
|
||||||
|
|
||||||
BlitStruct<ApplicationControlProperty> controlHolder = context.Device.System.ControlData;
|
BlitStruct<ApplicationControlProperty> controlHolder = context.Device.Application.ControlData;
|
||||||
|
|
||||||
ref ApplicationControlProperty control = ref controlHolder.Value;
|
ref ApplicationControlProperty control = ref controlHolder.Value;
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp
|
|||||||
|
|
||||||
return new ApplicationLaunchProperty
|
return new ApplicationLaunchProperty
|
||||||
{
|
{
|
||||||
TitleId = context.Device.System.TitleId,
|
TitleId = context.Device.Application.TitleId,
|
||||||
Version = 0x00,
|
Version = 0x00,
|
||||||
BaseGameStorageId = (byte)StorageId.NandSystem,
|
BaseGameStorageId = (byte)StorageId.NandSystem,
|
||||||
UpdateGameStorageId = (byte)StorageId.None
|
UpdateGameStorageId = (byte)StorageId.None
|
||||||
|
@ -22,7 +22,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp
|
|||||||
launchProperty = new LibHac.Arp.ApplicationLaunchProperty();
|
launchProperty = new LibHac.Arp.ApplicationLaunchProperty();
|
||||||
|
|
||||||
launchProperty.BaseStorageId = StorageId.BuiltInUser;
|
launchProperty.BaseStorageId = StorageId.BuiltInUser;
|
||||||
launchProperty.ApplicationId = new ApplicationId(System.TitleId);
|
launchProperty.ApplicationId = new ApplicationId(System.Device.Application.TitleId);
|
||||||
|
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
long position = context.Request.ReceiveBuff[0].Position;
|
long position = context.Request.ReceiveBuff[0].Position;
|
||||||
|
|
||||||
byte[] nacpData = context.Device.System.ControlData.ByteSpan.ToArray();
|
byte[] nacpData = context.Device.Application.ControlData.ByteSpan.ToArray();
|
||||||
|
|
||||||
context.Memory.Write((ulong)position, nacpData);
|
context.Memory.Write((ulong)position, nacpData);
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
long position = context.Request.ReceiveBuff[0].Position;
|
long position = context.Request.ReceiveBuff[0].Position;
|
||||||
|
|
||||||
byte[] nacpData = context.Device.System.ControlData.ByteSpan.ToArray();
|
byte[] nacpData = context.Device.Application.ControlData.ByteSpan.ToArray();
|
||||||
|
|
||||||
context.Memory.Write((ulong)position, nacpData);
|
context.Memory.Write((ulong)position, nacpData);
|
||||||
|
|
||||||
|
@ -43,8 +43,8 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
|
|||||||
_titleId = titleId;
|
_titleId = titleId;
|
||||||
|
|
||||||
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented, if it return ResultCode.Success we assign fields.
|
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented, if it return ResultCode.Success we assign fields.
|
||||||
_ratingAge = Array.ConvertAll(context.Device.System.ControlData.Value.RatingAge.ToArray(), Convert.ToInt32);
|
_ratingAge = Array.ConvertAll(context.Device.Application.ControlData.Value.RatingAge.ToArray(), Convert.ToInt32);
|
||||||
_freeCommunicationEnabled = context.Device.System.ControlData.Value.ParentalControl == LibHac.Ns.ParentalControlFlagValue.FreeCommunication;
|
_freeCommunicationEnabled = context.Device.Application.ControlData.Value.ParentalControl == LibHac.Ns.ParentalControlFlagValue.FreeCommunication;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)context.Device.System.ControlData.Value.PlayLogQueryCapability;
|
PlayLogQueryCapability queryCapability = (PlayLogQueryCapability)context.Device.Application.ControlData.Value.PlayLogQueryCapability;
|
||||||
|
|
||||||
List<ulong> titleIds = new List<ulong>();
|
List<ulong> titleIds = new List<ulong>();
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService
|
|||||||
// Check if input title ids are in the whitelist.
|
// Check if input title ids are in the whitelist.
|
||||||
foreach (ulong titleId in titleIds)
|
foreach (ulong titleId in titleIds)
|
||||||
{
|
{
|
||||||
if (!context.Device.System.ControlData.Value.PlayLogQueryableApplicationId.Contains(titleId))
|
if (!context.Device.Application.ControlData.Value.PlayLogQueryableApplicationId.Contains(titleId))
|
||||||
{
|
{
|
||||||
return (ResultCode)Am.ResultCode.ObjectInvalid;
|
return (ResultCode)Am.ResultCode.ObjectInvalid;
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,8 @@ namespace Ryujinx.HLE
|
|||||||
|
|
||||||
public Horizon System { get; private set; }
|
public Horizon System { get; private set; }
|
||||||
|
|
||||||
|
public ApplicationLoader Application { get; }
|
||||||
|
|
||||||
public PerformanceStatistics Statistics { get; private set; }
|
public PerformanceStatistics Statistics { get; private set; }
|
||||||
|
|
||||||
public Hid Hid { get; private set; }
|
public Hid Hid { get; private set; }
|
||||||
@ -59,6 +61,8 @@ namespace Ryujinx.HLE
|
|||||||
|
|
||||||
Hid = new Hid(this, System.HidBaseAddress);
|
Hid = new Hid(this, System.HidBaseAddress);
|
||||||
Hid.InitDevices();
|
Hid.InitDevices();
|
||||||
|
|
||||||
|
Application = new ApplicationLoader(this, fileSystem, contentManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
@ -76,14 +80,14 @@ namespace Ryujinx.HLE
|
|||||||
System.EnableMultiCoreScheduling();
|
System.EnableMultiCoreScheduling();
|
||||||
}
|
}
|
||||||
|
|
||||||
System.FsIntegrityCheckLevel = GetIntigrityCheckLevel();
|
System.FsIntegrityCheckLevel = GetIntegrityCheckLevel();
|
||||||
|
|
||||||
System.GlobalAccessLogMode = ConfigurationState.Instance.System.FsGlobalAccessLogMode;
|
System.GlobalAccessLogMode = ConfigurationState.Instance.System.FsGlobalAccessLogMode;
|
||||||
|
|
||||||
ServiceConfiguration.IgnoreMissingServices = ConfigurationState.Instance.System.IgnoreMissingServices;
|
ServiceConfiguration.IgnoreMissingServices = ConfigurationState.Instance.System.IgnoreMissingServices;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IntegrityCheckLevel GetIntigrityCheckLevel()
|
public static IntegrityCheckLevel GetIntegrityCheckLevel()
|
||||||
{
|
{
|
||||||
return ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
return ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||||
? IntegrityCheckLevel.ErrorOnInvalid
|
? IntegrityCheckLevel.ErrorOnInvalid
|
||||||
@ -92,27 +96,27 @@ namespace Ryujinx.HLE
|
|||||||
|
|
||||||
public void LoadCart(string exeFsDir, string romFsFile = null)
|
public void LoadCart(string exeFsDir, string romFsFile = null)
|
||||||
{
|
{
|
||||||
System.LoadCart(exeFsDir, romFsFile);
|
Application.LoadCart(exeFsDir, romFsFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadXci(string xciFile)
|
public void LoadXci(string xciFile)
|
||||||
{
|
{
|
||||||
System.LoadXci(xciFile);
|
Application.LoadXci(xciFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadNca(string ncaFile)
|
public void LoadNca(string ncaFile)
|
||||||
{
|
{
|
||||||
System.LoadNca(ncaFile);
|
Application.LoadNca(ncaFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadNsp(string nspFile)
|
public void LoadNsp(string nspFile)
|
||||||
{
|
{
|
||||||
System.LoadNsp(nspFile);
|
Application.LoadNsp(nspFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadProgram(string fileName)
|
public void LoadProgram(string fileName)
|
||||||
{
|
{
|
||||||
System.LoadProgram(fileName);
|
Application.LoadProgram(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool WaitFifo()
|
public bool WaitFifo()
|
||||||
|
@ -474,17 +474,7 @@ namespace Ryujinx.Ui
|
|||||||
Nca controlNca = null;
|
Nca controlNca = null;
|
||||||
|
|
||||||
// Add keys to key set if needed
|
// Add keys to key set if needed
|
||||||
foreach (DirectoryEntryEx ticketEntry in pfs.EnumerateEntries("/", "*.tik"))
|
_virtualFileSystem.ImportTickets(pfs);
|
||||||
{
|
|
||||||
Result result = pfs.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read);
|
|
||||||
|
|
||||||
if (result.IsSuccess())
|
|
||||||
{
|
|
||||||
Ticket ticket = new Ticket(ticketFile.AsStream());
|
|
||||||
|
|
||||||
_virtualFileSystem.KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(_virtualFileSystem.KeySet)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the Control NCA and store it in variable called controlNca
|
// Find the Control NCA and store it in variable called controlNca
|
||||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||||
@ -661,17 +651,7 @@ namespace Ryujinx.Ui
|
|||||||
{
|
{
|
||||||
PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
|
PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
|
||||||
|
|
||||||
foreach (DirectoryEntryEx ticketEntry in nsp.EnumerateEntries("/", "*.tik"))
|
_virtualFileSystem.ImportTickets(nsp);
|
||||||
{
|
|
||||||
Result result = nsp.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read);
|
|
||||||
|
|
||||||
if (result.IsSuccess())
|
|
||||||
{
|
|
||||||
Ticket ticket = new Ticket(ticketFile.AsStream());
|
|
||||||
|
|
||||||
_virtualFileSystem.KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(_virtualFileSystem.KeySet)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
|
foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
|
||||||
{
|
{
|
||||||
|
@ -180,16 +180,16 @@ namespace Ryujinx.Ui
|
|||||||
{
|
{
|
||||||
parent.Present();
|
parent.Present();
|
||||||
|
|
||||||
string titleNameSection = string.IsNullOrWhiteSpace(_device.System.TitleName) ? string.Empty
|
string titleNameSection = string.IsNullOrWhiteSpace(_device.Application.TitleName) ? string.Empty
|
||||||
: $" - {_device.System.TitleName}";
|
: $" - {_device.Application.TitleName}";
|
||||||
|
|
||||||
string titleVersionSection = string.IsNullOrWhiteSpace(_device.System.TitleVersionString) ? string.Empty
|
string titleVersionSection = string.IsNullOrWhiteSpace(_device.Application.TitleVersionString) ? string.Empty
|
||||||
: $" v{_device.System.TitleVersionString}";
|
: $" v{_device.Application.TitleVersionString}";
|
||||||
|
|
||||||
string titleIdSection = string.IsNullOrWhiteSpace(_device.System.TitleIdText) ? string.Empty
|
string titleIdSection = string.IsNullOrWhiteSpace(_device.Application.TitleIdText) ? string.Empty
|
||||||
: $" ({_device.System.TitleIdText.ToUpper()})";
|
: $" ({_device.Application.TitleIdText.ToUpper()})";
|
||||||
|
|
||||||
string titleArchSection = _device.System.TitleIs64Bit ? " (64-bit)" : " (32-bit)";
|
string titleArchSection = _device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)";
|
||||||
|
|
||||||
parent.Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
|
parent.Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
|
||||||
});
|
});
|
||||||
|
@ -280,17 +280,7 @@ namespace Ryujinx.Ui
|
|||||||
FileStream updateFile = new FileStream(updatePath, FileMode.Open, FileAccess.Read);
|
FileStream updateFile = new FileStream(updatePath, FileMode.Open, FileAccess.Read);
|
||||||
PartitionFileSystem nsp = new PartitionFileSystem(updateFile.AsStorage());
|
PartitionFileSystem nsp = new PartitionFileSystem(updateFile.AsStorage());
|
||||||
|
|
||||||
foreach (DirectoryEntryEx ticketEntry in nsp.EnumerateEntries("/", "*.tik"))
|
_virtualFileSystem.ImportTickets(nsp);
|
||||||
{
|
|
||||||
Result result = nsp.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read);
|
|
||||||
|
|
||||||
if (result.IsSuccess())
|
|
||||||
{
|
|
||||||
Ticket ticket = new Ticket(ticketFile.AsStream());
|
|
||||||
|
|
||||||
_virtualFileSystem.KeySet.ExternalKeySet.Add(new LibHac.Fs.RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(_virtualFileSystem.KeySet)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
|
foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
|
||||||
{
|
{
|
||||||
|
@ -435,9 +435,9 @@ namespace Ryujinx.Ui
|
|||||||
_firmwareInstallFile.Sensitive = false;
|
_firmwareInstallFile.Sensitive = false;
|
||||||
_firmwareInstallDirectory.Sensitive = false;
|
_firmwareInstallDirectory.Sensitive = false;
|
||||||
|
|
||||||
DiscordIntegrationModule.SwitchToPlayingState(device.System.TitleIdText, device.System.TitleName);
|
DiscordIntegrationModule.SwitchToPlayingState(device.Application.TitleIdText, device.Application.TitleName);
|
||||||
|
|
||||||
ApplicationLibrary.LoadAndSaveMetaData(device.System.TitleIdText, appMetadata =>
|
ApplicationLibrary.LoadAndSaveMetaData(device.Application.TitleIdText, appMetadata =>
|
||||||
{
|
{
|
||||||
appMetadata.LastPlayed = DateTime.UtcNow.ToString();
|
appMetadata.LastPlayed = DateTime.UtcNow.ToString();
|
||||||
});
|
});
|
||||||
@ -580,7 +580,7 @@ namespace Ryujinx.Ui
|
|||||||
|
|
||||||
if (device != null)
|
if (device != null)
|
||||||
{
|
{
|
||||||
UpdateGameMetadata(device.System.TitleIdText);
|
UpdateGameMetadata(device.Application.TitleIdText);
|
||||||
|
|
||||||
if (_glWidget != null)
|
if (_glWidget != null)
|
||||||
{
|
{
|
||||||
|
@ -81,17 +81,7 @@ namespace Ryujinx.Ui
|
|||||||
{
|
{
|
||||||
PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
|
PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
|
||||||
|
|
||||||
foreach (DirectoryEntryEx ticketEntry in nsp.EnumerateEntries("/", "*.tik"))
|
_virtualFileSystem.ImportTickets(nsp);
|
||||||
{
|
|
||||||
Result result = nsp.OpenFile(out IFile ticketFile, ticketEntry.FullPath.ToU8Span(), OpenMode.Read);
|
|
||||||
|
|
||||||
if (result.IsSuccess())
|
|
||||||
{
|
|
||||||
Ticket ticket = new Ticket(ticketFile.AsStream());
|
|
||||||
|
|
||||||
_virtualFileSystem.KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(_virtualFileSystem.KeySet)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
|
foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user