Update to LibHac 0.13.1 (#2328)

Update the LibHac dependency to version 0.13.1. This brings a ton of improvements and changes such as:
- Refactor `FsSrv` to match the official refactoring done in FS.
- Change how the `Horizon` and `HorizonClient` classes are handled. Each client created represents a different process with its own process ID and client state.
- Add FS access control to handle permissions for FS service method calls.
- Add FS program registry to keep track of the program ID, location and permissions of each process.
- Add FS program index map info manager to track the program IDs and indexes of multi-application programs.
- Add all FS IPC interfaces.
- Rewrite `Fs.Fsa` code to be more accurate.
- Rewrite a lot of `FsSrv` code to be more accurate.
- Extend directory save data to store `SaveDataExtraData`
- Extend directory save data to lock the save directory to allow only one accessor at a time.
- Improve waiting and retrying when encountering access issues in `LocalFileSystem` and `DirectorySaveDataFileSystem`.
- More `IFileSystemProxy` methods should work now.
- Probably a bunch more stuff.

On the Ryujinx side:
- Forward most `IFileSystemProxy` methods to LibHac.
- Register programs and program index map info when launching an application.
- Remove hacks and workarounds for missing LibHac functionality.
- Recreate missing save data extra data found on emulator startup.
- Create system save data that wasn't indexed correctly on an older LibHac version.

`FsSrv` now enforces access control for each process. When a process tries to open a save data file system, FS reads the save's extra data to determine who the save owner is and if the caller has permission to open the save data. Previously-created save data did not have extra data created when the save was created.
With access control checks in place, this means that processes with no permissions (most games) wouldn't be able to access their own save data. The extra data can be partially created from data in the save data indexer, which should be enough for access control purposes.
This commit is contained in:
Alex Barney 2021-07-13 01:19:28 -07:00 committed by GitHub
parent 04dce402ac
commit 19afb3209c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 1795 additions and 574 deletions

View file

@ -653,11 +653,11 @@ namespace Ryujinx.HLE.FileSystem.Content
public SystemVersion VerifyFirmwarePackage(string firmwarePackage) public SystemVersion VerifyFirmwarePackage(string firmwarePackage)
{ {
_virtualFileSystem.Reload(); _virtualFileSystem.ReloadKeySet();
// LibHac.NcaHeader's DecryptHeader doesn't check if HeaderKey is empty and throws InvalidDataException instead // LibHac.NcaHeader's DecryptHeader doesn't check if HeaderKey is empty and throws InvalidDataException instead
// So, we check it early for a better user experience. // So, we check it early for a better user experience.
if (_virtualFileSystem.KeySet.HeaderKey.IsEmpty()) if (_virtualFileSystem.KeySet.HeaderKey.IsZeros())
{ {
throw new MissingKeyException("HeaderKey is empty. Cannot decrypt NCA headers."); throw new MissingKeyException("HeaderKey is empty. Cannot decrypt NCA headers.");
} }

View file

@ -1,4 +1,3 @@
using System;
using System.IO; using System.IO;
using System.Text; using System.Text;

View file

@ -1,45 +0,0 @@
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using Ryujinx.HLE.HOS;
using System.IO;
namespace Ryujinx.HLE.FileSystem
{
static class SaveHelper
{
public static IFileSystem OpenSystemSaveData(ServiceCtx context, ulong saveId)
{
SaveInfo saveInfo = new SaveInfo(0, (long)saveId, SaveDataType.SystemSaveData, SaveSpaceId.NandSystem);
string savePath = context.Device.FileSystem.GetSavePath(context, saveInfo, false);
if (File.Exists(savePath))
{
string tempDirectoryPath = $"{savePath}_temp";
Directory.CreateDirectory(tempDirectoryPath);
IFileSystem outputFolder = new LocalFileSystem(tempDirectoryPath);
using (LocalStorage systemSaveData = new LocalStorage(savePath, FileAccess.Read, FileMode.Open))
{
IFileSystem saveFs = new LibHac.FsSystem.Save.SaveDataFileSystem(context.Device.System.KeySet, systemSaveData, IntegrityCheckLevel.None, false);
saveFs.CopyDirectory(outputFolder, "/", "/");
}
File.Delete(savePath);
Directory.Move(tempDirectoryPath, savePath);
}
else
{
if (!Directory.Exists(savePath))
{
Directory.CreateDirectory(savePath);
}
}
return new LocalFileSystem(savePath);
}
}
}

View file

@ -1,15 +1,23 @@
using LibHac; using LibHac;
using LibHac.Common; using LibHac.Common;
using LibHac.Common.Keys;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.Fsa; using LibHac.Fs.Fsa;
using LibHac.Fs.Shim;
using LibHac.FsSrv; using LibHac.FsSrv;
using LibHac.FsSystem; using LibHac.FsSystem;
using LibHac.Ncm;
using LibHac.Spl; using LibHac.Spl;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem.Content; using Ryujinx.HLE.FileSystem.Content;
using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS;
using System; using System;
using System.Buffers.Text;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using RightsId = LibHac.Fs.RightsId;
namespace Ryujinx.HLE.FileSystem namespace Ryujinx.HLE.FileSystem
{ {
@ -24,9 +32,7 @@ namespace Ryujinx.HLE.FileSystem
private static bool _isInitialized = false; private static bool _isInitialized = false;
public Keyset KeySet { get; private set; } public KeySet KeySet { get; private set; }
public FileSystemServer FsServer { get; private set; }
public FileSystemClient FsClient { get; private set; }
public EmulatedGameCard GameCard { get; private set; } public EmulatedGameCard GameCard { get; private set; }
public EmulatedSdCard SdCard { get; private set; } public EmulatedSdCard SdCard { get; private set; }
@ -34,7 +40,7 @@ namespace Ryujinx.HLE.FileSystem
private VirtualFileSystem() private VirtualFileSystem()
{ {
Reload(); ReloadKeySet();
ModLoader = new ModLoader(); // Should only be created once ModLoader = new ModLoader(); // Should only be created once
} }
@ -80,39 +86,6 @@ namespace Ryujinx.HLE.FileSystem
internal string GetSdCardPath() => MakeFullPath(SdCardPath); internal string GetSdCardPath() => MakeFullPath(SdCardPath);
public string GetNandPath() => MakeFullPath(NandPath); public string GetNandPath() => MakeFullPath(NandPath);
internal string GetSavePath(ServiceCtx context, SaveInfo saveInfo, bool isDirectory = true)
{
string saveUserPath = "";
string baseSavePath = NandPath;
ulong currentTitleId = saveInfo.TitleId;
switch (saveInfo.SaveSpaceId)
{
case SaveSpaceId.NandUser: baseSavePath = UserNandPath; break;
case SaveSpaceId.NandSystem: baseSavePath = SystemNandPath; break;
case SaveSpaceId.SdCard: baseSavePath = Path.Combine(SdCardPath, "Nintendo"); break;
}
baseSavePath = Path.Combine(baseSavePath, "save");
if (saveInfo.TitleId == 0 && saveInfo.SaveDataType == SaveDataType.SaveData)
{
currentTitleId = context.Process.TitleId;
}
if (saveInfo.SaveSpaceId == SaveSpaceId.NandUser)
{
saveUserPath = saveInfo.UserId.IsNull ? "savecommon" : saveInfo.UserId.ToString();
}
string savePath = Path.Combine(baseSavePath,
saveInfo.SaveId.ToString("x16"),
saveUserPath,
saveInfo.SaveDataType == SaveDataType.SaveData ? currentTitleId.ToString("x16") : string.Empty);
return MakeFullPath(savePath, isDirectory);
}
public string GetFullPartitionPath(string partitionPath) public string GetFullPartitionPath(string partitionPath)
{ {
return MakeFullPath(partitionPath); return MakeFullPath(partitionPath);
@ -196,33 +169,34 @@ namespace Ryujinx.HLE.FileSystem
return new DriveInfo(Path.GetPathRoot(GetBasePath())); return new DriveInfo(Path.GetPathRoot(GetBasePath()));
} }
public void Reload() public void InitializeFsServer(LibHac.Horizon horizon, out HorizonClient fsServerClient)
{ {
ReloadKeySet();
LocalFileSystem serverBaseFs = new LocalFileSystem(GetBasePath()); LocalFileSystem serverBaseFs = new LocalFileSystem(GetBasePath());
DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet); fsServerClient = horizon.CreatePrivilegedHorizonClient();
var fsServer = new FileSystemServer(fsServerClient);
DefaultFsServerObjects fsServerObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(serverBaseFs, KeySet, fsServer);
GameCard = fsServerObjects.GameCard; GameCard = fsServerObjects.GameCard;
SdCard = fsServerObjects.SdCard; SdCard = fsServerObjects.SdCard;
SdCard.SetSdCardInsertionStatus(true); SdCard.SetSdCardInsertionStatus(true);
FileSystemServerConfig fsServerConfig = new FileSystemServerConfig var fsServerConfig = new FileSystemServerConfig
{ {
FsCreators = fsServerObjects.FsCreators,
DeviceOperator = fsServerObjects.DeviceOperator, DeviceOperator = fsServerObjects.DeviceOperator,
ExternalKeySet = KeySet.ExternalKeySet ExternalKeySet = KeySet.ExternalKeySet,
FsCreators = fsServerObjects.FsCreators
}; };
FsServer = new FileSystemServer(fsServerConfig); FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, fsServerConfig);
FsClient = FsServer.CreateFileSystemClient();
} }
public void ReloadKeySet()
private void ReloadKeySet()
{ {
KeySet ??= KeySet.CreateDefaultKeySet();
string keyFile = null; string keyFile = null;
string titleKeyFile = null; string titleKeyFile = null;
string consoleKeyFile = null; string consoleKeyFile = null;
@ -256,7 +230,7 @@ namespace Ryujinx.HLE.FileSystem
} }
} }
KeySet = ExternalKeyReader.ReadKeyFile(keyFile, titleKeyFile, consoleKeyFile); ExternalKeyReader.ReadKeyFile(KeySet, keyFile, titleKeyFile, consoleKeyFile, null);
} }
public void ImportTickets(IFileSystem fs) public void ImportTickets(IFileSystem fs)
@ -277,6 +251,269 @@ namespace Ryujinx.HLE.FileSystem
} }
} }
// Save data created before we supported extra data in directory save data will not work properly if
// given empty extra data. Luckily some of that extra data can be created using the data from the
// save data indexer, which should be enough to check access permissions for user saves.
// Every single save data's extra data will be checked and fixed if needed each time the emulator is opened.
// Consider removing this at some point in the future when we don't need to worry about old saves.
public static Result FixExtraData(HorizonClient hos)
{
Result rc = GetSystemSaveList(hos, out List<ulong> systemSaveIds);
if (rc.IsFailure()) return rc;
rc = FixUnindexedSystemSaves(hos, systemSaveIds);
if (rc.IsFailure()) return rc;
rc = FixExtraDataInSpaceId(hos, SaveDataSpaceId.System);
if (rc.IsFailure()) return rc;
rc = FixExtraDataInSpaceId(hos, SaveDataSpaceId.User);
if (rc.IsFailure()) return rc;
rc = FixExtraDataInSpaceId(hos, SaveDataSpaceId.SdCache);
if (rc.IsFailure()) return rc;
return Result.Success;
}
private static Result FixExtraDataInSpaceId(HorizonClient hos, SaveDataSpaceId spaceId)
{
Span<SaveDataInfo> info = stackalloc SaveDataInfo[8];
Result rc = hos.Fs.OpenSaveDataIterator(out var iterator, spaceId);
if (rc.IsFailure()) return rc;
while (true)
{
rc = iterator.ReadSaveDataInfo(out long count, info);
if (rc.IsFailure()) return rc;
if (count == 0)
return Result.Success;
for (int i = 0; i < count; i++)
{
rc = FixExtraData(out bool wasFixNeeded, hos, in info[i]);
if (rc.IsFailure())
{
Logger.Warning?.Print(LogClass.Application, $"Error {rc.ToStringWithName()} when fixing extra data for save data 0x{info[i].SaveDataId:x} in the {spaceId} save data space");
}
else if (wasFixNeeded)
{
Logger.Info?.Print(LogClass.Application, $"Tried to rebuild extra data for save data 0x{info[i].SaveDataId:x} in the {spaceId} save data space");
}
}
}
}
// Gets a list of all the save data files or directories in the system partition.
private static Result GetSystemSaveList(HorizonClient hos, out List<ulong> list)
{
list = null;
var mountName = "system".ToU8Span();
DirectoryHandle handle = default;
List<ulong> localList = new List<ulong>();
try
{
Result rc = hos.Fs.MountBis(mountName, BisPartitionId.System);
if (rc.IsFailure()) return rc;
rc = hos.Fs.OpenDirectory(out handle, "system:/save".ToU8Span(), OpenDirectoryMode.All);
if (rc.IsFailure()) return rc;
DirectoryEntry entry = new DirectoryEntry();
while (true)
{
rc = hos.Fs.ReadDirectory(out long readCount, SpanHelpers.AsSpan(ref entry), handle);
if (rc.IsFailure()) return rc;
if (readCount == 0)
break;
if (Utf8Parser.TryParse(entry.Name, out ulong saveDataId, out int bytesRead, 'x') &&
bytesRead == 16 && (long)saveDataId < 0)
{
localList.Add(saveDataId);
}
}
list = localList;
return Result.Success;
}
finally
{
if (handle.IsValid)
{
hos.Fs.CloseDirectory(handle);
}
if (hos.Fs.IsMounted(mountName))
{
hos.Fs.Unmount(mountName);
}
}
}
// Adds system save data that isn't in the save data indexer to the indexer and creates extra data for it.
// Only save data IDs added to SystemExtraDataFixInfo will be fixed.
private static Result FixUnindexedSystemSaves(HorizonClient hos, List<ulong> existingSaveIds)
{
foreach (var fixInfo in SystemExtraDataFixInfo)
{
if (!existingSaveIds.Contains(fixInfo.StaticSaveDataId))
{
continue;
}
Result rc = FixSystemExtraData(out bool wasFixNeeded, hos, in fixInfo);
if (rc.IsFailure())
{
Logger.Warning?.Print(LogClass.Application,
$"Error {rc.ToStringWithName()} when fixing extra data for system save data 0x{fixInfo.StaticSaveDataId:x}");
}
else if (wasFixNeeded)
{
Logger.Info?.Print(LogClass.Application,
$"Tried to rebuild extra data for system save data 0x{fixInfo.StaticSaveDataId:x}");
}
}
return Result.Success;
}
private static Result FixSystemExtraData(out bool wasFixNeeded, HorizonClient hos, in ExtraDataFixInfo info)
{
wasFixNeeded = true;
Result rc = hos.Fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, info.StaticSaveDataId);
if (!rc.IsSuccess())
{
if (!ResultFs.TargetNotFound.Includes(rc))
return rc;
// We'll reach this point only if the save data directory exists but it's not in the save data indexer.
// Creating the save will add it to the indexer while leaving its existing contents intact.
return hos.Fs.CreateSystemSaveData(info.StaticSaveDataId, UserId.InvalidId, info.OwnerId, info.DataSize,
info.JournalSize, info.Flags);
}
if (extraData.Attribute.StaticSaveDataId != 0 && extraData.OwnerId != 0)
{
wasFixNeeded = false;
return Result.Success;
}
extraData = new SaveDataExtraData
{
Attribute = { StaticSaveDataId = info.StaticSaveDataId },
OwnerId = info.OwnerId,
Flags = info.Flags,
DataSize = info.DataSize,
JournalSize = info.JournalSize
};
// Make a mask for writing the entire extra data
Unsafe.SkipInit(out SaveDataExtraData extraDataMask);
SpanHelpers.AsByteSpan(ref extraDataMask).Fill(0xFF);
return hos.Fs.Impl.WriteSaveDataFileSystemExtraData(SaveDataSpaceId.System, info.StaticSaveDataId,
in extraData, in extraDataMask);
}
private static Result FixExtraData(out bool wasFixNeeded, HorizonClient hos, in SaveDataInfo info)
{
wasFixNeeded = true;
Result rc = hos.Fs.Impl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, info.SpaceId,
info.SaveDataId);
if (rc.IsFailure()) return rc;
// The extra data should have program ID or static save data ID set if it's valid.
// We only try to fix the extra data if the info from the save data indexer has a program ID or static save data ID.
bool canFixByProgramId = extraData.Attribute.ProgramId == ProgramId.InvalidId &&
info.ProgramId != ProgramId.InvalidId;
bool canFixBySaveDataId = extraData.Attribute.StaticSaveDataId == 0 && info.StaticSaveDataId != 0;
if (!canFixByProgramId && !canFixBySaveDataId)
{
wasFixNeeded = false;
return Result.Success;
}
// The save data attribute struct can be completely created from the save data info.
extraData.Attribute.ProgramId = info.ProgramId;
extraData.Attribute.UserId = info.UserId;
extraData.Attribute.StaticSaveDataId = info.StaticSaveDataId;
extraData.Attribute.Type = info.Type;
extraData.Attribute.Rank = info.Rank;
extraData.Attribute.Index = info.Index;
// The rest of the extra data can't be created from the save data info.
// On user saves the owner ID will almost certainly be the same as the program ID.
if (info.Type != LibHac.Fs.SaveDataType.System)
{
extraData.OwnerId = info.ProgramId.Value;
}
else
{
// Try to match the system save with one of the known saves
foreach (ExtraDataFixInfo fixInfo in SystemExtraDataFixInfo)
{
if (extraData.Attribute.StaticSaveDataId == fixInfo.StaticSaveDataId)
{
extraData.OwnerId = fixInfo.OwnerId;
extraData.Flags = fixInfo.Flags;
extraData.DataSize = fixInfo.DataSize;
extraData.JournalSize = fixInfo.JournalSize;
break;
}
}
}
// Make a mask for writing the entire extra data
Unsafe.SkipInit(out SaveDataExtraData extraDataMask);
SpanHelpers.AsByteSpan(ref extraDataMask).Fill(0xFF);
return hos.Fs.Impl.WriteSaveDataFileSystemExtraData(info.SpaceId, info.SaveDataId, in extraData, in extraDataMask);
}
struct ExtraDataFixInfo
{
public ulong StaticSaveDataId;
public ulong OwnerId;
public SaveDataFlags Flags;
public long DataSize;
public long JournalSize;
}
private static readonly ExtraDataFixInfo[] SystemExtraDataFixInfo =
{
new ExtraDataFixInfo()
{
StaticSaveDataId = 0x8000000000000030,
OwnerId = 0x010000000000001F,
Flags = SaveDataFlags.KeepAfterResettingSystemSaveDataWithoutUserSaveData,
DataSize = 0x10000,
JournalSize = 0x10000
},
new ExtraDataFixInfo()
{
StaticSaveDataId = 0x8000000000001040,
OwnerId = 0x0100000000001009,
Flags = SaveDataFlags.None,
DataSize = 0xC000,
JournalSize = 0xC000
}
};
public void Unload() public void Unload()
{ {
RomFs?.Dispose(); RomFs?.Dispose();
@ -299,7 +536,7 @@ namespace Ryujinx.HLE.FileSystem
{ {
if (_isInitialized) if (_isInitialized)
{ {
throw new InvalidOperationException($"VirtualFileSystem can only be instantiated once!"); throw new InvalidOperationException("VirtualFileSystem can only be instantiated once!");
} }
_isInitialized = true; _isInitialized = true;

View file

@ -1,8 +1,6 @@
using LibHac.FsSystem; using LibHac.FsSystem;
using Ryujinx.Audio.Integration; using Ryujinx.Audio.Integration;
using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.FileSystem.Content; using Ryujinx.HLE.FileSystem.Content;
@ -10,7 +8,6 @@ using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.HOS.SystemState;
using System; using System;
using System.Collections.Generic;
namespace Ryujinx.HLE namespace Ryujinx.HLE
{ {
@ -25,6 +22,12 @@ namespace Ryujinx.HLE
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks> /// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
internal readonly VirtualFileSystem VirtualFileSystem; internal readonly VirtualFileSystem VirtualFileSystem;
/// <summary>
/// The manager for handling a LibHac Horizon instance.
/// </summary>
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
internal readonly LibHacHorizonManager LibHacHorizonManager;
/// <summary> /// <summary>
/// The account manager used by the account service. /// The account manager used by the account service.
/// </summary> /// </summary>
@ -38,7 +41,7 @@ namespace Ryujinx.HLE
internal readonly ContentManager ContentManager; internal readonly ContentManager ContentManager;
/// <summary> /// <summary>
/// The persistant information between run for multi-application capabilities. /// The persistent information between run for multi-application capabilities.
/// </summary> /// </summary>
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks> /// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
public readonly UserChannelPersistence UserChannelPersistence; public readonly UserChannelPersistence UserChannelPersistence;
@ -124,7 +127,7 @@ namespace Ryujinx.HLE
public MemoryManagerMode MemoryManagerMode { internal get; set; } public MemoryManagerMode MemoryManagerMode { internal get; set; }
/// <summary> /// <summary>
/// Control the inital state of the ignore missing services setting. /// Control the initial state of the ignore missing services setting.
/// If this is set to true, when a missing service is encountered, it will try to automatically handle it instead of throwing an exception. /// If this is set to true, when a missing service is encountered, it will try to automatically handle it instead of throwing an exception.
/// </summary> /// </summary>
/// TODO: Update this again. /// TODO: Update this again.
@ -141,6 +144,7 @@ namespace Ryujinx.HLE
public Action RefreshInputConfig { internal get; set; } public Action RefreshInputConfig { internal get; set; }
public HLEConfiguration(VirtualFileSystem virtualFileSystem, public HLEConfiguration(VirtualFileSystem virtualFileSystem,
LibHacHorizonManager libHacHorizonManager,
ContentManager contentManager, ContentManager contentManager,
AccountManager accountManager, AccountManager accountManager,
UserChannelPersistence userChannelPersistence, UserChannelPersistence userChannelPersistence,
@ -162,6 +166,7 @@ namespace Ryujinx.HLE
AspectRatio aspectRatio) AspectRatio aspectRatio)
{ {
VirtualFileSystem = virtualFileSystem; VirtualFileSystem = virtualFileSystem;
LibHacHorizonManager = libHacHorizonManager;
AccountManager = accountManager; AccountManager = accountManager;
ContentManager = contentManager; ContentManager = contentManager;
UserChannelPersistence = userChannelPersistence; UserChannelPersistence = userChannelPersistence;

View file

@ -4,15 +4,17 @@ using LibHac.Account;
using LibHac.Common; using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.Fsa; using LibHac.Fs.Fsa;
using LibHac.Fs.Shim;
using LibHac.FsSystem; using LibHac.FsSystem;
using LibHac.FsSystem.NcaUtils; using LibHac.FsSystem.NcaUtils;
using LibHac.Loader;
using LibHac.Ncm;
using LibHac.Ns; using LibHac.Ns;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.Loaders.Executables; using Ryujinx.HLE.Loaders.Executables;
using Ryujinx.HLE.Loaders.Npdm;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
@ -77,7 +79,7 @@ namespace Ryujinx.HLE.HOS
LocalFileSystem codeFs = new LocalFileSystem(exeFsDir); LocalFileSystem codeFs = new LocalFileSystem(exeFsDir);
Npdm metaData = ReadNpdm(codeFs); MetaLoader metaData = ReadNpdm(codeFs);
_device.Configuration.VirtualFileSystem.ModLoader.CollectMods(new[] { TitleId }, _device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath()); _device.Configuration.VirtualFileSystem.ModLoader.CollectMods(new[] { TitleId }, _device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath());
@ -220,6 +222,8 @@ namespace Ryujinx.HLE.HOS
try try
{ {
(mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, securePartition, _device.Configuration.UserChannelPersistence.Index); (mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, securePartition, _device.Configuration.UserChannelPersistence.Index);
RegisterProgramMapInfo(securePartition).ThrowIfFailure();
} }
catch (Exception e) catch (Exception e)
{ {
@ -254,6 +258,8 @@ namespace Ryujinx.HLE.HOS
try try
{ {
(mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, nsp, _device.Configuration.UserChannelPersistence.Index); (mainNca, patchNca, controlNca) = GetGameData(_device.Configuration.VirtualFileSystem, nsp, _device.Configuration.UserChannelPersistence.Index);
RegisterProgramMapInfo(nsp).ThrowIfFailure();
} }
catch (Exception e) catch (Exception e)
{ {
@ -366,7 +372,7 @@ namespace Ryujinx.HLE.HOS
return; return;
} }
Npdm metaData = ReadNpdm(codeFs); MetaLoader metaData = ReadNpdm(codeFs);
_device.Configuration.VirtualFileSystem.ModLoader.CollectMods(_device.Configuration.ContentManager.GetAocTitleIds().Prepend(TitleId), _device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath()); _device.Configuration.VirtualFileSystem.ModLoader.CollectMods(_device.Configuration.ContentManager.GetAocTitleIds().Prepend(TitleId), _device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath());
@ -400,9 +406,12 @@ namespace Ryujinx.HLE.HOS
_device.Configuration.VirtualFileSystem.SetRomFs(newStorage.AsStream(FileAccess.Read)); _device.Configuration.VirtualFileSystem.SetRomFs(newStorage.AsStream(FileAccess.Read));
} }
if (TitleId != 0) // Don't create save data for system programs.
if (TitleId != 0 && (TitleId < SystemProgramId.Start.Value || TitleId > SystemAppletId.End.Value))
{ {
EnsureSaveData(new ApplicationId(TitleId)); // Multi-program applications can technically use any program ID for the main program, but in practice they always use 0 in the low nibble.
// We'll know if this changes in the future because stuff will get errors when trying to mount the correct save.
EnsureSaveData(new ApplicationId(TitleId & ~0xFul));
} }
LoadExeFs(codeFs, metaData); LoadExeFs(codeFs, metaData);
@ -411,11 +420,11 @@ namespace Ryujinx.HLE.HOS
} }
// Sets TitleId, so be sure to call before using it // Sets TitleId, so be sure to call before using it
private Npdm ReadNpdm(IFileSystem fs) private MetaLoader ReadNpdm(IFileSystem fs)
{ {
Result result = fs.OpenFile(out IFile npdmFile, "/main.npdm".ToU8Span(), OpenMode.Read); Result result = fs.OpenFile(out IFile npdmFile, "/main.npdm".ToU8Span(), OpenMode.Read);
Npdm metaData; MetaLoader metaData;
if (ResultFs.PathNotFound.Includes(result)) if (ResultFs.PathNotFound.Includes(result))
{ {
@ -425,11 +434,20 @@ namespace Ryujinx.HLE.HOS
} }
else else
{ {
metaData = new Npdm(npdmFile.AsStream()); npdmFile.GetSize(out long fileSize).ThrowIfFailure();
var npdmBuffer = new byte[fileSize];
npdmFile.Read(out _, 0, npdmBuffer).ThrowIfFailure();
metaData = new MetaLoader();
metaData.Load(npdmBuffer).ThrowIfFailure();
} }
TitleId = metaData.Aci0.TitleId; metaData.GetNpdm(out var npdm).ThrowIfFailure();
TitleIs64Bit = metaData.Is64Bit;
TitleId = npdm.Aci.Value.ProgramId.Value;
TitleIs64Bit = (npdm.Meta.Value.Flags & 1) != 0;
_device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId);
return metaData; return metaData;
} }
@ -461,7 +479,7 @@ namespace Ryujinx.HLE.HOS
} }
} }
private void LoadExeFs(IFileSystem codeFs, Npdm metaData = null) private void LoadExeFs(IFileSystem codeFs, MetaLoader metaData = null)
{ {
if (_device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(TitleId, ref codeFs)) if (_device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(TitleId, ref codeFs))
{ {
@ -519,14 +537,18 @@ namespace Ryujinx.HLE.HOS
Ptc.Initialize(TitleIdText, DisplayVersion, usePtc, _device.Configuration.MemoryManagerMode); Ptc.Initialize(TitleIdText, DisplayVersion, usePtc, _device.Configuration.MemoryManagerMode);
ProgramLoader.LoadNsos(_device.System.KernelContext, out ProcessTamperInfo tamperInfo, metaData, executables: programs); metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
ProgramLoader.LoadNsos(_device.System.KernelContext, out ProcessTamperInfo tamperInfo, metaData, new ProgramInfo(in npdm), executables: programs);
_device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, tamperInfo, _device.TamperMachine); _device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, tamperInfo, _device.TamperMachine);
} }
public void LoadProgram(string filePath) public void LoadProgram(string filePath)
{ {
Npdm metaData = GetDefaultNpdm(); MetaLoader metaData = GetDefaultNpdm();
metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
ProgramInfo programInfo = new ProgramInfo(in npdm);
bool isNro = Path.GetExtension(filePath).ToLower() == ".nro"; bool isNro = Path.GetExtension(filePath).ToLower() == ".nro";
IExecutable executable; IExecutable executable;
@ -573,28 +595,28 @@ namespace Ryujinx.HLE.HOS
ref ApplicationControlProperty nacp = ref ControlData.Value; ref ApplicationControlProperty nacp = ref ControlData.Value;
metaData.TitleName = nacp.Titles[(int)_device.System.State.DesiredTitleLanguage].Name.ToString(); programInfo.Name = nacp.Titles[(int)_device.System.State.DesiredTitleLanguage].Name.ToString();
if (string.IsNullOrWhiteSpace(metaData.TitleName)) if (string.IsNullOrWhiteSpace(programInfo.Name))
{ {
metaData.TitleName = nacp.Titles.ToArray().FirstOrDefault(x => x.Name[0] != 0).Name.ToString(); programInfo.Name = nacp.Titles.ToArray().FirstOrDefault(x => x.Name[0] != 0).Name.ToString();
} }
if (nacp.PresenceGroupId != 0) if (nacp.PresenceGroupId != 0)
{ {
metaData.Aci0.TitleId = nacp.PresenceGroupId; programInfo.ProgramId = nacp.PresenceGroupId;
} }
else if (nacp.SaveDataOwnerId.Value != 0) else if (nacp.SaveDataOwnerId.Value != 0)
{ {
metaData.Aci0.TitleId = nacp.SaveDataOwnerId.Value; programInfo.ProgramId = nacp.SaveDataOwnerId.Value;
} }
else if (nacp.AddOnContentBaseId != 0) else if (nacp.AddOnContentBaseId != 0)
{ {
metaData.Aci0.TitleId = nacp.AddOnContentBaseId - 0x1000; programInfo.ProgramId = nacp.AddOnContentBaseId - 0x1000;
} }
else else
{ {
metaData.Aci0.TitleId = 0000000000000000; programInfo.ProgramId = 0000000000000000;
} }
} }
} }
@ -612,29 +634,109 @@ namespace Ryujinx.HLE.HOS
_device.Configuration.ContentManager.LoadEntries(_device); _device.Configuration.ContentManager.LoadEntries(_device);
_titleName = metaData.TitleName; _titleName = programInfo.Name;
TitleId = metaData.Aci0.TitleId; TitleId = programInfo.ProgramId;
TitleIs64Bit = metaData.Is64Bit; TitleIs64Bit = (npdm.Meta.Value.Flags & 1) != 0;
_device.System.LibHacHorizonManager.ArpIReader.ApplicationId = new LibHac.ApplicationId(TitleId);
// Explicitly null titleid to disable the shader cache // Explicitly null titleid to disable the shader cache
Graphics.Gpu.GraphicsConfig.TitleId = null; Graphics.Gpu.GraphicsConfig.TitleId = null;
_device.Gpu.HostInitalized.Set(); _device.Gpu.HostInitalized.Set();
ProgramLoader.LoadNsos(_device.System.KernelContext, out ProcessTamperInfo tamperInfo, metaData, executables: executable); ProgramLoader.LoadNsos(_device.System.KernelContext, out ProcessTamperInfo tamperInfo, metaData, programInfo, executables: executable);
_device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, tamperInfo, _device.TamperMachine); _device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, tamperInfo, _device.TamperMachine);
} }
private Npdm GetDefaultNpdm() private MetaLoader GetDefaultNpdm()
{ {
Assembly asm = Assembly.GetCallingAssembly(); Assembly asm = Assembly.GetCallingAssembly();
using (Stream npdmStream = asm.GetManifestResourceStream("Ryujinx.HLE.Homebrew.npdm")) using (Stream npdmStream = asm.GetManifestResourceStream("Ryujinx.HLE.Homebrew.npdm"))
{ {
return new Npdm(npdmStream); var npdmBuffer = new byte[npdmStream.Length];
npdmStream.Read(npdmBuffer);
var metaLoader = new MetaLoader();
metaLoader.Load(npdmBuffer).ThrowIfFailure();
return metaLoader;
} }
} }
private static (ulong applicationId, int programCount) GetMultiProgramInfo(VirtualFileSystem fileSystem, PartitionFileSystem pfs)
{
ulong mainProgramId = 0;
Span<bool> hasIndex = stackalloc bool[0x10];
fileSystem.ImportTickets(pfs);
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
{
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)
{
continue;
}
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
if (nca.Header.GetFsHeader(dataIndex).IsPatchSection())
{
continue;
}
ulong currentProgramId = nca.Header.TitleId;
ulong currentMainProgramId = currentProgramId & ~0xFFFul;
if (mainProgramId == 0 && currentMainProgramId != 0)
{
mainProgramId = currentMainProgramId;
}
if (mainProgramId != currentMainProgramId)
{
// As far as I know there aren't any multi-application game cards containing multi-program applications,
// so because multi-application game cards are the only way we should run into multiple applications
// we'll just return that there's a single program.
return (mainProgramId, 1);
}
hasIndex[(int)(currentProgramId & 0xF)] = true;
}
int programCount = 0;
for (int i = 0; i < hasIndex.Length && hasIndex[i]; i++)
{
programCount++;
}
return (mainProgramId, programCount);
}
private Result RegisterProgramMapInfo(PartitionFileSystem pfs)
{
(ulong applicationId, int programCount) = GetMultiProgramInfo(_device.Configuration.VirtualFileSystem, pfs);
if (programCount <= 0)
return Result.Success;
Span<ProgramIndexMapInfo> mapInfo = stackalloc ProgramIndexMapInfo[0x10];
for (int i = 0; i < programCount; i++)
{
mapInfo[i].ProgramId = new ProgramId(applicationId + (uint)i);
mapInfo[i].MainProgramId = new ProgramId(applicationId);
mapInfo[i].ProgramIndex = (byte)i;
}
return _device.System.LibHacHorizonManager.NsClient.Fs.RegisterProgramIndexMapInfo(mapInfo.Slice(0, programCount));
}
private Result EnsureSaveData(ApplicationId applicationId) private Result EnsureSaveData(ApplicationId applicationId)
{ {
Logger.Info?.Print(LogClass.Application, "Ensuring required savedata exists."); Logger.Info?.Print(LogClass.Application, "Ensuring required savedata exists.");
@ -643,7 +745,7 @@ namespace Ryujinx.HLE.HOS
ref ApplicationControlProperty control = ref ControlData.Value; ref ApplicationControlProperty control = ref ControlData.Value;
if (LibHac.Utilities.IsEmpty(ControlData.ByteSpan)) if (LibHac.Utilities.IsZeros(ControlData.ByteSpan))
{ {
// If the current application doesn't have a loaded control property, create a dummy one // 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. // and set the savedata sizes so a user savedata will be created.
@ -657,8 +759,8 @@ namespace Ryujinx.HLE.HOS
"No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
} }
FileSystemClient fileSystem = _device.Configuration.VirtualFileSystem.FsClient; HorizonClient hos = _device.System.LibHacHorizonManager.RyujinxClient;
Result resultCode = fileSystem.EnsureApplicationCacheStorage(out _, applicationId, ref control); Result resultCode = hos.Fs.EnsureApplicationCacheStorage(out _, out _, applicationId, ref control);
if (resultCode.IsFailure()) if (resultCode.IsFailure())
{ {
@ -667,7 +769,7 @@ namespace Ryujinx.HLE.HOS
return resultCode; return resultCode;
} }
resultCode = EnsureApplicationSaveData(fileSystem, out _, applicationId, ref control, ref user); resultCode = EnsureApplicationSaveData(hos.Fs, out _, applicationId, ref control, ref user);
if (resultCode.IsFailure()) if (resultCode.IsFailure())
{ {

View file

@ -1,6 +1,6 @@
using LibHac; using LibHac.Common.Keys;
using LibHac.Bcat;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.Shim;
using LibHac.FsSystem; using LibHac.FsSystem;
using Ryujinx.Audio; using Ryujinx.Audio;
using Ryujinx.Audio.Input; using Ryujinx.Audio.Input;
@ -18,7 +18,6 @@ using Ryujinx.HLE.HOS.Services;
using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy; using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy;
using Ryujinx.HLE.HOS.Services.Apm; using Ryujinx.HLE.HOS.Services.Apm;
using Ryujinx.HLE.HOS.Services.Arp;
using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer; using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
using Ryujinx.HLE.HOS.Services.Caps; using Ryujinx.HLE.HOS.Services.Caps;
using Ryujinx.HLE.HOS.Services.Mii; using Ryujinx.HLE.HOS.Services.Mii;
@ -38,6 +37,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using TimeSpanType = Ryujinx.HLE.HOS.Services.Time.Clock.TimeSpanType;
namespace Ryujinx.HLE.HOS namespace Ryujinx.HLE.HOS
{ {
@ -97,7 +97,7 @@ namespace Ryujinx.HLE.HOS
internal KEvent DisplayResolutionChangeEvent { get; private set; } internal KEvent DisplayResolutionChangeEvent { get; private set; }
public Keyset KeySet => Device.FileSystem.KeySet; public KeySet KeySet => Device.FileSystem.KeySet;
private bool _isDisposed; private bool _isDisposed;
@ -111,8 +111,7 @@ namespace Ryujinx.HLE.HOS
internal NvHostSyncpt HostSyncpoint { get; private set; } internal NvHostSyncpt HostSyncpoint { get; private set; }
internal LibHac.Horizon LibHacHorizonServer { get; private set; } internal LibHacHorizonManager LibHacHorizonManager { get; private set; }
internal HorizonClient LibHacHorizonClient { get; private set; }
public Horizon(Switch device) public Horizon(Switch device)
{ {
@ -184,6 +183,8 @@ namespace Ryujinx.HLE.HOS
ContentManager = device.Configuration.ContentManager; ContentManager = device.Configuration.ContentManager;
CaptureManager = new CaptureManager(device); CaptureManager = new CaptureManager(device);
LibHacHorizonManager = device.Configuration.LibHacHorizonManager;
// TODO: use set:sys (and get external clock source id from settings) // TODO: use set:sys (and get external clock source id from settings)
// TODO: use "time!standard_steady_clock_rtc_update_interval_minutes" and implement a worker thread to be accurate. // TODO: use "time!standard_steady_clock_rtc_update_interval_minutes" and implement a worker thread to be accurate.
UInt128 clockSourceId = new UInt128(Guid.NewGuid().ToByteArray()); UInt128 clockSourceId = new UInt128(Guid.NewGuid().ToByteArray());
@ -223,17 +224,16 @@ namespace Ryujinx.HLE.HOS
TimeServiceManager.Instance.SetupStandardUserSystemClock(null, false, SteadyClockTimePoint.GetRandom()); TimeServiceManager.Instance.SetupStandardUserSystemClock(null, false, SteadyClockTimePoint.GetRandom());
// FIXME: TimeZone shoud be init here but it's actually done in ContentManager // FIXME: TimeZone should be init here but it's actually done in ContentManager
TimeServiceManager.Instance.SetupEphemeralNetworkSystemClock(); TimeServiceManager.Instance.SetupEphemeralNetworkSystemClock();
DatabaseImpl.Instance.InitializeDatabase(device); DatabaseImpl.Instance.InitializeDatabase(LibHacHorizonManager.SdbClient);
HostSyncpoint = new NvHostSyncpt(device); HostSyncpoint = new NvHostSyncpt(device);
SurfaceFlinger = new SurfaceFlinger(device); SurfaceFlinger = new SurfaceFlinger(device);
InitLibHacHorizon();
InitializeAudioRenderer(); InitializeAudioRenderer();
} }
@ -309,20 +309,6 @@ namespace Ryujinx.HLE.HOS
ProgramLoader.LoadKip(KernelContext, new KipExecutable(kipFile)); ProgramLoader.LoadKip(KernelContext, new KipExecutable(kipFile));
} }
private void InitLibHacHorizon()
{
LibHac.Horizon horizon = new LibHac.Horizon(null, Device.FileSystem.FsServer);
horizon.CreateHorizonClient(out HorizonClient ryujinxClient).ThrowIfFailure();
horizon.CreateHorizonClient(out HorizonClient bcatClient).ThrowIfFailure();
ryujinxClient.Sm.RegisterService(new LibHacIReader(this), "arp:r").ThrowIfFailure();
new BcatServer(bcatClient);
LibHacHorizonServer = horizon;
LibHacHorizonClient = ryujinxClient;
}
public void ChangeDockedModeState(bool newState) public void ChangeDockedModeState(bool newState)
{ {
if (newState != State.DockedMode) if (newState != State.DockedMode)
@ -453,6 +439,8 @@ namespace Ryujinx.HLE.HOS
AudioRendererManager.Dispose(); AudioRendererManager.Dispose();
LibHacHorizonManager.AmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value);
KernelContext.Dispose(); KernelContext.Dispose();
} }
} }

View file

@ -0,0 +1,124 @@
using LibHac;
using LibHac.Bcat;
using LibHac.FsSrv.Impl;
using LibHac.Loader;
using LibHac.Ncm;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Services.Arp;
using System;
using StorageId = LibHac.Ncm.StorageId;
namespace Ryujinx.HLE.HOS
{
public class LibHacHorizonManager
{
private LibHac.Horizon Server { get; set; }
public HorizonClient RyujinxClient { get; private set; }
public HorizonClient ApplicationClient { get; private set; }
public HorizonClient AccountClient { get; private set; }
public HorizonClient AmClient { get; private set; }
public HorizonClient BcatClient { get; private set; }
public HorizonClient FsClient { get; private set; }
public HorizonClient NsClient { get; private set; }
public HorizonClient SdbClient { get; private set; }
internal LibHacIReader ArpIReader { get; private set; }
public LibHacHorizonManager()
{
InitializeServer();
}
private void InitializeServer()
{
Server = new LibHac.Horizon(new HorizonConfiguration());
RyujinxClient = Server.CreatePrivilegedHorizonClient();
}
public void InitializeArpServer()
{
ArpIReader = new LibHacIReader();
RyujinxClient.Sm.RegisterService(new LibHacArpServiceObject(ArpIReader), "arp:r").ThrowIfFailure();
}
public void InitializeBcatServer()
{
BcatClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Bcat, StorageId.BuiltInSystem),
BcatFsPermissions);
_ = new BcatServer(BcatClient);
}
public void InitializeFsServer(VirtualFileSystem virtualFileSystem)
{
virtualFileSystem.InitializeFsServer(Server, out var fsClient);
FsClient = fsClient;
}
public void InitializeSystemClients()
{
AccountClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Account, StorageId.BuiltInSystem),
AccountFsPermissions);
AmClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Am, StorageId.BuiltInSystem),
AmFsPermissions);
NsClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Ns, StorageId.BuiltInSystem),
NsFsPermissions);
SdbClient = Server.CreateHorizonClient(new ProgramLocation(SystemProgramId.Sdb, StorageId.BuiltInSystem),
SdbFacData, SdbFacDescriptor);
}
public void InitializeApplicationClient(ProgramId programId, in Npdm npdm)
{
ApplicationClient = Server.CreateHorizonClient(new ProgramLocation(programId, StorageId.BuiltInUser),
npdm.FsAccessControlData, npdm.FsAccessControlDescriptor);
}
private static AccessControlBits.Bits AccountFsPermissions => AccessControlBits.Bits.SystemSaveData |
AccessControlBits.Bits.GameCard |
AccessControlBits.Bits.SaveDataMeta |
AccessControlBits.Bits.GetRightsId;
private static AccessControlBits.Bits AmFsPermissions => AccessControlBits.Bits.SaveDataManagement |
AccessControlBits.Bits.CreateSaveData |
AccessControlBits.Bits.SystemData;
private static AccessControlBits.Bits BcatFsPermissions => AccessControlBits.Bits.SystemSaveData;
private static AccessControlBits.Bits NsFsPermissions => AccessControlBits.Bits.ApplicationInfo |
AccessControlBits.Bits.SystemSaveData |
AccessControlBits.Bits.GameCard |
AccessControlBits.Bits.SaveDataManagement |
AccessControlBits.Bits.ContentManager |
AccessControlBits.Bits.ImageManager |
AccessControlBits.Bits.SystemSaveDataManagement |
AccessControlBits.Bits.SystemUpdate |
AccessControlBits.Bits.SdCard |
AccessControlBits.Bits.FormatSdCard |
AccessControlBits.Bits.GetRightsId |
AccessControlBits.Bits.RegisterProgramIndexMapInfo |
AccessControlBits.Bits.MoveCacheStorage;
// Sdb has save data access control info so we can't store just its access control bits
private static ReadOnlySpan<byte> SdbFacData => new byte[]
{
0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x03, 0x03, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x09, 0x10, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01
};
private static ReadOnlySpan<byte> SdbFacDescriptor => new byte[]
{
0x01, 0x00, 0x02, 0x00, 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x09, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
};
}
}

