Add a simple emulated GC API implementation

This commit is contained in:
Alex Barney 2023-01-06 13:46:51 -07:00
parent 80588438c0
commit fab5efc4de
19 changed files with 506 additions and 438 deletions

View file

@ -14,7 +14,7 @@ public class DefaultFsServerObjects
public FileSystemCreatorInterfaces FsCreators { get; set; }
public EmulatedGameCard GameCard { get; set; }
public SdmmcApi Sdmmc { get; set; }
public GameCardDummy GameCardNew { get; set; }
public GameCardEmulated GameCardNew { get; set; }
public EmulatedStorageDeviceManagerFactory StorageDeviceManagerFactory { get; set; }
public static DefaultFsServerObjects GetDefaultEmulatedCreators(IFileSystem rootFileSystem, KeySet keySet,
@ -23,10 +23,10 @@ public class DefaultFsServerObjects
var creators = new FileSystemCreatorInterfaces();
var gameCard = new EmulatedGameCard(keySet);
var gameCardNew = new GameCardDummy();
var gameCardNew = new GameCardEmulated();
var sdmmcNew = new SdmmcApi(fsServer);
var gcStorageCreator = new EmulatedGameCardStorageCreator(gameCard);
var gcStorageCreator = new GameCardStorageCreator(fsServer);
using var sharedRootFileSystem = new SharedRef<IFileSystem>(rootFileSystem);
using SharedRef<IFileSystem> sharedRootFileSystemCopy =
@ -39,7 +39,7 @@ public class DefaultFsServerObjects
creators.SubDirectoryFileSystemCreator = new SubDirectoryFileSystemCreator();
creators.SaveDataFileSystemCreator = new SaveDataFileSystemCreator(fsServer, keySet, null, randomGenerator);
creators.GameCardStorageCreator = gcStorageCreator;
creators.GameCardFileSystemCreator = new EmulatedGameCardFsCreator(gcStorageCreator, gameCard);
creators.GameCardFileSystemCreator = new GameCardFileSystemCreator(new ArrayPoolMemoryResource(), gcStorageCreator, fsServer);
creators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator(keySet);
creators.BuiltInStorageFileSystemCreator = new EmulatedBisFileSystemCreator(ref sharedRootFileSystem.Ref);
creators.SdCardFileSystemCreator = new EmulatedSdCardFileSystemCreator(sdmmcNew, ref sharedRootFileSystemCopy.Ref);

View file

@ -1,39 +0,0 @@
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Tools.Fs;
namespace LibHac.FsSrv.FsCreator;
public class EmulatedGameCardFsCreator : IGameCardFileSystemCreator
{
// ReSharper disable once NotAccessedField.Local
private EmulatedGameCardStorageCreator _storageCreator;
private EmulatedGameCard _gameCard;
public EmulatedGameCardFsCreator(EmulatedGameCardStorageCreator storageCreator, EmulatedGameCard gameCard)
{
_storageCreator = storageCreator;
_gameCard = gameCard;
}
public void Dispose() { }
public Result Create(ref SharedRef<IFileSystem> outFileSystem, GameCardHandle handle,
GameCardPartition partitionType)
{
// Use the old xci code temporarily
Result res = _gameCard.GetXci(out Xci xci, handle);
if (res.IsFailure()) return res.Miss();
if (!xci.HasPartition((XciPartitionType)partitionType))
{
return ResultFs.PartitionNotFound.Log();
}
XciPartition fs = xci.OpenPartition((XciPartitionType)partitionType);
outFileSystem.Reset(fs);
return Result.Success;
}
}

View file

@ -1,131 +0,0 @@
using System;
using LibHac.Common;
using LibHac.Fs;
namespace LibHac.FsSrv.FsCreator;
public class EmulatedGameCardStorageCreator : IGameCardStorageCreator
{
private EmulatedGameCard GameCard { get; }
public EmulatedGameCardStorageCreator(EmulatedGameCard gameCard)
{
GameCard = gameCard;
}
public void Dispose() { }
public Result CreateReadOnly(GameCardHandle handle, ref SharedRef<IStorage> outStorage)
{
if (GameCard.IsGameCardHandleInvalid(handle))
{
return ResultFs.GameCardFsCheckHandleInCreateReadOnlyFailure.Log();
}
using var baseStorage = new SharedRef<IStorage>(new ReadOnlyGameCardStorage(GameCard, handle));
Result res = GameCard.GetCardInfo(out GameCardInfo cardInfo, handle);
if (res.IsFailure()) return res.Miss();
outStorage.Reset(new SubStorage(in baseStorage, 0, cardInfo.SecureAreaOffset));
return Result.Success;
}
public Result CreateSecureReadOnly(GameCardHandle handle, ref SharedRef<IStorage> outStorage)
{
if (GameCard.IsGameCardHandleInvalid(handle))
{
return ResultFs.GameCardFsCheckHandleInCreateSecureReadOnlyFailure.Log();
}
Span<byte> deviceId = stackalloc byte[0x10];
Span<byte> imageHash = stackalloc byte[0x20];
Result res = GameCard.GetGameCardDeviceId(deviceId);
if (res.IsFailure()) return res.Miss();
res = GameCard.GetGameCardImageHash(imageHash);
if (res.IsFailure()) return res.Miss();
using var baseStorage =
new SharedRef<IStorage>(new ReadOnlyGameCardStorage(GameCard, handle, deviceId, imageHash));
res = GameCard.GetCardInfo(out GameCardInfo cardInfo, handle);
if (res.IsFailure()) return res.Miss();
outStorage.Reset(new SubStorage(in baseStorage, cardInfo.SecureAreaOffset, cardInfo.SecureAreaSize));
return Result.Success;
}
public Result CreateWriteOnly(GameCardHandle handle, ref SharedRef<IStorage> outStorage)
{
throw new NotImplementedException();
}
private class ReadOnlyGameCardStorage : IStorage
{
private EmulatedGameCard GameCard { get; }
private GameCardHandle Handle { get; set; }
// ReSharper disable once UnusedAutoPropertyAccessor.Local
private bool IsSecureMode { get; }
private byte[] DeviceId { get; } = new byte[0x10];
private byte[] ImageHash { get; } = new byte[0x20];
public ReadOnlyGameCardStorage(EmulatedGameCard gameCard, GameCardHandle handle)
{
GameCard = gameCard;
Handle = handle;
}
public ReadOnlyGameCardStorage(EmulatedGameCard gameCard, GameCardHandle handle,
ReadOnlySpan<byte> deviceId, ReadOnlySpan<byte> imageHash)
{
GameCard = gameCard;
Handle = handle;
IsSecureMode = true;
deviceId.CopyTo(DeviceId);
imageHash.CopyTo(ImageHash);
}
public override Result Read(long offset, Span<byte> destination)
{
// In secure mode, if Handle is old and the card's device ID and
// header hash are still the same, Handle is updated to the new handle
return GameCard.Read(Handle, offset, destination);
}
public override Result Write(long offset, ReadOnlySpan<byte> source)
{
return ResultFs.UnsupportedWriteForReadOnlyGameCardStorage.Log();
}
public override Result Flush()
{
return Result.Success;
}
public override Result SetSize(long size)
{
return ResultFs.UnsupportedSetSizeForReadOnlyGameCardStorage.Log();
}
public override Result GetSize(out long size)
{
UnsafeHelpers.SkipParamInit(out size);
Result res = GameCard.GetCardInfo(out GameCardInfo info, Handle);
if (res.IsFailure()) return res.Miss();
size = info.Size;
return Result.Success;
}
public override Result OperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size,
ReadOnlySpan<byte> inBuffer)
{
throw new NotImplementedException();
}
}
}

