Update the NCA filesystem service for FS 18

This commit is contained in:
Alex Barney 2024-08-03 17:31:04 -07:00
parent 78d9847b6a
commit b5158764b9
24 changed files with 323 additions and 271 deletions

View file

@ -124,6 +124,7 @@ public enum OperationId
QueryUnpreparedRange = 4,
QueryLazyLoadCompletionRate = 5,
SetLazyLoadPriority = 6,
GetAsynchronousAccessSplitter = 7,
ReadyLazyLoadFile = 10001
}

View file

@ -1,5 +1,6 @@
using System;
using LibHac.Common;
using LibHac.Fs;
using LibHac.FsSrv.FsCreator;
namespace LibHac.FsSrv;
@ -7,6 +8,8 @@ namespace LibHac.FsSrv;
public delegate Result GenerateSeedUniqueMac(Span<byte> outMacBuffer, ReadOnlySpan<byte> data, ReadOnlySpan<byte> seed);
public delegate Result GenerateDeviceUniqueMac(Span<byte> outMacBuffer, ReadOnlySpan<byte> data, DeviceUniqueMacType macType);
public delegate void GenerateSdEncryptionKey(Span<byte> outKey, IEncryptedFileSystemCreator.KeyId keyId, in EncryptionSeed seed);
public delegate Result SaveTransferAesKeyGenerator(Span<byte> outKeyBuffer,
SaveDataTransferCryptoConfiguration.KeyIndex index, ReadOnlySpan<byte> keySource, int keyGeneration);

View file

@ -66,7 +66,7 @@ public class FileSystemProxyCoreImpl
tempFs.SetByMove(ref fileSystem.Ref);
res = _fsCreators.EncryptedFileSystemCreator.Create(ref fileSystem.Ref, in tempFs,
IEncryptedFileSystemCreator.KeyId.CustomStorage, in _sdEncryptionSeed);
IEncryptedFileSystemCreator.KeyId.CustomStorage);
if (res.IsFailure()) return res.Miss();
}
else
@ -114,7 +114,6 @@ public class FileSystemProxyCoreImpl
public Result SetSdCardEncryptionSeed(in EncryptionSeed seed)
{
_sdEncryptionSeed = seed;
return Result.Success;
return _fsCreators.EncryptedFileSystemCreator.SetEncryptionSeed(IEncryptedFileSystemCreator.KeyId.Save, in seed).Ret();
}
}

View file

@ -957,13 +957,7 @@ public class FileSystemProxyImpl : IFileSystemProxy, IFileSystemProxyForLoader
res = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService);
if (res.IsFailure()) return res.Miss();
res = saveFsService.SetSdCardEncryptionSeed(in seed);
if (res.IsFailure()) return res.Miss();
res = GetNcaFileSystemService(out NcaFileSystemService ncaFsService);
if (res.IsFailure()) return res.Miss();
return ncaFsService.SetSdCardEncryptionSeed(in seed).Ret();
return saveFsService.SetSdCardEncryptionSeed(in seed).Ret();
}
public Result GetAndClearErrorInfo(out FileSystemProxyErrorInfo errorInfo)

View file

@ -47,6 +47,7 @@ internal struct FileSystemServerGlobals : IDisposable
public GameCardServiceGlobals GameCardService;
public HierarchicalIntegrityVerificationStorageGlobals HierarchicalIntegrityVerificationStorage;
public SpeedEmulationConfigurationGlobals SpeedEmulationConfiguration;
public StorageAccessSpeedControlGlobals StorageAccessSpeedControl;
public void Initialize(HorizonClient horizonClient, FileSystemServer fsServer)
{

View file

@ -15,6 +15,9 @@ public static class FileSystemServerInitializer
{
private const ulong SpeedEmulationProgramIdWithoutPlatformIdMinimum = 0;
private const ulong SpeedEmulationProgramIdWithoutPlatformIdMaximum = 0x1FFF;
private const ulong StorageAccessSpeedControlProgramIdWithoutPlatformIdMinimum = 0x3000;
private const ulong StorageAccessSpeedControlProgramIdWithoutPlatformIdMaximum = 0x_00FF_FFFF_FFFF_FFFF;
private const uint ContentDivisionSize = ConcatenationFileSystem.DefaultInternalFileSize;
@ -109,9 +112,11 @@ public static class FileSystemServerInitializer
var accessFailureManagementService =
new AccessFailureManagementServiceImpl(in accessFailureManagementServiceConfig);
var speedEmulationRange =
new InternalProgramIdRangeForSpeedEmulation(SpeedEmulationProgramIdWithoutPlatformIdMinimum,
SpeedEmulationProgramIdWithoutPlatformIdMaximum);
var speedEmulationRange = new InternalProgramIdRangeForSpeedEmulation(
SpeedEmulationProgramIdWithoutPlatformIdMinimum, SpeedEmulationProgramIdWithoutPlatformIdMaximum);
var storageAccessSpeedControlRange = new InternalProgramIdRangeForStorageAccessSpeedControl(
StorageAccessSpeedControlProgramIdWithoutPlatformIdMinimum, StorageAccessSpeedControlProgramIdWithoutPlatformIdMaximum);
var ncaFsServiceConfig = new NcaFileSystemServiceImpl.Configuration();
ncaFsServiceConfig.BaseFsService = baseFsService;
@ -127,6 +132,7 @@ public static class FileSystemServerInitializer
ncaFsServiceConfig.SpeedEmulationRange = speedEmulationRange;
ncaFsServiceConfig.AddOnContentDivisionSize = ContentDivisionSize;
ncaFsServiceConfig.RomDivisionSize = ContentDivisionSize;
ncaFsServiceConfig.StorageAccessSpeedControlRange = storageAccessSpeedControlRange;
ncaFsServiceConfig.FsServer = server;
var ncaFsService = new NcaFileSystemServiceImpl(in ncaFsServiceConfig);

View file

@ -1,7 +1,11 @@
using LibHac.Common;
#pragma warning disable CS0169 // Field is never used
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Common.Keys;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.Tools.FsSystem;
using static LibHac.FsSrv.FsCreator.IEncryptedFileSystemCreator;
@ -9,28 +13,67 @@ namespace LibHac.FsSrv.FsCreator;
public class EncryptedFileSystemCreator : IEncryptedFileSystemCreator
{
private KeySet KeySet { get; }
private readonly KeySet _keySet;
private Configuration _configuration;
private EncryptionSeed _seed;
public struct Configuration
{
public RandomDataGenerator GenerateRandomData;
public GenerateSdEncryptionKey GenerateSdEncryptionKey;
}
public EncryptedFileSystemCreator(KeySet keySet)
{
KeySet = keySet;
_keySet = keySet;
}
public Result Create(ref SharedRef<IFileSystem> outEncryptedFileSystem, ref readonly SharedRef<IFileSystem> baseFileSystem, KeyId idIndex, in EncryptionSeed encryptionSeed)
private ref readonly EncryptionSeed GetSeed(KeyId keyId)
{
if (idIndex < KeyId.Save || idIndex > KeyId.CustomStorage)
switch (keyId)
{
case KeyId.Save:
case KeyId.Content:
case KeyId.CustomStorage:
return ref _seed;
default:
Abort.UnexpectedDefault();
return ref Unsafe.NullRef<EncryptionSeed>();
}
}
public Result Create(ref SharedRef<IFileSystem> outEncryptedFileSystem, ref readonly SharedRef<IFileSystem> baseFileSystem, KeyId keyId)
{
if (keyId < KeyId.Save || keyId > KeyId.CustomStorage)
{
return ResultFs.InvalidArgument.Log();
}
// todo: "proper" key generation instead of a lazy hack
KeySet.SetSdSeed(encryptionSeed.Value);
_keySet.SetSdSeed(GetSeed(keyId).Value);
using var encryptedFileSystem = new SharedRef<AesXtsFileSystem>(new AesXtsFileSystem(in baseFileSystem,
KeySet.SdCardEncryptionKeys[(int)idIndex].DataRo.ToArray(), 0x4000));
_keySet.SdCardEncryptionKeys[(int)keyId].DataRo.ToArray(), 0x4000));
outEncryptedFileSystem.SetByMove(ref encryptedFileSystem.Ref);
return Result.Success;
}
public Result SetEncryptionSeed(KeyId keyId, in EncryptionSeed encryptionSeed)
{
switch (keyId)
{
case KeyId.Save:
case KeyId.Content:
case KeyId.CustomStorage:
_seed = encryptionSeed;
break;
default:
Abort.UnexpectedDefault();
break;
}
return Result.Success;
}
}

View file

@ -14,5 +14,7 @@ public interface IEncryptedFileSystemCreator
}
Result Create(ref SharedRef<IFileSystem> outEncryptedFileSystem, ref readonly SharedRef<IFileSystem> baseFileSystem,
KeyId idIndex, in EncryptionSeed encryptionSeed);
KeyId keyId);
Result SetEncryptionSeed(KeyId keyId, in EncryptionSeed encryptionSeed);
}

View file

