Add GameCardService

This commit is contained in:
Alex Barney 2022-02-01 00:11:02 -07:00
parent 2fb3d88261
commit 49759d3d10
15 changed files with 984 additions and 36 deletions

View file

@ -1,4 +1,5 @@
using System; using System;
using LibHac.Common.FixedArrays;
namespace LibHac.Fs; namespace LibHac.Fs;
@ -41,3 +42,51 @@ public enum GameCardAttribute : byte
DifferentRegionCupToTerraDeviceFlag = 1 << 3, DifferentRegionCupToTerraDeviceFlag = 1 << 3,
DifferentRegionCupToGlobalDeviceFlag = 1 << 4 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<byte> Reserved38;
}
public readonly struct GameCardHandle : IEquatable<GameCardHandle>
{
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);
}

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using LibHac.Common; using LibHac.Common;
using LibHac.FsSrv.Storage.Sf;
using LibHac.Sf; using LibHac.Sf;
using IStorageSf = LibHac.FsSrv.Sf.IStorage; using IStorageSf = LibHac.FsSrv.Sf.IStorage;
@ -20,6 +21,11 @@ internal class StorageServiceObjectAdapter : IStorage
_baseStorage = SharedRef<IStorageSf>.CreateMove(ref baseStorage); _baseStorage = SharedRef<IStorageSf>.CreateMove(ref baseStorage);
} }
public StorageServiceObjectAdapter(ref SharedRef<IStorageDevice> baseStorage)
{
_baseStorage = SharedRef<IStorageSf>.CreateMove(ref baseStorage);
}
public override void Dispose() public override void Dispose()
{ {
_baseStorage.Destroy(); _baseStorage.Destroy();

View file

@ -4,7 +4,6 @@ using LibHac.Common;
using LibHac.Diag; using LibHac.Diag;
using LibHac.Fs.Fsa; using LibHac.Fs.Fsa;
using LibHac.Fs.Impl; using LibHac.Fs.Impl;
using LibHac.FsSrv;
using LibHac.FsSrv.Sf; using LibHac.FsSrv.Sf;
using LibHac.Os; using LibHac.Os;
using LibHac.Util; using LibHac.Util;

View file

@ -1,10 +1,11 @@
using LibHac.FsSrv.Impl; using System;
using LibHac.FsSrv.Impl;
using LibHac.FsSrv.Storage; using LibHac.FsSrv.Storage;
using LibHac.FsSystem; using LibHac.FsSystem;
namespace LibHac.FsSrv; namespace LibHac.FsSrv;
public class FileSystemServer public class FileSystemServer : IDisposable
{ {
internal FileSystemServerGlobals Globals; internal FileSystemServerGlobals Globals;
@ -20,9 +21,15 @@ public class FileSystemServer
{ {
Globals.Initialize(horizonClient, this); Globals.Initialize(horizonClient, this);
} }
public void Dispose()
{
Globals.Dispose();
Globals = default;
}
} }
internal struct FileSystemServerGlobals internal struct FileSystemServerGlobals : IDisposable
{ {
public HorizonClient Hos; public HorizonClient Hos;
public object InitMutex; public object InitMutex;
@ -36,6 +43,7 @@ internal struct FileSystemServerGlobals
public MultiCommitManagerGlobals MultiCommitManager; public MultiCommitManagerGlobals MultiCommitManager;
public LocationResolverSetGlobals LocationResolverSet; public LocationResolverSetGlobals LocationResolverSet;
public PooledBufferGlobals PooledBuffer; public PooledBufferGlobals PooledBuffer;
public GameCardServiceGlobals GameCardService;
public void Initialize(HorizonClient horizonClient, FileSystemServer fsServer) public void Initialize(HorizonClient horizonClient, FileSystemServer fsServer)
{ {
@ -46,6 +54,12 @@ internal struct FileSystemServerGlobals
MultiCommitManager.Initialize(); MultiCommitManager.Initialize();
LocationResolverSet.Initialize(); LocationResolverSet.Initialize();
PooledBuffer.Initialize(); PooledBuffer.Initialize();
GameCardService.Initialize();
}
public void Dispose()
{
GameCardService.Dispose();
} }
} }

View file

@ -1,19 +0,0 @@
using System;
namespace LibHac.FsSrv;
public readonly struct GameCardHandle : IEquatable<GameCardHandle>
{
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);
}

View file

@ -1,4 +1,5 @@
using System; using System;
using LibHac.Fs;
namespace LibHac.FsSrv.Sf; namespace LibHac.FsSrv.Sf;

View file

@ -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<IStorageDevice> CachedStorageDevice;
public void Initialize()
{
StorageDeviceMutex = new SdkMutexType();
}
public void Dispose()
{
CachedStorageDevice.Destroy();
}
}
/// <summary>
/// Contains functions for interacting with the game card storage device.
/// </summary>
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
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<IStorageDeviceManager> outManager)
{
return service.CreateStorageDeviceManager(ref outManager, StorageDevicePortId.GameCard);
}
private static Result OpenAndCacheGameCardDevice(this StorageService service,
ref SharedRef<IStorageDevice> outStorageDevice, OpenGameCardAttribute attribute)
{
using var storageDeviceManager = new SharedRef<IStorageDeviceManager>();
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<IStorageDevice>();
using ScopedLock<SdkMutexType> 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<IStorageDeviceOperator> outDeviceOperator)
{
using var storageDeviceManager = new SharedRef<IStorageDeviceManager>();
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<IStorageDeviceOperator> outDeviceOperator, OpenGameCardAttribute attribute)
{
using var storageDevice = new SharedRef<IStorageDevice>();
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<IStorageDeviceOperator> 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<IStorage> outStorage,
OpenGameCardAttribute attribute, uint handle)
{
using var gameCardStorageDevice = new SharedRef<IStorageDevice>();
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<IStorage>(new StorageServiceObjectAdapter(ref gameCardStorageDevice.Ref()));
using var deviceEventSimulationStorage = new SharedRef<DeviceEventSimulationStorage>(
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<IStorageDevice>();
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<IStorageDeviceManager>();
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<IStorageDeviceManager>();
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<IStorageDeviceOperator>();
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<IStorageDeviceOperator>();
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<IStorageDeviceOperator>();
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<GameCardStatus>(), bytesWritten);
return Result.Success;
}
public static Result FinalizeGameCardLibrary(this StorageService service)
{
using var gcOperator = new SharedRef<IStorageDeviceOperator>();
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<byte> outBuffer, uint handle)
{
using var gcOperator = new SharedRef<IStorageDeviceOperator>();
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<byte> outResponseBuffer,
ReadOnlySpan<byte> challengeSeed, ReadOnlySpan<byte> challengeValue, uint handle)
{
using var gcOperator = new SharedRef<IStorageDeviceOperator>();
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<IStorageDeviceOperator>();
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<GameCardHandle>(), bytesWritten);
// Clear the cached storage device if it has an old handle.
ref GameCardServiceGlobals g = ref service.Globals.GameCardService;
using ScopedLock<SdkMutexType> 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<byte> firmwareBuffer)
{
UnsafeHelpers.SkipParamInit(out rmaInfo);
using var gcOperator = new SharedRef<IStorageDeviceOperator>();
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<RmaInformation>(), bytesWritten);
return Result.Success;
}
public static Result GetGameCardIdSet(this StorageService service, out GameCardIdSet outIdSet)
{
UnsafeHelpers.SkipParamInit(out outIdSet);
using var gcOperator = new SharedRef<IStorageDeviceOperator>();
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<GameCardIdSet>(), bytesWritten);
return Result.Success;
}
public static Result WriteToGameCardDirectly(this StorageService service, long offset, Span<byte> buffer)
{
using var gcOperator = new SharedRef<IStorageDeviceOperator>();
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<IStorageDeviceOperator>();
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<byte> outBuffer, uint handle)
{
using var gcOperator = new SharedRef<IStorageDeviceOperator>();
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<byte> outBuffer,
ReadOnlySpan<byte> devHeaderBuffer)
{
using var gcOperator = new SharedRef<IStorageDeviceOperator>();
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<byte> devParamBuffer)
{
using var gcOperator = new SharedRef<IStorageDeviceOperator>();
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<byte> outDevParamBuffer)
{
using var gcOperator = new SharedRef<IStorageDeviceOperator>();
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<IStorageDeviceOperator>();
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<IStorageDeviceOperator>();
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<GameCardErrorInfo>(), bytesWritten);
return Result.Success;
}
public static Result GetGameCardErrorReportInfo(this StorageService service,
out GameCardErrorReportInfo errorReportInfo)
{
UnsafeHelpers.SkipParamInit(out errorReportInfo);
using var gcOperator = new SharedRef<IStorageDeviceOperator>();
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<GameCardErrorReportInfo>(), bytesWritten);
return Result.Success;
}
public static Result GetGameCardDeviceId(this StorageService service, Span<byte> outBuffer)
{
using var gcOperator = new SharedRef<IStorageDeviceOperator>();
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<IStorageDeviceOperator>();
Result rc = service.GetGameCardManagerOperator(ref gcOperator.Ref());
if (rc.IsFailure()) return Result.ConvertResultToReturnType<bool>(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<bool>(rc);
Assert.SdkEqual(Unsafe.SizeOf<bool>(), bytesWritten);
return isValid;
}
public static Result OpenGameCardDetectionEvent(this StorageService service,
ref SharedRef<IEventNotifier> outEventNotifier)
{
using var storageDeviceManager = new SharedRef<IStorageDeviceManager>();
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<IStorageDeviceOperator>();
Result rc = service.GetGameCardManagerOperator(ref gcOperator.Ref());
if (rc.IsFailure()) return rc.Miss();
int operationId = MakeOperationId(GameCardManagerOperationIdValue.SimulateDetectionEventSignaled);
return gcOperator.Get.Operate(operationId);
}
}