View file

@ -108,6 +108,7 @@ public class GameCardRootPartition : IDisposable
return ResultFs.PartitionNotFound.Log();
ref readonly PartitionEntry entry = ref _partitionFsMeta.Get.GetEntry(entryIndex);
outEntry = new ReadOnlyRef<PartitionEntry>(in entry);
switch (partitionType)
{
@ -261,9 +262,9 @@ public class GameCardFileSystemCreator : IGameCardFileSystemCreator
if (res.IsFailure()) return res.Miss();
// Get an IStorage of the start of the root partition to the start of the secure area.
long updateAndNormalPartitionSize = status.SecureAreaOffset - status.PartitionFsHeaderOffset;
long updateAndNormalPartitionSize = status.NormalAreaSize - status.PartitionFsHeaderAddress;
using var rootFsStorage = new SharedRef<IStorage>(new SubStorage(in alignedRootStorage,
status.PartitionFsHeaderOffset, updateAndNormalPartitionSize));
status.PartitionFsHeaderAddress, updateAndNormalPartitionSize));
if (!rootFsStorage.HasValue)
return ResultFs.AllocationMemoryFailedInGameCardFileSystemCreatorD.Log();

View file

@ -251,8 +251,8 @@ public class DeviceOperator : IDeviceOperator
Result res = _fsServer.Storage.GetGameCardStatus(out GameCardStatus gameCardStatus, handle);
if (res.IsFailure()) return res.Miss();
outCupVersion = gameCardStatus.UpdatePartitionVersion;
outCupId = gameCardStatus.UpdatePartitionId;
outCupVersion = gameCardStatus.CupVersion;
outCupId = gameCardStatus.CupId;
return Result.Success;
}
@ -273,7 +273,7 @@ public class DeviceOperator : IDeviceOperator
Result res = _fsServer.Storage.GetGameCardStatus(out GameCardStatus gameCardStatus, handle);
if (res.IsFailure()) return res.Miss();
outAttribute = gameCardStatus.GameCardAttribute;
outAttribute = gameCardStatus.Flags;
return Result.Success;
}

View file