@ -6,12 +6,10 @@ namespace LibHac.FsSrv.FsCreator;
public interface IStorageOnNcaCreator
{
Result Create(ref SharedRef<IStorage> outStorage,
ref SharedRef<IAsynchronousAccessSplitter> outStorageAccessSplitter, ref NcaFsHeaderReader outHeaderReader,
Result Create(ref SharedRef<IStorage> outStorage, ref NcaFsHeaderReader outHeaderReader,
ref readonly SharedRef<NcaReader> ncaReader, int fsIndex);
Result CreateWithPatch(ref SharedRef<IStorage> outStorage,
ref SharedRef<IAsynchronousAccessSplitter> outStorageAccessSplitter, ref NcaFsHeaderReader outHeaderReader,
Result CreateWithPatch(ref SharedRef<IStorage> outStorage, ref NcaFsHeaderReader outHeaderReader,
ref readonly SharedRef<NcaReader> originalNcaReader, ref readonly SharedRef<NcaReader> currentNcaReader,
int fsIndex);

View file

@ -27,28 +27,22 @@ public class StorageOnNcaCreator : IStorageOnNcaCreator
_hashGeneratorFactorySelector = hashGeneratorFactorySelector;
}
public Result Create(ref SharedRef<IStorage> outStorage,
ref SharedRef<IAsynchronousAccessSplitter> outStorageAccessSplitter, ref NcaFsHeaderReader outHeaderReader,
ref readonly SharedRef<NcaReader> ncaReader, int fsIndex)
public Result Create(ref SharedRef<IStorage> outStorage, ref NcaFsHeaderReader outHeaderReader, ref readonly SharedRef<NcaReader> ncaReader, int fsIndex)
{
var ncaFsDriver = new NcaFileSystemDriver(in ncaReader, _memoryResource, _bufferManager, _hashGeneratorFactorySelector);
using var storage = new SharedRef<IStorage>();
using var storageAccessSplitter = new SharedRef<IAsynchronousAccessSplitter>();
Result res = RomResultConverter.ConvertRomResult(ncaFsDriver.OpenStorage(ref storage.Ref,
ref storageAccessSplitter.Ref, ref outHeaderReader, fsIndex));
Result res = RomResultConverter.ConvertRomResult(ncaFsDriver.OpenStorage(ref storage.Ref, ref outHeaderReader, fsIndex));
if (res.IsFailure()) return res.Miss();
using var resultConvertStorage = new SharedRef<RomResultConvertStorage>(new RomResultConvertStorage(in storage));
outStorage.SetByMove(ref resultConvertStorage.Ref);
outStorageAccessSplitter.SetByMove(ref storageAccessSplitter.Ref);
return Result.Success;
}
public Result CreateWithPatch(ref SharedRef<IStorage> outStorage,
ref SharedRef<IAsynchronousAccessSplitter> outStorageAccessSplitter, ref NcaFsHeaderReader outHeaderReader,
ref NcaFsHeaderReader outHeaderReader,
ref readonly SharedRef<NcaReader> originalNcaReader, ref readonly SharedRef<NcaReader> currentNcaReader,
int fsIndex)
{
@ -56,15 +50,11 @@ public class StorageOnNcaCreator : IStorageOnNcaCreator
_bufferManager, _hashGeneratorFactorySelector);
using var storage = new SharedRef<IStorage>();
using var storageAccessSplitter = new SharedRef<IAsynchronousAccessSplitter>();
Result res = RomResultConverter.ConvertRomResult(ncaFsDriver.OpenStorage(ref storage.Ref,
ref storageAccessSplitter.Ref, ref outHeaderReader, fsIndex));
Result res = RomResultConverter.ConvertRomResult(ncaFsDriver.OpenStorage(ref storage.Ref, ref outHeaderReader, fsIndex));
if (res.IsFailure()) return res.Miss();
using var resultConvertStorage = new SharedRef<RomResultConvertStorage>(new RomResultConvertStorage(in storage));
outStorage.SetByMove(ref resultConvertStorage.Ref);
outStorageAccessSplitter.SetByMove(ref storageAccessSplitter.Ref);
return Result.Success;
}

View file

@ -12,7 +12,6 @@ namespace LibHac.FsSrv.Impl;
public class DeepRetryStorage : IStorage
{
private AsynchronousAccessStorage _asyncStorage;
private SharedRef<IAsynchronousAccessSplitter> _accessSplitter;
private SharedRef<IRomFileSystemAccessFailureManager> _parent;
private UniqueRef<IUniqueLock> _mountCountLock;
private DataStorageContext _dataStorageContext;
@ -52,14 +51,12 @@ public class DeepRetryStorage : IStorage
}
public DeepRetryStorage(ref readonly SharedRef<IStorage> baseStorage,
ref readonly SharedRef<IAsynchronousAccessSplitter> accessSplitter,
ref readonly SharedRef<IRomFileSystemAccessFailureManager> parent,
ref UniqueRef<IUniqueLock> mountCountSemaphore,
bool deepRetryEnabled, FileSystemServer fsServer)
{
// Missing: Getting the thread pool via GetRegisteredThreadPool()
_asyncStorage = new AsynchronousAccessStorage(in baseStorage, accessSplitter.Get);
_accessSplitter = SharedRef<IAsynchronousAccessSplitter>.CreateCopy(in accessSplitter);
_asyncStorage = new AsynchronousAccessStorage(in baseStorage);
_parent = SharedRef<IRomFileSystemAccessFailureManager>.CreateCopy(in parent);
_mountCountLock = UniqueRef<IUniqueLock>.Create(ref mountCountSemaphore);
_dataStorageContext = new DataStorageContext();
@ -70,14 +67,12 @@ public class DeepRetryStorage : IStorage
}
public DeepRetryStorage(ref readonly SharedRef<IStorage> baseStorage,
ref readonly SharedRef<IAsynchronousAccessSplitter> accessSplitter,
ref readonly SharedRef<IRomFileSystemAccessFailureManager> parent,
ref UniqueRef<IUniqueLock> mountCountSemaphore,
in Hash hash, ulong programId, StorageId storageId, FileSystemServer fsServer)
{
// Missing: Getting the thread pool via GetRegisteredThreadPool()
_asyncStorage = new AsynchronousAccessStorage(in baseStorage, accessSplitter.Get);
_accessSplitter = SharedRef<IAsynchronousAccessSplitter>.CreateCopy(in accessSplitter);
_asyncStorage = new AsynchronousAccessStorage(in baseStorage);
_parent = SharedRef<IRomFileSystemAccessFailureManager>.CreateCopy(in parent);
_mountCountLock = UniqueRef<IUniqueLock>.Create(ref mountCountSemaphore);
_dataStorageContext = new DataStorageContext(in hash, programId, storageId);
@ -92,7 +87,6 @@ public class DeepRetryStorage : IStorage
_readWriteLock.Dispose();
_mountCountLock.Destroy();
_parent.Destroy();
_accessSplitter.Destroy();
_asyncStorage.Dispose();
base.Dispose();
@ -143,7 +137,6 @@ public class DeepRetryStorage : IStorage
Assert.SdkNotNull(_parent);
using var remountStorage = new SharedRef<IStorage>();
using var remountStorageAccessSplitter = new SharedRef<IAsynchronousAccessSplitter>();
const int maxRetryCount = 2;
@ -151,8 +144,8 @@ public class DeepRetryStorage : IStorage
Hash digest = default;
for (int i = 0; i < maxRetryCount; i++)
{
retryResult = _parent.Get.OpenDataStorageCore(ref remountStorage.Ref, ref remountStorageAccessSplitter.Ref,
ref digest, _dataStorageContext.GetProgramIdValue(), _dataStorageContext.GetStorageId());
retryResult = _parent.Get.OpenDataStorageCore(ref remountStorage.Ref, ref digest,
_dataStorageContext.GetProgramIdValue(), _dataStorageContext.GetStorageId());
if (!ResultFs.DataCorrupted.Includes(retryResult))
break;
@ -170,8 +163,7 @@ public class DeepRetryStorage : IStorage
return ResultFs.NcaDigestInconsistent.Log();
}
_accessSplitter.SetByMove(ref remountStorageAccessSplitter.Ref);
_asyncStorage.SetBaseStorage(in remountStorage, _accessSplitter.Get);
_asyncStorage.SetBaseStorage(in remountStorage);
return Result.Success;
}

View file

@ -8,7 +8,7 @@ namespace LibHac.FsSrv.Impl;
public interface IRomFileSystemAccessFailureManager : IDisposable
{
Result OpenDataStorageCore(ref SharedRef<IStorage> outStorage, ref SharedRef<IAsynchronousAccessSplitter> outStorageAccessSplitter, ref Hash outNcaDigest, ulong id, StorageId storageId);
Result OpenDataStorageCore(ref SharedRef<IStorage> outStorage, ref Hash outNcaDigest, ulong id, StorageId storageId);
Result HandleResolubleAccessFailure(out bool wasDeferred, Result nonDeferredResult);
void IncrementRomFsDeepRetryStartCount();
void IncrementRomFsRemountForDataCorruptionCount();

View file

@ -0,0 +1,44 @@
namespace LibHac.FsSrv
{
public readonly struct InternalProgramIdRangeForStorageAccessSpeedControl
{
public readonly ulong ProgramIdWithoutPlatformIdMin;
public readonly ulong ProgramIdWithoutPlatformIdMax;
public InternalProgramIdRangeForStorageAccessSpeedControl(ulong min, ulong max)
{
ProgramIdWithoutPlatformIdMin = min;
ProgramIdWithoutPlatformIdMax = max;
}
}
}
namespace LibHac.FsSrv.Impl
{
internal struct StorageAccessSpeedControlGlobals
{
public InternalProgramIdRangeForStorageAccessSpeedControl TargetProgramIdRange;
}
/// <summary>
/// Handles getting and setting the configuration for storage access speed control.
/// </summary>
/// <remarks>Based on nnSdk 18.3.0 (FS 18.0.0)</remarks>
public static class StorageAccessSpeedControl
{
public static void SetTargetProgramIdRange(FileSystemServer fsServer,
InternalProgramIdRangeForStorageAccessSpeedControl range)
{
fsServer.Globals.StorageAccessSpeedControl.TargetProgramIdRange = range;
}
public static bool IsTargetProgramId(FileSystemServer fsServer, ulong programId)
{
var range = fsServer.Globals.StorageAccessSpeedControl.TargetProgramIdRange;
ulong programIdWithoutPlatformId = Utility.ClearPlatformIdInProgramId(programId);
return programIdWithoutPlatformId >= range.ProgramIdWithoutPlatformIdMin &&
programIdWithoutPlatformId <= range.ProgramIdWithoutPlatformIdMax;
}
}
}

View file

@ -24,7 +24,7 @@ namespace LibHac.FsSrv;
/// </summary>
/// <remarks>FS will have one instance of this class for every connected process.
/// The FS permissions of the calling process are checked on every function call.
/// <br/>Based on nnSdk 17.5.0 (FS 17.0.0)</remarks>
/// <br/>Based on nnSdk 18.3.0 (FS 18.0.0)</remarks>
internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
{
private const int AocSemaphoreCount = 128;
@ -95,18 +95,18 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
public Result OpenFileSystemWithPatch(ref SharedRef<IFileSystemSf> outFileSystem, ProgramId programId,
FileSystemProxyType type)
{
const StorageLayoutType storageFlag = StorageLayoutType.All;
using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag);
// Get the program info for the caller and verify permissions
Result res = GetProgramInfo(out ProgramInfo callerProgramInfo);
Result res = GetProgramInfo(out ProgramInfo programInfo);
if (res.IsFailure()) return res.Miss();
StorageLayoutType storageFlag = _serviceImpl.GetStorageFlag(programInfo.ProgramIdValue);
using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag);
switch (type)
{
case FileSystemProxyType.Manual:
Accessibility accessibility =
callerProgramInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountContentManual);
programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountContentManual);
if (!accessibility.CanRead)
return ResultFs.PermissionDenied.Log();
@ -123,14 +123,14 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
}
// Get the program info for the owner of the file system being opened
res = GetProgramInfoByProgramId(out ProgramInfo ownerProgramInfo, programId.Value);
res = GetProgramInfoByProgramId(out ProgramInfo applicationProgramInfo, programId.Value);
if (res.IsFailure()) return res.Miss();
// Try to find the path to the original version of the file system
using var originalPath = new Path();
Result originalResult = _serviceImpl.ResolveApplicationHtmlDocumentPath(out bool isDirectory,
ref originalPath.Ref(), out ContentAttributes contentAttributes, out ulong originalProgramId,
programId.Value, ownerProgramInfo.StorageId);
programId.Value, applicationProgramInfo.StorageId);
// The file system might have a patch version with no original version, so continue if not found
if (originalResult.IsFailure() && !ResultLr.HtmlDocumentNotFound.Includes(originalResult))
@ -334,8 +334,7 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
_serviceImpl.IncrementRomFsUnrecoverableByGameCardAccessFailedCount();
}
private Result OpenDataStorageCore(ref SharedRef<IStorage> outStorage,
ref SharedRef<IAsynchronousAccessSplitter> outStorageAccessSplitter, ref Hash outNcaDigest, ulong id,
private Result OpenDataStorageCore(ref SharedRef<IStorage> outStorage, ref Hash outNcaDigest, ulong id,
StorageId storageId)
{
using var programPath = new Path();
@ -347,7 +346,6 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
out ContentAttributes patchContentAttributes, id);
using var storage = new SharedRef<IStorage>();
using var storageAccessSplitter = new SharedRef<IAsynchronousAccessSplitter>();
if (ResultLr.ProgramNotFound.Includes(patchResult))
{
@ -360,8 +358,8 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
if (originalResult.IsFailure())
return originalResult.Miss();
Result res = _serviceImpl.OpenDataStorage(ref storage.Ref, ref storageAccessSplitter.Ref,
ref outNcaDigest, in programPath, contentAttributes, FileSystemProxyType.Rom, id);
Result res = _serviceImpl.OpenDataStorage(ref storage.Ref, ref outNcaDigest, in programPath,
contentAttributes, FileSystemProxyType.Rom, id);
if (res.IsFailure()) return res.Miss();
}
else
@ -373,14 +371,12 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
? ref programPath
: ref PathExtensions.GetNullRef();
Result res = _serviceImpl.OpenStorageWithPatch(ref storage.Ref, ref storageAccessSplitter.Ref,
ref outNcaDigest, in originalNcaPath, contentAttributes, in patchPath, patchContentAttributes,
FileSystemProxyType.Rom, originalProgramId, id);
Result res = _serviceImpl.OpenStorageWithPatch(ref storage.Ref, ref outNcaDigest, in originalNcaPath,
contentAttributes, in patchPath, patchContentAttributes, FileSystemProxyType.Rom, originalProgramId, id);
if (res.IsFailure()) return res.Miss();
}
outStorage.SetByMove(ref storage.Ref);
outStorageAccessSplitter.SetByMove(ref storageAccessSplitter.Ref);
return Result.Success;
}
@ -401,8 +397,7 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
StorageId storageId = programInfo.StorageId;
using var storage = new SharedRef<IStorage>();
using var storageAccessSplitter = new SharedRef<IAsynchronousAccessSplitter>();
res = OpenDataStorageCore(ref storage.Ref, ref storageAccessSplitter.Ref, ref digest, programInfo.ProgramIdValue, storageId);
res = OpenDataStorageCore(ref storage.Ref, ref digest, programInfo.ProgramIdValue, storageId);
if (res.IsFailure()) return res.Miss();
using var romDivisionSizeUnitCountSemaphore = new UniqueRef<IUniqueLock>();
@ -412,9 +407,9 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
using SharedRef<IRomFileSystemAccessFailureManager> accessFailureManager = GetSharedAccessFailureManagerFromThis();
using var retryStorage = new SharedRef<IStorage>(new DeepRetryStorage(in storage, in storageAccessSplitter,
in accessFailureManager, ref romDivisionSizeUnitCountSemaphore.Ref, in digest, programInfo.ProgramIdValue,
storageId, _serviceImpl.FsServer));
using var retryStorage = new SharedRef<IStorage>(new DeepRetryStorage(in storage, in accessFailureManager,
ref romDivisionSizeUnitCountSemaphore.Ref, in digest, programInfo.ProgramIdValue, storageId,
_serviceImpl.FsServer));
using var typeSetStorage = new SharedRef<IStorage>(new StorageLayoutTypeSetStorage(in retryStorage, storageFlag));
using var storageAdapter = new SharedRef<IStorageSf>(new StorageInterfaceAdapter(in typeSetStorage));
@ -452,9 +447,7 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
res = ncaPath.Normalize(flags);
if (res.IsFailure()) return res.Miss();
using var storageAccessSplitter = new SharedRef<IAsynchronousAccessSplitter>();
res = _serviceImpl.OpenDataStorage(ref storage.Ref, ref storageAccessSplitter.Ref, ref digest, in ncaPath,
attributes, type, ulong.MaxValue);
res = _serviceImpl.OpenDataStorage(ref storage.Ref, ref digest, in ncaPath, attributes, type, ulong.MaxValue);
if (res.IsFailure()) return res.Miss();
using var romDivisionSizeUnitCountSemaphore = new UniqueRef<IUniqueLock>();
@ -464,9 +457,9 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
using SharedRef<IRomFileSystemAccessFailureManager> accessFailureManager = GetSharedAccessFailureManagerFromThis();
using var retryStorage = new SharedRef<IStorage>(new DeepRetryStorage(in storage, in storageAccessSplitter,
in accessFailureManager, ref romDivisionSizeUnitCountSemaphore.Ref, in digest, ProgramId.InvalidId.Value,
StorageId.None, _serviceImpl.FsServer));
using var retryStorage = new SharedRef<IStorage>(new DeepRetryStorage(in storage, in accessFailureManager,
ref romDivisionSizeUnitCountSemaphore.Ref, in digest, ProgramId.InvalidId.Value, StorageId.None,
_serviceImpl.FsServer));
using var typeSetStorage = new SharedRef<IStorage>(new StorageLayoutTypeSetStorage(in retryStorage, storageFlag));
using var storageAdapter = new SharedRef<IStorageSf>(new StorageInterfaceAdapter(in typeSetStorage));
@ -478,12 +471,12 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
public Result OpenDataStorageByProgramId(ref SharedRef<IStorageSf> outStorage, ProgramId programId)
{
StorageLayoutType storageFlag = _serviceImpl.GetStorageFlag(programId.Value);
using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag);
Result res = GetProgramInfoByProgramId(out ProgramInfo programInfo, programId.Value);
if (res.IsFailure()) return res.Miss();
StorageLayoutType storageFlag = _serviceImpl.GetStorageFlag(programId.Value, programInfo.ProgramIdValue);
using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag);
Hash digest = default;
using var romMountCountSemaphore = new UniqueRef<IUniqueLock>();
@ -491,8 +484,7 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
if (res.IsFailure()) return res.Miss();
using var storage = new SharedRef<IStorage>();
using var storageAccessSplitter = new SharedRef<IAsynchronousAccessSplitter>();
res = OpenDataStorageCore(ref storage.Ref, ref storageAccessSplitter.Ref, ref digest, programId.Value, programInfo.StorageId);
res = OpenDataStorageCore(ref storage.Ref, ref digest, programId.Value, programInfo.StorageId);
if (res.IsFailure()) return res.Miss();
res = GetProgramInfo(out ProgramInfo properProgramInfo);
@ -508,9 +500,9 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
using SharedRef<IRomFileSystemAccessFailureManager> accessFailureManager = GetSharedAccessFailureManagerFromThis();
using var retryStorage = new SharedRef<IStorage>(new DeepRetryStorage(in storage, in storageAccessSplitter,
in accessFailureManager, ref romDivisionSizeUnitCountSemaphore.Ref, in digest, programId.Value,
programInfo.StorageId, _serviceImpl.FsServer));
using var retryStorage = new SharedRef<IStorage>(new DeepRetryStorage(in storage, in accessFailureManager,
ref romDivisionSizeUnitCountSemaphore.Ref, in digest, programId.Value, programInfo.StorageId,
_serviceImpl.FsServer));
using var typeSetStorage = new SharedRef<IStorage>(new StorageLayoutTypeSetStorage(in retryStorage, storageFlag));
using var storageAdapter = new SharedRef<IStorageSf>(new StorageInterfaceAdapter(in typeSetStorage));
@ -523,12 +515,12 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
public Result OpenFileSystemWithId(ref SharedRef<IFileSystemSf> outFileSystem, in FspPath path,
ContentAttributes attributes, ulong id, FileSystemProxyType type)
{
const StorageLayoutType storageFlag = StorageLayoutType.All;
using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag);
Result res = GetProgramInfo(out ProgramInfo programInfo);
if (res.IsFailure()) return res.Miss();
StorageLayoutType storageFlag = _serviceImpl.GetStorageFlag(id, programInfo.ProgramIdValue);
using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag);
AccessControl ac = programInfo.AccessControl;
switch (type)
@ -626,12 +618,14 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
public Result OpenDataStorageByDataId(ref SharedRef<IStorageSf> outStorage, DataId dataId, StorageId storageId)
{
Result res;
StorageLayoutType storageFlag = _serviceImpl.GetStorageFlag(dataId.Value);
using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag);
Result res = GetProgramInfo(out ProgramInfo programInfo);
if (res.IsFailure()) return res.Miss();
bool isAoc = storageId == StorageId.None;
DataId targetDataId = _serviceImpl.RedirectDataId(dataId, storageId);
StorageLayoutType storageFlag = _serviceImpl.GetStorageFlag(targetDataId.Value, programInfo.ProgramIdValue);
using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag);
using var systemDataPath = new Path();
using var systemDataPatchPath = new Path();
@ -642,38 +636,33 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
if (isAoc)
{
res = _serviceImpl.ResolveAddOnContentPath(ref systemDataPath.Ref(), out contentAttributes,
ref systemDataPatchPath.Ref(), out patchContentAttributes, dataId);
ref systemDataPatchPath.Ref(), out patchContentAttributes, targetDataId);
if (res.IsFailure()) return res.Miss();
}
else
{
res = _serviceImpl.ResolveDataPath(ref systemDataPath.Ref(), out contentAttributes, dataId, storageId);
res = _serviceImpl.ResolveDataPath(ref systemDataPath.Ref(), out contentAttributes, targetDataId, storageId);
if (res.IsFailure()) return res.Miss();
Assert.SdkAssert(systemDataPatchPath.IsEmpty());
}
res = GetProgramInfo(out ProgramInfo programInfo);
if (res.IsFailure()) return res.Miss();
var accessibility = programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountSystemDataPrivate);
bool canMountSystemDataPrivate = accessibility.CanRead;
using var storage = new SharedRef<IStorage>();
using var storageAccessSplitter = new SharedRef<IAsynchronousAccessSplitter>();
if (systemDataPatchPath.IsEmpty())
{
res = _serviceImpl.OpenDataStorage(ref storage.Ref, ref storageAccessSplitter.Ref,
ref Unsafe.NullRef<Hash>(), in systemDataPath, contentAttributes, FileSystemProxyType.Data,
dataId.Value, canMountSystemDataPrivate);
res = _serviceImpl.OpenDataStorage(ref storage.Ref, ref Unsafe.NullRef<Hash>(), in systemDataPath,
contentAttributes, FileSystemProxyType.Data, targetDataId.Value, canMountSystemDataPrivate);
if (res.IsFailure()) return res.Miss();
}
else
{
res = _serviceImpl.OpenStorageWithPatch(ref storage.Ref, ref storageAccessSplitter.Ref,
ref Unsafe.NullRef<Hash>(), in systemDataPath, contentAttributes, in systemDataPatchPath,
patchContentAttributes, FileSystemProxyType.Data, dataId.Value, dataId.Value, canMountSystemDataPrivate);
res = _serviceImpl.OpenStorageWithPatch(ref storage.Ref, ref Unsafe.NullRef<Hash>(), in systemDataPath,
contentAttributes, in systemDataPatchPath, patchContentAttributes, FileSystemProxyType.Data,
targetDataId.Value, targetDataId.Value, canMountSystemDataPrivate);
if (res.IsFailure()) return res.Miss();
}
@ -687,8 +676,8 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
using SharedRef<IRomFileSystemAccessFailureManager> accessFailureManager = GetSharedAccessFailureManagerFromThis();
using var retryStorage = new SharedRef<IStorage>(new DeepRetryStorage(in storage, in storageAccessSplitter,
in accessFailureManager, ref mountCountSemaphore.Ref, deepRetryEnabled: isAoc, _serviceImpl.FsServer));
using var retryStorage = new SharedRef<IStorage>(new DeepRetryStorage(in storage, in accessFailureManager,
ref mountCountSemaphore.Ref, deepRetryEnabled: isAoc, _serviceImpl.FsServer));
using var typeSetStorage = new SharedRef<IStorage>(new StorageLayoutTypeSetStorage(in retryStorage, storageFlag));
using var storageAdapter = new SharedRef<IStorageSf>(new StorageInterfaceAdapter(in typeSetStorage));
@ -701,12 +690,13 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
public Result OpenDataFileSystemByDataId(ref SharedRef<IFileSystemSf> outFileSystem, DataId dataId, StorageId storageId)
{
Result res;
bool isAoc = storageId == StorageId.None;
DataId targetDataId = _serviceImpl.RedirectDataId(dataId, storageId);
StorageLayoutType storageFlag = _serviceImpl.GetStorageFlag(dataId.Value);
using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag);
bool isAoc = storageId == StorageId.None;
using var dataPath = new Path();
using var dataPatchPathUnused = new Path();
@ -715,12 +705,12 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
if (isAoc)
{
res = _serviceImpl.ResolveAddOnContentPath(ref dataPath.Ref(), out contentAttributes,
ref dataPatchPathUnused.Ref(), out _, dataId);
ref dataPatchPathUnused.Ref(), out _, targetDataId);
if (res.IsFailure()) return res.Miss();
}
else
{
res = _serviceImpl.ResolveDataPath(ref dataPath.Ref(), out contentAttributes, dataId, storageId);
res = _serviceImpl.ResolveDataPath(ref dataPath.Ref(), out contentAttributes, targetDataId, storageId);
if (res.IsFailure()) return res.Miss();
Assert.SdkAssert(dataPatchPathUnused.IsEmpty());
@ -728,7 +718,7 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
using var fileSystem = new SharedRef<IFileSystem>();
res = _serviceImpl.OpenDataFileSystem(ref fileSystem.Ref, in dataPath, contentAttributes,
FileSystemProxyType.Data, dataId.Value, isDirectory: true);
FileSystemProxyType.Data, targetDataId.Value, isDirectory: true);
if (!res.IsSuccess())
{
@ -766,7 +756,7 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
if (res.IsFailure()) return res.Miss();
// Verify the caller has access to the file system
if (targetProgramId != programInfo.ProgramId &&
if (programInfo.ProgramId != targetProgramId &&
!programInfo.AccessControl.HasContentOwnerId(targetProgramId.Value))
{
return ResultFs.PermissionDenied.Log();
@ -790,7 +780,7 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
res = _serviceImpl.ResolveRomReferenceProgramId(out ProgramId targetProgramId, programInfo.ProgramId, programIndex);
if (res.IsFailure()) return res.Miss();
StorageLayoutType storageFlag = _serviceImpl.GetStorageFlag(targetProgramId.Value);
StorageLayoutType storageFlag = _serviceImpl.GetStorageFlag(targetProgramId.Value, programInfo.ProgramIdValue);
using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag);
Hash digest = default;
@ -800,8 +790,7 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
if (res.IsFailure()) return res.Miss();
using var storage = new SharedRef<IStorage>();
using var storageAccessSplitter = new SharedRef<IAsynchronousAccessSplitter>();
res = OpenDataStorageCore(ref storage.Ref, ref storageAccessSplitter.Ref, ref digest, targetProgramId.Value, programInfo.StorageId);
res = OpenDataStorageCore(ref storage.Ref, ref digest, targetProgramId.Value, programInfo.StorageId);
if (res.IsFailure()) return res.Miss();
if (programInfo.ProgramId != targetProgramId && !programInfo.AccessControl.HasContentOwnerId(targetProgramId.Value))
@ -814,9 +803,9 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
using SharedRef<IRomFileSystemAccessFailureManager> accessFailureManager = GetSharedAccessFailureManagerFromThis();
using var retryStorage = new SharedRef<IStorage>(new DeepRetryStorage(in storage, in storageAccessSplitter,
in accessFailureManager, ref romDivisionSizeUnitCountSemaphore.Ref, in digest, targetProgramId.Value,
programInfo.StorageId, _serviceImpl.FsServer));
using var retryStorage = new SharedRef<IStorage>(new DeepRetryStorage(in storage, in accessFailureManager,
ref romDivisionSizeUnitCountSemaphore.Ref, in digest, targetProgramId.Value, programInfo.StorageId,
_serviceImpl.FsServer));
using var typeSetStorage = new SharedRef<IStorage>(new StorageLayoutTypeSetStorage(in retryStorage, storageFlag));
using var storageAdapter = new SharedRef<IStorageSf>(new StorageInterfaceAdapter(in typeSetStorage));
@ -955,12 +944,12 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
public Result OpenContentStorageFileSystem(ref SharedRef<IFileSystemSf> outFileSystem,
ContentStorageId contentStorageId)
{
StorageLayoutType storageFlag = contentStorageId == ContentStorageId.System ? StorageLayoutType.Bis : StorageLayoutType.All;
using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag);
Result res = GetProgramInfo(out ProgramInfo programInfo);
if (res.IsFailure()) return res.Miss();
StorageLayoutType storageFlag = _serviceImpl.GetStorageFlagByContentStorageId(contentStorageId, programInfo.ProgramIdValue);
using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag);
Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountContentStorage);
if (!accessibility.CanRead || !accessibility.CanWrite)
@ -1088,17 +1077,6 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
return Result.Success;
}
public Result SetSdCardEncryptionSeed(in EncryptionSeed encryptionSeed)
{
Result res = GetProgramInfo(out ProgramInfo programInfo);
if (res.IsFailure()) return res.Miss();
if (!programInfo.AccessControl.CanCall(OperationType.SetEncryptionSeed))
return ResultFs.PermissionDenied.Log();
return _serviceImpl.SetSdCardEncryptionSeed(in encryptionSeed).Ret();
}
public Result OpenHostFileSystem(ref SharedRef<IFileSystemSf> outFileSystem, ref readonly FspPath path)
{
Result res = GetProgramInfo(out ProgramInfo programInfo);
@ -1163,9 +1141,8 @@ internal class NcaFileSystemService : IRomFileSystemAccessFailureManager
}
Result IRomFileSystemAccessFailureManager.OpenDataStorageCore(ref SharedRef<IStorage> outStorage,
ref SharedRef<IAsynchronousAccessSplitter> outStorageAccessSplitter, ref Hash outNcaDigest, ulong id,
StorageId storageId)
ref Hash outNcaDigest, ulong id, StorageId storageId)
{
return OpenDataStorageCore(ref outStorage, ref outStorageAccessSplitter, ref outNcaDigest, id, storageId).Ret();
return OpenDataStorageCore(ref outStorage, ref outNcaDigest, id, storageId).Ret();
}
}