View file

@ -3,6 +3,7 @@ using LibHac.Fs;
using LibHac.Fs.Fsa; using LibHac.Fs.Fsa;
using LibHac.FsSystem; using LibHac.FsSystem;
using LibHac.FsSystem.RomFs; using LibHac.FsSystem.RomFs;
using LibHac.Loader;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.Loaders.Mods; using Ryujinx.HLE.Loaders.Mods;
@ -12,7 +13,6 @@ using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq; using System.Linq;
using System.IO; using System.IO;
using Ryujinx.HLE.Loaders.Npdm;
using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Process;
using System.Globalization; using System.Globalization;
@ -522,7 +522,7 @@ namespace Ryujinx.HLE.HOS
{ {
public BitVector32 Stubs; public BitVector32 Stubs;
public BitVector32 Replaces; public BitVector32 Replaces;
public Npdm Npdm; public MetaLoader Npdm;
public bool Modified => (Stubs.Data | Replaces.Data) != 0; public bool Modified => (Stubs.Data | Replaces.Data) != 0;
} }
@ -582,9 +582,10 @@ namespace Ryujinx.HLE.HOS
continue; continue;
} }
modLoadResult.Npdm = new Npdm(npdmFile.OpenRead()); modLoadResult.Npdm = new MetaLoader();
modLoadResult.Npdm.Load(File.ReadAllBytes(npdmFile.FullName));
Logger.Info?.Print(LogClass.ModLoader, $"main.npdm replaced"); Logger.Info?.Print(LogClass.ModLoader, "main.npdm replaced");
} }
} }