View file

@ -1,18 +1,21 @@
using System; using LibHac.Common;
using LibHac.Common; using IStorage = LibHac.FsSrv.Sf.IStorage;
using LibHac.Fs;
namespace LibHac.FsSrv.Storage.Sf; 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 GetHandle(out uint handle);
Result IsHandleValid(out bool isValid); Result IsHandleValid(out bool isValid);
Result OpenOperator(ref SharedRef<IStorageDeviceOperator> outDeviceOperator); Result OpenOperator(ref SharedRef<IStorageDeviceOperator> outDeviceOperator);
Result Read(long offset, Span<byte> destination);
Result Write(long offset, ReadOnlySpan<byte> source); // These methods come from inheriting IStorage
Result Flush(); //Result Read(long offset, OutBuffer destination, long size);
Result SetSize(long size); //Result Write(long offset, InBuffer source, long size);
Result GetSize(out long size); //Result Flush();
Result OperateRange(out QueryRangeInfo rangeInfo, int operationId, long offset, long size); //Result SetSize(long size);
//Result GetSize(out long size);
//Result OperateRange(out QueryRangeInfo rangeInfo, int operationId, long offset, long size);
} }

View file

@ -0,0 +1,33 @@
using LibHac.Common.FixedArrays;
using LibHac.Gc.Impl;
namespace LibHac.Gc;
public struct GameCardStatus
{
public Array32<byte> 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<byte> Reserved61;
public byte GameCardAttribute;
public Array11<byte> Reserved65;
}
public struct RmaInformation
{
public Array512<byte> Data;
}
public struct GameCardIdSet
{
public CardId1 Id1;
public CardId2 Id2;
public CardId3 Id3;
}