View file

@ -166,14 +166,13 @@ file static class Anonymous
/// <summary>
/// Handles locating and opening NCA content files.
/// </summary>
/// <remarks>Based on nnSdk 17.5.0 (FS 17.0.0)</remarks>
/// <remarks>Based on nnSdk 18.3.0 (FS 18.0.0)</remarks>
public class NcaFileSystemServiceImpl : IDisposable
{
private readonly Configuration _config;
private readonly UpdatePartitionPath _updatePartitionPath;
private readonly ExternalKeyManager _externalKeyManager;
private readonly SystemDataUpdateEventManager _systemDataUpdateEventManager;
private EncryptionSeed _encryptionSeed;
private uint _romFsDeepRetryStartCount;
private uint _romFsRemountForDataCorruptionCount;
private uint _romfsUnrecoverableDataCorruptionByRemountCount;
@ -193,11 +192,13 @@ public class NcaFileSystemServiceImpl : IDisposable
public IEncryptedFileSystemCreator EncryptedFsCreator;
public INspRootFileSystemCreator NspRootFileSystemCreator;
public LocationResolverSet LocationResolverSet;
public Func<DataId, StorageId, DataId> RedirectDataId;
public ProgramRegistryServiceImpl ProgramRegistryService;
public AccessFailureManagementServiceImpl AccessFailureManagementService;
public InternalProgramIdRangeForSpeedEmulation SpeedEmulationRange;
public long AddOnContentDivisionSize;
public long RomDivisionSize;
public InternalProgramIdRangeForStorageAccessSpeedControl StorageAccessSpeedControlRange;
// LibHac additions
public FileSystemServer FsServer;
@ -244,6 +245,8 @@ public class NcaFileSystemServiceImpl : IDisposable
_romFsUnrecoverableByGameCardAccessFailedCount = 0;
_romfsCountMutex = new SdkMutexType();
StorageAccessSpeedControl.SetTargetProgramIdRange(_config.FsServer, _config.StorageAccessSpeedControlRange);
}
public void Dispose()
@ -386,9 +389,8 @@ public class NcaFileSystemServiceImpl : IDisposable
if (res.IsFailure()) return res.Miss();
using var storage = new SharedRef<IStorage>();
using var storageAccessSplitter = new SharedRef<IAsynchronousAccessSplitter>();
res = OpenStorageByContentType(ref storage.Ref, ref storageAccessSplitter.Ref, in ncaReader,
out NcaFsHeader.FsType fsType, type, mountInfo.IsGameCard(), canMountSystemDataPrivate);
res = OpenStorageByContentType(ref storage.Ref, in ncaReader, out NcaFsHeader.FsType fsType, type,
mountInfo.IsGameCard(), canMountSystemDataPrivate);
if (res.IsFailure()) return res.Miss();
switch (fsType)
@ -475,9 +477,8 @@ public class NcaFileSystemServiceImpl : IDisposable
if (res.IsFailure()) return res.Miss();
using var storage = new SharedRef<IStorage>();
using var storageAccessSplitter = new SharedRef<IAsynchronousAccessSplitter>();
res = OpenStorageByContentType(ref storage.Ref, ref storageAccessSplitter.Ref, in ncaReader,
out NcaFsHeader.FsType fsType, type, mountInfo.IsGameCard(), canMountSystemDataPrivate: false);
res = OpenStorageByContentType(ref storage.Ref, in ncaReader, out NcaFsHeader.FsType fsType, type,
mountInfo.IsGameCard(), canMountSystemDataPrivate: false);
if (res.IsFailure()) return res.Miss();
if (fsType != NcaFsHeader.FsType.RomFs)
@ -486,18 +487,15 @@ public class NcaFileSystemServiceImpl : IDisposable
return _config.RomFsCreator.Create(ref outFileSystem, in storage).Ret();
}
public Result OpenDataStorage(ref SharedRef<IStorage> outStorage,
ref SharedRef<IAsynchronousAccessSplitter> outStorageAccessSplitter, ref Hash outNcaDigest,
ref readonly Path path, ContentAttributes attributes, FileSystemProxyType type, ulong id)
public Result OpenDataStorage(ref SharedRef<IStorage> outStorage, ref Hash outNcaDigest, ref readonly Path path,
ContentAttributes attributes, FileSystemProxyType type, ulong id)
{
return OpenDataStorage(ref outStorage, ref outStorageAccessSplitter, ref outNcaDigest, in path, attributes,
type, id, canMountSystemDataPrivate: false).Ret();
return OpenDataStorage(ref outStorage, ref outNcaDigest, in path, attributes, type, id,
canMountSystemDataPrivate: false).Ret();
}
public Result OpenDataStorage(ref SharedRef<IStorage> outStorage,
ref SharedRef<IAsynchronousAccessSplitter> outStorageAccessSplitter, ref Hash outNcaDigest,
ref readonly Path path, ContentAttributes attributes, FileSystemProxyType type, ulong id,
bool canMountSystemDataPrivate)
public Result OpenDataStorage(ref SharedRef<IStorage> outStorage, ref Hash outNcaDigest, ref readonly Path path,
ContentAttributes attributes, FileSystemProxyType type, ulong id, bool canMountSystemDataPrivate)
{
using var ncaReader = new SharedRef<NcaReader>();
Result res = ParseNca(ref ncaReader.Ref, out bool isGameCard, path.GetString(), attributes, id);
@ -508,8 +506,8 @@ public class NcaFileSystemServiceImpl : IDisposable
GenerateNcaDigest(out outNcaDigest, ncaReader.Get, null);
}
res = OpenStorageByContentType(ref outStorage, ref outStorageAccessSplitter, in ncaReader,
out NcaFsHeader.FsType fsType, type, isGameCard, canMountSystemDataPrivate);
res = OpenStorageByContentType(ref outStorage, in ncaReader, out NcaFsHeader.FsType fsType, type, isGameCard,
canMountSystemDataPrivate);
if (res.IsFailure()) return res.Miss();
if (fsType != NcaFsHeader.FsType.RomFs)
@ -518,18 +516,15 @@ public class NcaFileSystemServiceImpl : IDisposable
return Result.Success;
}
public Result OpenStorageWithPatch(ref SharedRef<IStorage> outStorage,
ref SharedRef<IAsynchronousAccessSplitter> outStorageAccessSplitter, ref Hash outNcaDigest,
public Result OpenStorageWithPatch(ref SharedRef<IStorage> outStorage, ref Hash outNcaDigest,
ref readonly Path originalNcaPath, ContentAttributes originalAttributes, ref readonly Path currentNcaPath,
ContentAttributes currentAttributes, FileSystemProxyType type, ulong originalId, ulong currentId)
{
return OpenStorageWithPatch(ref outStorage, ref outStorageAccessSplitter, ref outNcaDigest,
in originalNcaPath, originalAttributes, in currentNcaPath, currentAttributes, type, originalId, currentId,
false).Ret();
return OpenStorageWithPatch(ref outStorage, ref outNcaDigest, in originalNcaPath, originalAttributes,
in currentNcaPath, currentAttributes, type, originalId, currentId, false).Ret();
}
public Result OpenStorageWithPatch(ref SharedRef<IStorage> outStorage,
ref SharedRef<IAsynchronousAccessSplitter> outStorageAccessSplitter, ref Hash outNcaDigest,
public Result OpenStorageWithPatch(ref SharedRef<IStorage> outStorage, ref Hash outNcaDigest,
ref readonly Path originalNcaPath, ContentAttributes originalAttributes, ref readonly Path currentNcaPath,
ContentAttributes currentAttributes, FileSystemProxyType type, ulong originalId, ulong currentId,
bool canMountSystemDataPrivate)
@ -567,8 +562,8 @@ public class NcaFileSystemServiceImpl : IDisposable
GenerateNcaDigest(out outNcaDigest, originalNcaReader.Get, currentNcaReader.Get);
}
res = OpenStorageWithPatchByContentType(ref outStorage, ref outStorageAccessSplitter, in originalNcaReader,
in currentNcaReader, out NcaFsHeader.FsType fsType, type, canMountSystemDataPrivate);
res = OpenStorageWithPatchByContentType(ref outStorage, in originalNcaReader, in currentNcaReader,
out NcaFsHeader.FsType fsType, type, canMountSystemDataPrivate);
if (res.IsFailure()) return res.Miss();
if (fsType != NcaFsHeader.FsType.RomFs)
@ -582,11 +577,10 @@ public class NcaFileSystemServiceImpl : IDisposable
FileSystemProxyType type, ulong originalId, ulong currentId)
{
using var storage = new SharedRef<IStorage>();
using var storageAccessSplitter = new SharedRef<IAsynchronousAccessSplitter>();
using var fileSystem = new SharedRef<IFileSystem>();
Result res = OpenStorageWithPatch(ref storage.Ref, ref storageAccessSplitter.Ref, ref Unsafe.NullRef<Hash>(),
in originalNcaPath, originalAttributes, in currentNcaPath, currentAttributes, type, originalId, currentId,
Result res = OpenStorageWithPatch(ref storage.Ref, ref Unsafe.NullRef<Hash>(), in originalNcaPath,
originalAttributes, in currentNcaPath, currentAttributes, type, originalId, currentId,
canMountSystemDataPrivate: false);
if (res.IsFailure()) return res.Miss();
@ -658,7 +652,7 @@ public class NcaFileSystemServiceImpl : IDisposable
{
using SharedRef<IFileSystem> tempFileSystem = SharedRef<IFileSystem>.CreateMove(ref subDirFs.Ref);
res = _config.EncryptedFsCreator.Create(ref subDirFs.Ref, in tempFileSystem,
IEncryptedFileSystemCreator.KeyId.Content, in _encryptionSeed);
IEncryptedFileSystemCreator.KeyId.Content);
if (res.IsFailure()) return res.Miss();
}
@ -933,7 +927,7 @@ public class NcaFileSystemServiceImpl : IDisposable
res = OpenHostFileSystem(ref outFileSystem, in pathRoot, openCaseSensitive: true);
if (res.IsFailure()) return res.Miss();
break;
case MountInfo.FileSystemType.LocalFs:
res = _config.LocalFsCreator.Create(ref outFileSystem, in pathRoot, openCaseSensitive: true);
if (res.IsFailure()) return res.Miss();
@ -1148,7 +1142,6 @@ public class NcaFileSystemServiceImpl : IDisposable
}
public Result OpenStorageByContentType(ref SharedRef<IStorage> outNcaStorage,
ref SharedRef<IAsynchronousAccessSplitter> outStorageAccessSplitter,
ref readonly SharedRef<NcaReader> ncaReader, out NcaFsHeader.FsType outFsType, FileSystemProxyType fsProxyType,
bool isGameCard, bool canMountSystemDataPrivate)
{
@ -1221,8 +1214,7 @@ public class NcaFileSystemServiceImpl : IDisposable
var ncaFsHeaderReader = new NcaFsHeaderReader();
res = _config.StorageOnNcaCreator.Create(ref outNcaStorage, ref outStorageAccessSplitter, ref ncaFsHeaderReader,
in ncaReader, partitionIndex);
res = _config.StorageOnNcaCreator.Create(ref outNcaStorage, ref ncaFsHeaderReader, in ncaReader, partitionIndex);
if (res.IsFailure()) return res.Miss();
outFsType = ncaFsHeaderReader.GetFsType();
@ -1230,7 +1222,6 @@ public class NcaFileSystemServiceImpl : IDisposable
}
public Result OpenStorageWithPatchByContentType(ref SharedRef<IStorage> outNcaStorage,
ref SharedRef<IAsynchronousAccessSplitter> outStorageAccessSplitter,
ref readonly SharedRef<NcaReader> originalNcaReader, ref readonly SharedRef<NcaReader> currentNcaReader,
out NcaFsHeader.FsType outFsType, FileSystemProxyType fsProxyType, bool canMountSystemDataPrivate)
{
@ -1279,21 +1270,14 @@ public class NcaFileSystemServiceImpl : IDisposable
var ncaFsHeaderReader = new NcaFsHeaderReader();
res = _config.StorageOnNcaCreator.CreateWithPatch(ref outNcaStorage, ref outStorageAccessSplitter,
ref ncaFsHeaderReader, in originalNcaReader, in currentNcaReader, partitionIndex);
res = _config.StorageOnNcaCreator.CreateWithPatch(ref outNcaStorage, ref ncaFsHeaderReader,
in originalNcaReader, in currentNcaReader, partitionIndex);
if (res.IsFailure()) return res.Miss();
outFsType = ncaFsHeaderReader.GetFsType();
return Result.Success;
}
public Result SetSdCardEncryptionSeed(in EncryptionSeed encryptionSeed)
{
_encryptionSeed = encryptionSeed;
return Result.Success;
}
public Result ResolveRomReferenceProgramId(out ProgramId outTargetProgramId, ProgramId programId,
byte programIndex)
{
@ -1393,6 +1377,11 @@ public class NcaFileSystemServiceImpl : IDisposable
return Result.Success;
}
public DataId RedirectDataId(DataId dataId, StorageId storageId)
{
return _config.RedirectDataId?.Invoke(dataId, storageId) ?? dataId;
}
public Result ResolveDataPath(ref Path outPath, out ContentAttributes outContentAttributes, DataId dataId,
StorageId storageId)
{
@ -1418,17 +1407,38 @@ public class NcaFileSystemServiceImpl : IDisposable
return _config.LocationResolverSet.ResolveRegisteredHtmlDocumentPath(ref outPath, out outContentAttributes, programId).Ret();
}
internal StorageLayoutType GetStorageFlag(ulong programId)
internal StorageLayoutType GetStorageFlag(ulong contentProgramId, ulong callerProgramId)
{
Assert.SdkRequiresNotEqual(_config.SpeedEmulationRange.ProgramIdWithoutPlatformIdMax, 0ul);
ulong programIdWithoutPlatformId = Impl.Utility.ClearPlatformIdInProgramId(programId);
StorageLayoutType storageFlag = 0;
if (StorageAccessSpeedControl.IsTargetProgramId(_config.FsServer, callerProgramId))
{
storageFlag |= StorageLayoutType.IsApp;
}
ulong programIdWithoutPlatformId = Impl.Utility.ClearPlatformIdInProgramId(contentProgramId);
if (programIdWithoutPlatformId >= _config.SpeedEmulationRange.ProgramIdWithoutPlatformIdMin &&
programIdWithoutPlatformId <= _config.SpeedEmulationRange.ProgramIdWithoutPlatformIdMax)
return StorageLayoutType.Bis;
else
return StorageLayoutType.All;
return storageFlag | StorageLayoutType.All;
}
internal StorageLayoutType GetStorageFlag(ulong programId)
{
return GetStorageFlag(programId, programId);
}
internal StorageLayoutType GetStorageFlagByContentStorageId(ContentStorageId storageId, ulong programId)
{
if (storageId == ContentStorageId.System)
return StorageLayoutType.Bis;
StorageLayoutType flag = StorageAccessSpeedControl.IsTargetProgramId(_config.FsServer, programId) ? StorageLayoutType.IsApp : 0;
return flag | StorageLayoutType.All;
}
public Result HandleResolubleAccessFailure(out bool wasDeferred, Result nonDeferredResult,

View file

@ -1192,7 +1192,7 @@ public class SaveDataFileSystemServiceImpl : IDisposable
if (res.IsFailure()) return res.Miss();
res = _config.EncryptedFsCreator.Create(ref outFileSystem, in baseFileSystem,
IEncryptedFileSystemCreator.KeyId.Save, in _encryptionSeed);
IEncryptedFileSystemCreator.KeyId.Save);
if (res.IsFailure()) return res.Miss();
break;

