mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Add GameCardStorageDevice
This commit is contained in:
parent
43d63086bf
commit
f4c59771cb
7 changed files with 400 additions and 7 deletions
|
@ -7,7 +7,7 @@ namespace LibHac.FsSrv.Storage.Sf;
|
||||||
// StorageServiceObjectAdapter is a template that is used with either IStorage or IStorageDevice
|
// StorageServiceObjectAdapter is a template that is used with either IStorage or IStorageDevice
|
||||||
public interface IStorageDevice : IStorage
|
public interface IStorageDevice : IStorage
|
||||||
{
|
{
|
||||||
Result GetHandle(out uint handle);
|
Result GetHandle(out GameCardHandle handle);
|
||||||
Result IsHandleValid(out bool isValid);
|
Result IsHandleValid(out bool isValid);
|
||||||
Result OpenOperator(ref SharedRef<IStorageDeviceOperator> outDeviceOperator);
|
Result OpenOperator(ref SharedRef<IStorageDeviceOperator> outDeviceOperator);
|
||||||
|
|
||||||
|
|
|
@ -8,4 +8,5 @@ public static class Values
|
||||||
public static int GcCardExistenceResponseDataSize => 0x58;
|
public static int GcCardExistenceResponseDataSize => 0x58;
|
||||||
public static int GcCardImageHashSize => 0x20;
|
public static int GcCardImageHashSize => 0x20;
|
||||||
public static int GcDeviceCertificateSize => 0x200;
|
public static int GcDeviceCertificateSize => 0x200;
|
||||||
|
public static int GcCardKeyAreaSize => 0x1000;
|
||||||
}
|
}
|
|
@ -16,7 +16,7 @@ using IStorage = LibHac.FsSrv.Sf.IStorage;
|
||||||
|
|
||||||
namespace LibHac.GcSrv;
|
namespace LibHac.GcSrv;
|
||||||
|
|
||||||
public class GameCardManager : IStorageDeviceManager, IStorageDeviceOperator, IGameCardDeviceManager
|
public class GameCardManager : IStorageDeviceManager, IStorageDeviceOperator, IGameCardManager, IGameCardKeyManager
|
||||||
{
|
{
|
||||||
private enum CardState
|
private enum CardState
|
||||||
{
|
{
|
||||||
|
@ -64,7 +64,7 @@ public class GameCardManager : IStorageDeviceManager, IStorageDeviceOperator, IG
|
||||||
_rwLock = null;
|
_rwLock = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private uint BytesToPages(long byteCount)
|
public static uint BytesToPages(long byteCount)
|
||||||
{
|
{
|
||||||
return (uint)((ulong)byteCount / (ulong)Values.GcPageSize);
|
return (uint)((ulong)byteCount / (ulong)Values.GcPageSize);
|
||||||
}
|
}
|
||||||
|
@ -320,15 +320,50 @@ public class GameCardManager : IStorageDeviceManager, IStorageDeviceOperator, IG
|
||||||
|
|
||||||
public Result OpenOperator(ref SharedRef<IStorageDeviceOperator> outDeviceOperator)
|
public Result OpenOperator(ref SharedRef<IStorageDeviceOperator> outDeviceOperator)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
Result res = InitializeGcLibrary();
|
||||||
|
if (res.IsFailure()) return res.Miss();
|
||||||
|
|
||||||
|
using SharedRef<GameCardManager> deviceOperator = SharedRef<GameCardManager>.Create(in _selfReference);
|
||||||
|
|
||||||
|
if (!deviceOperator.HasValue)
|
||||||
|
return ResultFs.AllocationMemoryFailedInGameCardManagerG.Log();
|
||||||
|
|
||||||
|
outDeviceOperator.SetByMove(ref deviceOperator.Ref);
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result OpenDevice(ref SharedRef<IStorageDevice> outStorageDevice, ulong attribute)
|
public Result OpenDevice(ref SharedRef<IStorageDevice> outStorageDevice, ulong attribute)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
Result res = InitializeGcLibrary();
|
||||||
|
if (res.IsFailure()) return res.Miss();
|
||||||
|
|
||||||
|
using var storageDevice = new SharedRef<IStorageDevice>();
|
||||||
|
|
||||||
|
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<IStorage> outStorage, ulong attribute)
|
public Result OpenStorage(ref SharedRef<IStorage> outStorage, ulong attribute)
|
||||||
|
{
|
||||||
|
Result res = InitializeGcLibrary();
|
||||||
|
if (res.IsFailure()) return res.Miss();
|
||||||
|
|
||||||
|
using var storageDevice = new SharedRef<IStorageDevice>();
|
||||||
|
|
||||||
|
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<IStorageDevice> outStorageDevice, OpenGameCardAttribute attribute)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
@ -894,4 +929,9 @@ public class GameCardManager : IStorageDeviceManager, IStorageDeviceOperator, IG
|
||||||
{
|
{
|
||||||
return _state == CardState.Secure;
|
return _state == CardState.Secure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void PresetInternalKeys(ReadOnlySpan<byte> gameCardKey, ReadOnlySpan<byte> gameCardCertificate)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
156
src/LibHac/GcSrv/GameCardStorage.cs
Normal file
156
src/LibHac/GcSrv/GameCardStorage.cs
Normal file
|
@ -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<IGameCardManager> _deviceManager;
|
||||||
|
|
||||||
|
// LibHac additions
|
||||||
|
private readonly GameCardDummy _gc;
|
||||||
|
|
||||||
|
public ReadOnlyGameCardStorage(ref SharedRef<IGameCardManager> deviceManger, GameCardDummy gc)
|
||||||
|
{
|
||||||
|
_deviceManager = SharedRef<IGameCardManager>.CreateMove(ref deviceManger);
|
||||||
|
_gc = gc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
_deviceManager.Destroy();
|
||||||
|
|
||||||
|
base.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Result Read(long offset, Span<byte> 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<byte> 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<byte> outBuffer, OperationId operationId, long offset, long size,
|
||||||
|
ReadOnlySpan<byte> inBuffer)
|
||||||
|
{
|
||||||
|
switch (operationId)
|
||||||
|
{
|
||||||
|
case OperationId.InvalidateCache:
|
||||||
|
return Result.Success;
|
||||||
|
case OperationId.QueryRange:
|
||||||
|
if (outBuffer.Length != Unsafe.SizeOf<QueryRangeInfo>())
|
||||||
|
return ResultFs.InvalidSize.Log();
|
||||||
|
|
||||||
|
SpanHelpers.AsStruct<QueryRangeInfo>(outBuffer).Clear();
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
default:
|
||||||
|
return ResultFs.UnsupportedOperateRangeForReadOnlyGameCardStorage.Log();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class WriteOnlyGameCardStorage : IStorage
|
||||||
|
{
|
||||||
|
private SharedRef<IGameCardManager> _deviceManager;
|
||||||
|
|
||||||
|
// LibHac additions
|
||||||
|
private readonly GameCardDummy _gc;
|
||||||
|
|
||||||
|
public WriteOnlyGameCardStorage(ref SharedRef<IGameCardManager> deviceManger, GameCardDummy gc)
|
||||||
|
{
|
||||||
|
_deviceManager = SharedRef<IGameCardManager>.CreateMove(ref deviceManger);
|
||||||
|
_gc = gc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
_deviceManager.Destroy();
|
||||||
|
|
||||||
|
base.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Result Read(long offset, Span<byte> destination)
|
||||||
|
{
|
||||||
|
return ResultFs.UnsupportedReadForWriteOnlyGameCardStorage.Log();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Result Write(long offset, ReadOnlySpan<byte> 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<byte> outBuffer, OperationId operationId, long offset, long size,
|
||||||
|
ReadOnlySpan<byte> inBuffer)
|
||||||
|
{
|
||||||
|
return ResultFs.NotImplemented.Log();
|
||||||
|
}
|
||||||
|
}
|
196
src/LibHac/GcSrv/GameCardStorageDevice.cs
Normal file
196
src/LibHac/GcSrv/GameCardStorageDevice.cs
Normal file
|
@ -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<IStorage> _baseStorage;
|
||||||
|
|
||||||
|
protected GameCardStorageInterfaceAdapter(ref SharedRef<IStorage> baseStorage)
|
||||||
|
{
|
||||||
|
_baseStorage = SharedRef<IStorage>.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<byte>.Empty).Ret();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class GameCardStorageDevice : GameCardStorageInterfaceAdapter, IStorageDevice
|
||||||
|
{
|
||||||
|
private SharedRef<IGameCardManager> _manager;
|
||||||
|
private GameCardHandle _handle;
|
||||||
|
private bool _isSecure;
|
||||||
|
private Array16<byte> _cardDeviceId;
|
||||||
|
private Array32<byte> _cardImageHash;
|
||||||
|
|
||||||
|
public GameCardStorageDevice(ref SharedRef<IGameCardManager> manager, ref SharedRef<IStorage> baseStorage,
|
||||||
|
GameCardHandle handle) : base(ref baseStorage)
|
||||||
|
{
|
||||||
|
_manager = SharedRef<IGameCardManager>.CreateMove(ref manager);
|
||||||
|
_handle = handle;
|
||||||
|
_isSecure = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GameCardStorageDevice(ref SharedRef<IGameCardManager> manager, ref SharedRef<IStorage> baseStorage,
|
||||||
|
GameCardHandle handle, bool isSecure, ReadOnlySpan<byte> cardDeviceId, ReadOnlySpan<byte> cardImageHash)
|
||||||
|
: base(ref baseStorage)
|
||||||
|
{
|
||||||
|
Assert.SdkRequiresEqual(cardDeviceId.Length, Values.GcCardDeviceIdSize);
|
||||||
|
Assert.SdkRequiresEqual(cardImageHash.Length, Values.GcCardImageHashSize);
|
||||||
|
|
||||||
|
_manager = SharedRef<IGameCardManager>.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<ReaderWriterLock> 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<ReaderWriterLock> 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<ReaderWriterLock>();
|
||||||
|
isValid = _manager.Get.AcquireReadLock(ref readLock.Ref(), _handle).IsSuccess();
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result OpenOperator(ref SharedRef<IStorageDeviceOperator> outDeviceOperator)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Result Read(long offset, OutBuffer destination, long size)
|
||||||
|
{
|
||||||
|
using var readLock = new SharedLock<ReaderWriterLock>();
|
||||||
|
|
||||||
|
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<ReaderWriterLock>();
|
||||||
|
|
||||||
|
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<ReaderWriterLock>())
|
||||||
|
{
|
||||||
|
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<ReaderWriterLock>())
|
||||||
|
{
|
||||||
|
Result res = AcquireWriteLock(ref writeLock.Ref());
|
||||||
|
if (res.IsFailure()) return res.Miss();
|
||||||
|
|
||||||
|
return HandleGameCardAccessResult(resultGetSize).Ret();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace LibHac.GcSrv;
|
namespace LibHac.GcSrv;
|
||||||
|
|
||||||
public interface IGameCardKeyManager
|
public interface IGameCardKeyManager : IDisposable
|
||||||
{
|
{
|
||||||
void PresetInternalKeys(ReadOnlySpan<byte> gameCardKey, ReadOnlySpan<byte> gameCardCertificate);
|
void PresetInternalKeys(ReadOnlySpan<byte> gameCardKey, ReadOnlySpan<byte> gameCardCertificate);
|
||||||
}
|
}
|
|
@ -3,7 +3,7 @@ using LibHac.Os;
|
||||||
|
|
||||||
namespace LibHac.GcSrv;
|
namespace LibHac.GcSrv;
|
||||||
|
|
||||||
internal interface IGameCardDeviceManager
|
internal interface IGameCardManager : IDisposable
|
||||||
{
|
{
|
||||||
Result AcquireReadLock(ref SharedLock<ReaderWriterLock> outLock, GameCardHandle handle);
|
Result AcquireReadLock(ref SharedLock<ReaderWriterLock> outLock, GameCardHandle handle);
|
||||||
Result AcquireSecureLock(ref SharedLock<ReaderWriterLock> outLock, ref GameCardHandle handle, ReadOnlySpan<byte> cardDeviceId, ReadOnlySpan<byte> cardImageHash);
|
Result AcquireSecureLock(ref SharedLock<ReaderWriterLock> outLock, ref GameCardHandle handle, ReadOnlySpan<byte> cardDeviceId, ReadOnlySpan<byte> cardImageHash);
|
Loading…
Reference in a new issue