@ -25,9 +25,9 @@ public class EmulatedStorageDeviceManagerFactory : IStorageDeviceManagerFactory
private readonly FileSystemServer _fsServer;
private readonly SdmmcApi _sdmmc;
private readonly GameCardDummy _gc;
private readonly GameCardEmulated _gc;
public EmulatedStorageDeviceManagerFactory(FileSystemServer fsServer, SdmmcApi sdmmc, GameCardDummy gc,
public EmulatedStorageDeviceManagerFactory(FileSystemServer fsServer, SdmmcApi sdmmc, GameCardEmulated gc,
bool hasGameCard)
{
_fsServer = fsServer;

View file

@ -320,7 +320,7 @@ internal static class GameCardService
size: 0, operationId);
if (res.IsFailure()) return res.Miss();
Assert.SdkEqual(GcCardExistenceResponseDataSize, bytesWritten);
Assert.SdkEqual(GcChallengeCardExistenceResponseSize, bytesWritten);
return Result.Success;
}

View file

@ -1,200 +0,0 @@
using System;
using LibHac.Fs;
using LibHac.Gc.Impl;
using LibHac.Gc.Writer;
namespace LibHac.Gc;
public class GameCardDummy
{
public GameCardWriter Writer => new GameCardWriter();
public readonly struct GameCardWriter
{
public GameCardWriter()
{
}
public void ChangeMode(AsicMode mode)
{
throw new NotImplementedException();
}
public Result ActivateForWriter()
{
throw new NotImplementedException();
}
public Result EraseAndWriteParameter(MemorySize size, uint romAreaStartPageIndex)
{
throw new NotImplementedException();
}
public Result Write(ReadOnlySpan<byte> source, uint pageIndex, uint pageCount)
{
throw new NotImplementedException();
}
public Result GetCardAvailableRawSize(out long outSize)
{
throw new NotImplementedException();
}
public void SetVerifyEnableFlag(bool isEnabled)
{
throw new NotImplementedException();
}
public void SetUserAsicFirmwareBuffer(ReadOnlySpan<byte> firmwareBuffer)
{
throw new NotImplementedException();
}
public Result GetRmaInformation(out RmaInformation outRmaInformation)
{
throw new NotImplementedException();
}
public Result WriteDevCardParam(in DevCardParameter devCardParam)
{
throw new NotImplementedException();
}
public Result ReadDevCardParam(out DevCardParameter outDevCardParam)
{
throw new NotImplementedException();
}
public Result ForceErase()
{
throw new NotImplementedException();
}
}
public void PresetInternalKeys(ReadOnlySpan<byte> gameCardKey, ReadOnlySpan<byte> gameCardCertificate)
{
throw new NotImplementedException();
}
public void Initialize(Memory<byte> workBuffer, ulong deviceBufferAddress)
{
throw new NotImplementedException();
}
public void FinalizeGc()
{
throw new NotImplementedException();
}
public void PowerOffGameCard()
{
throw new NotImplementedException();
}
public void RegisterDeviceVirtualAddress(Memory<byte> buffer, ulong deviceBufferAddress)
{
throw new NotImplementedException();
}
public void UnregisterDeviceVirtualAddress(Memory<byte> buffer, ulong deviceBufferAddress)
{
throw new NotImplementedException();
}
public Result GetInitializationResult()
{
throw new NotImplementedException();
}
public Result Activate()
{
throw new NotImplementedException();
}
public void Deactivate()
{
throw new NotImplementedException();
}
public Result SetCardToSecureMode()
{
throw new NotImplementedException();
}
public Result Read(Span<byte> destination, uint pageIndex, uint pageCount)
{
throw new NotImplementedException();
}
public void PutToSleep()
{
throw new NotImplementedException();
}
public void Awaken()
{
throw new NotImplementedException();
}
public bool IsCardInserted()
{
throw new NotImplementedException();
}
public bool IsCardActivationValid()
{
throw new NotImplementedException();
}
public Result GetCardStatus(out GameCardStatus outStatus)
{
throw new NotImplementedException();
}
public Result GetCardDeviceId(Span<byte> destBuffer)
{
throw new NotImplementedException();
}
public Result GetCardDeviceCertificate(Span<byte> destBuffer)
{
throw new NotImplementedException();
}
public Result ChallengeCardExistence(Span<byte> responseBuffer, ReadOnlySpan<byte> challengeSeedBuffer,
ReadOnlySpan<byte> challengeValueBuffer)
{
throw new NotImplementedException();
}
public Result GetCardImageHash(Span<byte> destBuffer)
{
throw new NotImplementedException();
}
public Result GetGameCardIdSet(out GameCardIdSet outGcIdSet)
{
throw new NotImplementedException();
}
public void RegisterDetectionEventCallback(Action<object> function, object args)
{
throw new NotImplementedException();
}
public void UnregisterDetectionEventCallback()
{
throw new NotImplementedException();
}
public Result GetCardHeader(Span<byte> destBuffer)
{
throw new NotImplementedException();
}
public Result GetErrorInfo(out GameCardErrorReportInfo outErrorReportInfo)
{
throw new NotImplementedException();
}
}

View file