View file

@ -7,13 +7,36 @@ using LibHac.Util;
namespace LibHac.FsSystem;
/// <summary>
/// The default <see cref="IAsynchronousAccessSplitter"/> that is used when an <see cref="IStorage"/>
/// or <see cref="IFile"/> doesn't need any special logic to split a request into multiple chunks.
/// </summary>
/// <remarks>Based on nnSdk 18.3.0 (FS 18.0.0)</remarks>
public class DefaultAsynchronousAccessSplitter : IAsynchronousAccessSplitter
{
public void Dispose() { }
public Result QueryAppropriateOffset(out long offsetAppropriate, long startOffset, long accessSize, long alignmentSize)
{
offsetAppropriate = Alignment.AlignDown(startOffset + accessSize, alignmentSize);
return Result.Success;
}
public Result QueryInvocationCount(out long count, long startOffset, long endOffset, long accessSize, long alignmentSize)
{
long alignedStartOffset = Alignment.AlignDown(startOffset, alignmentSize);
count = BitUtil.DivideUp(endOffset - alignedStartOffset, accessSize);
return Result.Success;
}
}
/// <summary>
/// <para>Splits read and write requests on an <see cref="IFile"/> or <see cref="IStorage"/> into smaller chunks
/// so the request can be processed by multiple threads simultaneously.</para>
/// <para>This interface exists because of <see cref="CompressedStorage"/> where it will split requests into
/// chunks that start and end on the boundaries of the compressed blocks.</para>
/// </summary>
/// <remarks>Based on nnSdk 13.4.0 (FS 13.1.0)</remarks>
/// <remarks>Based on nnSdk 18.3.0 (FS 18.0.0)</remarks>
public interface IAsynchronousAccessSplitter : IDisposable
{
private static readonly DefaultAsynchronousAccessSplitter DefaultAccessSplitter = new();
@ -40,7 +63,7 @@ public interface IAsynchronousAccessSplitter : IDisposable
if (res.IsFailure()) return res.Miss();
Assert.SdkNotEqual(startOffset, offsetAppropriate);
nextOffset = Math.Min(startOffset, offsetAppropriate);
nextOffset = Math.Min(offsetAppropriate, endOffset);
return Result.Success;
}
@ -66,47 +89,18 @@ public interface IAsynchronousAccessSplitter : IDisposable
Result QueryAppropriateOffset(out long offsetAppropriate, long startOffset, long accessSize, long alignmentSize);
}
/// <summary>
/// The default <see cref="IAsynchronousAccessSplitter"/> that is used when an <see cref="IStorage"/>
/// or <see cref="IFile"/> doesn't need any special logic to split a request into multiple chunks.
/// </summary>
/// <remarks>Based on nnSdk 13.4.0 (FS 13.1.0)</remarks>
public class DefaultAsynchronousAccessSplitter : IAsynchronousAccessSplitter
{
public void Dispose() { }
public Result QueryAppropriateOffset(out long offsetAppropriate, long startOffset, long accessSize, long alignmentSize)
{
offsetAppropriate = Alignment.AlignDown(startOffset + accessSize, alignmentSize);
return Result.Success;
}
public Result QueryInvocationCount(out long count, long startOffset, long endOffset, long accessSize, long alignmentSize)
{
long alignedStartOffset = Alignment.AlignDown(startOffset, alignmentSize);
count = BitUtil.DivideUp(endOffset - alignedStartOffset, accessSize);
return Result.Success;
}
}
public class AsynchronousAccessStorage : IStorage
{
private SharedRef<IStorage> _baseStorage;
// private ThreadPool _threadPool;
private IAsynchronousAccessSplitter _baseStorageAccessSplitter;
private bool _baseStorageHasAccessSplitter;
public AsynchronousAccessStorage(ref readonly SharedRef<IStorage> baseStorage) : this(in baseStorage,
IAsynchronousAccessSplitter.GetDefaultAsynchronousAccessSplitter())
{
}
public AsynchronousAccessStorage(ref readonly SharedRef<IStorage> baseStorage, IAsynchronousAccessSplitter baseStorageAccessSplitter)
public AsynchronousAccessStorage(ref readonly SharedRef<IStorage> baseStorage)
{
_baseStorage = SharedRef<IStorage>.CreateCopy(in baseStorage);
_baseStorageAccessSplitter = baseStorageAccessSplitter;
_baseStorageHasAccessSplitter = true;
Assert.SdkRequiresNotNull(in _baseStorage);
Assert.SdkRequiresNotNull(_baseStorageAccessSplitter);
}
public override void Dispose()
@ -115,10 +109,9 @@ public class AsynchronousAccessStorage : IStorage
base.Dispose();
}
public void SetBaseStorage(ref readonly SharedRef<IStorage> baseStorage, IAsynchronousAccessSplitter baseStorageAccessSplitter)
public void SetBaseStorage(ref readonly SharedRef<IStorage> baseStorage)
{
_baseStorage.SetByCopy(in baseStorage);
_baseStorageAccessSplitter = baseStorageAccessSplitter;
}
// Todo: Implement

