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
19 changed files with 609 additions and 641 deletions
|
@ -580,7 +580,7 @@ namespace Ryujinx.HLE.FileSystem.Content
|
|||
|
||||
SystemVersion VerifyAndGetVersionZip(ZipArchive archive)
|
||||
{
|
||||
IntegrityCheckLevel integrityCheckLevel = Switch.GetIntigrityCheckLevel();
|
||||
IntegrityCheckLevel integrityCheckLevel = Switch.GetIntegrityCheckLevel();
|
||||
|
||||
SystemVersion systemVersion = null;
|
||||
|
||||
|
@ -743,7 +743,7 @@ namespace Ryujinx.HLE.FileSystem.Content
|
|||
|
||||
SystemVersion VerifyAndGetVersion(IFileSystem filesystem)
|
||||
{
|
||||
IntegrityCheckLevel integrityCheckLevel = Switch.GetIntigrityCheckLevel();
|
||||
IntegrityCheckLevel integrityCheckLevel = Switch.GetIntegrityCheckLevel();
|
||||
|
||||
SystemVersion systemVersion = null;
|
||||
|
||||
|
@ -874,7 +874,7 @@ namespace Ryujinx.HLE.FileSystem.Content
|
|||
|
||||
public SystemVersion GetCurrentFirmwareVersion()
|
||||
{
|
||||
IntegrityCheckLevel integrityCheckLevel = Switch.GetIntigrityCheckLevel();
|
||||
IntegrityCheckLevel integrityCheckLevel = Switch.GetIntegrityCheckLevel();
|
||||
|
||||
LoadEntries();
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
using LibHac;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.FsService;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Spl;
|
||||
using Ryujinx.HLE.FileSystem.Content;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using System;
|
||||
|
@ -261,6 +263,21 @@ namespace Ryujinx.HLE.FileSystem
|
|||
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()
|
||||
{
|
||||
RomFs?.Dispose();
|
||||
|
@ -283,7 +300,7 @@ namespace Ryujinx.HLE.FileSystem
|
|||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
throw new InvalidOperationException($"VirtualFileSystem can only be instanciated once!");
|
||||
throw new InvalidOperationException($"VirtualFileSystem can only be instantiated once!");
|
||||
}
|
||||
|
||||
_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.Account;
|
||||
using LibHac.Bcat;
|
||||
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;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Configuration;
|
||||
using Ryujinx.HLE.FileSystem.Content;
|
||||
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.SystemState;
|
||||
using Ryujinx.HLE.Loaders.Executables;
|
||||
using Ryujinx.HLE.Loaders.Npdm;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
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 TimeServiceManager = Services.Time.TimeManager;
|
||||
using JsonHelper = Common.Utilities.JsonHelper;
|
||||
|
||||
public class Horizon : IDisposable
|
||||
{
|
||||
|
@ -80,16 +66,6 @@ namespace Ryujinx.HLE.HOS
|
|||
#pragma warning restore CS0649
|
||||
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; }
|
||||
|
||||
|
@ -104,8 +80,6 @@ namespace Ryujinx.HLE.HOS
|
|||
|
||||
public Horizon(Switch device, ContentManager contentManager)
|
||||
{
|
||||
ControlData = new BlitStruct<ApplicationControlProperty>(1);
|
||||
|
||||
KernelContext = new KernelContext(device, device.Memory);
|
||||
|
||||
Device = device;
|
||||
|
@ -207,6 +181,13 @@ namespace Ryujinx.HLE.HOS
|
|||
InitLibHacHorizon();
|
||||
}
|
||||
|
||||
public void LoadKip(string kipFile)
|
||||
{
|
||||
using IStorage fs = new LocalStorage(kipFile, FileAccess.Read);
|
||||
|
||||
ProgramLoader.LoadKip(KernelContext, new KipExecutable(fs));
|
||||
}
|
||||
|
||||
private void InitLibHacHorizon()
|
||||
{
|
||||
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()
|
||||
{
|
||||
DisplayResolutionChangeEvent.ReadableEvent.Signal();
|
||||
|
|
|
@ -121,21 +121,21 @@ namespace Ryujinx.HLE.HOS
|
|||
}
|
||||
|
||||
public static bool LoadNsos(
|
||||
KernelContext context,
|
||||
Npdm metaData,
|
||||
IExecutable[] nsos,
|
||||
byte[] arguments = null)
|
||||
KernelContext context,
|
||||
Npdm metaData,
|
||||
byte[] arguments = null,
|
||||
params IExecutable[] executables)
|
||||
{
|
||||
ulong argsStart = 0;
|
||||
int argsSize = 0;
|
||||
ulong codeStart = metaData.Is64Bit ? 0x8000000UL : 0x200000UL;
|
||||
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 roEnd = staticObject.RoOffset + staticObject.Ro.Length;
|
||||
|
@ -226,11 +226,11 @@ namespace Ryujinx.HLE.HOS
|
|||
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}...");
|
||||
|
||||
result = LoadIntoMemory(process, nsos[index], nsoBase[index]);
|
||||
result = LoadIntoMemory(process, executables[index], nsoBase[index]);
|
||||
|
||||
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.
|
||||
// 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);
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
|||
Uid userId = context.RequestData.ReadStruct<AccountUid>().ToLibHacUid();
|
||||
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;
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp
|
|||
|
||||
return new ApplicationLaunchProperty
|
||||
{
|
||||
TitleId = context.Device.System.TitleId,
|
||||
TitleId = context.Device.Application.TitleId,
|
||||
Version = 0x00,
|
||||
BaseGameStorageId = (byte)StorageId.NandSystem,
|
||||
UpdateGameStorageId = (byte)StorageId.None
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace Ryujinx.HLE.HOS.Services.Arp
|
|||
launchProperty = new LibHac.Arp.ApplicationLaunchProperty();
|
||||
|
||||
launchProperty.BaseStorageId = StorageId.BuiltInUser;
|
||||
launchProperty.ApplicationId = new ApplicationId(System.TitleId);
|
||||
launchProperty.ApplicationId = new ApplicationId(System.Device.Application.TitleId);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
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);
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
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);
|
||||
|
||||
|
|
|
@ -43,8 +43,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.System.ControlData.Value.RatingAge.ToArray(), Convert.ToInt32);
|
||||
_freeCommunicationEnabled = context.Device.System.ControlData.Value.ParentalControl == LibHac.Ns.ParentalControlFlagValue.FreeCommunication;
|
||||
_ratingAge = Array.ConvertAll(context.Device.Application.ControlData.Value.RatingAge.ToArray(), Convert.ToInt32);
|
||||
_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>();
|
||||
|
||||
|
@ -45,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 (!context.Device.System.ControlData.Value.PlayLogQueryableApplicationId.Contains(titleId))
|
||||
if (!context.Device.Application.ControlData.Value.PlayLogQueryableApplicationId.Contains(titleId))
|
||||
{
|
||||
return (ResultCode)Am.ResultCode.ObjectInvalid;
|
||||
}
|
||||
|
|
|
@ -27,6 +27,8 @@ namespace Ryujinx.HLE
|
|||
|
||||
public Horizon System { get; private set; }
|
||||
|
||||
public ApplicationLoader Application { get; }
|
||||
|
||||
public PerformanceStatistics Statistics { get; private set; }
|
||||
|
||||
public Hid Hid { get; private set; }
|
||||
|
@ -59,6 +61,8 @@ namespace Ryujinx.HLE
|
|||
|
||||
Hid = new Hid(this, System.HidBaseAddress);
|
||||
Hid.InitDevices();
|
||||
|
||||
Application = new ApplicationLoader(this, fileSystem, contentManager);
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
|
@ -76,14 +80,14 @@ namespace Ryujinx.HLE
|
|||
System.EnableMultiCoreScheduling();
|
||||
}
|
||||
|
||||
System.FsIntegrityCheckLevel = GetIntigrityCheckLevel();
|
||||
System.FsIntegrityCheckLevel = GetIntegrityCheckLevel();
|
||||
|
||||
System.GlobalAccessLogMode = ConfigurationState.Instance.System.FsGlobalAccessLogMode;
|
||||
|
||||
ServiceConfiguration.IgnoreMissingServices = ConfigurationState.Instance.System.IgnoreMissingServices;
|
||||
}
|
||||
|
||||
public static IntegrityCheckLevel GetIntigrityCheckLevel()
|
||||
public static IntegrityCheckLevel GetIntegrityCheckLevel()
|
||||
{
|
||||
return ConfigurationState.Instance.System.EnableFsIntegrityChecks
|
||||
? IntegrityCheckLevel.ErrorOnInvalid
|
||||
|
@ -92,27 +96,27 @@ namespace Ryujinx.HLE
|
|||
|
||||
public void LoadCart(string exeFsDir, string romFsFile = null)
|
||||
{
|
||||
System.LoadCart(exeFsDir, romFsFile);
|
||||
Application.LoadCart(exeFsDir, romFsFile);
|
||||
}
|
||||
|
||||
public void LoadXci(string xciFile)
|
||||
{
|
||||
System.LoadXci(xciFile);
|
||||
Application.LoadXci(xciFile);
|
||||
}
|
||||
|
||||
public void LoadNca(string ncaFile)
|
||||
{
|
||||
System.LoadNca(ncaFile);
|
||||
Application.LoadNca(ncaFile);
|
||||
}
|
||||
|
||||
public void LoadNsp(string nspFile)
|
||||
{
|
||||
System.LoadNsp(nspFile);
|
||||
Application.LoadNsp(nspFile);
|
||||
}
|
||||
|
||||
public void LoadProgram(string fileName)
|
||||
{
|
||||
System.LoadProgram(fileName);
|
||||
Application.LoadProgram(fileName);
|
||||
}
|
||||
|
||||
public bool WaitFifo()
|
||||
|
|
|
@ -474,17 +474,7 @@ namespace Ryujinx.Ui
|
|||
Nca controlNca = null;
|
||||
|
||||
// Add keys to key set if needed
|
||||
foreach (DirectoryEntryEx ticketEntry in pfs.EnumerateEntries("/", "*.tik"))
|
||||
{
|
||||
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)));
|
||||
}
|
||||
}
|
||||
_virtualFileSystem.ImportTickets(pfs);
|
||||
|
||||
// Find the Control NCA and store it in variable called controlNca
|
||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||
|
@ -661,17 +651,7 @@ namespace Ryujinx.Ui
|
|||
{
|
||||
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());
|
||||
|
||||
_virtualFileSystem.KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(_virtualFileSystem.KeySet)));
|
||||
}
|
||||
}
|
||||
_virtualFileSystem.ImportTickets(nsp);
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
|
|
|
@ -180,16 +180,16 @@ namespace Ryujinx.Ui
|
|||
{
|
||||
parent.Present();
|
||||
|
||||
string titleNameSection = string.IsNullOrWhiteSpace(_device.System.TitleName) ? string.Empty
|
||||
: $" - {_device.System.TitleName}";
|
||||
string titleNameSection = string.IsNullOrWhiteSpace(_device.Application.TitleName) ? string.Empty
|
||||
: $" - {_device.Application.TitleName}";
|
||||
|
||||
string titleVersionSection = string.IsNullOrWhiteSpace(_device.System.TitleVersionString) ? string.Empty
|
||||
: $" v{_device.System.TitleVersionString}";
|
||||
string titleVersionSection = string.IsNullOrWhiteSpace(_device.Application.TitleVersionString) ? string.Empty
|
||||
: $" v{_device.Application.TitleVersionString}";
|
||||
|
||||
string titleIdSection = string.IsNullOrWhiteSpace(_device.System.TitleIdText) ? string.Empty
|
||||
: $" ({_device.System.TitleIdText.ToUpper()})";
|
||||
string titleIdSection = string.IsNullOrWhiteSpace(_device.Application.TitleIdText) ? string.Empty
|
||||
: $" ({_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}";
|
||||
});
|
||||
|
|
|
@ -280,17 +280,7 @@ namespace Ryujinx.Ui
|
|||
FileStream updateFile = new FileStream(updatePath, FileMode.Open, FileAccess.Read);
|
||||
PartitionFileSystem nsp = new PartitionFileSystem(updateFile.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());
|
||||
|
||||
_virtualFileSystem.KeySet.ExternalKeySet.Add(new LibHac.Fs.RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(_virtualFileSystem.KeySet)));
|
||||
}
|
||||
}
|
||||
_virtualFileSystem.ImportTickets(nsp);
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
|
|
|
@ -435,9 +435,9 @@ namespace Ryujinx.Ui
|
|||
_firmwareInstallFile.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();
|
||||
});
|
||||
|
@ -580,7 +580,7 @@ namespace Ryujinx.Ui
|
|||
|
||||
if (device != null)
|
||||
{
|
||||
UpdateGameMetadata(device.System.TitleIdText);
|
||||
UpdateGameMetadata(device.Application.TitleIdText);
|
||||
|
||||
if (_glWidget != null)
|
||||
{
|
||||
|
|
|
@ -81,17 +81,7 @@ namespace Ryujinx.Ui
|
|||
{
|
||||
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());
|
||||
|
||||
_virtualFileSystem.KeySet.ExternalKeySet.Add(new RightsId(ticket.RightsId), new AccessKey(ticket.GetTitleKey(_virtualFileSystem.KeySet)));
|
||||
}
|
||||
}
|
||||
_virtualFileSystem.ImportTickets(nsp);
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
|
|
Reference in a new issue