@ -0,0 +1,430 @@
using System;
using LibHac.Common;
using LibHac.Common.FixedArrays;
using LibHac.Crypto;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Gc.Impl;
using LibHac.Gc.Writer;
using static LibHac.Gc.Values;
namespace LibHac.Gc;
public class GameCardEmulated
{
private static ReadOnlySpan<byte> CardHeaderKey => new byte[]
{ 0x01, 0xC5, 0x8F, 0xE7, 0x00, 0x2D, 0x13, 0x5A, 0xB2, 0x9A, 0x3F, 0x69, 0x33, 0x95, 0x74, 0xB1 };
private const string LibNotInitializedMessage = "Error: Gc lib is not initialized\n";
private SharedRef<IStorage> _cardStorage;
private bool _attached;
private bool _activated;
private bool _isSecureMode;
private bool _initialized;
private bool _writeMode;
private bool _hasKeyArea;
private CardHeader _cardHeader;
private T1CardCertificate _certificate;
private Array32<byte> _imageHash;
public GameCardWriter Writer => new GameCardWriter(this);
private Result CheckCardReady()
{
if (!_attached)
return ResultFs.GameCardCardNotInserted.Log();
if (!_activated)
return ResultFs.GameCardCardNotActivated.Log();
return Result.Success;
}
private void DecryptCardHeader(ref CardHeader header)
{
Span<byte> iv = stackalloc byte[GcAesCbcIvLength];
for (int i = 0; i < GcAesCbcIvLength; i++)
{
iv[i] = header.Iv[GcAesCbcIvLength - 1 - i];
}
Aes.DecryptCbc128(SpanHelpers.AsReadOnlyByteSpan(in header.EncryptedData),
SpanHelpers.AsByteSpan(ref header.EncryptedData), CardHeaderKey, iv);
}
private long GetCardSize(MemoryCapacity memoryCapacity)
{
return memoryCapacity switch
{
MemoryCapacity.Capacity1GB => AvailableSizeBase * 1,
MemoryCapacity.Capacity2GB => AvailableSizeBase * 2,
MemoryCapacity.Capacity4GB => AvailableSizeBase * 4,
MemoryCapacity.Capacity8GB => AvailableSizeBase * 8,
MemoryCapacity.Capacity16GB => AvailableSizeBase * 16,
MemoryCapacity.Capacity32GB => AvailableSizeBase * 32,
_ => 0
};
}
public void InsertGameCard(in SharedRef<IStorage> storage)
{
_attached = false;
_activated = false;
_cardStorage.SetByCopy(in storage);
_hasKeyArea = HasKeyArea(_cardStorage.Get);
if (storage.HasValue)
{
Abort.DoAbortUnlessSuccess(ReadBaseStorage(0x100, SpanHelpers.AsByteSpan(ref _cardHeader)));
Abort.DoAbortUnlessSuccess(ReadBaseStorage(GcCertAreaPageAddress * GcPageSize, SpanHelpers.AsByteSpan(ref _certificate)));
Sha256.GenerateSha256Hash(SpanHelpers.AsReadOnlyByteSpan(in _cardHeader), _imageHash.Items);
DecryptCardHeader(ref _cardHeader);
_attached = true;
}
}
public void RemoveGameCard()
{
_cardStorage.Destroy();
_attached = false;
_activated = false;
}
private static bool HasKeyArea(IStorage baseStorage)
{
if (baseStorage is null)
return false;
Result res = baseStorage.GetSize(out long storageSize);
if (res.IsFailure()) return false;
if (storageSize >= 0x1104)
{
uint magic = 0;
res = baseStorage.Read(0x1100, SpanHelpers.AsByteSpan(ref magic));
if (res.IsFailure()) return false;
if (magic == CardHeader.HeaderMagic)
{
return true;
}
}
return false;
}
private Result ReadBaseStorage(long offset, Span<byte> destination)
{
long baseStorageOffset = _hasKeyArea ? GcCardKeyAreaSize + offset : offset;
return _cardStorage.Get.Read(baseStorageOffset, destination).Ret();
}
public readonly struct GameCardWriter
{
private readonly GameCardEmulated _card;
public GameCardWriter(GameCardEmulated card)
{
_card = card;
}
public void ChangeMode(AsicMode mode)
{
Abort.DoAbortUnless(_card._initialized, LibNotInitializedMessage);
}
public Result ActivateForWriter()
{
Abort.DoAbortUnless(_card._initialized, LibNotInitializedMessage);
_card._writeMode = true;
_card._activated = true;
return Result.Success;
}
public Result EraseAndWriteParameter(MemorySize size, uint romAreaStartPageIndex)
{
return ResultFs.NotImplemented.Log();
}
public Result Write(ReadOnlySpan<byte> source, uint pageIndex, uint pageCount)
{
return ResultFs.NotImplemented.Log();
}
public Result GetCardAvailableRawSize(out long outSize)
{
outSize = 0;
return Result.Success;
}
public void SetVerifyEnableFlag(bool isEnabled)
{
// ...
}
public void SetUserAsicFirmwareBuffer(ReadOnlySpan<byte> firmwareBuffer)
{
// ...
}
public Result GetRmaInformation(out RmaInformation outRmaInformation)
{
outRmaInformation = default;
return Result.Success;
}
public Result WriteDevCardParam(in DevCardParameter devCardParam)
{
return Result.Success;
}
public Result ReadDevCardParam(out DevCardParameter outDevCardParam)
{
outDevCardParam = default;
return Result.Success;
}
public Result ForceErase()
{
return Result.Success;
}
}
public void PresetInternalKeys(ReadOnlySpan<byte> gameCardKey, ReadOnlySpan<byte> gameCardCertificate)
{
// ...
}
public void Initialize(Memory<byte> workBuffer, ulong deviceBufferAddress)
{
_initialized = true;
}
public void FinalizeGc()
{
_initialized = false;
}
public void PowerOffGameCard()
{
// ...
}
public void RegisterDeviceVirtualAddress(Memory<byte> buffer, ulong deviceBufferAddress)
{
// ...
}
public void UnregisterDeviceVirtualAddress(Memory<byte> buffer, ulong deviceBufferAddress)
{
// ...
}
public Result GetInitializationResult()
{
Abort.DoAbortUnless(_initialized, LibNotInitializedMessage);
return Result.Success;
}
public Result Activate()
{
Abort.DoAbortUnless(_initialized, LibNotInitializedMessage);
_activated = true;
_writeMode = false;
return Result.Success;
}
public void Deactivate()
{
Abort.DoAbortUnless(_initialized, LibNotInitializedMessage);
_activated = false;
_isSecureMode = false;
}
public Result SetCardToSecureMode()
{
Abort.DoAbortUnless(_initialized, LibNotInitializedMessage);
Result res = CheckCardReady();
if (res.IsFailure()) return res.Miss();
_isSecureMode = true;
return Result.Success;
}
public Result Read(Span<byte> destination, uint pageAddress, uint pageCount)
{
Abort.DoAbortUnless(_initialized, LibNotInitializedMessage);
Result res = CheckCardReady();
if (res.IsFailure()) return res.Miss();
if (destination.Length == 0)
return Result.Success;
int limArea = (int)_cardHeader.LimAreaPage;
bool isNormal = pageAddress < limArea;
bool isSecure = (pageAddress + pageCount - 1) >= limArea;
// Reads cannot span the boundary between the normal area and secure area.
if (isNormal && isSecure)
return ResultFs.GameCardInvalidAccessAcrossMode.Log();
// Reads to the secure area cannot be done in normal mode.
if (isSecure && !_isSecureMode)
return ResultFs.GameCardInvalidSecureAccess.Log();
// Reads to the normal area cannot be done in secure mode.
if (isNormal && _isSecureMode)
return ResultFs.GameCardInvalidNormalAccess.Log();
res = ReadBaseStorage(pageAddress * GcPageSize, destination);
if (res.IsFailure()) return res.Miss();
return Result.Success;
}
public void PutToSleep()
{
Abort.DoAbortUnless(_initialized, LibNotInitializedMessage);
}
public void Awaken()
{
Abort.DoAbortUnless(_initialized, LibNotInitializedMessage);
}
public bool IsCardInserted()
{
Abort.DoAbortUnless(_initialized, LibNotInitializedMessage);
return _attached;
}
public bool IsCardActivationValid()
{
Abort.DoAbortUnless(_initialized, LibNotInitializedMessage);
return _activated;
}
public Result GetCardStatus(out GameCardStatus outStatus)
{
outStatus = default;
Abort.DoAbortUnless(_initialized, LibNotInitializedMessage);
Result res = CheckCardReady();
if (res.IsFailure()) return res.Miss();
long cardSize = GetCardSize((MemoryCapacity)_cardHeader.RomSize);
GameCardStatus status = default;
status.CupVersion = _cardHeader.EncryptedData.CupVersion;
status.PackageId = _cardHeader.PackageId;
status.CardSize = cardSize;
status.PartitionFsHeaderHash = _cardHeader.PartitionFsHeaderHash;
status.CupId = _cardHeader.EncryptedData.CupId;
status.CompatibilityType = _cardHeader.EncryptedData.CompatibilityType;
status.PartitionFsHeaderAddress = _cardHeader.PartitionFsHeaderAddress;
status.PartitionFsHeaderSize = _cardHeader.PartitionFsHeaderSize;
status.NormalAreaSize = _cardHeader.LimAreaPage * GcPageSize;
status.SecureAreaSize = cardSize - status.NormalAreaSize;
status.Flags = _cardHeader.Flags;
outStatus = status;
return Result.Success;
}
public Result GetCardDeviceId(Span<byte> destBuffer)
{
Abort.DoAbortUnless(_initialized, LibNotInitializedMessage);
if (!_isSecureMode)
return ResultFs.GameCardStateCardSecureModeRequired.Log();
Result res = CheckCardReady();
if (res.IsFailure()) return res.Miss();
_certificate.T1CardDeviceId.ItemsRo.CopyTo(destBuffer);
return Result.Success;
}
public Result GetCardDeviceCertificate(Span<byte> destBuffer)
{
Abort.DoAbortUnless(_initialized, LibNotInitializedMessage);
if (!_isSecureMode)
return ResultFs.GameCardInvalidGetCardDeviceCertificate.Log();
Result res = CheckCardReady();
if (res.IsFailure()) return res.Miss();
SpanHelpers.AsReadOnlyByteSpan(in _certificate).Slice(0, GcDeviceCertificateSize).CopyTo(destBuffer);
return Result.Success;
}
public Result ChallengeCardExistence(Span<byte> responseBuffer, ReadOnlySpan<byte> challengeSeedBuffer,
ReadOnlySpan<byte> challengeValueBuffer)
{
Abort.DoAbortUnless(_initialized, LibNotInitializedMessage);
return CheckCardReady().Ret();
}
public Result GetCardImageHash(Span<byte> destBuffer)
{
Abort.DoAbortUnless(_initialized, LibNotInitializedMessage);
Result res = CheckCardReady();
if (res.IsFailure()) return res.Miss();
_imageHash.ItemsRo.CopyTo(destBuffer);
return Result.Success;
}
public Result GetGameCardIdSet(out GameCardIdSet outGcIdSet)
{
Abort.DoAbortUnless(_initialized, LibNotInitializedMessage);
outGcIdSet = default;
return Result.Success;
}
public void RegisterDetectionEventCallback(Action<object> function, object args)
{
// ...
}
public void UnregisterDetectionEventCallback()
{
// ...
}
public Result GetCardHeader(Span<byte> destBuffer)
{
Abort.DoAbortUnless(_initialized, LibNotInitializedMessage);
return Result.Success;
}
public Result GetErrorInfo(out GameCardErrorReportInfo outErrorReportInfo)
{
Abort.DoAbortUnless(_initialized, LibNotInitializedMessage);
outErrorReportInfo = default;
return Result.Success;
}
}