View file

@ -188,8 +188,7 @@ public class BlockCacheBufferedStorage : IStorage
throw new NotImplementedException();
}
private Result GetAssociateBuffer(out Buffer outRange, out CacheEntry outEntry, long offset, int idealSize,
bool isAllocateForWrite)
private Result GetAssociateBuffer(out Buffer outRange, out CacheEntry outEntry, long offset, bool isAllocateForWrite)
{
throw new NotImplementedException();
}
@ -241,8 +240,9 @@ public class BlockCacheBufferedStorage : IStorage
throw new NotImplementedException();
}
private Result BulkRead(long offset, Span<byte> buffer, ref Buffer memoryRangeHead, ref Buffer memoryRangeTail,
ref CacheEntry entryHead, ref CacheEntry entryTail, bool isHeadCacheNeeded, bool isTailCacheNeeded)
private Result BulkRead(out bool outIsHeadCacheStored, out bool outIsTailCacheStored, long offset,
Span<byte> buffer, ref Buffer memoryRangeHead, ref Buffer memoryRangeTail, ref CacheEntry entryHead,
ref CacheEntry entryTail)
{
throw new NotImplementedException();
}

View file

@ -18,7 +18,8 @@ public class CompressedStorage : IStorage, IAsynchronousAccessSplitter
private long _continuousReadingSizeMax;
private readonly BucketTree _bucketTree;
private ValueSubStorage _dataStorage;
private GetDecompressorFunction _getDecompressorFunction;
private IDecompressorFactory _decompressorFactory;
private ulong _alignment;
public CompressedStorageCore()
{
@ -67,12 +68,12 @@ public class CompressedStorage : IStorage, IAsynchronousAccessSplitter
public Result Initialize(MemoryResource allocatorForBucketTree, ref readonly ValueSubStorage dataStorage,
ref readonly ValueSubStorage nodeStorage, ref readonly ValueSubStorage entryStorage,
int bucketTreeEntryCount, long blockSizeMax, long continuousReadingSizeMax,
GetDecompressorFunction getDecompressorFunc)
IDecompressorFactory decompressorFactory, ulong alignment)
{
Assert.SdkRequiresNotNull(allocatorForBucketTree);
Assert.SdkRequiresLess(0, blockSizeMax);
Assert.SdkRequiresLessEqual(blockSizeMax, continuousReadingSizeMax);
Assert.SdkRequiresNotNull(getDecompressorFunc);
Assert.SdkRequiresNotNull(decompressorFactory);
Result res = _bucketTree.Initialize(allocatorForBucketTree, in nodeStorage, in entryStorage, NodeSize,
Unsafe.SizeOf<Entry>(), bucketTreeEntryCount);
@ -81,7 +82,8 @@ public class CompressedStorage : IStorage, IAsynchronousAccessSplitter
_blockSizeMax = blockSizeMax;
_continuousReadingSizeMax = continuousReadingSizeMax;
_dataStorage.Set(in dataStorage);
_getDecompressorFunction = getDecompressorFunc;
_decompressorFactory = decompressorFactory;
_alignment = alignment;
return Result.Success;
}
@ -120,14 +122,6 @@ public class CompressedStorage : IStorage, IAsynchronousAccessSplitter
{
throw new NotImplementedException();
}
private DecompressorFunction GetDecompressor(CompressionType type)
{
if (CompressionTypeUtility.IsUnknownType(type))
return null;
return _getDecompressorFunction(type);
}
}
public class CacheManager : IDisposable
@ -299,11 +293,11 @@ public class CompressedStorage : IStorage, IAsynchronousAccessSplitter
public Result Initialize(MemoryResource allocatorForBucketTree, IBufferManager allocatorForCacheManager,
ref readonly ValueSubStorage dataStorage, ref readonly ValueSubStorage nodeStorage,
ref readonly ValueSubStorage entryStorage, int bucketTreeEntryCount, long blockSizeMax,
long continuousReadingSizeMax, GetDecompressorFunction getDecompressorFunc, long cacheSize0, long cacheSize1,
int maxCacheEntries)
long continuousReadingSizeMax, IDecompressorFactory decompressorFactory, ulong alignment, long cacheSize0,
long cacheSize1, int maxCacheEntries)
{
Result res = _core.Initialize(allocatorForBucketTree, in dataStorage, in nodeStorage, in entryStorage,
bucketTreeEntryCount, blockSizeMax, continuousReadingSizeMax, getDecompressorFunc);
bucketTreeEntryCount, blockSizeMax, continuousReadingSizeMax, decompressorFactory, alignment);
if (res.IsFailure()) return res.Miss();
res = _core.GetSize(out long size);

