diff --git a/src/LibHac/Fs/GameCard.cs b/src/LibHac/Fs/GameCard.cs index 81ff7bc4..73d4fb11 100644 --- a/src/LibHac/Fs/GameCard.cs +++ b/src/LibHac/Fs/GameCard.cs @@ -1,4 +1,5 @@ using System; +using LibHac.Common.FixedArrays; namespace LibHac.Fs; @@ -41,3 +42,51 @@ public enum GameCardAttribute : byte DifferentRegionCupToTerraDeviceFlag = 1 << 3, DifferentRegionCupToGlobalDeviceFlag = 1 << 4 } + +public struct GameCardErrorInfo +{ + public ushort GameCardCrcErrorCount; + public ushort Reserved2; + public ushort AsicCrcErrorCount; + public ushort Reserved6; + public ushort RefreshCount; + public ushort ReservedA; + public ushort ReadRetryCount; + public ushort TimeoutRetryErrorCount; +} + +public struct GameCardErrorReportInfo +{ + public GameCardErrorInfo ErrorInfo; + public ushort AsicReinitializeFailureDetail; + public ushort InsertionCount; + public ushort RemovalCount; + public ushort AsicReinitializeCount; + public uint AsicInitializeCount; + public ushort AsicReinitializeFailureCount; + public ushort AwakenFailureCount; + public ushort Reserved20; + public ushort RefreshCount; + public uint LastReadErrorPageAddress; + public uint LastReadErrorPageCount; + public uint AwakenCount; + public uint ReadCountFromInsert; + public uint ReadCountFromAwaken; + public Array8 Reserved38; +} + +public readonly struct GameCardHandle : IEquatable +{ + public readonly int Value; + + public GameCardHandle(int value) + { + Value = value; + } + + public override bool Equals(object obj) => obj is GameCardHandle handle && Equals(handle); + public bool Equals(GameCardHandle other) => Value == other.Value; + public override int GetHashCode() => Value.GetHashCode(); + public static bool operator ==(GameCardHandle left, GameCardHandle right) => left.Equals(right); + public static bool operator !=(GameCardHandle left, GameCardHandle right) => !(left == right); +} \ No newline at end of file diff --git a/src/LibHac/Fs/Impl/StorageServiceObjectAdapter.cs b/src/LibHac/Fs/Impl/StorageServiceObjectAdapter.cs index d837248f..51664c4c 100644 --- a/src/LibHac/Fs/Impl/StorageServiceObjectAdapter.cs +++ b/src/LibHac/Fs/Impl/StorageServiceObjectAdapter.cs @@ -1,6 +1,7 @@ using System; using System.Runtime.CompilerServices; using LibHac.Common; +using LibHac.FsSrv.Storage.Sf; using LibHac.Sf; using IStorageSf = LibHac.FsSrv.Sf.IStorage; @@ -20,6 +21,11 @@ internal class StorageServiceObjectAdapter : IStorage _baseStorage = SharedRef.CreateMove(ref baseStorage); } + public StorageServiceObjectAdapter(ref SharedRef baseStorage) + { + _baseStorage = SharedRef.CreateMove(ref baseStorage); + } + public override void Dispose() { _baseStorage.Destroy(); diff --git a/src/LibHac/Fs/Shim/GameCard.cs b/src/LibHac/Fs/Shim/GameCard.cs index 0e7947df..d6a92f8a 100644 --- a/src/LibHac/Fs/Shim/GameCard.cs +++ b/src/LibHac/Fs/Shim/GameCard.cs @@ -4,7 +4,6 @@ using LibHac.Common; using LibHac.Diag; using LibHac.Fs.Fsa; using LibHac.Fs.Impl; -using LibHac.FsSrv; using LibHac.FsSrv.Sf; using LibHac.Os; using LibHac.Util; diff --git a/src/LibHac/FsSrv/FileSystemServer.cs b/src/LibHac/FsSrv/FileSystemServer.cs index 010cf410..cf0de519 100644 --- a/src/LibHac/FsSrv/FileSystemServer.cs +++ b/src/LibHac/FsSrv/FileSystemServer.cs @@ -1,10 +1,11 @@ -using LibHac.FsSrv.Impl; +using System; +using LibHac.FsSrv.Impl; using LibHac.FsSrv.Storage; using LibHac.FsSystem; namespace LibHac.FsSrv; -public class FileSystemServer +public class FileSystemServer : IDisposable { internal FileSystemServerGlobals Globals; @@ -20,9 +21,15 @@ public class FileSystemServer { Globals.Initialize(horizonClient, this); } + + public void Dispose() + { + Globals.Dispose(); + Globals = default; + } } -internal struct FileSystemServerGlobals +internal struct FileSystemServerGlobals : IDisposable { public HorizonClient Hos; public object InitMutex; @@ -36,6 +43,7 @@ internal struct FileSystemServerGlobals public MultiCommitManagerGlobals MultiCommitManager; public LocationResolverSetGlobals LocationResolverSet; public PooledBufferGlobals PooledBuffer; + public GameCardServiceGlobals GameCardService; public void Initialize(HorizonClient horizonClient, FileSystemServer fsServer) { @@ -46,6 +54,12 @@ internal struct FileSystemServerGlobals MultiCommitManager.Initialize(); LocationResolverSet.Initialize(); PooledBuffer.Initialize(); + GameCardService.Initialize(); + } + + public void Dispose() + { + GameCardService.Dispose(); } } diff --git a/src/LibHac/FsSrv/GameCardHandle.cs b/src/LibHac/FsSrv/GameCardHandle.cs deleted file mode 100644 index 3ccfb923..00000000 --- a/src/LibHac/FsSrv/GameCardHandle.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; - -namespace LibHac.FsSrv; - -public readonly struct GameCardHandle : IEquatable -{ - public readonly int Value; - - public GameCardHandle(int value) - { - Value = value; - } - - public override bool Equals(object obj) => obj is GameCardHandle handle && Equals(handle); - public bool Equals(GameCardHandle other) => Value == other.Value; - public override int GetHashCode() => Value.GetHashCode(); - public static bool operator ==(GameCardHandle left, GameCardHandle right) => left.Equals(right); - public static bool operator !=(GameCardHandle left, GameCardHandle right) => !(left == right); -} diff --git a/src/LibHac/FsSrv/Sf/IDeviceOperator.cs b/src/LibHac/FsSrv/Sf/IDeviceOperator.cs index 3a22e3d9..13164242 100644 --- a/src/LibHac/FsSrv/Sf/IDeviceOperator.cs +++ b/src/LibHac/FsSrv/Sf/IDeviceOperator.cs @@ -1,4 +1,5 @@ using System; +using LibHac.Fs; namespace LibHac.FsSrv.Sf; @@ -7,4 +8,4 @@ public interface IDeviceOperator : IDisposable Result IsSdCardInserted(out bool isInserted); Result IsGameCardInserted(out bool isInserted); Result GetGameCardHandle(out GameCardHandle handle); -} +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/Storage/GameCardService.cs b/src/LibHac/FsSrv/Storage/GameCardService.cs new file mode 100644 index 00000000..6a7009ad --- /dev/null +++ b/src/LibHac/FsSrv/Storage/GameCardService.cs @@ -0,0 +1,616 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Diag; +using LibHac.Fs; +using LibHac.Fs.Impl; +using LibHac.FsSrv.Impl; +using LibHac.FsSrv.Sf; +using LibHac.FsSrv.Storage.Sf; +using LibHac.Gc; +using LibHac.GcSrv; +using LibHac.Os; +using LibHac.Sf; +using static LibHac.Gc.Values; +using IStorage = LibHac.Fs.IStorage; + +namespace LibHac.FsSrv.Storage; + +internal struct GameCardServiceGlobals : IDisposable +{ + public SdkMutexType StorageDeviceMutex; + public SharedRef CachedStorageDevice; + + public void Initialize() + { + StorageDeviceMutex = new SdkMutexType(); + } + + public void Dispose() + { + CachedStorageDevice.Destroy(); + } +} + +/// +/// Contains functions for interacting with the game card storage device. +/// +/// Based on FS 13.1.0 (nnSdk 13.4.0) +internal static class GameCardService +{ + private static ulong MakeAttributeId(OpenGameCardAttribute attribute) => (ulong)attribute; + private static int MakeOperationId(GameCardManagerOperationIdValue operation) => (int)operation; + private static int MakeOperationId(GameCardOperationIdValue operation) => (int)operation; + + private static Result GetGameCardManager(this StorageService service, + ref SharedRef outManager) + { + return service.CreateStorageDeviceManager(ref outManager, StorageDevicePortId.GameCard); + } + + private static Result OpenAndCacheGameCardDevice(this StorageService service, + ref SharedRef outStorageDevice, OpenGameCardAttribute attribute) + { + using var storageDeviceManager = new SharedRef(); + Result rc = service.GetGameCardManager(ref storageDeviceManager.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + ref GameCardServiceGlobals g = ref service.Globals.GameCardService; + + using var gameCardStorageDevice = new SharedRef(); + using ScopedLock scopedLock = ScopedLock.Lock(ref g.StorageDeviceMutex); + + rc = storageDeviceManager.Get.OpenDevice(ref gameCardStorageDevice.Ref(), MakeAttributeId(attribute)); + if (rc.IsFailure()) return rc.Miss(); + + g.CachedStorageDevice.SetByCopy(in gameCardStorageDevice); + outStorageDevice.SetByCopy(in gameCardStorageDevice); + + return Result.Success; + } + + private static Result GetGameCardManagerOperator(this StorageService service, + ref SharedRef outDeviceOperator) + { + using var storageDeviceManager = new SharedRef(); + Result rc = service.GetGameCardManager(ref storageDeviceManager.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + return storageDeviceManager.Get.OpenOperator(ref outDeviceOperator); + } + + private static Result GetGameCardOperator(this StorageService service, + ref SharedRef outDeviceOperator, OpenGameCardAttribute attribute) + { + using var storageDevice = new SharedRef(); + Result rc = service.OpenAndCacheGameCardDevice(ref storageDevice.Ref(), attribute); + if (rc.IsFailure()) return rc.Miss(); + + return storageDevice.Get.OpenOperator(ref outDeviceOperator.Ref()); + } + + private static Result GetGameCardOperator(this StorageService service, + ref SharedRef outDeviceOperator) + { + ref GameCardServiceGlobals g = ref service.Globals.GameCardService; + + using (ScopedLock.Lock(ref g.StorageDeviceMutex)) + { + if (g.CachedStorageDevice.HasValue) + { + return g.CachedStorageDevice.Get.OpenOperator(ref outDeviceOperator); + } + } + + return service.GetGameCardOperator(ref outDeviceOperator, OpenGameCardAttribute.ReadOnly); + } + + public static Result OpenGameCardStorage(this StorageService service, ref SharedRef outStorage, + OpenGameCardAttribute attribute, uint handle) + { + using var gameCardStorageDevice = new SharedRef(); + + Result rc = service.OpenAndCacheGameCardDevice(ref gameCardStorageDevice.Ref(), attribute); + if (rc.IsFailure()) return rc.Miss(); + + // Verify that the game card handle hasn't changed. + rc = service.GetCurrentGameCardHandle(out StorageDeviceHandle newHandle); + if (rc.IsFailure()) return rc.Miss(); + + if (newHandle.Value != handle) + { + switch (attribute) + { + case OpenGameCardAttribute.ReadOnly: + return ResultFs.GameCardFsCheckHandleInCreateReadOnlyFailure.Log(); + case OpenGameCardAttribute.SecureReadOnly: + return ResultFs.GameCardFsCheckHandleInCreateSecureReadOnlyFailure.Log(); + case OpenGameCardAttribute.WriteOnly: + break; + default: + return ResultFs.GameCardFsFailure.Log(); + } + } + + // Open the storage and add IPC and event simulation wrappers. + using var storage = new SharedRef(new StorageServiceObjectAdapter(ref gameCardStorageDevice.Ref())); + + using var deviceEventSimulationStorage = new SharedRef( + new DeviceEventSimulationStorage(ref storage.Ref(), service.FsSrv.Impl.GetGameCardEventSimulator())); + + outStorage.SetByMove(ref deviceEventSimulationStorage.Ref()); + + return Result.Success; + } + + public static Result GetCurrentGameCardHandle(this StorageService service, out StorageDeviceHandle outHandle) + { + UnsafeHelpers.SkipParamInit(out outHandle); + + ref GameCardServiceGlobals g = ref service.Globals.GameCardService; + + using (ScopedLock.Lock(ref g.StorageDeviceMutex)) + { + if (g.CachedStorageDevice.HasValue) + { + Result rc = g.CachedStorageDevice.Get.GetHandle(out uint handleValue); + if (rc.IsFailure()) return rc.Miss(); + + outHandle = new StorageDeviceHandle(handleValue, StorageDevicePortId.GameCard); + return Result.Success; + } + } + + { + using var gameCardStorageDevice = new SharedRef(); + Result rc = service.OpenAndCacheGameCardDevice(ref gameCardStorageDevice.Ref(), + OpenGameCardAttribute.ReadOnly); + if (rc.IsFailure()) return rc.Miss(); + + rc = gameCardStorageDevice.Get.GetHandle(out uint handleValue); + if (rc.IsFailure()) return rc.Miss(); + + outHandle = new StorageDeviceHandle(handleValue, StorageDevicePortId.GameCard); + return Result.Success; + } + } + + public static Result IsGameCardHandleValid(this StorageService service, out bool isValid, + in StorageDeviceHandle handle) + { + UnsafeHelpers.SkipParamInit(out isValid); + + using var storageDeviceManager = new SharedRef(); + Result rc = service.GetGameCardManager(ref storageDeviceManager.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + return storageDeviceManager.Get.IsHandleValid(out isValid, handle.Value); + } + + public static Result IsGameCardInserted(this StorageService service, out bool outIsInserted) + { + UnsafeHelpers.SkipParamInit(out outIsInserted); + + using var storageDeviceManager = new SharedRef(); + Result rc = service.GetGameCardManager(ref storageDeviceManager.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + // Get the actual state of the game card. + rc = storageDeviceManager.Get.IsInserted(out bool isInserted); + if (rc.IsFailure()) return rc.Miss(); + + // Get the simulated state of the game card based on the actual state. + outIsInserted = service.FsSrv.Impl.GetGameCardEventSimulator().FilterDetectionState(isInserted); + return Result.Success; + } + + public static Result EraseGameCard(this StorageService service, uint gameCardSize, ulong romAreaStartPageAddress) + { + using var gcOperator = new SharedRef(); + Result rc = service.GetGameCardOperator(ref gcOperator.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + InBuffer inBuffer = InBuffer.FromStruct(in romAreaStartPageAddress); + int operationId = MakeOperationId(GameCardOperationIdValue.EraseGameCard); + + return gcOperator.Get.OperateIn(inBuffer, offset: 0, gameCardSize, operationId); + } + + public static Result GetInitializationResult(this StorageService service) + { + using var gcOperator = new SharedRef(); + Result rc = service.GetGameCardManagerOperator(ref gcOperator.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + int operationId = MakeOperationId(GameCardManagerOperationIdValue.GetInitializationResult); + + return gcOperator.Get.Operate(operationId); + } + + public static Result GetGameCardStatus(this StorageService service, out GameCardStatus outGameCardStatus, + uint handle) + { + UnsafeHelpers.SkipParamInit(out outGameCardStatus); + + using var gcOperator = new SharedRef(); + Result rc = service.GetGameCardOperator(ref gcOperator.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + // Verify that the game card handle hasn't changed. + var deviceHandle = new StorageDeviceHandle(handle, StorageDevicePortId.GameCard); + rc = service.IsGameCardHandleValid(out bool isValidHandle, in deviceHandle); + if (rc.IsFailure()) return rc.Miss(); + + if (!isValidHandle) + return ResultFs.GameCardFsCheckHandleInGetStatusFailure.Log(); + + // Get the GameCardStatus. + OutBuffer outCardStatusBuffer = OutBuffer.FromStruct(ref outGameCardStatus); + int operationId = MakeOperationId(GameCardOperationIdValue.GetGameCardStatus); + + rc = gcOperator.Get.OperateOut(out long bytesWritten, outCardStatusBuffer, operationId); + if (rc.IsFailure()) return rc.Miss(); + + Assert.SdkEqual(Unsafe.SizeOf(), bytesWritten); + + return Result.Success; + } + + public static Result FinalizeGameCardLibrary(this StorageService service) + { + using var gcOperator = new SharedRef(); + Result rc = service.GetGameCardManagerOperator(ref gcOperator.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + int operationId = MakeOperationId(GameCardManagerOperationIdValue.Finalize); + + return gcOperator.Get.Operate(operationId); + } + + public static Result GetGameCardDeviceCertificate(this StorageService service, Span outBuffer, uint handle) + { + using var gcOperator = new SharedRef(); + Result rc = service.GetGameCardOperator(ref gcOperator.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + // Verify that the game card handle hasn't changed. + var deviceHandle = new StorageDeviceHandle(handle, StorageDevicePortId.GameCard); + rc = service.IsGameCardHandleValid(out bool isValidHandle, in deviceHandle); + if (rc.IsFailure()) return rc.Miss(); + + if (!isValidHandle) + return ResultFs.GameCardFsCheckHandleInGetDeviceCertFailure.Log(); + + // Get the device certificate. + var outCertBuffer = new OutBuffer(outBuffer); + int operationId = MakeOperationId(GameCardOperationIdValue.GetGameCardDeviceCertificate); + + rc = gcOperator.Get.OperateOut(out long bytesWritten, outCertBuffer, operationId); + if (rc.IsFailure()) return rc.Miss(); + + Assert.SdkEqual(GcDeviceCertificateSize, bytesWritten); + + return Result.Success; + } + + public static Result ChallengeCardExistence(this StorageService service, Span outResponseBuffer, + ReadOnlySpan challengeSeed, ReadOnlySpan challengeValue, uint handle) + { + using var gcOperator = new SharedRef(); + Result rc = service.GetGameCardOperator(ref gcOperator.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + // Verify that the game card handle hasn't changed. + var deviceHandle = new StorageDeviceHandle(handle, StorageDevicePortId.GameCard); + rc = service.IsGameCardHandleValid(out bool isValidHandle, in deviceHandle); + if (rc.IsFailure()) return rc.Miss(); + + if (!isValidHandle) + return ResultFs.GameCardFsCheckHandleInChallengeCardExistence.Log(); + + // Get the challenge response. + var valueBuffer = new InBuffer(challengeValue); + var seedBuffer = new InBuffer(challengeSeed); + var responseBuffer = new OutBuffer(outResponseBuffer); + int operationId = MakeOperationId(GameCardOperationIdValue.ChallengeCardExistence); + + rc = gcOperator.Get.OperateIn2Out(out long bytesWritten, responseBuffer, valueBuffer, seedBuffer, offset: 0, + size: 0, operationId); + if (rc.IsFailure()) return rc.Miss(); + + Assert.SdkEqual(GcCardExistenceResponseDataSize, bytesWritten); + + return Result.Success; + } + + public static Result GetGameCardHandle(this StorageService service, out uint handle) + { + UnsafeHelpers.SkipParamInit(out handle); + + using var gcOperator = new SharedRef(); + Result rc = service.GetGameCardManagerOperator(ref gcOperator.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + // Get the current handle. + OutBuffer handleOutBuffer = OutBuffer.FromStruct(ref handle); + int operationId = MakeOperationId(GameCardManagerOperationIdValue.GetHandle); + + rc = gcOperator.Get.OperateOut(out long bytesWritten, handleOutBuffer, operationId); + if (rc.IsFailure()) return rc.Miss(); + + Assert.SdkEqual(Unsafe.SizeOf(), bytesWritten); + + // Clear the cached storage device if it has an old handle. + ref GameCardServiceGlobals g = ref service.Globals.GameCardService; + using ScopedLock scopedLock = ScopedLock.Lock(ref g.StorageDeviceMutex); + + if (g.CachedStorageDevice.HasValue) + { + g.CachedStorageDevice.Get.GetHandle(out uint handleValue); + if (rc.IsFailure()) return rc.Miss(); + + var currentHandle = new StorageDeviceHandle(handleValue, StorageDevicePortId.GameCard); + rc = service.IsGameCardHandleValid(out bool isHandleValid, in currentHandle); + if (rc.IsFailure()) return rc.Miss(); + + if (!isHandleValid) + g.CachedStorageDevice.Reset(); + } + + return Result.Success; + } + + public static Result GetGameCardAsicInfo(this StorageService service, out RmaInformation rmaInfo, + ReadOnlySpan firmwareBuffer) + { + UnsafeHelpers.SkipParamInit(out rmaInfo); + + using var gcOperator = new SharedRef(); + Result rc = service.GetGameCardManagerOperator(ref gcOperator.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + var inFirmwareBuffer = new InBuffer(firmwareBuffer); + OutBuffer outRmaInfoBuffer = OutBuffer.FromStruct(ref rmaInfo); + int operationId = MakeOperationId(GameCardManagerOperationIdValue.GetGameCardAsicInfo); + + rc = gcOperator.Get.OperateInOut(out long bytesWritten, outRmaInfoBuffer, inFirmwareBuffer, offset: 0, + size: firmwareBuffer.Length, operationId); + if (rc.IsFailure()) return rc.Miss(); + + Assert.SdkEqual(Unsafe.SizeOf(), bytesWritten); + + return Result.Success; + } + + public static Result GetGameCardIdSet(this StorageService service, out GameCardIdSet outIdSet) + { + UnsafeHelpers.SkipParamInit(out outIdSet); + + using var gcOperator = new SharedRef(); + Result rc = service.GetGameCardOperator(ref gcOperator.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + OutBuffer outIdSetBuffer = OutBuffer.FromStruct(ref outIdSet); + int operationId = MakeOperationId(GameCardOperationIdValue.GetGameCardIdSet); + + rc = gcOperator.Get.OperateOut(out long bytesWritten, outIdSetBuffer, operationId); + if (rc.IsFailure()) return rc.Miss(); + + Assert.SdkEqual(Unsafe.SizeOf(), bytesWritten); + + return Result.Success; + } + + public static Result WriteToGameCardDirectly(this StorageService service, long offset, Span buffer) + { + using var gcOperator = new SharedRef(); + Result rc = service.GetGameCardManagerOperator(ref gcOperator.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + var outBuffer = new OutBuffer(buffer); + var inUnusedBuffer = new InBuffer(); + int operationId = MakeOperationId(GameCardManagerOperationIdValue.WriteToGameCardDirectly); + + // Missing: Register device buffer + + rc = gcOperator.Get.OperateInOut(out _, outBuffer, inUnusedBuffer, offset, buffer.Length, operationId); + + // Missing: Unregister device buffer + + return rc; + } + + public static Result SetVerifyWriteEnableFlag(this StorageService service, bool isEnabled) + { + using var gcOperator = new SharedRef(); + Result rc = service.GetGameCardManagerOperator(ref gcOperator.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + InBuffer inIsEnabledBuffer = InBuffer.FromStruct(in isEnabled); + int operationId = MakeOperationId(GameCardManagerOperationIdValue.SetVerifyEnableFlag); + + return gcOperator.Get.OperateIn(inIsEnabledBuffer, offset: 0, size: 0, operationId); + } + + public static Result GetGameCardImageHash(this StorageService service, Span outBuffer, uint handle) + { + using var gcOperator = new SharedRef(); + Result rc = service.GetGameCardOperator(ref gcOperator.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + // Verify that the game card handle hasn't changed. + var deviceHandle = new StorageDeviceHandle(handle, StorageDevicePortId.GameCard); + rc = service.IsGameCardHandleValid(out bool isValidHandle, in deviceHandle); + if (rc.IsFailure()) return rc.Miss(); + + if (!isValidHandle) + return ResultFs.GameCardFsCheckHandleInGetCardImageHashFailure.Log(); + + // Get the card image hash. + var outImageHashBuffer = new OutBuffer(outBuffer); + int operationId = MakeOperationId(GameCardOperationIdValue.GetGameCardImageHash); + + rc = gcOperator.Get.OperateOut(out long bytesWritten, outImageHashBuffer, operationId); + if (rc.IsFailure()) return rc.Miss(); + + Assert.SdkEqual(GcCardImageHashSize, bytesWritten); + + return Result.Success; + } + + public static Result GetGameCardDeviceIdForProdCard(this StorageService service, Span outBuffer, + ReadOnlySpan devHeaderBuffer) + { + using var gcOperator = new SharedRef(); + Result rc = service.GetGameCardManagerOperator(ref gcOperator.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + var inDevHeaderBuffer = new InBuffer(devHeaderBuffer); + var outDeviceIdBuffer = new OutBuffer(outBuffer); + int operationId = MakeOperationId(GameCardManagerOperationIdValue.GetGameCardDeviceIdForProdCard); + + rc = gcOperator.Get.OperateInOut(out long bytesWritten, outDeviceIdBuffer, inDevHeaderBuffer, offset: 0, + size: 0, operationId); + if (rc.IsFailure()) return rc.Miss(); + + Assert.SdkEqual(GcPageSize, bytesWritten); + + return Result.Success; + } + + public static Result EraseAndWriteParamDirectly(this StorageService service, ReadOnlySpan devParamBuffer) + { + using var gcOperator = new SharedRef(); + Result rc = service.GetGameCardManagerOperator(ref gcOperator.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + var inDevParamBuffer = new InBuffer(devParamBuffer); + int operationId = MakeOperationId(GameCardManagerOperationIdValue.EraseAndWriteParamDirectly); + + return gcOperator.Get.OperateIn(inDevParamBuffer, offset: 0, size: devParamBuffer.Length, operationId); + } + + public static Result ReadParamDirectly(this StorageService service, Span outDevParamBuffer) + { + using var gcOperator = new SharedRef(); + Result rc = service.GetGameCardManagerOperator(ref gcOperator.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + int operationId = MakeOperationId(GameCardManagerOperationIdValue.ReadParamDirectly); + + rc = gcOperator.Get.OperateOut(out long bytesWritten, new OutBuffer(outDevParamBuffer), operationId); + if (rc.IsFailure()) return rc.Miss(); + + Assert.SdkEqual(GcPageSize, bytesWritten); + + return Result.Success; + } + + public static Result ForceEraseGameCard(this StorageService service) + { + using var gcOperator = new SharedRef(); + Result rc = service.GetGameCardManagerOperator(ref gcOperator.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + int operationId = MakeOperationId(GameCardManagerOperationIdValue.ForceErase); + + return gcOperator.Get.Operate(operationId); + } + + public static Result GetGameCardErrorInfo(this StorageService service, out GameCardErrorInfo errorInfo) + { + UnsafeHelpers.SkipParamInit(out errorInfo); + + using var gcOperator = new SharedRef(); + Result rc = service.GetGameCardManagerOperator(ref gcOperator.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + OutBuffer outErrorInfoBuffer = OutBuffer.FromStruct(ref errorInfo); + int operationId = MakeOperationId(GameCardManagerOperationIdValue.GetGameCardErrorInfo); + + rc = gcOperator.Get.OperateOut(out long bytesWritten, outErrorInfoBuffer, operationId); + if (rc.IsFailure()) return rc.Miss(); + + Assert.SdkEqual(Unsafe.SizeOf(), bytesWritten); + + return Result.Success; + } + + public static Result GetGameCardErrorReportInfo(this StorageService service, + out GameCardErrorReportInfo errorReportInfo) + { + UnsafeHelpers.SkipParamInit(out errorReportInfo); + + using var gcOperator = new SharedRef(); + Result rc = service.GetGameCardManagerOperator(ref gcOperator.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + OutBuffer outErrorReportInfoBuffer = OutBuffer.FromStruct(ref errorReportInfo); + int operationId = MakeOperationId(GameCardManagerOperationIdValue.GetGameCardErrorReportInfo); + + rc = gcOperator.Get.OperateOut(out long bytesWritten, outErrorReportInfoBuffer, operationId); + if (rc.IsFailure()) return rc.Miss(); + + Assert.SdkEqual(Unsafe.SizeOf(), bytesWritten); + + return Result.Success; + } + + public static Result GetGameCardDeviceId(this StorageService service, Span outBuffer) + { + using var gcOperator = new SharedRef(); + Result rc = service.GetGameCardOperator(ref gcOperator.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + var outDeviceIdBuffer = new OutBuffer(outBuffer); + int operationId = MakeOperationId(GameCardOperationIdValue.GetGameCardDeviceId); + + rc = gcOperator.Get.OperateOut(out long bytesWritten, outDeviceIdBuffer, operationId); + if (rc.IsFailure()) return rc.Miss(); + + Assert.SdkEqual(GcCardDeviceIdSize, bytesWritten); + + return Result.Success; + } + + public static bool IsGameCardActivationValid(this StorageService service, uint handle) + { + using var gcOperator = new SharedRef(); + Result rc = service.GetGameCardManagerOperator(ref gcOperator.Ref()); + if (rc.IsFailure()) return Result.ConvertResultToReturnType(rc); + + bool isValid = false; + InBuffer inHandleBuffer = InBuffer.FromStruct(in handle); + OutBuffer outIsValidBuffer = OutBuffer.FromStruct(ref isValid); + int operationId = MakeOperationId(GameCardManagerOperationIdValue.IsGameCardActivationValid); + + rc = gcOperator.Get.OperateInOut(out long bytesWritten, outIsValidBuffer, inHandleBuffer, offset: 0, size: 0, + operationId); + if (rc.IsFailure()) return Result.ConvertResultToReturnType(rc); + + Assert.SdkEqual(Unsafe.SizeOf(), bytesWritten); + + return isValid; + } + + public static Result OpenGameCardDetectionEvent(this StorageService service, + ref SharedRef outEventNotifier) + { + using var storageDeviceManager = new SharedRef(); + Result rc = service.GetGameCardManager(ref storageDeviceManager.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + return storageDeviceManager.Get.OpenDetectionEvent(ref outEventNotifier); + } + + public static Result SimulateGameCardDetectionEventSignaled(this StorageService service) + { + using var gcOperator = new SharedRef(); + Result rc = service.GetGameCardManagerOperator(ref gcOperator.Ref()); + if (rc.IsFailure()) return rc.Miss(); + + int operationId = MakeOperationId(GameCardManagerOperationIdValue.SimulateDetectionEventSignaled); + + return gcOperator.Get.Operate(operationId); + } +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/Storage/Sf/IStorageDevice.cs b/src/LibHac/FsSrv/Storage/Sf/IStorageDevice.cs index 004d3aed..aa127913 100644 --- a/src/LibHac/FsSrv/Storage/Sf/IStorageDevice.cs +++ b/src/LibHac/FsSrv/Storage/Sf/IStorageDevice.cs @@ -1,18 +1,21 @@ -using System; -using LibHac.Common; -using LibHac.Fs; +using LibHac.Common; +using IStorage = LibHac.FsSrv.Sf.IStorage; namespace LibHac.FsSrv.Storage.Sf; -public interface IStorageDevice : IDisposable +// Note: This interface doesn't actually implement IStorage. We're giving it IStorage as a base because +// StorageServiceObjectAdapter is a template that is used with either IStorage or IStorageDevice +public interface IStorageDevice : IStorage { Result GetHandle(out uint handle); Result IsHandleValid(out bool isValid); Result OpenOperator(ref SharedRef outDeviceOperator); - Result Read(long offset, Span destination); - Result Write(long offset, ReadOnlySpan source); - Result Flush(); - Result SetSize(long size); - Result GetSize(out long size); - Result OperateRange(out QueryRangeInfo rangeInfo, int operationId, long offset, long size); -} + + // These methods come from inheriting IStorage + //Result Read(long offset, OutBuffer destination, long size); + //Result Write(long offset, InBuffer source, long size); + //Result Flush(); + //Result SetSize(long size); + //Result GetSize(out long size); + //Result OperateRange(out QueryRangeInfo rangeInfo, int operationId, long offset, long size); +} \ No newline at end of file diff --git a/src/LibHac/Gc/GameCardTypes.cs b/src/LibHac/Gc/GameCardTypes.cs new file mode 100644 index 00000000..c4ef2521 --- /dev/null +++ b/src/LibHac/Gc/GameCardTypes.cs @@ -0,0 +1,33 @@ +using LibHac.Common.FixedArrays; +using LibHac.Gc.Impl; + +namespace LibHac.Gc; + +public struct GameCardStatus +{ + public Array32 PartitionFsHeaderHash; + public ulong PackageId; + public long Size; + public long PartitionFsHeaderOffset; + public long PartitionFsHeaderSize; + public long SecureAreaOffset; + public long SecureAreaSize; + public uint UpdatePartitionVersion; + public ulong UpdatePartitionId; + public byte CompatibilityType; + public Array3 Reserved61; + public byte GameCardAttribute; + public Array11 Reserved65; +} + +public struct RmaInformation +{ + public Array512 Data; +} + +public struct GameCardIdSet +{ + public CardId1 Id1; + public CardId2 Id2; + public CardId3 Id3; +} \ No newline at end of file diff --git a/src/LibHac/Gc/GameCardValues.cs b/src/LibHac/Gc/GameCardValues.cs new file mode 100644 index 00000000..1e6ad1f9 --- /dev/null +++ b/src/LibHac/Gc/GameCardValues.cs @@ -0,0 +1,11 @@ +namespace LibHac.Gc; + +public static class Values +{ + public static int GcPageSize => 0x200; + public static int GcAsicFirmwareSize => 0x7800; + public static int GcCardDeviceIdSize => 0x10; + public static int GcCardExistenceResponseDataSize => 0x58; + public static int GcCardImageHashSize => 0x20; + public static int GcDeviceCertificateSize => 0x200; +} \ No newline at end of file diff --git a/src/LibHac/Gc/Impl/GameCardImplTypes.cs b/src/LibHac/Gc/Impl/GameCardImplTypes.cs new file mode 100644 index 00000000..21136c43 --- /dev/null +++ b/src/LibHac/Gc/Impl/GameCardImplTypes.cs @@ -0,0 +1,35 @@ +using LibHac.Common.FixedArrays; + +namespace LibHac.Gc.Impl; + +public struct CardId1 +{ + public byte MakerCode; + public MemoryCapacity MemoryCapacity; + public byte Reserved; + public byte MemoryType; +} + +public struct CardId2 +{ + public byte CardSecurityNumber; + public byte CardType; + public Array2 Reserved; +} + +public struct CardId3 +{ + public Array4 Reserved; +} + +public enum MemoryCapacity : byte +{ + // ReSharper disable InconsistentNaming + Capacity1GB = 0xFA, + Capacity2GB = 0xF8, + Capacity4GB = 0xF0, + Capacity8GB = 0xE0, + Capacity16GB = 0xE1, + Capacity32GB = 0xE2 + // ReSharper restore InconsistentNaming +} \ No newline at end of file diff --git a/src/LibHac/GcSrv/GcSrvEnums.cs b/src/LibHac/GcSrv/GcSrvEnums.cs new file mode 100644 index 00000000..dcac60f4 --- /dev/null +++ b/src/LibHac/GcSrv/GcSrvEnums.cs @@ -0,0 +1,37 @@ +namespace LibHac.GcSrv; + +public enum GameCardManagerOperationIdValue +{ + Finalize = 1, + GetHandle = 2, + IsGameCardActivationValid = 3, + GetInitializationResult = 4, + GetGameCardErrorInfo = 5, + GetGameCardErrorReportInfo = 6, + SetVerifyEnableFlag = 7, + GetGameCardAsicInfo = 8, + GetGameCardDeviceIdForProdCard = 9, + EraseAndWriteParamDirectly = 10, + ReadParamDirectly = 11, + WriteToGameCardDirectly = 12, + ForceErase = 13, + SimulateDetectionEventSignaled = 14 +} + +public enum GameCardOperationIdValue +{ + EraseGameCard = 1, + GetGameCardIdSet = 2, + GetGameCardDeviceId = 3, + GetGameCardImageHash = 4, + GetGameCardDeviceCertificate = 5, + ChallengeCardExistence = 6, + GetGameCardStatus = 7 +} + +public enum OpenGameCardAttribute : long +{ + ReadOnly = 0, + SecureReadOnly = 1, + WriteOnly = 2 +} \ No newline at end of file diff --git a/src/LibHac/Result.cs b/src/LibHac/Result.cs index 08affaac..cfd6fc79 100644 --- a/src/LibHac/Result.cs +++ b/src/LibHac/Result.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using LibHac.Common; +using LibHac.Diag; using BaseType = System.UInt32; namespace LibHac; @@ -209,6 +210,27 @@ public readonly struct Result : IEquatable } + internal static T ConvertResultToReturnType(Result result) + { + result.Miss(); + ConvertInvalidImpl(result); + + return default; + } + + internal static void ConvertInvalidImpl(Result result) + { + OnUnhandledResult(result); + } + + internal static void OnUnhandledResult(Result result) + { + Abort.DoAbortUnless(result.IsSuccess()); + + // The original does an infinite loop here + throw new HorizonResultException(result, "Unhandled result."); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static BaseType GetBitsValue(BaseType value, int bitsOffset, int bitsCount) { @@ -232,7 +254,7 @@ public readonly struct Result : IEquatable ///
A Result definition should look like this: public static Result.Base PathNotFound => new Result.Base(ModuleFs, 1); ///
Being a computed property like this will allow the compiler to do constant propagation to optimize comparisons. ///

