diff --git a/src/LibHac/FsSrv/Storage/Sf/IStorageDevice.cs b/src/LibHac/FsSrv/Storage/Sf/IStorageDevice.cs index aa127913..9fe61981 100644 --- a/src/LibHac/FsSrv/Storage/Sf/IStorageDevice.cs +++ b/src/LibHac/FsSrv/Storage/Sf/IStorageDevice.cs @@ -7,7 +7,7 @@ namespace LibHac.FsSrv.Storage.Sf; // StorageServiceObjectAdapter is a template that is used with either IStorage or IStorageDevice public interface IStorageDevice : IStorage { - Result GetHandle(out uint handle); + Result GetHandle(out GameCardHandle handle); Result IsHandleValid(out bool isValid); Result OpenOperator(ref SharedRef outDeviceOperator); diff --git a/src/LibHac/Gc/GameCardValues.cs b/src/LibHac/Gc/GameCardValues.cs index 1e6ad1f9..2abcfd05 100644 --- a/src/LibHac/Gc/GameCardValues.cs +++ b/src/LibHac/Gc/GameCardValues.cs @@ -8,4 +8,5 @@ public static class Values public static int GcCardExistenceResponseDataSize => 0x58; public static int GcCardImageHashSize => 0x20; public static int GcDeviceCertificateSize => 0x200; + public static int GcCardKeyAreaSize => 0x1000; } \ No newline at end of file diff --git a/src/LibHac/GcSrv/GameCardManager.cs b/src/LibHac/GcSrv/GameCardManager.cs index 846b5525..60afb754 100644 --- a/src/LibHac/GcSrv/GameCardManager.cs +++ b/src/LibHac/GcSrv/GameCardManager.cs @@ -16,7 +16,7 @@ using IStorage = LibHac.FsSrv.Sf.IStorage; namespace LibHac.GcSrv; -public class GameCardManager : IStorageDeviceManager, IStorageDeviceOperator, IGameCardDeviceManager +public class GameCardManager : IStorageDeviceManager, IStorageDeviceOperator, IGameCardManager, IGameCardKeyManager { private enum CardState { @@ -64,7 +64,7 @@ public class GameCardManager : IStorageDeviceManager, IStorageDeviceOperator, IG _rwLock = null; } - private uint BytesToPages(long byteCount) + public static uint BytesToPages(long byteCount) { return (uint)((ulong)byteCount / (ulong)Values.GcPageSize); } @@ -320,15 +320,50 @@ public class GameCardManager : IStorageDeviceManager, IStorageDeviceOperator, IG public Result OpenOperator(ref SharedRef outDeviceOperator) { - throw new NotImplementedException(); + Result res = InitializeGcLibrary(); + if (res.IsFailure()) return res.Miss(); + + using SharedRef deviceOperator = SharedRef.Create(in _selfReference); + + if (!deviceOperator.HasValue) + return ResultFs.AllocationMemoryFailedInGameCardManagerG.Log(); + + outDeviceOperator.SetByMove(ref deviceOperator.Ref); + + return Result.Success; } public Result OpenDevice(ref SharedRef outStorageDevice, ulong attribute) { - throw new NotImplementedException(); + Result res = InitializeGcLibrary(); + if (res.IsFailure()) return res.Miss(); + + using var storageDevice = new SharedRef(); + + res = OpenDeviceImpl(ref storageDevice.Ref, (OpenGameCardAttribute)attribute); + if (res.IsFailure()) return res.Miss(); + + outStorageDevice.SetByMove(ref storageDevice.Ref); + + return Result.Success; } public Result OpenStorage(ref SharedRef outStorage, ulong attribute) + { + Result res = InitializeGcLibrary(); + if (res.IsFailure()) return res.Miss(); + + using var storageDevice = new SharedRef(); + + res = OpenDeviceImpl(ref storageDevice.Ref, (OpenGameCardAttribute)attribute); + if (res.IsFailure()) return res.Miss(); + + outStorage.SetByMove(ref storageDevice.Ref); + + return Result.Success; + } + + private Result OpenDeviceImpl(ref SharedRef outStorageDevice, OpenGameCardAttribute attribute) { throw new NotImplementedException(); } @@ -894,4 +929,9 @@ public class GameCardManager : IStorageDeviceManager, IStorageDeviceOperator, IG { return _state == CardState.Secure; } + + public void PresetInternalKeys(ReadOnlySpan gameCardKey, ReadOnlySpan gameCardCertificate) + { + throw new NotImplementedException(); + } } \ No newline at end of file diff --git a/src/LibHac/GcSrv/GameCardStorage.cs b/src/LibHac/GcSrv/GameCardStorage.cs new file mode 100644 index 00000000..d67b4bfa --- /dev/null +++ b/src/LibHac/GcSrv/GameCardStorage.cs @@ -0,0 +1,156 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Diag; +using LibHac.Fs; +using LibHac.Gc; +using static LibHac.Gc.Values; + +namespace LibHac.GcSrv; + +internal class ReadOnlyGameCardStorage : IStorage +{ + private SharedRef _deviceManager; + + // LibHac additions + private readonly GameCardDummy _gc; + + public ReadOnlyGameCardStorage(ref SharedRef deviceManger, GameCardDummy gc) + { + _deviceManager = SharedRef.CreateMove(ref deviceManger); + _gc = gc; + } + + public override void Dispose() + { + _deviceManager.Destroy(); + + base.Dispose(); + } + + public override Result Read(long offset, Span destination) + { + Assert.SdkRequiresAligned(offset, GcPageSize); + Assert.SdkRequiresAligned(destination.Length, GcPageSize); + + if (destination.Length == 0) + return Result.Success; + + // Missing: Allocate a device buffer if the destination buffer is not one + + return _gc.Read(destination, GameCardManager.BytesToPages(offset), + GameCardManager.BytesToPages(destination.Length)).Ret(); + } + + public override Result Write(long offset, ReadOnlySpan source) + { + return ResultFs.UnsupportedWriteForReadOnlyGameCardStorage.Log(); + } + + public override Result Flush() + { + return Result.Success; + } + + public override Result GetSize(out long size) + { + UnsafeHelpers.SkipParamInit(out size); + + Result res = _gc.GetCardStatus(out GameCardStatus status); + if (res.IsFailure()) return res.Miss(); + + size = status.Size; + return Result.Success; + } + + public override Result SetSize(long size) + { + return ResultFs.UnsupportedSetSizeForReadOnlyGameCardStorage.Log(); + } + + public override Result OperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + switch (operationId) + { + case OperationId.InvalidateCache: + return Result.Success; + case OperationId.QueryRange: + if (outBuffer.Length != Unsafe.SizeOf()) + return ResultFs.InvalidSize.Log(); + + SpanHelpers.AsStruct(outBuffer).Clear(); + + return Result.Success; + default: + return ResultFs.UnsupportedOperateRangeForReadOnlyGameCardStorage.Log(); + } + } +} + +internal class WriteOnlyGameCardStorage : IStorage +{ + private SharedRef _deviceManager; + + // LibHac additions + private readonly GameCardDummy _gc; + + public WriteOnlyGameCardStorage(ref SharedRef deviceManger, GameCardDummy gc) + { + _deviceManager = SharedRef.CreateMove(ref deviceManger); + _gc = gc; + } + + public override void Dispose() + { + _deviceManager.Destroy(); + + base.Dispose(); + } + + public override Result Read(long offset, Span destination) + { + return ResultFs.UnsupportedReadForWriteOnlyGameCardStorage.Log(); + } + + public override Result Write(long offset, ReadOnlySpan source) + { + Assert.SdkRequiresAligned(offset, GcPageSize); + Assert.SdkRequiresAligned(source.Length, GcPageSize); + + if (source.Length == 0) + return Result.Success; + + // Missing: Allocate a device buffer if the destination buffer is not one + + return _gc.Writer.Write(source, GameCardManager.BytesToPages(offset), + GameCardManager.BytesToPages(source.Length)).Ret(); + } + + public override Result Flush() + { + return Result.Success; + } + + public override Result GetSize(out long size) + { + UnsafeHelpers.SkipParamInit(out size); + + Result res = _gc.Writer.GetCardAvailableRawSize(out long gameCardSize); + if (res.IsFailure()) return res.Miss(); + + size = gameCardSize + GcCardKeyAreaSize; + return Result.Success; + } + + public override Result SetSize(long size) + { + return ResultFs.UnsupportedSetSizeForWriteOnlyGameCardStorage.Log(); + } + + public override Result OperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + return ResultFs.NotImplemented.Log(); + } +} \ No newline at end of file diff --git a/src/LibHac/GcSrv/GameCardStorageDevice.cs b/src/LibHac/GcSrv/GameCardStorageDevice.cs new file mode 100644 index 00000000..be34bea5 --- /dev/null +++ b/src/LibHac/GcSrv/GameCardStorageDevice.cs @@ -0,0 +1,196 @@ +using System; +using LibHac.Common; +using LibHac.Common.FixedArrays; +using LibHac.Diag; +using LibHac.Fs; +using LibHac.FsSrv.Storage.Sf; +using LibHac.Gc; +using LibHac.Os; +using LibHac.Sf; +using IStorageSf = LibHac.FsSrv.Sf.IStorage; + +namespace LibHac.GcSrv; + +internal abstract class GameCardStorageInterfaceAdapter : IStorageSf +{ + private SharedRef _baseStorage; + + protected GameCardStorageInterfaceAdapter(ref SharedRef baseStorage) + { + _baseStorage = SharedRef.CreateMove(ref baseStorage); + } + + public virtual void Dispose() + { + _baseStorage.Destroy(); + } + + public virtual Result Read(long offset, OutBuffer destination, long size) + { + return _baseStorage.Get.Read(offset, destination.Buffer.Slice(0, (int)size)).Ret(); + } + + public virtual Result Write(long offset, InBuffer source, long size) + { + return _baseStorage.Get.Write(offset, source.Buffer.Slice(0, (int)size)).Ret(); + } + + public virtual Result Flush() + { + return _baseStorage.Get.Flush().Ret(); + } + + public virtual Result SetSize(long size) + { + return _baseStorage.Get.SetSize(size).Ret(); + } + + public virtual Result GetSize(out long size) + { + return _baseStorage.Get.GetSize(out size).Ret(); + } + + public virtual Result OperateRange(out QueryRangeInfo rangeInfo, int operationId, long offset, long size) + { + UnsafeHelpers.SkipParamInit(out rangeInfo); + + return _baseStorage.Get.OperateRange(SpanHelpers.AsByteSpan(ref rangeInfo), (OperationId)operationId, offset, + size, ReadOnlySpan.Empty).Ret(); + } +} + +internal class GameCardStorageDevice : GameCardStorageInterfaceAdapter, IStorageDevice +{ + private SharedRef _manager; + private GameCardHandle _handle; + private bool _isSecure; + private Array16 _cardDeviceId; + private Array32 _cardImageHash; + + public GameCardStorageDevice(ref SharedRef manager, ref SharedRef baseStorage, + GameCardHandle handle) : base(ref baseStorage) + { + _manager = SharedRef.CreateMove(ref manager); + _handle = handle; + _isSecure = false; + } + + public GameCardStorageDevice(ref SharedRef manager, ref SharedRef baseStorage, + GameCardHandle handle, bool isSecure, ReadOnlySpan cardDeviceId, ReadOnlySpan cardImageHash) + : base(ref baseStorage) + { + Assert.SdkRequiresEqual(cardDeviceId.Length, Values.GcCardDeviceIdSize); + Assert.SdkRequiresEqual(cardImageHash.Length, Values.GcCardImageHashSize); + + _manager = SharedRef.CreateMove(ref manager); + _handle = handle; + _isSecure = isSecure; + + cardDeviceId.CopyTo(_cardDeviceId.Items); + cardImageHash.CopyTo(_cardImageHash.Items); + } + + public override void Dispose() + { + _manager.Destroy(); + + base.Dispose(); + } + + public Result AcquireReadLock(ref SharedLock outLock) + { + if (_isSecure) + { + Result res = _manager.Get.AcquireSecureLock(ref outLock, ref _handle, _cardDeviceId, _cardImageHash); + if (res.IsFailure()) return res.Miss(); + } + else + { + Result res = _manager.Get.AcquireReadLock(ref outLock, _handle); + if (res.IsFailure()) return res.Miss(); + } + + return Result.Success; + } + + public Result AcquireWriteLock(ref UniqueLock outLock) + { + return _manager.Get.AcquireWriteLock(ref outLock).Ret(); + } + + public Result HandleGameCardAccessResult(Result result) + { + return _manager.Get.HandleGameCardAccessResult(result); + } + + public Result GetHandle(out GameCardHandle handle) + { + handle = _handle; + + return Result.Success; + } + + public Result IsHandleValid(out bool isValid) + { + using var readLock = new SharedLock(); + isValid = _manager.Get.AcquireReadLock(ref readLock.Ref(), _handle).IsSuccess(); + + return Result.Success; + } + + public Result OpenOperator(ref SharedRef outDeviceOperator) + { + throw new NotImplementedException(); + } + + public override Result Read(long offset, OutBuffer destination, long size) + { + using var readLock = new SharedLock(); + + Result res = AcquireReadLock(ref readLock.Ref()); + if (res.IsFailure()) return res.Miss(); + + res = base.Read(offset, destination, size); + if (res.IsFailure()) return res.Miss(); + + return Result.Success; + } + + public override Result Write(long offset, InBuffer source, long size) + { + using var readLock = new SharedLock(); + + Result res = AcquireReadLock(ref readLock.Ref()); + if (res.IsFailure()) return res.Miss(); + + res = base.Write(offset, source, size); + if (res.IsFailure()) return res.Miss(); + + return Result.Success; + } + + public override Result GetSize(out long size) + { + Result resultGetSize; + UnsafeHelpers.SkipParamInit(out size); + + using (var readLock = new SharedLock()) + { + Result res = AcquireReadLock(ref readLock.Ref()); + if (res.IsFailure()) return res.Miss(); + + resultGetSize = base.GetSize(out size); + } + + if (resultGetSize.IsSuccess()) + return Result.Success; + + using (var writeLock = new UniqueLock()) + { + Result res = AcquireWriteLock(ref writeLock.Ref()); + if (res.IsFailure()) return res.Miss(); + + return HandleGameCardAccessResult(resultGetSize).Ret(); + } + } +} \ No newline at end of file diff --git a/src/LibHac/GcSrv/IGameCardKeyManager.cs b/src/LibHac/GcSrv/IGameCardKeyManager.cs index a2b38bbd..247f710e 100644 --- a/src/LibHac/GcSrv/IGameCardKeyManager.cs +++ b/src/LibHac/GcSrv/IGameCardKeyManager.cs @@ -2,7 +2,7 @@ namespace LibHac.GcSrv; -public interface IGameCardKeyManager +public interface IGameCardKeyManager : IDisposable { void PresetInternalKeys(ReadOnlySpan gameCardKey, ReadOnlySpan gameCardCertificate); } \ No newline at end of file diff --git a/src/LibHac/GcSrv/IGameCardDeviceManager.cs b/src/LibHac/GcSrv/IGameCardManager.cs similarity index 91% rename from src/LibHac/GcSrv/IGameCardDeviceManager.cs rename to src/LibHac/GcSrv/IGameCardManager.cs index 446fb562..472edc6c 100644 --- a/src/LibHac/GcSrv/IGameCardDeviceManager.cs +++ b/src/LibHac/GcSrv/IGameCardManager.cs @@ -3,7 +3,7 @@ using LibHac.Os; namespace LibHac.GcSrv; -internal interface IGameCardDeviceManager +internal interface IGameCardManager : IDisposable { Result AcquireReadLock(ref SharedLock outLock, GameCardHandle handle); Result AcquireSecureLock(ref SharedLock outLock, ref GameCardHandle handle, ReadOnlySpan cardDeviceId, ReadOnlySpan cardImageHash);