View file

@ -1,35 +1,29 @@
using System;
using LibHac.Common;
namespace LibHac.FsSystem;
public enum CompressionType : byte
{
None = 0,
Zeroed = 1,
FillZero = 1,
Lz4 = 3,
Unknown = 4
}
public ref struct DecompressionTask
{
public Span<byte> Destination;
public ReadOnlySpan<byte> Source;
}
public delegate Result DecompressorFunction(DecompressionTask task);
public delegate DecompressorFunction GetDecompressorFunction(CompressionType compressionType);
public delegate Result CompressionGetData(Span<byte> buffer);
public delegate Result CompressionProcessData(Span<byte> workBuffer, int sizeBufferRequired, CompressionGetData getDataFunc);
public static class CompressionTypeUtility
{
public static bool IsBlockAlignmentRequired(CompressionType type)
{
return type != CompressionType.None && type != CompressionType.Zeroed;
return type != CompressionType.None && type != CompressionType.FillZero;
}
public static bool IsDataStorageAccessRequired(CompressionType type)
{
return type != CompressionType.Zeroed;
return type != CompressionType.FillZero;
}
public static bool IsRandomAccessible(CompressionType type)
@ -41,4 +35,15 @@ public static class CompressionTypeUtility
{
return type >= CompressionType.Unknown;
}
}
public interface IDecompressorFactory
{
public Result GetDecompressor(ref UniqueRef<IDecompressor> outDecompressor, CompressionProcessData processDataFunc, Span<byte> buffer);
}
public interface IDecompressor : IDisposable
{
Result Decompress(CompressionType type, int compressedSize, Span<byte> buffer, int decompressedSize);
Result Unk();
}