View file

@ -6,18 +6,18 @@ namespace LibHac.Gc;
public struct GameCardStatus
{
public Array32<byte> PartitionFsHeaderHash;
public ulong PackageId;
public long Size;
public long PartitionFsHeaderOffset;
public Array8<byte> PackageId;
public long CardSize;
public long PartitionFsHeaderAddress;
public long PartitionFsHeaderSize;
public long SecureAreaOffset;
public long NormalAreaSize;
public long SecureAreaSize;
public uint UpdatePartitionVersion;
public ulong UpdatePartitionId;
public uint CupVersion;
public ulong CupId;
public byte CompatibilityType;
public Array3<byte> Reserved61;
public byte GameCardAttribute;
public Array11<byte> Reserved65;
public Array3<byte> Reserved1;
public byte Flags;
public Array11<byte> Reserved2;
}
public struct RmaInformation

View file

@ -1,14 +1,21 @@
namespace LibHac.Gc;
using LibHac.Crypto;
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;
public static int GcCardKeyAreaSize => 0x1000;
public static int GcCardKeyAreaPageCount => 8;
public static int GcCertAreaStartPageAddress => 56;
public const int GcPageSize = 0x200;
public const int GcAsicFirmwareSize = 1024 * 30; // 30 KiB
public const int GcCardDeviceIdSize = 0x10;
public const int GcChallengeCardExistenceResponseSize = 0x58;
public const int GcCardImageHashSize = 0x20;
public const int GcDeviceCertificateSize = 0x200;
public const int GcCardKeyAreaSize = GcCardKeyAreaPageCount * GcPageSize;
public const int GcCardKeyAreaPageCount = 8;
public const int GcCertAreaPageAddress = 56;
public const int GcAesCbcIvLength = Aes.KeySize128;
public const long UnusedAreaSizeBase = 1024 * 1024 * 72; // 72 MiB
public const long MemorySizeBase = 1024 * 1024 * 1024; // 1 GiB
public const long AvailableSizeBase = MemorySizeBase - UnusedAreaSizeBase;
}