This is an example of how a Result should be returned from a function: return PathNotFound.Log(); - ///
The method will return the for the specified , and + ///
The method will return the for the specified , and /// will optionally log the returned Result when running in debug mode for easier debugging. All Result logging functionality /// is removed from release builds. /// If the is not being used as a return value, will get the Result without logging anything. diff --git a/tests/LibHac.Tests/Fs/TypeLayoutTests.cs b/tests/LibHac.Tests/Fs/TypeLayoutTests.cs index 113caed0..e7bb54cc 100644 --- a/tests/LibHac.Tests/Fs/TypeLayoutTests.cs +++ b/tests/LibHac.Tests/Fs/TypeLayoutTests.cs @@ -419,4 +419,56 @@ public class TypeLayoutTests Assert.Equal(0, GetOffset(in s, in s.Value)); } + + [Fact] + public static void GameCardErrorInfo_Layout() + { + var s = new GameCardErrorInfo(); + + Assert.Equal(0x10, Unsafe.SizeOf()); + + Assert.Equal(0x0, GetOffset(in s, in s.GameCardCrcErrorCount)); + Assert.Equal(0x2, GetOffset(in s, in s.Reserved2)); + Assert.Equal(0x4, GetOffset(in s, in s.AsicCrcErrorCount)); + Assert.Equal(0x6, GetOffset(in s, in s.Reserved6)); + Assert.Equal(0x8, GetOffset(in s, in s.RefreshCount)); + Assert.Equal(0xA, GetOffset(in s, in s.ReservedA)); + Assert.Equal(0xC, GetOffset(in s, in s.ReadRetryCount)); + Assert.Equal(0xE, GetOffset(in s, in s.TimeoutRetryErrorCount)); + } + + [Fact] + public static void GameCardErrorReportInfo_Layout() + { + var s = new GameCardErrorReportInfo(); + + Assert.Equal(0x40, Unsafe.SizeOf()); + + Assert.Equal(0x00, GetOffset(in s, in s.ErrorInfo)); + Assert.Equal(0x10, GetOffset(in s, in s.AsicReinitializeFailureDetail)); + Assert.Equal(0x12, GetOffset(in s, in s.InsertionCount)); + Assert.Equal(0x14, GetOffset(in s, in s.RemovalCount)); + Assert.Equal(0x16, GetOffset(in s, in s.AsicReinitializeCount)); + Assert.Equal(0x18, GetOffset(in s, in s.AsicInitializeCount)); + Assert.Equal(0x1C, GetOffset(in s, in s.AsicReinitializeFailureCount)); + Assert.Equal(0x1E, GetOffset(in s, in s.AwakenFailureCount)); + Assert.Equal(0x20, GetOffset(in s, in s.Reserved20)); + Assert.Equal(0x22, GetOffset(in s, in s.RefreshCount)); + Assert.Equal(0x24, GetOffset(in s, in s.LastReadErrorPageAddress)); + Assert.Equal(0x28, GetOffset(in s, in s.LastReadErrorPageCount)); + Assert.Equal(0x2C, GetOffset(in s, in s.AwakenCount)); + Assert.Equal(0x30, GetOffset(in s, in s.ReadCountFromInsert)); + Assert.Equal(0x34, GetOffset(in s, in s.ReadCountFromAwaken)); + Assert.Equal(0x38, GetOffset(in s, in s.Reserved38)); + } + + [Fact] + public static void GameCardHandle_Layout() + { + var s = new GameCardHandle(); + + Assert.Equal(4, Unsafe.SizeOf()); + + Assert.Equal(0, GetOffset(in s, in s.Value)); + } } \ No newline at end of file diff --git a/tests/LibHac.Tests/Gc/TypeLayoutTests.cs b/tests/LibHac.Tests/Gc/TypeLayoutTests.cs new file mode 100644 index 00000000..3914d2f3 --- /dev/null +++ b/tests/LibHac.Tests/Gc/TypeLayoutTests.cs @@ -0,0 +1,89 @@ +using System.Runtime.CompilerServices; +using LibHac.Gc; +using LibHac.Gc.Impl; +using Xunit; +using static LibHac.Tests.Common.Layout; + +namespace LibHac.Tests.Gc; + +public class TypeLayoutTests +{ + [Fact] + public static void GameCardStatus_Layout() + { + var s = new GameCardStatus(); + + Assert.Equal(0x70, Unsafe.SizeOf()); + + Assert.Equal(0x00, GetOffset(in s, in s.PartitionFsHeaderHash)); + Assert.Equal(0x20, GetOffset(in s, in s.PackageId)); + Assert.Equal(0x28, GetOffset(in s, in s.Size)); + Assert.Equal(0x30, GetOffset(in s, in s.PartitionFsHeaderOffset)); + Assert.Equal(0x38, GetOffset(in s, in s.PartitionFsHeaderSize)); + Assert.Equal(0x40, GetOffset(in s, in s.SecureAreaOffset)); + Assert.Equal(0x48, GetOffset(in s, in s.SecureAreaSize)); + Assert.Equal(0x50, GetOffset(in s, in s.UpdatePartitionVersion)); + Assert.Equal(0x58, GetOffset(in s, in s.UpdatePartitionId)); + Assert.Equal(0x60, GetOffset(in s, in s.CompatibilityType)); + Assert.Equal(0x61, GetOffset(in s, in s.Reserved61)); + Assert.Equal(0x64, GetOffset(in s, in s.GameCardAttribute)); + Assert.Equal(0x65, GetOffset(in s, in s.Reserved65)); + } + + [Fact] + public static void RmaInformation_Layout() + { + var s = new RmaInformation(); + + Assert.Equal(0x200, Unsafe.SizeOf()); + + Assert.Equal(0x00, GetOffset(in s, in s.Data)); + } + + [Fact] + public static void GameCardIdSet_Layout() + { + var s = new GameCardIdSet(); + + Assert.Equal(12, Unsafe.SizeOf()); + + Assert.Equal(0, GetOffset(in s, in s.Id1)); + Assert.Equal(4, GetOffset(in s, in s.Id2)); + Assert.Equal(8, GetOffset(in s, in s.Id3)); + } + + [Fact] + public static void CardId1_Layout() + { + var s = new CardId1(); + + Assert.Equal(4, Unsafe.SizeOf()); + + Assert.Equal(0, GetOffset(in s, in s.MakerCode)); + Assert.Equal(1, GetOffset(in s, in s.MemoryCapacity)); + Assert.Equal(2, GetOffset(in s, in s.Reserved)); + Assert.Equal(3, GetOffset(in s, in s.MemoryType)); + } + + [Fact] + public static void CardId2_Layout() + { + var s = new CardId2(); + + Assert.Equal(4, Unsafe.SizeOf()); + + Assert.Equal(0, GetOffset(in s, in s.CardSecurityNumber)); + Assert.Equal(1, GetOffset(in s, in s.CardType)); + Assert.Equal(2, GetOffset(in s, in s.Reserved)); + } + + [Fact] + public static void CardId3_Layout() + { + var s = new CardId3(); + + Assert.Equal(4, Unsafe.SizeOf()); + + Assert.Equal(0, GetOffset(in s, in s.Reserved)); + } +} \ No newline at end of file