View file

@ -31,7 +31,7 @@ public struct NcaCryptoConfiguration
public struct NcaCompressionConfiguration
{
public GetDecompressorFunction GetDecompressorFunc;
public IDecompressorFactory DecompressorFactory;
}
public static class NcaKeyFunctions
@ -203,9 +203,7 @@ public class NcaFileSystemDriver : IDisposable
throw new NotImplementedException();
}
public Result OpenStorage(ref SharedRef<IStorage> outStorage,
ref SharedRef<IAsynchronousAccessSplitter> outStorageAccessSplitter, ref NcaFsHeaderReader outHeaderReader,
int fsIndex)
public Result OpenStorage(ref SharedRef<IStorage> outStorage, ref NcaFsHeaderReader outHeaderReader, int fsIndex)
{
throw new NotImplementedException();
}
@ -323,7 +321,7 @@ public class NcaFileSystemDriver : IDisposable
private Result CreateIntegrityVerificationStorage(ref SharedRef<IStorage> outStorage,
ref readonly SharedRef<IStorage> baseStorage, in NcaFsHeader.HashData.IntegrityMetaInfo metaInfo,
IHash256GeneratorFactory hashGeneratorFactory)
bool alwaysCreateCacheEntries, IHash256GeneratorFactory hashGeneratorFactory)
{
throw new NotImplementedException();
}
@ -338,22 +336,22 @@ public class NcaFileSystemDriver : IDisposable
private Result CreateIntegrityVerificationStorageImpl(ref SharedRef<IStorage> outStorage,
ref readonly SharedRef<IStorage> baseStorage, in NcaFsHeader.HashData.IntegrityMetaInfo metaInfo,
long layerInfoOffset, int maxDataCacheEntries, int maxHashCacheEntries, sbyte bufferLevel,
IHash256GeneratorFactory hashGeneratorFactory)
bool alwaysCreateCacheEntries, IHash256GeneratorFactory hashGeneratorFactory)
{
throw new NotImplementedException();
}
public static Result CreateCompressedStorage(ref SharedRef<IStorage> outStorage,
ref SharedRef<CompressedStorage> outCompressedStorage, ref SharedRef<IStorage> outMetaStorage,
ref readonly SharedRef<IStorage> baseStorage, in NcaCompressionInfo compressionInfo,
GetDecompressorFunction getDecompressor, MemoryResource allocator, IBufferManager bufferManager)
ref readonly SharedRef<IStorage> baseStorage, in NcaCompressionInfo compressionInfo, uint alignment,
IDecompressorFactory decompressorFactory, MemoryResource allocator, IBufferManager bufferManager)
{
throw new NotImplementedException();
}
private Result CreateCompressedStorage(ref SharedRef<IStorage> outStorage,
ref SharedRef<CompressedStorage> outCompressedStorage, ref SharedRef<IStorage> outMetaStorage,
ref readonly SharedRef<IStorage> baseStorage, in NcaCompressionInfo compressionInfo)
ref readonly SharedRef<IStorage> baseStorage, in NcaCompressionInfo compressionInfo, uint alignment)
{
throw new NotImplementedException();
}