View file

@ -1,4 +1,7 @@
using ARMeilleure.Translation.PTC; using ARMeilleure.Translation.PTC;
using LibHac.Loader;
using LibHac.Ncm;
using LibHac.Util;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Kernel;
@ -6,12 +9,25 @@ using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.Loaders.Executables; using Ryujinx.HLE.Loaders.Executables;
using Ryujinx.HLE.Loaders.Npdm;
using System; using System;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using Npdm = LibHac.Loader.Npdm;
namespace Ryujinx.HLE.HOS namespace Ryujinx.HLE.HOS
{ {
struct ProgramInfo
{
public string Name;
public ulong ProgramId;
public ProgramInfo(in Npdm npdm)
{
Name = StringUtils.Utf8ZToString(npdm.Meta.Value.ProgramName);
ProgramId = npdm.Aci.Value.ProgramId.Value;
}
}
static class ProgramLoader static class ProgramLoader
{ {
private const bool AslrEnabled = true; private const bool AslrEnabled = true;
@ -125,11 +141,21 @@ namespace Ryujinx.HLE.HOS
return true; return true;
} }
public static bool LoadNsos(KernelContext context, out ProcessTamperInfo tamperInfo, Npdm metaData, byte[] arguments = null, params IExecutable[] executables) public static bool LoadNsos(KernelContext context, out ProcessTamperInfo tamperInfo, MetaLoader metaData, ProgramInfo programInfo, byte[] arguments = null, params IExecutable[] executables)
{ {
LibHac.Result rc = metaData.GetNpdm(out var npdm);
if (rc.IsFailure())
{
tamperInfo = null;
return false;
}
ref readonly var meta = ref npdm.Meta.Value;
ulong argsStart = 0; ulong argsStart = 0;
uint argsSize = 0; uint argsSize = 0;
ulong codeStart = metaData.Is64Bit ? 0x8000000UL : 0x200000UL; ulong codeStart = (meta.Flags & 1) != 0 ? 0x8000000UL : 0x200000UL;
uint codeSize = 0; uint codeSize = 0;
var buildIds = executables.Select(e => (e switch var buildIds = executables.Select(e => (e switch
@ -182,18 +208,20 @@ namespace Ryujinx.HLE.HOS
int codePagesCount = (int)(codeSize / KPageTableBase.PageSize); int codePagesCount = (int)(codeSize / KPageTableBase.PageSize);
int personalMmHeapPagesCount = metaData.PersonalMmHeapSize / KPageTableBase.PageSize; int personalMmHeapPagesCount = (int)(meta.SystemResourceSize / KPageTableBase.PageSize);
ProcessCreationInfo creationInfo = new ProcessCreationInfo( ProcessCreationInfo creationInfo = new ProcessCreationInfo(
metaData.TitleName, programInfo.Name,
metaData.Version, (int)meta.Version,
metaData.Aci0.TitleId, programInfo.ProgramId,
codeStart, codeStart,
codePagesCount, codePagesCount,
(ProcessCreationFlags)metaData.ProcessFlags | ProcessCreationFlags.IsApplication, (ProcessCreationFlags)meta.Flags | ProcessCreationFlags.IsApplication,
0, 0,
personalMmHeapPagesCount); personalMmHeapPagesCount);
context.Device.System.LibHacHorizonManager.InitializeApplicationClient(new ProgramId(programInfo.ProgramId), in npdm);
KernelResult result; KernelResult result;
KResourceLimit resourceLimit = new KResourceLimit(context); KResourceLimit resourceLimit = new KResourceLimit(context);
@ -217,7 +245,7 @@ namespace Ryujinx.HLE.HOS
KProcess process = new KProcess(context); KProcess process = new KProcess(context);
MemoryRegion memoryRegion = (MemoryRegion)((metaData.Acid.Flags >> 2) & 0xf); MemoryRegion memoryRegion = (MemoryRegion)((npdm.Acid.Value.Flags >> 2) & 0xf);
if (memoryRegion > MemoryRegion.NvServices) if (memoryRegion > MemoryRegion.NvServices)
{ {
@ -232,7 +260,7 @@ namespace Ryujinx.HLE.HOS
result = process.Initialize( result = process.Initialize(
creationInfo, creationInfo,
metaData.Aci0.KernelAccessControl.Capabilities, MemoryMarshal.Cast<byte, int>(npdm.KernelCapabilityData).ToArray(),
resourceLimit, resourceLimit,
memoryRegion, memoryRegion,
processContextFactory); processContextFactory);
@ -262,9 +290,9 @@ namespace Ryujinx.HLE.HOS
} }
} }
process.DefaultCpuCore = metaData.DefaultCpuId; process.DefaultCpuCore = meta.DefaultCpuId;
result = process.Start(metaData.MainThreadPriority, (ulong)metaData.MainThreadStackSize); result = process.Start(meta.MainThreadPriority, meta.MainThreadStackSize);
if (result != KernelResult.Success) if (result != KernelResult.Success)
{ {

View file

@ -2,12 +2,9 @@
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.Shim; using LibHac.Fs.Shim;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.FileSystem.Content;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
namespace Ryujinx.HLE.HOS.Services.Account.Acc namespace Ryujinx.HLE.HOS.Services.Account.Acc
@ -16,16 +13,20 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
{ {
public static readonly UserId DefaultUserId = new UserId("00000000000000010000000000000000"); public static readonly UserId DefaultUserId = new UserId("00000000000000010000000000000000");
private readonly VirtualFileSystem _virtualFileSystem;
private readonly AccountSaveDataManager _accountSaveDataManager; private readonly AccountSaveDataManager _accountSaveDataManager;
// Todo: The account service doesn't have the permissions to delete save data. Qlaunch takes care of deleting
// save data, so we're currently passing a client with full permissions. Consider moving save data deletion
// outside of the AccountManager.
private readonly HorizonClient _horizonClient;
private ConcurrentDictionary<string, UserProfile> _profiles; private ConcurrentDictionary<string, UserProfile> _profiles;
public UserProfile LastOpenedUser { get; private set; } public UserProfile LastOpenedUser { get; private set; }
public AccountManager(VirtualFileSystem virtualFileSystem) public AccountManager(HorizonClient horizonClient)
{ {
_virtualFileSystem = virtualFileSystem; _horizonClient = horizonClient;
_profiles = new ConcurrentDictionary<string, UserProfile>(); _profiles = new ConcurrentDictionary<string, UserProfile>();
@ -169,14 +170,13 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
SaveDataFilter saveDataFilter = new SaveDataFilter(); SaveDataFilter saveDataFilter = new SaveDataFilter();
saveDataFilter.SetUserId(new LibHac.Fs.UserId((ulong)userId.High, (ulong)userId.Low)); saveDataFilter.SetUserId(new LibHac.Fs.UserId((ulong)userId.High, (ulong)userId.Low));
Result result = _virtualFileSystem.FsClient.OpenSaveDataIterator(out SaveDataIterator saveDataIterator, SaveDataSpaceId.User, ref saveDataFilter); _horizonClient.Fs.OpenSaveDataIterator(out SaveDataIterator saveDataIterator, SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
if (result.IsSuccess())
{
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10]; Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
while (true) while (true)
{ {
saveDataIterator.ReadSaveDataInfo(out long readCount, saveDataInfo); saveDataIterator.ReadSaveDataInfo(out long readCount, saveDataInfo).ThrowIfFailure();
if (readCount == 0) if (readCount == 0)
{ {
@ -185,15 +185,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
for (int i = 0; i < readCount; i++) for (int i = 0; i < readCount; i++)
{ {
// TODO: We use Directory.Delete workaround because DeleteSaveData softlock without, due to a bug in LibHac 0.12.0. _horizonClient.Fs.DeleteSaveData(SaveDataSpaceId.User, saveDataInfo[i].SaveDataId).ThrowIfFailure();
string savePath = Path.Combine(_virtualFileSystem.GetNandPath(), $"user/save/{saveDataInfo[i].SaveDataId:x16}");
string saveMetaPath = Path.Combine(_virtualFileSystem.GetNandPath(), $"user/saveMeta/{saveDataInfo[i].SaveDataId:x16}");
Directory.Delete(savePath, true);
Directory.Delete(saveMetaPath, true);
_virtualFileSystem.FsClient.DeleteSaveData(SaveDataSpaceId.User, saveDataInfo[i].SaveDataId);
}
} }
} }
} }

View file

@ -37,6 +37,8 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
private int _notificationStorageChannelEventHandle; private int _notificationStorageChannelEventHandle;
private int _healthWarningDisappearedSystemEventHandle; private int _healthWarningDisappearedSystemEventHandle;
private HorizonClient _horizon;
public IApplicationFunctions(Horizon system) public IApplicationFunctions(Horizon system)
{ {
// TODO: Find where they are signaled. // TODO: Find where they are signaled.
@ -44,6 +46,8 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
_friendInvitationStorageChannelEvent = new KEvent(system.KernelContext); _friendInvitationStorageChannelEvent = new KEvent(system.KernelContext);
_notificationStorageChannelEvent = new KEvent(system.KernelContext); _notificationStorageChannelEvent = new KEvent(system.KernelContext);
_healthWarningDisappearedSystemEvent = new KEvent(system.KernelContext); _healthWarningDisappearedSystemEvent = new KEvent(system.KernelContext);
_horizon = system.LibHacHorizonManager.AmClient;
} }
[CommandHipc(1)] [CommandHipc(1)]
@ -104,13 +108,15 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
public ResultCode EnsureSaveData(ServiceCtx context) public ResultCode EnsureSaveData(ServiceCtx context)
{ {
Uid userId = context.RequestData.ReadStruct<AccountUid>().ToLibHacUid(); Uid userId = context.RequestData.ReadStruct<AccountUid>().ToLibHacUid();
ApplicationId applicationId = new ApplicationId(context.Process.TitleId);
// Mask out the low nibble of the program ID to get the application ID
ApplicationId applicationId = new ApplicationId(context.Device.Application.TitleId & ~0xFul);
BlitStruct<ApplicationControlProperty> controlHolder = context.Device.Application.ControlData; BlitStruct<ApplicationControlProperty> controlHolder = context.Device.Application.ControlData;
ref ApplicationControlProperty control = ref controlHolder.Value; ref ApplicationControlProperty control = ref controlHolder.Value;
if (LibHac.Utilities.IsEmpty(controlHolder.ByteSpan)) if (LibHac.Utilities.IsZeros(controlHolder.ByteSpan))
{ {
// If the current application doesn't have a loaded control property, create a dummy one // 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. // and set the savedata sizes so a user savedata will be created.
@ -124,7 +130,8 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
"No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games."); "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
} }
Result result = EnsureApplicationSaveData(context.Device.FileSystem.FsClient, out long requiredSize, applicationId, ref control, ref userId); HorizonClient hos = context.Device.System.LibHacHorizonManager.AmClient;
Result result = EnsureApplicationSaveData(hos.Fs, out long requiredSize, applicationId, ref control, ref userId);
context.ResponseData.Write(requiredSize); context.ResponseData.Write(requiredSize);
@ -195,7 +202,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
public ResultCode ExtendSaveData(ServiceCtx context) public ResultCode ExtendSaveData(ServiceCtx context)
{ {
SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadUInt64(); SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadUInt64();
Uid userId = context.RequestData.ReadStruct<AccountUid>().ToLibHacUid(); Uid userId = context.RequestData.ReadStruct<Uid>();
ulong saveDataSize = context.RequestData.ReadUInt64(); ulong saveDataSize = context.RequestData.ReadUInt64();
ulong journalSize = context.RequestData.ReadUInt64(); ulong journalSize = context.RequestData.ReadUInt64();
@ -217,7 +224,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
public ResultCode GetSaveDataSize(ServiceCtx context) public ResultCode GetSaveDataSize(ServiceCtx context)
{ {
SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadUInt64(); SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadUInt64();
Uid userId = context.RequestData.ReadStruct<AccountUid>().ToLibHacUid(); Uid userId = context.RequestData.ReadStruct<Uid>();
// NOTE: Service calls nn::fs::FindSaveDataWithFilter with SaveDataType = 1 hardcoded. // NOTE: Service calls nn::fs::FindSaveDataWithFilter with SaveDataType = 1 hardcoded.
// Then it calls nn::fs::GetSaveDataAvailableSize and nn::fs::GetSaveDataJournalSize to get the sizes. // Then it calls nn::fs::GetSaveDataAvailableSize and nn::fs::GetSaveDataJournalSize to get the sizes.
@ -231,6 +238,31 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
return ResultCode.Success; return ResultCode.Success;
} }
[CommandHipc(27)] // 5.0.0+
// CreateCacheStorage(u16 index, s64 save_size, s64 journal_size) -> (u32 storageTarget, u64 requiredSize)
public ResultCode CreateCacheStorage(ServiceCtx context)
{
ushort index = (ushort)context.RequestData.ReadUInt64();
long saveSize = context.RequestData.ReadInt64();
long journalSize = context.RequestData.ReadInt64();
// Mask out the low nibble of the program ID to get the application ID
ApplicationId applicationId = new ApplicationId(context.Device.Application.TitleId & ~0xFul);
BlitStruct<ApplicationControlProperty> controlHolder = context.Device.Application.ControlData;
Result result = _horizon.Fs.CreateApplicationCacheStorage(out long requiredSize,
out CacheStorageTargetMedia storageTarget, applicationId, ref controlHolder.Value, index, saveSize,
journalSize);
if (result.IsFailure()) return (ResultCode)result.Value;
context.ResponseData.Write((ulong)storageTarget);
context.ResponseData.Write(requiredSize);
return ResultCode.Success;
}
[CommandHipc(30)] [CommandHipc(30)]
// BeginBlockingHomeButtonShortAndLongPressed() // BeginBlockingHomeButtonShortAndLongPressed()
public ResultCode BeginBlockingHomeButtonShortAndLongPressed(ServiceCtx context) public ResultCode BeginBlockingHomeButtonShortAndLongPressed(ServiceCtx context)
@ -517,7 +549,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_gpuErrorDetectedSystemEventHandle); context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_gpuErrorDetectedSystemEventHandle);
// NOTE: This is used by "sdk" NSO during applet-application initialization. // NOTE: This is used by "sdk" NSO during applet-application initialization.
// A seperate thread is setup where event-waiting is handled. // A separate thread is setup where event-waiting is handled.
// When the Event is signaled, official sw will assert. // When the Event is signaled, official sw will assert.
return ResultCode.Success; return ResultCode.Success;

View file

@ -9,19 +9,14 @@ namespace Ryujinx.HLE.HOS.Services.Arp
{ {
class LibHacIReader : LibHac.Arp.Impl.IReader class LibHacIReader : LibHac.Arp.Impl.IReader
{ {
private Horizon System { get; } public ApplicationId ApplicationId { get; set; }
public LibHacIReader(Horizon system)
{
System = system;
}
public Result GetApplicationLaunchProperty(out LibHac.Arp.ApplicationLaunchProperty launchProperty, ulong processId) public Result GetApplicationLaunchProperty(out LibHac.Arp.ApplicationLaunchProperty launchProperty, ulong processId)
{ {
launchProperty = new LibHac.Arp.ApplicationLaunchProperty launchProperty = new LibHac.Arp.ApplicationLaunchProperty
{ {
BaseStorageId = StorageId.BuiltInUser, BaseStorageId = StorageId.BuiltInUser,
ApplicationId = new ApplicationId(System.Device.Application.TitleId) ApplicationId = ApplicationId
}; };
return Result.Success; return Result.Success;
@ -47,5 +42,27 @@ namespace Ryujinx.HLE.HOS.Services.Arp
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public Result GetServiceObject(out object serviceObject)
{
throw new NotImplementedException();
}
}
internal class LibHacArpServiceObject : LibHac.Sm.IServiceObject
{
private LibHacIReader _serviceObject;
public LibHacArpServiceObject(LibHacIReader serviceObject)
{
_serviceObject = serviceObject;
}
public Result GetServiceObject(out object serviceObject)
{
serviceObject = _serviceObject;
return Result.Success;
}
} }
} }

View file

@ -11,11 +11,12 @@ namespace Ryujinx.HLE.HOS.Services.Bcat
[Service("bcat:s", "bcat:s")] [Service("bcat:s", "bcat:s")]
class IServiceCreator : IpcService class IServiceCreator : IpcService
{ {
private LibHac.Bcat.Detail.Ipc.IServiceCreator _base; private LibHac.Bcat.Impl.Ipc.IServiceCreator _base;
public IServiceCreator(ServiceCtx context, string serviceName) public IServiceCreator(ServiceCtx context, string serviceName)
{ {
context.Device.System.LibHacHorizonClient.Sm.GetService(out _base, serviceName).ThrowIfFailure(); var applicationClient = context.Device.System.LibHacHorizonManager.ApplicationClient;
applicationClient.Sm.GetService(out _base, serviceName).ThrowIfFailure();
} }
[CommandHipc(0)] [CommandHipc(0)]
@ -42,7 +43,7 @@ namespace Ryujinx.HLE.HOS.Services.Bcat
{ {
ulong pid = context.RequestData.ReadUInt64(); ulong pid = context.RequestData.ReadUInt64();
Result rc = _base.CreateDeliveryCacheStorageService(out LibHac.Bcat.Detail.Ipc.IDeliveryCacheStorageService serv, pid); Result rc = _base.CreateDeliveryCacheStorageService(out LibHac.Bcat.Impl.Ipc.IDeliveryCacheStorageService serv, pid);
if (rc.IsSuccess()) if (rc.IsSuccess())
{ {
@ -58,7 +59,7 @@ namespace Ryujinx.HLE.HOS.Services.Bcat
{ {
ApplicationId applicationId = context.RequestData.ReadStruct<ApplicationId>(); ApplicationId applicationId = context.RequestData.ReadStruct<ApplicationId>();
Result rc = _base.CreateDeliveryCacheStorageServiceWithApplicationId(out LibHac.Bcat.Detail.Ipc.IDeliveryCacheStorageService serv, Result rc = _base.CreateDeliveryCacheStorageServiceWithApplicationId(out LibHac.Bcat.Impl.Ipc.IDeliveryCacheStorageService serv,
applicationId); applicationId);
if (rc.IsSuccess()) if (rc.IsSuccess())

View file

@ -7,9 +7,9 @@ namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator
{ {
class IDeliveryCacheDirectoryService : DisposableIpcService class IDeliveryCacheDirectoryService : DisposableIpcService
{ {
private LibHac.Bcat.Detail.Ipc.IDeliveryCacheDirectoryService _base; private LibHac.Bcat.Impl.Ipc.IDeliveryCacheDirectoryService _base;
public IDeliveryCacheDirectoryService(LibHac.Bcat.Detail.Ipc.IDeliveryCacheDirectoryService baseService) public IDeliveryCacheDirectoryService(LibHac.Bcat.Impl.Ipc.IDeliveryCacheDirectoryService baseService)
{ {
_base = baseService; _base = baseService;
} }

View file

@ -6,9 +6,9 @@ namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator
{ {
class IDeliveryCacheFileService : DisposableIpcService class IDeliveryCacheFileService : DisposableIpcService
{ {
private LibHac.Bcat.Detail.Ipc.IDeliveryCacheFileService _base; private LibHac.Bcat.Impl.Ipc.IDeliveryCacheFileService _base;
public IDeliveryCacheFileService(LibHac.Bcat.Detail.Ipc.IDeliveryCacheFileService baseService) public IDeliveryCacheFileService(LibHac.Bcat.Impl.Ipc.IDeliveryCacheFileService baseService)
{ {
_base = baseService; _base = baseService;
} }

View file

@ -6,9 +6,9 @@ namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator
{ {
class IDeliveryCacheStorageService : DisposableIpcService class IDeliveryCacheStorageService : DisposableIpcService
{ {
private LibHac.Bcat.Detail.Ipc.IDeliveryCacheStorageService _base; private LibHac.Bcat.Impl.Ipc.IDeliveryCacheStorageService _base;
public IDeliveryCacheStorageService(ServiceCtx context, LibHac.Bcat.Detail.Ipc.IDeliveryCacheStorageService baseService) public IDeliveryCacheStorageService(ServiceCtx context, LibHac.Bcat.Impl.Ipc.IDeliveryCacheStorageService baseService)
{ {
_base = baseService; _base = baseService;
} }
@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator
// CreateFileService() -> object<nn::bcat::detail::ipc::IDeliveryCacheFileService> // CreateFileService() -> object<nn::bcat::detail::ipc::IDeliveryCacheFileService>
public ResultCode CreateFileService(ServiceCtx context) public ResultCode CreateFileService(ServiceCtx context)
{ {
Result result = _base.CreateFileService(out LibHac.Bcat.Detail.Ipc.IDeliveryCacheFileService service); Result result = _base.CreateFileService(out LibHac.Bcat.Impl.Ipc.IDeliveryCacheFileService service);
if (result.IsSuccess()) if (result.IsSuccess())
{ {
@ -31,7 +31,7 @@ namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator
// CreateDirectoryService() -> object<nn::bcat::detail::ipc::IDeliveryCacheDirectoryService> // CreateDirectoryService() -> object<nn::bcat::detail::ipc::IDeliveryCacheDirectoryService>
public ResultCode CreateDirectoryService(ServiceCtx context) public ResultCode CreateDirectoryService(ServiceCtx context)
{ {
Result result = _base.CreateDirectoryService(out LibHac.Bcat.Detail.Ipc.IDeliveryCacheDirectoryService service); Result result = _base.CreateDirectoryService(out LibHac.Bcat.Impl.Ipc.IDeliveryCacheDirectoryService service);
if (result.IsSuccess()) if (result.IsSuccess())
{ {

View file

@ -1,10 +1,16 @@
using LibHac; using LibHac;
using LibHac.Common; using LibHac.Common;
using LibHac.Common.Keys;
using LibHac.Fs; using LibHac.Fs;
using LibHac.FsSrv.Impl;
using LibHac.FsSrv.Sf;
using LibHac.FsSystem; using LibHac.FsSystem;
using LibHac.FsSystem.NcaUtils; using LibHac.FsSystem.NcaUtils;
using LibHac.Spl; using LibHac.Spl;
using System;
using System.IO; using System.IO;
using System.Runtime.InteropServices;
using Path = System.IO.Path;
namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
{ {
@ -17,11 +23,11 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
try try
{ {
LocalStorage storage = new LocalStorage(pfsPath, FileAccess.Read, FileMode.Open); LocalStorage storage = new LocalStorage(pfsPath, FileAccess.Read, FileMode.Open);
PartitionFileSystem nsp = new PartitionFileSystem(storage); ReferenceCountedDisposable<LibHac.Fs.Fsa.IFileSystem> nsp = new(new PartitionFileSystem(storage));
ImportTitleKeysFromNsp(nsp, context.Device.System.KeySet); ImportTitleKeysFromNsp(nsp.Target, context.Device.System.KeySet);
openedFileSystem = new IFileSystem(nsp); openedFileSystem = new IFileSystem(FileSystemInterfaceAdapter.CreateShared(ref nsp));
} }
catch (HorizonResultException ex) catch (HorizonResultException ex)
{ {
@ -45,8 +51,9 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
} }
LibHac.Fs.Fsa.IFileSystem fileSystem = nca.OpenFileSystem(NcaSectionType.Data, context.Device.System.FsIntegrityCheckLevel); LibHac.Fs.Fsa.IFileSystem fileSystem = nca.OpenFileSystem(NcaSectionType.Data, context.Device.System.FsIntegrityCheckLevel);
var sharedFs = new ReferenceCountedDisposable<LibHac.Fs.Fsa.IFileSystem>(fileSystem);
openedFileSystem = new IFileSystem(fileSystem); openedFileSystem = new IFileSystem(FileSystemInterfaceAdapter.CreateShared(ref sharedFs));
} }
catch (HorizonResultException ex) catch (HorizonResultException ex)
{ {
@ -99,7 +106,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
return ResultCode.PathDoesNotExist; return ResultCode.PathDoesNotExist;
} }
public static void ImportTitleKeysFromNsp(LibHac.Fs.Fsa.IFileSystem nsp, Keyset keySet) public static void ImportTitleKeysFromNsp(LibHac.Fs.Fsa.IFileSystem nsp, KeySet keySet)
{ {
foreach (DirectoryEntryEx ticketEntry in nsp.EnumerateEntries("/", "*.tik")) foreach (DirectoryEntryEx ticketEntry in nsp.EnumerateEntries("/", "*.tik"))
{ {
@ -125,5 +132,27 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
return FsPath.FromSpan(out path, pathBytes); return FsPath.FromSpan(out path, pathBytes);
} }
public static ref readonly FspPath GetFspPath(ServiceCtx context, int index = 0)
{
ulong position = (ulong)context.Request.PtrBuff[index].Position;
ulong size = (ulong)context.Request.PtrBuff[index].Size;
ReadOnlySpan<byte> buffer = context.Memory.GetSpan(position, (int)size);
ReadOnlySpan<FspPath> fspBuffer = MemoryMarshal.Cast<byte, FspPath>(buffer);
return ref fspBuffer[0];
}
public static ref readonly LibHac.FsSrv.Sf.Path GetSfPath(ServiceCtx context, int index = 0)
{
ulong position = (ulong)context.Request.PtrBuff[index].Position;
ulong size = (ulong)context.Request.PtrBuff[index].Size;
ReadOnlySpan<byte> buffer = context.Memory.GetSpan(position, (int)size);
ReadOnlySpan<LibHac.FsSrv.Sf.Path> pathBuffer = MemoryMarshal.Cast<byte, LibHac.FsSrv.Sf.Path>(buffer);
return ref pathBuffer[0];
}
} }
} }

View file

@ -1,15 +1,13 @@
using LibHac; using LibHac;
using LibHac.Fs; using LibHac.Sf;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
{ {
class IDirectory : IpcService class IDirectory : DisposableIpcService
{ {
private LibHac.Fs.Fsa.IDirectory _baseDirectory; private ReferenceCountedDisposable<LibHac.FsSrv.Sf.IDirectory> _baseDirectory;
public IDirectory(LibHac.Fs.Fsa.IDirectory directory) public IDirectory(ReferenceCountedDisposable<LibHac.FsSrv.Sf.IDirectory> directory)
{ {
_baseDirectory = directory; _baseDirectory = directory;
} }
@ -21,12 +19,11 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
ulong bufferPosition = context.Request.ReceiveBuff[0].Position; ulong bufferPosition = context.Request.ReceiveBuff[0].Position;
ulong bufferLen = context.Request.ReceiveBuff[0].Size; ulong bufferLen = context.Request.ReceiveBuff[0].Size;
byte[] entriesBytes = new byte[bufferLen]; byte[] entryBuffer = new byte[bufferLen];
Span<DirectoryEntry> entries = MemoryMarshal.Cast<byte, DirectoryEntry>(entriesBytes);
Result result = _baseDirectory.Read(out long entriesRead, entries); Result result = _baseDirectory.Target.Read(out long entriesRead, new OutBuffer(entryBuffer));
context.Memory.Write(bufferPosition, entriesBytes); context.Memory.Write(bufferPosition, entryBuffer);
context.ResponseData.Write(entriesRead); context.ResponseData.Write(entriesRead);
return (ResultCode)result.Value; return (ResultCode)result.Value;
@ -36,11 +33,19 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
// GetEntryCount() -> u64 // GetEntryCount() -> u64
public ResultCode GetEntryCount(ServiceCtx context) public ResultCode GetEntryCount(ServiceCtx context)
{ {
Result result = _baseDirectory.GetEntryCount(out long entryCount); Result result = _baseDirectory.Target.GetEntryCount(out long entryCount);
context.ResponseData.Write(entryCount); context.ResponseData.Write(entryCount);
return (ResultCode)result.Value; return (ResultCode)result.Value;
} }
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
_baseDirectory?.Dispose();
}
}
} }
} }

View file

@ -1,13 +1,15 @@
using LibHac; using LibHac;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Sf;
using Ryujinx.Common;
namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
{ {
class IFile : DisposableIpcService class IFile : DisposableIpcService
{ {
private LibHac.Fs.Fsa.IFile _baseFile; private ReferenceCountedDisposable<LibHac.FsSrv.Sf.IFile> _baseFile;
public IFile(LibHac.Fs.Fsa.IFile baseFile) public IFile(ReferenceCountedDisposable<LibHac.FsSrv.Sf.IFile> baseFile)
{ {
_baseFile = baseFile; _baseFile = baseFile;
} }
@ -18,15 +20,15 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
{ {
ulong position = context.Request.ReceiveBuff[0].Position; ulong position = context.Request.ReceiveBuff[0].Position;
ReadOption readOption = new ReadOption(context.RequestData.ReadInt32()); ReadOption readOption = context.RequestData.ReadStruct<ReadOption>();
context.RequestData.BaseStream.Position += 4; context.RequestData.BaseStream.Position += 4;
long offset = context.RequestData.ReadInt64(); long offset = context.RequestData.ReadInt64();
long size = context.RequestData.ReadInt64(); long size = context.RequestData.ReadInt64();
byte[] data = new byte[size]; byte[] data = new byte[context.Request.ReceiveBuff[0].Size];
Result result = _baseFile.Read(out long bytesRead, offset, data, readOption); Result result = _baseFile.Target.Read(out long bytesRead, offset, new OutBuffer(data), size, readOption);
context.Memory.Write(position, data); context.Memory.Write(position, data);
@ -41,24 +43,24 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
{ {
ulong position = context.Request.SendBuff[0].Position; ulong position = context.Request.SendBuff[0].Position;
WriteOption writeOption = new WriteOption(context.RequestData.ReadInt32()); WriteOption writeOption = context.RequestData.ReadStruct<WriteOption>();
context.RequestData.BaseStream.Position += 4; context.RequestData.BaseStream.Position += 4;
long offset = context.RequestData.ReadInt64(); long offset = context.RequestData.ReadInt64();
long size = context.RequestData.ReadInt64(); long size = context.RequestData.ReadInt64();
byte[] data = new byte[size]; byte[] data = new byte[context.Request.SendBuff[0].Size];
context.Memory.Read(position, data); context.Memory.Read(position, data);
return (ResultCode)_baseFile.Write(offset, data, writeOption).Value; return (ResultCode)_baseFile.Target.Write(offset, new InBuffer(data), size, writeOption).Value;
} }
[CommandHipc(2)] [CommandHipc(2)]
// Flush() // Flush()
public ResultCode Flush(ServiceCtx context) public ResultCode Flush(ServiceCtx context)
{ {
return (ResultCode)_baseFile.Flush().Value; return (ResultCode)_baseFile.Target.Flush().Value;
} }
[CommandHipc(3)] [CommandHipc(3)]
@ -67,14 +69,14 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
{ {
long size = context.RequestData.ReadInt64(); long size = context.RequestData.ReadInt64();
return (ResultCode)_baseFile.SetSize(size).Value; return (ResultCode)_baseFile.Target.SetSize(size).Value;
} }
[CommandHipc(4)] [CommandHipc(4)]
// GetSize() -> u64 fileSize // GetSize() -> u64 fileSize
public ResultCode GetSize(ServiceCtx context) public ResultCode GetSize(ServiceCtx context)
{ {
Result result = _baseFile.GetSize(out long size); Result result = _baseFile.Target.GetSize(out long size);
context.ResponseData.Write(size); context.ResponseData.Write(size);

View file

@ -1,21 +1,19 @@
using LibHac; using LibHac;
using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.Fsa; using LibHac.FsSrv.Sf;
using static Ryujinx.HLE.Utilities.StringUtils;
namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
{ {
class IFileSystem : IpcService class IFileSystem : DisposableIpcService
{ {
private LibHac.Fs.Fsa.IFileSystem _fileSystem; private ReferenceCountedDisposable<LibHac.FsSrv.Sf.IFileSystem> _fileSystem;
public IFileSystem(LibHac.Fs.Fsa.IFileSystem provider) public IFileSystem(ReferenceCountedDisposable<LibHac.FsSrv.Sf.IFileSystem> provider)
{ {
_fileSystem = provider; _fileSystem = provider;
} }
public LibHac.Fs.Fsa.IFileSystem GetBaseFileSystem() public ReferenceCountedDisposable<LibHac.FsSrv.Sf.IFileSystem> GetBaseFileSystem()
{ {
return _fileSystem; return _fileSystem;
} }
@ -24,79 +22,79 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
// CreateFile(u32 createOption, u64 size, buffer<bytes<0x301>, 0x19, 0x301> path) // CreateFile(u32 createOption, u64 size, buffer<bytes<0x301>, 0x19, 0x301> path)
public ResultCode CreateFile(ServiceCtx context) public ResultCode CreateFile(ServiceCtx context)
{ {
U8Span name = ReadUtf8Span(context); ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context);
CreateFileOptions createOption = (CreateFileOptions)context.RequestData.ReadInt32(); int createOption = context.RequestData.ReadInt32();
context.RequestData.BaseStream.Position += 4; context.RequestData.BaseStream.Position += 4;
long size = context.RequestData.ReadInt64(); long size = context.RequestData.ReadInt64();
return (ResultCode)_fileSystem.CreateFile(name, size, createOption).Value; return (ResultCode)_fileSystem.Target.CreateFile(in name, size, createOption).Value;
} }
[CommandHipc(1)] [CommandHipc(1)]
// DeleteFile(buffer<bytes<0x301>, 0x19, 0x301> path) // DeleteFile(buffer<bytes<0x301>, 0x19, 0x301> path)
public ResultCode DeleteFile(ServiceCtx context) public ResultCode DeleteFile(ServiceCtx context)
{ {
U8Span name = ReadUtf8Span(context); ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context);
return (ResultCode)_fileSystem.DeleteFile(name).Value; return (ResultCode)_fileSystem.Target.DeleteFile(in name).Value;
} }
[CommandHipc(2)] [CommandHipc(2)]
// CreateDirectory(buffer<bytes<0x301>, 0x19, 0x301> path) // CreateDirectory(buffer<bytes<0x301>, 0x19, 0x301> path)
public ResultCode CreateDirectory(ServiceCtx context) public ResultCode CreateDirectory(ServiceCtx context)
{ {
U8Span name = ReadUtf8Span(context); ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context);
return (ResultCode)_fileSystem.CreateDirectory(name).Value; return (ResultCode)_fileSystem.Target.CreateDirectory(in name).Value;
} }
[CommandHipc(3)] [CommandHipc(3)]
// DeleteDirectory(buffer<bytes<0x301>, 0x19, 0x301> path) // DeleteDirectory(buffer<bytes<0x301>, 0x19, 0x301> path)
public ResultCode DeleteDirectory(ServiceCtx context) public ResultCode DeleteDirectory(ServiceCtx context)
{ {
U8Span name = ReadUtf8Span(context); ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context);
return (ResultCode)_fileSystem.DeleteDirectory(name).Value; return (ResultCode)_fileSystem.Target.DeleteDirectory(in name).Value;
} }
[CommandHipc(4)] [CommandHipc(4)]
// DeleteDirectoryRecursively(buffer<bytes<0x301>, 0x19, 0x301> path) // DeleteDirectoryRecursively(buffer<bytes<0x301>, 0x19, 0x301> path)
public ResultCode DeleteDirectoryRecursively(ServiceCtx context) public ResultCode DeleteDirectoryRecursively(ServiceCtx context)
{ {
U8Span name = ReadUtf8Span(context); ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context);
return (ResultCode)_fileSystem.DeleteDirectoryRecursively(name).Value; return (ResultCode)_fileSystem.Target.DeleteDirectoryRecursively(in name).Value;
} }
[CommandHipc(5)] [CommandHipc(5)]
// RenameFile(buffer<bytes<0x301>, 0x19, 0x301> oldPath, buffer<bytes<0x301>, 0x19, 0x301> newPath) // RenameFile(buffer<bytes<0x301>, 0x19, 0x301> oldPath, buffer<bytes<0x301>, 0x19, 0x301> newPath)
public ResultCode RenameFile(ServiceCtx context) public ResultCode RenameFile(ServiceCtx context)
{ {
U8Span oldName = ReadUtf8Span(context, 0); ref readonly Path currentName = ref FileSystemProxyHelper.GetSfPath(context, index: 0);
U8Span newName = ReadUtf8Span(context, 1); ref readonly Path newName = ref FileSystemProxyHelper.GetSfPath(context, index: 1);
return (ResultCode)_fileSystem.RenameFile(oldName, newName).Value; return (ResultCode)_fileSystem.Target.RenameFile(in currentName, in newName).Value;
} }
[CommandHipc(6)] [CommandHipc(6)]
// RenameDirectory(buffer<bytes<0x301>, 0x19, 0x301> oldPath, buffer<bytes<0x301>, 0x19, 0x301> newPath) // RenameDirectory(buffer<bytes<0x301>, 0x19, 0x301> oldPath, buffer<bytes<0x301>, 0x19, 0x301> newPath)
public ResultCode RenameDirectory(ServiceCtx context) public ResultCode RenameDirectory(ServiceCtx context)
{ {
U8Span oldName = ReadUtf8Span(context, 0); ref readonly Path currentName = ref FileSystemProxyHelper.GetSfPath(context, index: 0);
U8Span newName = ReadUtf8Span(context, 1); ref readonly Path newName = ref FileSystemProxyHelper.GetSfPath(context, index: 1);
return (ResultCode)_fileSystem.RenameDirectory(oldName, newName).Value; return (ResultCode)_fileSystem.Target.RenameDirectory(in currentName, in newName).Value;
} }
[CommandHipc(7)] [CommandHipc(7)]
// GetEntryType(buffer<bytes<0x301>, 0x19, 0x301> path) -> nn::fssrv::sf::DirectoryEntryType // GetEntryType(buffer<bytes<0x301>, 0x19, 0x301> path) -> nn::fssrv::sf::DirectoryEntryType
public ResultCode GetEntryType(ServiceCtx context) public ResultCode GetEntryType(ServiceCtx context)
{ {
U8Span name = ReadUtf8Span(context); ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context);
Result result = _fileSystem.GetEntryType(out DirectoryEntryType entryType, name); Result result = _fileSystem.Target.GetEntryType(out uint entryType, in name);
context.ResponseData.Write((int)entryType); context.ResponseData.Write((int)entryType);
@ -107,11 +105,11 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
// OpenFile(u32 mode, buffer<bytes<0x301>, 0x19, 0x301> path) -> object<nn::fssrv::sf::IFile> file // OpenFile(u32 mode, buffer<bytes<0x301>, 0x19, 0x301> path) -> object<nn::fssrv::sf::IFile> file
public ResultCode OpenFile(ServiceCtx context) public ResultCode OpenFile(ServiceCtx context)
{ {
OpenMode mode = (OpenMode)context.RequestData.ReadInt32(); uint mode = context.RequestData.ReadUInt32();
U8Span name = ReadUtf8Span(context); ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context);
Result result = _fileSystem.OpenFile(out LibHac.Fs.Fsa.IFile file, name, mode); Result result = _fileSystem.Target.OpenFile(out ReferenceCountedDisposable<LibHac.FsSrv.Sf.IFile> file, in name, mode);
if (result.IsSuccess()) if (result.IsSuccess())
{ {
@ -127,11 +125,11 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
// OpenDirectory(u32 filter_flags, buffer<bytes<0x301>, 0x19, 0x301> path) -> object<nn::fssrv::sf::IDirectory> directory // OpenDirectory(u32 filter_flags, buffer<bytes<0x301>, 0x19, 0x301> path) -> object<nn::fssrv::sf::IDirectory> directory
public ResultCode OpenDirectory(ServiceCtx context) public ResultCode OpenDirectory(ServiceCtx context)
{ {
OpenDirectoryMode mode = (OpenDirectoryMode)context.RequestData.ReadInt32(); uint mode = context.RequestData.ReadUInt32();
U8Span name = ReadUtf8Span(context); ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context);
Result result = _fileSystem.OpenDirectory(out LibHac.Fs.Fsa.IDirectory dir, name, mode); Result result = _fileSystem.Target.OpenDirectory(out ReferenceCountedDisposable<LibHac.FsSrv.Sf.IDirectory> dir, name, mode);
if (result.IsSuccess()) if (result.IsSuccess())
{ {
@ -147,16 +145,16 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
// Commit() // Commit()
public ResultCode Commit(ServiceCtx context) public ResultCode Commit(ServiceCtx context)
{ {
return (ResultCode)_fileSystem.Commit().Value; return (ResultCode)_fileSystem.Target.Commit().Value;
} }
[CommandHipc(11)] [CommandHipc(11)]
// GetFreeSpaceSize(buffer<bytes<0x301>, 0x19, 0x301> path) -> u64 totalFreeSpace // GetFreeSpaceSize(buffer<bytes<0x301>, 0x19, 0x301> path) -> u64 totalFreeSpace
public ResultCode GetFreeSpaceSize(ServiceCtx context) public ResultCode GetFreeSpaceSize(ServiceCtx context)
{ {
U8Span name = ReadUtf8Span(context); ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context);
Result result = _fileSystem.GetFreeSpaceSize(out long size, name); Result result = _fileSystem.Target.GetFreeSpaceSize(out long size, in name);
context.ResponseData.Write(size); context.ResponseData.Write(size);
@ -167,9 +165,9 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
// GetTotalSpaceSize(buffer<bytes<0x301>, 0x19, 0x301> path) -> u64 totalSize // GetTotalSpaceSize(buffer<bytes<0x301>, 0x19, 0x301> path) -> u64 totalSize
public ResultCode GetTotalSpaceSize(ServiceCtx context) public ResultCode GetTotalSpaceSize(ServiceCtx context)
{ {
U8Span name = ReadUtf8Span(context); ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context);
Result result = _fileSystem.GetTotalSpaceSize(out long size, name); Result result = _fileSystem.Target.GetTotalSpaceSize(out long size, in name);
context.ResponseData.Write(size); context.ResponseData.Write(size);
@ -180,18 +178,18 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
// CleanDirectoryRecursively(buffer<bytes<0x301>, 0x19, 0x301> path) // CleanDirectoryRecursively(buffer<bytes<0x301>, 0x19, 0x301> path)
public ResultCode CleanDirectoryRecursively(ServiceCtx context) public ResultCode CleanDirectoryRecursively(ServiceCtx context)
{ {
U8Span name = ReadUtf8Span(context); ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context);
return (ResultCode)_fileSystem.CleanDirectoryRecursively(name).Value; return (ResultCode)_fileSystem.Target.CleanDirectoryRecursively(in name).Value;
} }
[CommandHipc(14)] [CommandHipc(14)]
// GetFileTimeStampRaw(buffer<bytes<0x301>, 0x19, 0x301> path) -> bytes<0x20> timestamp // GetFileTimeStampRaw(buffer<bytes<0x301>, 0x19, 0x301> path) -> bytes<0x20> timestamp
public ResultCode GetFileTimeStampRaw(ServiceCtx context) public ResultCode GetFileTimeStampRaw(ServiceCtx context)
{ {
U8Span name = ReadUtf8Span(context); ref readonly Path name = ref FileSystemProxyHelper.GetSfPath(context);
Result result = _fileSystem.GetFileTimeStampRaw(out FileTimeStampRaw timestamp, name); Result result = _fileSystem.Target.GetFileTimeStampRaw(out FileTimeStampRaw timestamp, in name);
context.ResponseData.Write(timestamp.Created); context.ResponseData.Write(timestamp.Created);
context.ResponseData.Write(timestamp.Modified); context.ResponseData.Write(timestamp.Modified);
@ -206,5 +204,13 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
return (ResultCode)result.Value; return (ResultCode)result.Value;
} }
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
_fileSystem?.Dispose();
}
}
} }
} }

View file

@ -1,13 +1,14 @@
using LibHac; using LibHac;
using LibHac.Sf;
using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Ipc;
namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
{ {
class IStorage : DisposableIpcService class IStorage : DisposableIpcService
{ {
private LibHac.Fs.IStorage _baseStorage; private ReferenceCountedDisposable<LibHac.FsSrv.Sf.IStorage> _baseStorage;
public IStorage(LibHac.Fs.IStorage baseStorage) public IStorage(ReferenceCountedDisposable<LibHac.FsSrv.Sf.IStorage> baseStorage)
{ {
_baseStorage = baseStorage; _baseStorage = baseStorage;
} }
@ -31,7 +32,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
byte[] data = new byte[size]; byte[] data = new byte[size];
Result result = _baseStorage.Read((long)offset, data); Result result = _baseStorage.Target.Read((long)offset, new OutBuffer(data), (long)size);
context.Memory.Write(buffDesc.Position, data); context.Memory.Write(buffDesc.Position, data);
@ -45,7 +46,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
// GetSize() -> u64 size // GetSize() -> u64 size
public ResultCode GetSize(ServiceCtx context) public ResultCode GetSize(ServiceCtx context)
{ {
Result result = _baseStorage.GetSize(out long size); Result result = _baseStorage.Target.GetSize(out long size);
context.ResponseData.Write(size); context.ResponseData.Write(size);

View file

@ -3,11 +3,11 @@ using LibHac.FsSrv;
namespace Ryujinx.HLE.HOS.Services.Fs namespace Ryujinx.HLE.HOS.Services.Fs
{ {
class IDeviceOperator : IpcService class IDeviceOperator : DisposableIpcService
{ {
private LibHac.FsSrv.IDeviceOperator _baseOperator; private ReferenceCountedDisposable<LibHac.FsSrv.Sf.IDeviceOperator> _baseOperator;
public IDeviceOperator(LibHac.FsSrv.IDeviceOperator baseOperator) public IDeviceOperator(ReferenceCountedDisposable<LibHac.FsSrv.Sf.IDeviceOperator> baseOperator)
{ {
_baseOperator = baseOperator; _baseOperator = baseOperator;
} }
@ -16,7 +16,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
// IsSdCardInserted() -> b8 is_inserted // IsSdCardInserted() -> b8 is_inserted
public ResultCode IsSdCardInserted(ServiceCtx context) public ResultCode IsSdCardInserted(ServiceCtx context)
{ {
Result result = _baseOperator.IsSdCardInserted(out bool isInserted); Result result = _baseOperator.Target.IsSdCardInserted(out bool isInserted);
context.ResponseData.Write(isInserted); context.ResponseData.Write(isInserted);
@ -27,7 +27,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
// IsGameCardInserted() -> b8 is_inserted // IsGameCardInserted() -> b8 is_inserted
public ResultCode IsGameCardInserted(ServiceCtx context) public ResultCode IsGameCardInserted(ServiceCtx context)
{ {
Result result = _baseOperator.IsGameCardInserted(out bool isInserted); Result result = _baseOperator.Target.IsGameCardInserted(out bool isInserted);
context.ResponseData.Write(isInserted); context.ResponseData.Write(isInserted);
@ -38,11 +38,19 @@ namespace Ryujinx.HLE.HOS.Services.Fs
// GetGameCardHandle() -> u32 gamecard_handle // GetGameCardHandle() -> u32 gamecard_handle
public ResultCode GetGameCardHandle(ServiceCtx context) public ResultCode GetGameCardHandle(ServiceCtx context)
{ {
Result result = _baseOperator.GetGameCardHandle(out GameCardHandle handle); Result result = _baseOperator.Target.GetGameCardHandle(out GameCardHandle handle);
context.ResponseData.Write(handle.Value); context.ResponseData.Write(handle.Value);
return (ResultCode)result.Value; return (ResultCode)result.Value;
} }
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
_baseOperator?.Dispose();
}
}
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -3,11 +3,11 @@ using Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy;
namespace Ryujinx.HLE.HOS.Services.Fs namespace Ryujinx.HLE.HOS.Services.Fs
{ {
class IMultiCommitManager : IpcService // 6.0.0+ class IMultiCommitManager : DisposableIpcService // 6.0.0+
{ {
private LibHac.FsSrv.IMultiCommitManager _baseCommitManager; private ReferenceCountedDisposable<LibHac.FsSrv.Sf.IMultiCommitManager> _baseCommitManager;
public IMultiCommitManager(LibHac.FsSrv.IMultiCommitManager baseCommitManager) public IMultiCommitManager(ReferenceCountedDisposable<LibHac.FsSrv.Sf.IMultiCommitManager> baseCommitManager)
{ {
_baseCommitManager = baseCommitManager; _baseCommitManager = baseCommitManager;
} }
@ -18,7 +18,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
{ {
IFileSystem fileSystem = GetObject<IFileSystem>(context, 0); IFileSystem fileSystem = GetObject<IFileSystem>(context, 0);
Result result = _baseCommitManager.Add(fileSystem.GetBaseFileSystem()); Result result = _baseCommitManager.Target.Add(fileSystem.GetBaseFileSystem());
return (ResultCode)result.Value; return (ResultCode)result.Value;
} }
@ -27,9 +27,17 @@ namespace Ryujinx.HLE.HOS.Services.Fs
// Commit() // Commit()
public ResultCode Commit(ServiceCtx context) public ResultCode Commit(ServiceCtx context)
{ {
Result result = _baseCommitManager.Commit(); Result result = _baseCommitManager.Target.Commit();
return (ResultCode)result.Value; return (ResultCode)result.Value;
} }
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
_baseCommitManager?.Dispose();
}
}
} }
} }

View file

@ -1,13 +1,13 @@
using System; using LibHac;
using LibHac; using LibHac.Sf;
namespace Ryujinx.HLE.HOS.Services.Fs namespace Ryujinx.HLE.HOS.Services.Fs
{ {
class ISaveDataInfoReader : DisposableIpcService class ISaveDataInfoReader : DisposableIpcService
{ {
private ReferenceCountedDisposable<LibHac.FsSrv.ISaveDataInfoReader> _baseReader; private ReferenceCountedDisposable<LibHac.FsSrv.Sf.ISaveDataInfoReader> _baseReader;
public ISaveDataInfoReader(ReferenceCountedDisposable<LibHac.FsSrv.ISaveDataInfoReader> baseReader) public ISaveDataInfoReader(ReferenceCountedDisposable<LibHac.FsSrv.Sf.ISaveDataInfoReader> baseReader)
{ {
_baseReader = baseReader; _baseReader = baseReader;
} }
@ -21,7 +21,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
byte[] infoBuffer = new byte[bufferLen]; byte[] infoBuffer = new byte[bufferLen];
Result result = _baseReader.Target.Read(out long readCount, infoBuffer); Result result = _baseReader.Target.Read(out long readCount, new OutBuffer(infoBuffer));
context.Memory.Write(bufferPosition, infoBuffer); context.Memory.Write(bufferPosition, infoBuffer);
context.ResponseData.Write(readCount); context.ResponseData.Write(readCount);

View file

@ -1,4 +1,5 @@
using Ryujinx.HLE.HOS.Services.Mii.Types; using LibHac;
using Ryujinx.HLE.HOS.Services.Mii.Types;
using System; using System;
namespace Ryujinx.HLE.HOS.Services.Mii namespace Ryujinx.HLE.HOS.Services.Mii
@ -147,9 +148,9 @@ namespace Ryujinx.HLE.HOS.Services.Mii
return GetDefault(flag, ref count, elements); return GetDefault(flag, ref count, elements);
} }
public ResultCode InitializeDatabase(Switch device) public ResultCode InitializeDatabase(HorizonClient horizonClient)
{ {
_miiDatabase.InitializeDatabase(device); _miiDatabase.InitializeDatabase(horizonClient);
_miiDatabase.LoadFromFile(out _isBroken); _miiDatabase.LoadFromFile(out _isBroken);
// Nintendo ignore any error code from before // Nintendo ignore any error code from before

View file

@ -1,7 +1,9 @@
using LibHac; using LibHac;
using LibHac.Common; using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Fs.Shim; using LibHac.Fs.Shim;
using LibHac.Ncm;
using Ryujinx.HLE.HOS.Services.Mii.Types; using Ryujinx.HLE.HOS.Services.Mii.Types;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -14,8 +16,6 @@ namespace Ryujinx.HLE.HOS.Services.Mii
private const ulong DatabaseTestSaveDataId = 0x8000000000000031; private const ulong DatabaseTestSaveDataId = 0x8000000000000031;
private const ulong DatabaseSaveDataId = 0x8000000000000030; private const ulong DatabaseSaveDataId = 0x8000000000000030;
private const ulong NsTitleId = 0x010000000000001F;
private const ulong SdbTitleId = 0x0100000000000039;
private static U8String DatabasePath = new U8String("mii:/MiiDatabase.dat"); private static U8String DatabasePath = new U8String("mii:/MiiDatabase.dat");
private static U8String MountName = new U8String("mii"); private static U8String MountName = new U8String("mii");
@ -23,7 +23,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii
private NintendoFigurineDatabase _database; private NintendoFigurineDatabase _database;
private bool _isDirty; private bool _isDirty;
private FileSystemClient _filesystemClient; private HorizonClient _horizonClient;
protected ulong UpdateCounter { get; private set; } protected ulong UpdateCounter { get; private set; }
@ -94,74 +94,62 @@ namespace Ryujinx.HLE.HOS.Services.Mii
return virtualIndex; return virtualIndex;
} }
public void InitializeDatabase(Switch device) public void InitializeDatabase(HorizonClient horizonClient)
{ {
_filesystemClient = device.FileSystem.FsClient; _horizonClient = horizonClient;
// Ensure we have valid data in the database // Ensure we have valid data in the database
_database.Format(); _database.Format();
// TODO: Unmount is currently not implemented properly at dispose, implement that and decrement MountCounter.
MountCounter = 0;
MountSave(); MountSave();
} }
private Result MountSave() private Result MountSave()
{ {
Result result = Result.Success; if (MountCounter != 0)
if (MountCounter == 0)
{ {
ulong targetSaveDataId; MountCounter++;
ulong targetTitleId; return Result.Success;
if (IsTestModeEnabled)
{
targetSaveDataId = DatabaseTestSaveDataId;
targetTitleId = SdbTitleId;
}
else
{
targetSaveDataId = DatabaseSaveDataId;
// Nintendo use NS TitleID when creating the production save even on sdb, let's follow that behaviour.
targetTitleId = NsTitleId;
} }
U8Span mountName = new U8Span(MountName); ulong saveDataId = IsTestModeEnabled ? DatabaseTestSaveDataId : DatabaseSaveDataId;
result = _filesystemClient.MountSystemSaveData(mountName, SaveDataSpaceId.System, targetSaveDataId); Result result = _horizonClient.Fs.MountSystemSaveData(MountName, SaveDataSpaceId.System, saveDataId);
if (result.IsFailure()) if (result.IsFailure())
{ {
if (ResultFs.TargetNotFound.Includes(result)) if (!ResultFs.TargetNotFound.Includes(result))
{ return result;
// TODO: We're currently always specifying the owner ID because FS doesn't have a way of
// knowing which process called it
result = _filesystemClient.CreateSystemSaveData(targetSaveDataId, targetTitleId, 0x10000,
0x10000, SaveDataFlags.KeepAfterResettingSystemSaveDataWithoutUserSaveData);
if (result.IsFailure()) return result;
result = _filesystemClient.MountSystemSaveData(mountName, SaveDataSpaceId.System, targetSaveDataId); if (IsTestModeEnabled)
{
result = _horizonClient.Fs.CreateSystemSaveData(saveDataId, 0x10000, 0x10000,
SaveDataFlags.KeepAfterResettingSystemSaveDataWithoutUserSaveData);
if (result.IsFailure()) return result; if (result.IsFailure()) return result;
} }
else
{
result = _horizonClient.Fs.CreateSystemSaveData(saveDataId, SystemProgramId.Ns.Value, 0x10000,
0x10000, SaveDataFlags.KeepAfterResettingSystemSaveDataWithoutUserSaveData);
if (result.IsFailure()) return result;
}
result = _horizonClient.Fs.MountSystemSaveData(MountName, SaveDataSpaceId.System, saveDataId);
if (result.IsFailure()) return result;
} }
if (result == Result.Success) if (result == Result.Success)
{ {
MountCounter++; MountCounter++;
} }
}
return result; return result;
} }
public ResultCode DeleteFile() public ResultCode DeleteFile()
{ {
ResultCode result = (ResultCode)_filesystemClient.DeleteFile(DatabasePath).Value; ResultCode result = (ResultCode)_horizonClient.Fs.DeleteFile(DatabasePath).Value;
_filesystemClient.Commit(MountName); _horizonClient.Fs.Commit(MountName);
return result; return result;
} }
@ -179,17 +167,17 @@ namespace Ryujinx.HLE.HOS.Services.Mii
ResetDatabase(); ResetDatabase();
Result result = _filesystemClient.OpenFile(out FileHandle handle, DatabasePath, OpenMode.Read); Result result = _horizonClient.Fs.OpenFile(out FileHandle handle, DatabasePath, OpenMode.Read);
if (result.IsSuccess()) if (result.IsSuccess())
{ {
result = _filesystemClient.GetFileSize(out long fileSize, handle); result = _horizonClient.Fs.GetFileSize(out long fileSize, handle);
if (result.IsSuccess()) if (result.IsSuccess())
{ {
if (fileSize == Unsafe.SizeOf<NintendoFigurineDatabase>()) if (fileSize == Unsafe.SizeOf<NintendoFigurineDatabase>())
{ {
result = _filesystemClient.ReadFile(handle, 0, _database.AsSpan()); result = _horizonClient.Fs.ReadFile(handle, 0, _database.AsSpan());
if (result.IsSuccess()) if (result.IsSuccess())
{ {
@ -211,7 +199,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii
} }
} }
_filesystemClient.CloseFile(handle); _horizonClient.Fs.CloseFile(handle);
return (ResultCode)result.Value; return (ResultCode)result.Value;
} }
@ -225,32 +213,32 @@ namespace Ryujinx.HLE.HOS.Services.Mii
private Result ForceSaveDatabase() private Result ForceSaveDatabase()
{ {
Result result = _filesystemClient.CreateFile(DatabasePath, Unsafe.SizeOf<NintendoFigurineDatabase>()); Result result = _horizonClient.Fs.CreateFile(DatabasePath, Unsafe.SizeOf<NintendoFigurineDatabase>());
if (result.IsSuccess() || ResultFs.PathAlreadyExists.Includes(result)) if (result.IsSuccess() || ResultFs.PathAlreadyExists.Includes(result))
{ {
result = _filesystemClient.OpenFile(out FileHandle handle, DatabasePath, OpenMode.Write); result = _horizonClient.Fs.OpenFile(out FileHandle handle, DatabasePath, OpenMode.Write);
if (result.IsSuccess()) if (result.IsSuccess())
{ {
result = _filesystemClient.GetFileSize(out long fileSize, handle); result = _horizonClient.Fs.GetFileSize(out long fileSize, handle);
if (result.IsSuccess()) if (result.IsSuccess())
{ {
// If the size doesn't match, recreate the file // If the size doesn't match, recreate the file
if (fileSize != Unsafe.SizeOf<NintendoFigurineDatabase>()) if (fileSize != Unsafe.SizeOf<NintendoFigurineDatabase>())
{ {
_filesystemClient.CloseFile(handle); _horizonClient.Fs.CloseFile(handle);
result = _filesystemClient.DeleteFile(DatabasePath); result = _horizonClient.Fs.DeleteFile(DatabasePath);
if (result.IsSuccess()) if (result.IsSuccess())
{ {
result = _filesystemClient.CreateFile(DatabasePath, Unsafe.SizeOf<NintendoFigurineDatabase>()); result = _horizonClient.Fs.CreateFile(DatabasePath, Unsafe.SizeOf<NintendoFigurineDatabase>());
if (result.IsSuccess()) if (result.IsSuccess())
{ {
result = _filesystemClient.OpenFile(out handle, DatabasePath, OpenMode.Write); result = _horizonClient.Fs.OpenFile(out handle, DatabasePath, OpenMode.Write);
} }
} }
@ -260,10 +248,10 @@ namespace Ryujinx.HLE.HOS.Services.Mii
} }
} }
result = _filesystemClient.WriteFile(handle, 0, _database.AsReadOnlySpan(), WriteOption.Flush); result = _horizonClient.Fs.WriteFile(handle, 0, _database.AsReadOnlySpan(), WriteOption.Flush);
} }
_filesystemClient.CloseFile(handle); _horizonClient.Fs.CloseFile(handle);
} }
} }
@ -271,7 +259,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii
{ {
_isDirty = false; _isDirty = false;
result = _filesystemClient.Commit(MountName); result = _horizonClient.Fs.Commit(MountName);
} }
return result; return result;

View file

@ -20,7 +20,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Concentus" Version="1.1.7" /> <PackageReference Include="Concentus" Version="1.1.7" />
<PackageReference Include="LibHac" Version="0.12.0" /> <PackageReference Include="LibHac" Version="0.13.1" />
<PackageReference Include="MsgPack.Cli" Version="1.0.1" /> <PackageReference Include="MsgPack.Cli" Version="1.0.1" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" /> <PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
</ItemGroup> </ItemGroup>

View file

@ -55,6 +55,7 @@ namespace Ryujinx.Ui
private readonly VirtualFileSystem _virtualFileSystem; private readonly VirtualFileSystem _virtualFileSystem;
private readonly ContentManager _contentManager; private readonly ContentManager _contentManager;
private readonly AccountManager _accountManager; private readonly AccountManager _accountManager;
private readonly LibHacHorizonManager _libHacHorizonManager;
private UserChannelPersistence _userChannelPersistence; private UserChannelPersistence _userChannelPersistence;
@ -156,13 +157,27 @@ namespace Ryujinx.Ui
// Hide emulation context status bar. // Hide emulation context status bar.
_statusBar.Hide(); _statusBar.Hide();
// Instanciate HLE objects. // Instantiate HLE objects.
_virtualFileSystem = VirtualFileSystem.CreateInstance(); _virtualFileSystem = VirtualFileSystem.CreateInstance();
_libHacHorizonManager = new LibHacHorizonManager();
_libHacHorizonManager.InitializeFsServer(_virtualFileSystem);
_libHacHorizonManager.InitializeArpServer();
_libHacHorizonManager.InitializeBcatServer();
_libHacHorizonManager.InitializeSystemClients();
// Save data created before we supported extra data in directory save data will not work properly if
// given empty extra data. Luckily some of that extra data can be created using the data from the
// save data indexer, which should be enough to check access permissions for user saves.
// Every single save data's extra data will be checked and fixed if needed each time the emulator is opened.
// Consider removing this at some point in the future when we don't need to worry about old saves.
VirtualFileSystem.FixExtraData(_libHacHorizonManager.RyujinxClient);
_contentManager = new ContentManager(_virtualFileSystem); _contentManager = new ContentManager(_virtualFileSystem);
_accountManager = new AccountManager(_virtualFileSystem); _accountManager = new AccountManager(_libHacHorizonManager.RyujinxClient);
_userChannelPersistence = new UserChannelPersistence(); _userChannelPersistence = new UserChannelPersistence();
// Instanciate GUI objects. // Instantiate GUI objects.
_applicationLibrary = new ApplicationLibrary(_virtualFileSystem); _applicationLibrary = new ApplicationLibrary(_virtualFileSystem);
_uiHandler = new GtkHostUiHandler(this); _uiHandler = new GtkHostUiHandler(this);
_deviceExitStatus = new AutoResetEvent(false); _deviceExitStatus = new AutoResetEvent(false);
@ -370,7 +385,7 @@ namespace Ryujinx.Ui
private void InitializeSwitchInstance() private void InitializeSwitchInstance()
{ {
_virtualFileSystem.Reload(); _virtualFileSystem.ReloadKeySet();
IRenderer renderer; IRenderer renderer;
@ -440,6 +455,7 @@ namespace Ryujinx.Ui
IntegrityCheckLevel fsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None; IntegrityCheckLevel fsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None;
HLE.HLEConfiguration configuration = new HLE.HLEConfiguration(_virtualFileSystem, HLE.HLEConfiguration configuration = new HLE.HLEConfiguration(_virtualFileSystem,
_libHacHorizonManager,
_contentManager, _contentManager,
_accountManager, _accountManager,
_userChannelPersistence, _userChannelPersistence,
@ -1092,7 +1108,7 @@ namespace Ryujinx.Ui
BlitStruct<ApplicationControlProperty> controlData = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10); BlitStruct<ApplicationControlProperty> controlData = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10);
_ = new GameTableContextMenu(this, _virtualFileSystem, _accountManager, titleFilePath, titleName, titleId, controlData); _ = new GameTableContextMenu(this, _virtualFileSystem, _accountManager, _libHacHorizonManager.RyujinxClient, titleFilePath, titleName, titleId, controlData);
} }
private void Load_Application_File(object sender, EventArgs args) private void Load_Application_File(object sender, EventArgs args)
@ -1208,15 +1224,15 @@ namespace Ryujinx.Ui
SystemVersion firmwareVersion = _contentManager.VerifyFirmwarePackage(filename); SystemVersion firmwareVersion = _contentManager.VerifyFirmwarePackage(filename);
string dialogTitle = $"Install Firmware {firmwareVersion.VersionString}"; if (firmwareVersion is null)
if (firmwareVersion == null)
{ {
GtkDialog.CreateErrorDialog($"A valid system firmware was not found in {filename}."); GtkDialog.CreateErrorDialog($"A valid system firmware was not found in {filename}.");
return; return;
} }
string dialogTitle = $"Install Firmware {firmwareVersion.VersionString}";
SystemVersion currentVersion = _contentManager.GetCurrentFirmwareVersion(); SystemVersion currentVersion = _contentManager.GetCurrentFirmwareVersion();
string dialogMessage = $"System version {firmwareVersion.VersionString} will be installed."; string dialogMessage = $"System version {firmwareVersion.VersionString} will be installed.";

View file

@ -33,6 +33,7 @@ namespace Ryujinx.Ui.Widgets
private readonly MainWindow _parent; private readonly MainWindow _parent;
private readonly VirtualFileSystem _virtualFileSystem; private readonly VirtualFileSystem _virtualFileSystem;
private readonly AccountManager _accountManager; private readonly AccountManager _accountManager;
private readonly HorizonClient _horizonClient;
private readonly BlitStruct<ApplicationControlProperty> _controlData; private readonly BlitStruct<ApplicationControlProperty> _controlData;
private readonly string _titleFilePath; private readonly string _titleFilePath;
@ -43,7 +44,7 @@ namespace Ryujinx.Ui.Widgets
private MessageDialog _dialog; private MessageDialog _dialog;
private bool _cancel; private bool _cancel;
public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, AccountManager accountManager, string titleFilePath, string titleName, string titleId, BlitStruct<ApplicationControlProperty> controlData) public GameTableContextMenu(MainWindow parent, VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient, string titleFilePath, string titleName, string titleId, BlitStruct<ApplicationControlProperty> controlData)
{ {
_parent = parent; _parent = parent;
@ -51,6 +52,7 @@ namespace Ryujinx.Ui.Widgets
_virtualFileSystem = virtualFileSystem; _virtualFileSystem = virtualFileSystem;
_accountManager = accountManager; _accountManager = accountManager;
_horizonClient = horizonClient;
_titleFilePath = titleFilePath; _titleFilePath = titleFilePath;
_titleName = titleName; _titleName = titleName;
_titleIdText = titleId; _titleIdText = titleId;
@ -63,9 +65,9 @@ namespace Ryujinx.Ui.Widgets
return; return;
} }
_openSaveUserDirMenuItem.Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0; _openSaveUserDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0;
_openSaveDeviceDirMenuItem.Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0; _openSaveDeviceDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0;
_openSaveBcatDirMenuItem.Sensitive = !Utilities.IsEmpty(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0; _openSaveBcatDirMenuItem.Sensitive = !Utilities.IsZeros(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0;
string fileExt = System.IO.Path.GetExtension(_titleFilePath).ToLower(); string fileExt = System.IO.Path.GetExtension(_titleFilePath).ToLower();
bool hasNca = fileExt == ".nca" || fileExt == ".nsp" || fileExt == ".pfs0" || fileExt == ".xci"; bool hasNca = fileExt == ".nca" || fileExt == ".nsp" || fileExt == ".pfs0" || fileExt == ".xci";
@ -81,7 +83,7 @@ namespace Ryujinx.Ui.Widgets
{ {
saveDataId = default; saveDataId = default;
Result result = _virtualFileSystem.FsClient.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, ref filter); Result result = _horizonClient.Fs.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, in filter);
if (ResultFs.TargetNotFound.Includes(result)) if (ResultFs.TargetNotFound.Includes(result))
{ {
@ -102,7 +104,7 @@ namespace Ryujinx.Ui.Widgets
ref ApplicationControlProperty control = ref controlHolder.Value; ref ApplicationControlProperty control = ref controlHolder.Value;
if (Utilities.IsEmpty(controlHolder.ByteSpan)) if (Utilities.IsZeros(controlHolder.ByteSpan))
{ {
// If the current application doesn't have a loaded control property, create a dummy one // 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. // and set the savedata sizes so a user savedata will be created.
@ -117,7 +119,7 @@ namespace Ryujinx.Ui.Widgets
Uid user = new Uid((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low); Uid user = new Uid((ulong)_accountManager.LastOpenedUser.UserId.High, (ulong)_accountManager.LastOpenedUser.UserId.Low);
result = EnsureApplicationSaveData(_virtualFileSystem.FsClient, out _, new LibHac.Ncm.ApplicationId(titleId), ref control, ref user); result = EnsureApplicationSaveData(_horizonClient.Fs, out _, new LibHac.Ncm.ApplicationId(titleId), ref control, ref user);
if (result.IsFailure()) if (result.IsFailure())
{ {
@ -127,7 +129,7 @@ namespace Ryujinx.Ui.Widgets
} }
// Try to find the savedata again after creating it // Try to find the savedata again after creating it
result = _virtualFileSystem.FsClient.FindSaveDataWithFilter(out saveDataInfo, SaveDataSpaceId.User, ref filter); result = _horizonClient.Fs.FindSaveDataWithFilter(out saveDataInfo, SaveDataSpaceId.User, in filter);
} }
if (result.IsSuccess()) if (result.IsSuccess())
@ -284,7 +286,7 @@ namespace Ryujinx.Ui.Widgets
IFileSystem ncaFileSystem = patchNca != null ? mainNca.OpenFileSystemWithPatch(patchNca, index, IntegrityCheckLevel.ErrorOnInvalid) IFileSystem ncaFileSystem = patchNca != null ? mainNca.OpenFileSystemWithPatch(patchNca, index, IntegrityCheckLevel.ErrorOnInvalid)
: mainNca.OpenFileSystem(index, IntegrityCheckLevel.ErrorOnInvalid); : mainNca.OpenFileSystem(index, IntegrityCheckLevel.ErrorOnInvalid);
FileSystemClient fsClient = _virtualFileSystem.FsClient; FileSystemClient fsClient = _horizonClient.Fs;
string source = DateTime.Now.ToFileTime().ToString()[10..]; string source = DateTime.Now.ToFileTime().ToString()[10..];
string output = DateTime.Now.ToFileTime().ToString()[10..]; string output = DateTime.Now.ToFileTime().ToString()[10..];
@ -409,7 +411,7 @@ namespace Ryujinx.Ui.Widgets
rc = fs.ReadFile(out long _, sourceHandle, offset, buf); rc = fs.ReadFile(out long _, sourceHandle, offset, buf);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
rc = fs.WriteFile(destHandle, offset, buf); rc = fs.WriteFile(destHandle, offset, buf, WriteOption.None);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
} }
} }