View file

@ -118,8 +118,8 @@ public struct CardHeader
public uint ValidDataEndPage;
public Array4<byte> Reserved11C;
public Array16<byte> Iv;
public ulong PartitionFsHeaderAddress;
public ulong PartitionFsHeaderSize;
public long PartitionFsHeaderAddress;
public long PartitionFsHeaderSize;
public Array32<byte> PartitionFsHeaderHash;
public Array32<byte> InitialDataHash;
public uint SelSec;

View file

@ -9,9 +9,9 @@ namespace LibHac.GcSrv;
/// <remarks>Based on nnSdk 14.3.0 (FS 14.1.0)</remarks>
internal class GameCardDetectionEventManager : CardDeviceDetectionEventManager
{
private GameCardDummy _gc;
private GameCardEmulated _gc;
public GameCardDetectionEventManager(GameCardDummy gc)
public GameCardDetectionEventManager(GameCardEmulated gc)
{
_gc = gc;

View file

@ -15,14 +15,14 @@ internal class GameCardDeviceOperator : IStorageDeviceOperator
private SharedRef<GameCardStorageDevice> _storageDevice;
// LibHac additions
private readonly GameCardDummy _gc;
private readonly GameCardEmulated _gc;
public static uint BytesToPages(long byteCount)
{
return (uint)((ulong)byteCount / (ulong)GcPageSize);
return (uint)((ulong)byteCount / GcPageSize);
}
public GameCardDeviceOperator(ref SharedRef<GameCardStorageDevice> storageDevice, GameCardDummy gc)
public GameCardDeviceOperator(ref SharedRef<GameCardStorageDevice> storageDevice, GameCardEmulated gc)
{
_storageDevice = SharedRef<GameCardStorageDevice>.CreateMove(ref storageDevice);
_gc = gc;
@ -179,11 +179,11 @@ internal class GameCardDeviceOperator : IStorageDeviceOperator
Result res = _storageDevice.Get.AcquireReadLock(ref readLock.Ref());
if (res.IsFailure()) return res.Miss();
if (outBuffer.Size < GcCardExistenceResponseDataSize)
if (outBuffer.Size < GcChallengeCardExistenceResponseSize)
return ResultFs.InvalidArgument.Log();
result = _gc.ChallengeCardExistence(outBuffer.Buffer, inBuffer1.Buffer, inBuffer2.Buffer);
bytesWritten = GcCardExistenceResponseDataSize;
bytesWritten = GcChallengeCardExistenceResponseSize;
break;
}

View file

@ -39,9 +39,9 @@ public class GameCardManager : IStorageDeviceManager, IStorageDeviceOperator, IG
// LibHac additions
private WeakRef<GameCardManager> _selfReference;
private readonly FileSystemServer _fsServer;
private readonly GameCardDummy _gc;
private readonly GameCardEmulated _gc;
private GameCardManager(GameCardDummy gc, FileSystemServer fsServer)
private GameCardManager(GameCardEmulated gc, FileSystemServer fsServer)
{
_rwLock = new ReaderWriterLock(fsServer.Hos.Os);
@ -49,7 +49,7 @@ public class GameCardManager : IStorageDeviceManager, IStorageDeviceOperator, IG
_gc = gc;
}
public static SharedRef<GameCardManager> CreateShared(GameCardDummy gc, FileSystemServer fsServer)
public static SharedRef<GameCardManager> CreateShared(GameCardEmulated gc, FileSystemServer fsServer)
{
var manager = new GameCardManager(gc, fsServer);
@ -506,7 +506,7 @@ public class GameCardManager : IStorageDeviceManager, IStorageDeviceOperator, IG
Result res = HandleGameCardAccessResult(_gc.GetCardStatus(out GameCardStatus cardStatus));
if (res.IsFailure()) return res.Miss();
long size = cardStatus.SecureAreaOffset;
long size = cardStatus.NormalAreaSize;
outStorage.Reset(new DelegatedSubStorage(ref storage.Ref, 0, size));
@ -522,7 +522,7 @@ public class GameCardManager : IStorageDeviceManager, IStorageDeviceOperator, IG
Result res = HandleGameCardAccessResult(_gc.GetCardStatus(out GameCardStatus cardStatus));
if (res.IsFailure()) return res.Miss();
long offset = cardStatus.SecureAreaOffset;
long offset = cardStatus.NormalAreaSize;
long size = cardStatus.SecureAreaSize;
outStorage.Reset(new DelegatedSubStorage(ref storage.Ref, offset, size));
@ -912,7 +912,7 @@ public class GameCardManager : IStorageDeviceManager, IStorageDeviceOperator, IG
Span<byte> originalHeaderBuffer = stackalloc byte[writeSize];
originalHeaderBuffer.Clear();
_gc.GetCardHeader(pooledBuffer.GetBuffer());
res = _gc.GetCardHeader(pooledBuffer.GetBuffer());
if (res.IsFailure()) return res.Miss();
pooledBuffer.GetBuffer().CopyTo(originalHeaderBuffer);
@ -923,7 +923,7 @@ public class GameCardManager : IStorageDeviceManager, IStorageDeviceOperator, IG
if (res.IsFailure()) return res.Miss();
devHeaderBuffer.CopyTo(pooledBuffer.GetBuffer());
res = _gc.Writer.Write(pooledBuffer.GetBuffer(), (uint)GcCardKeyAreaPageCount, 1);
res = _gc.Writer.Write(pooledBuffer.GetBuffer(), GcCardKeyAreaPageCount, 1);
if (res.IsFailure()) return res.Miss();
// Read the cert area
@ -931,7 +931,7 @@ public class GameCardManager : IStorageDeviceManager, IStorageDeviceOperator, IG
res = _gc.Activate();
if (res.IsFailure()) return res.Miss();
res = _gc.Read(pooledBuffer.GetBuffer(), (uint)GcCertAreaStartPageAddress, 1);
res = _gc.Read(pooledBuffer.GetBuffer(), GcCertAreaPageAddress, 1);
if (res.IsFailure()) return res.Miss();
Span<byte> deviceCert = stackalloc byte[writeSize];
@ -943,7 +943,7 @@ public class GameCardManager : IStorageDeviceManager, IStorageDeviceOperator, IG
if (res.IsFailure()) return res.Miss();
originalHeaderBuffer.CopyTo(pooledBuffer.GetBuffer());
res = _gc.Writer.Write(pooledBuffer.GetBuffer(), (uint)GcCardKeyAreaPageCount, 1);
res = _gc.Writer.Write(pooledBuffer.GetBuffer(), GcCardKeyAreaPageCount, 1);
if (res.IsFailure()) return res.Miss();
deviceCert.CopyTo(outBuffer);

View file

@ -20,9 +20,9 @@ internal class ReadOnlyGameCardStorage : IStorage
private SharedRef<IGameCardManager> _deviceManager;
// LibHac additions
private readonly GameCardDummy _gc;
private readonly GameCardEmulated _gc;
public ReadOnlyGameCardStorage(ref SharedRef<IGameCardManager> deviceManger, GameCardDummy gc)
public ReadOnlyGameCardStorage(ref SharedRef<IGameCardManager> deviceManger, GameCardEmulated gc)
{
_deviceManager = SharedRef<IGameCardManager>.CreateMove(ref deviceManger);
_gc = gc;
@ -65,7 +65,7 @@ internal class ReadOnlyGameCardStorage : IStorage
Result res = _gc.GetCardStatus(out GameCardStatus status);
if (res.IsFailure()) return res.Miss();
size = status.Size;
size = status.CardSize;
return Result.Success;
}
@ -103,9 +103,9 @@ internal class WriteOnlyGameCardStorage : IStorage
private SharedRef<IGameCardManager> _deviceManager;
// LibHac additions
private readonly GameCardDummy _gc;
private readonly GameCardEmulated _gc;
public WriteOnlyGameCardStorage(ref SharedRef<IGameCardManager> deviceManger, GameCardDummy gc)
public WriteOnlyGameCardStorage(ref SharedRef<IGameCardManager> deviceManger, GameCardEmulated gc)
{
_deviceManager = SharedRef<IGameCardManager>.CreateMove(ref deviceManger);
_gc = gc;

View file

@ -20,9 +20,9 @@ internal class GameCardStorageDevice : GameCardStorageInterfaceAdapter, IStorage
// LibHac additions
private WeakRef<GameCardStorageDevice> _selfReference;
private readonly GameCardDummy _gc;
private readonly GameCardEmulated _gc;
private GameCardStorageDevice(GameCardDummy gc, ref SharedRef<IGameCardManager> manager,
private GameCardStorageDevice(GameCardEmulated gc, ref SharedRef<IGameCardManager> manager,
in SharedRef<IStorage> baseStorage, GameCardHandle handle) : base(in baseStorage)
{
_manager = SharedRef<IGameCardManager>.CreateMove(ref manager);
@ -32,7 +32,7 @@ internal class GameCardStorageDevice : GameCardStorageInterfaceAdapter, IStorage
_gc = gc;
}
private GameCardStorageDevice(GameCardDummy gc, ref SharedRef<IGameCardManager> manager,
private GameCardStorageDevice(GameCardEmulated gc, ref SharedRef<IGameCardManager> manager,
in SharedRef<IStorage> baseStorage, GameCardHandle handle, bool isSecure, ReadOnlySpan<byte> cardDeviceId,
ReadOnlySpan<byte> cardImageHash)
: base(in baseStorage)
@ -50,7 +50,7 @@ internal class GameCardStorageDevice : GameCardStorageInterfaceAdapter, IStorage
_gc = gc;
}
public static SharedRef<GameCardStorageDevice> CreateShared(GameCardDummy gc,
public static SharedRef<GameCardStorageDevice> CreateShared(GameCardEmulated gc,
ref SharedRef<IGameCardManager> manager, in SharedRef<IStorage> baseStorage, GameCardHandle handle)
{
var storageDevice = new GameCardStorageDevice(gc, ref manager, in baseStorage, handle);
@ -61,7 +61,7 @@ internal class GameCardStorageDevice : GameCardStorageInterfaceAdapter, IStorage
return SharedRef<GameCardStorageDevice>.CreateMove(ref sharedStorageDevice.Ref);
}
public static SharedRef<GameCardStorageDevice> CreateShared(GameCardDummy gc,
public static SharedRef<GameCardStorageDevice> CreateShared(GameCardEmulated gc,
ref SharedRef<IGameCardManager> manager, in SharedRef<IStorage> baseStorage, GameCardHandle handle,
bool isSecure, ReadOnlySpan<byte> cardDeviceId, ReadOnlySpan<byte> cardImageHash)
{

View file

@ -205,7 +205,7 @@ public struct SharedLock<TMutex> : IDisposable where TMutex : class, ISharedMute
public void Unlock()
{
if (_ownsLock)
if (!_ownsLock)
throw new SynchronizationLockException("SharedLock.Unlock: Not locked");
_mutex.UnlockShared();

View file

@ -17,17 +17,17 @@ public class TypeLayoutTests
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(0x28, GetOffset(in s, in s.CardSize));
Assert.Equal(0x30, GetOffset(in s, in s.PartitionFsHeaderAddress));
Assert.Equal(0x38, GetOffset(in s, in s.PartitionFsHeaderSize));
Assert.Equal(0x40, GetOffset(in s, in s.SecureAreaOffset));
Assert.Equal(0x40, GetOffset(in s, in s.NormalAreaSize));
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(0x50, GetOffset(in s, in s.CupVersion));
Assert.Equal(0x58, GetOffset(in s, in s.CupId));
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));
Assert.Equal(0x61, GetOffset(in s, in s.Reserved1));
Assert.Equal(0x64, GetOffset(in s, in s.Flags));
Assert.Equal(0x65, GetOffset(in s, in s.Reserved2));
}
[Fact]