View file

@ -22,7 +22,7 @@ public class NcaReader : IDisposable
private SharedRef<IStorage> _bodyStorage;
private SharedRef<IStorage> _headerStorage;
private SharedRef<IAesCtrDecryptor> _aesCtrDecryptor;
private GetDecompressorFunction _getDecompressorFunc;
private IDecompressorFactory _decompressorFactory;
private IHash256GeneratorFactorySelector _hashGeneratorFactorySelector;
public NcaReader(in RuntimeNcaHeader runtimeNcaHeader, ref readonly SharedRef<IStorage> notVerifiedHeaderStorage,
@ -39,7 +39,7 @@ public class NcaReader : IDisposable
_bodyStorage = SharedRef<IStorage>.CreateCopy(in bodyStorage);
_aesCtrDecryptor = SharedRef<IAesCtrDecryptor>.CreateCopy(in aesCtrDecryptor);
_getDecompressorFunc = compressionConfig.GetDecompressorFunc;
_decompressorFactory = compressionConfig.DecompressorFactory;
_hashGeneratorFactorySelector = hashGeneratorFactorySelector;
}
@ -181,10 +181,10 @@ public class NcaReader : IDisposable
return SharedRef<IAesCtrDecryptor>.CreateCopy(in _aesCtrDecryptor);
}
public GetDecompressorFunction GetDecompressor()
public IDecompressorFactory GetDecompressorFactory()
{
Assert.SdkRequiresNotNull(_getDecompressorFunc);
return _getDecompressorFunc;
Assert.SdkRequiresNotNull(_decompressorFactory);
return _decompressorFactory;
}
public IHash256GeneratorFactorySelector GetHashGeneratorFactorySelector()

View file

@ -13,6 +13,8 @@ internal enum StorageLayoutType
SdCard = 1 << 1,
GameCard = 1 << 2,
Usb = 1 << 3,
IsApp = 1 << 24,
NonGameCard = Bis | SdCard | Usb,
All = Bis | SdCard | GameCard | Usb

View file

@ -141,7 +141,7 @@ internal class CompressedStorage : IStorage
res = _dataStorage.Read(currentEntry.PhysicalOffset + dataOffsetInEntry, entryDestination);
if (res.IsFailure()) return res.Miss();
}
else if (currentEntry.CompressionType == CompressionType.Zeroed)
else if (currentEntry.CompressionType == CompressionType.FillZero)
{
entryDestination.Clear();
}