View file

@ -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;
}

View file

@ -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<byte> Reserved;
}
public struct CardId3
{
public Array4<byte> Reserved;
}
public enum MemoryCapacity : byte
{
// ReSharper disable InconsistentNaming
Capacity1GB = 0xFA,
Capacity2GB = 0xF8,
Capacity4GB = 0xF0,
Capacity8GB = 0xE0,
Capacity16GB = 0xE1,
Capacity32GB = 0xE2
// ReSharper restore InconsistentNaming
}

View file

@ -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
}

View file

@ -2,6 +2,7 @@
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using LibHac.Common; using LibHac.Common;
using LibHac.Diag;
using BaseType = System.UInt32; using BaseType = System.UInt32;
namespace LibHac; namespace LibHac;
@ -209,6 +210,27 @@ public readonly struct Result : IEquatable<Result>
} }
internal static T ConvertResultToReturnType<T>(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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static BaseType GetBitsValue(BaseType value, int bitsOffset, int bitsCount) private static BaseType GetBitsValue(BaseType value, int bitsOffset, int bitsCount)
{ {
@ -232,7 +254,7 @@ public readonly struct Result : IEquatable<Result>
/// <br/>A Result definition should look like this: <c>public static Result.Base PathNotFound => new Result.Base(ModuleFs, 1);</c> /// <br/>A Result definition should look like this: <c>public static Result.Base PathNotFound => new Result.Base(ModuleFs, 1);</c>
/// <br/>Being a computed property like this will allow the compiler to do constant propagation to optimize comparisons. /// <br/>Being a computed property like this will allow the compiler to do constant propagation to optimize comparisons.
/// <br/><br/>This is an example of how a Result should be returned from a function: <c>return PathNotFound.Log();</c> /// <br/><br/>This is an example of how a Result should be returned from a function: <c>return PathNotFound.Log();</c>
/// <br/>The <see cref="Log()"/> method will return the <see cref="Result"/> for the specified <see cref="Base"/>, and /// <br/>The <see cref="Result.Log"/> method will return the <see cref="Result"/> for the specified <see cref="Base"/>, and
/// will optionally log the returned Result when running in debug mode for easier debugging. All Result logging functionality /// will optionally log the returned Result when running in debug mode for easier debugging. All Result logging functionality
/// is removed from release builds. /// is removed from release builds.
/// If the <see cref="Result"/> is not being used as a return value, <see cref="Value"/> will get the Result without logging anything. /// If the <see cref="Result"/> is not being used as a return value, <see cref="Value"/> will get the Result without logging anything.

View file

@ -419,4 +419,56 @@ public class TypeLayoutTests
Assert.Equal(0, GetOffset(in s, in s.Value)); Assert.Equal(0, GetOffset(in s, in s.Value));
} }
[Fact]
public static void GameCardErrorInfo_Layout()
{
var s = new GameCardErrorInfo();
Assert.Equal(0x10, Unsafe.SizeOf<GameCardErrorInfo>());
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<GameCardErrorReportInfo>());
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<GameCardHandle>());
Assert.Equal(0, GetOffset(in s, in s.Value));
}
} }

View file

@ -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<GameCardStatus>());
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<RmaInformation>());
Assert.Equal(0x00, GetOffset(in s, in s.Data));
}
[Fact]
public static void GameCardIdSet_Layout()
{
var s = new GameCardIdSet();
Assert.Equal(12, Unsafe.SizeOf<GameCardIdSet>());
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<CardId1>());
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<CardId2>());
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<CardId3>());
Assert.Equal(0, GetOffset(in s, in s.Reserved));
}
}