From fab5efc4de1fcb619dd94009603828295501a882 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Fri, 6 Jan 2023 13:46:51 -0700 Subject: [PATCH] Add a simple emulated GC API implementation --- src/LibHac/FsSrv/DefaultFsServerObjects.cs | 8 +- .../FsCreator/EmulatedGameCardFsCreator.cs | 39 -- .../EmulatedGameCardStorageCreator.cs | 131 ------ .../FsCreator/GameCardFileSystemCreator.cs | 5 +- src/LibHac/FsSrv/Impl/DeviceOperator.cs | 6 +- .../EmulatedStorageDeviceManagerFactory.cs | 4 +- src/LibHac/FsSrv/Storage/GameCardService.cs | 2 +- src/LibHac/Gc/GameCardDummy.cs | 200 -------- src/LibHac/Gc/GameCardEmulated.cs | 430 ++++++++++++++++++ src/LibHac/Gc/GameCardTypes.cs | 18 +- src/LibHac/Gc/GameCardValues.cs | 27 +- src/LibHac/Gc/Impl/GameCardImplTypes.cs | 4 +- .../GcSrv/GameCardDetectionEventManager.cs | 4 +- src/LibHac/GcSrv/GameCardDeviceOperator.cs | 10 +- src/LibHac/GcSrv/GameCardManager.cs | 18 +- src/LibHac/GcSrv/GameCardStorage.cs | 10 +- src/LibHac/GcSrv/GameCardStorageDevice.cs | 10 +- src/LibHac/Os/SharedLock.cs | 2 +- tests/LibHac.Tests/Gc/TypeLayoutTests.cs | 16 +- 19 files changed, 506 insertions(+), 438 deletions(-) delete mode 100644 src/LibHac/FsSrv/FsCreator/EmulatedGameCardFsCreator.cs delete mode 100644 src/LibHac/FsSrv/FsCreator/EmulatedGameCardStorageCreator.cs delete mode 100644 src/LibHac/Gc/GameCardDummy.cs create mode 100644 src/LibHac/Gc/GameCardEmulated.cs diff --git a/src/LibHac/FsSrv/DefaultFsServerObjects.cs b/src/LibHac/FsSrv/DefaultFsServerObjects.cs index 3e9f71ef..b9aee8d1 100644 --- a/src/LibHac/FsSrv/DefaultFsServerObjects.cs +++ b/src/LibHac/FsSrv/DefaultFsServerObjects.cs @@ -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(rootFileSystem); using SharedRef 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); diff --git a/src/LibHac/FsSrv/FsCreator/EmulatedGameCardFsCreator.cs b/src/LibHac/FsSrv/FsCreator/EmulatedGameCardFsCreator.cs deleted file mode 100644 index 8ba530a1..00000000 --- a/src/LibHac/FsSrv/FsCreator/EmulatedGameCardFsCreator.cs +++ /dev/null @@ -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 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; - } -} \ No newline at end of file diff --git a/src/LibHac/FsSrv/FsCreator/EmulatedGameCardStorageCreator.cs b/src/LibHac/FsSrv/FsCreator/EmulatedGameCardStorageCreator.cs deleted file mode 100644 index 440ffd18..00000000 --- a/src/LibHac/FsSrv/FsCreator/EmulatedGameCardStorageCreator.cs +++ /dev/null @@ -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 outStorage) - { - if (GameCard.IsGameCardHandleInvalid(handle)) - { - return ResultFs.GameCardFsCheckHandleInCreateReadOnlyFailure.Log(); - } - - using var baseStorage = new SharedRef(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 outStorage) - { - if (GameCard.IsGameCardHandleInvalid(handle)) - { - return ResultFs.GameCardFsCheckHandleInCreateSecureReadOnlyFailure.Log(); - } - - Span deviceId = stackalloc byte[0x10]; - Span 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(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 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 deviceId, ReadOnlySpan imageHash) - { - GameCard = gameCard; - Handle = handle; - IsSecureMode = true; - deviceId.CopyTo(DeviceId); - imageHash.CopyTo(ImageHash); - } - - public override Result Read(long offset, Span 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 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 outBuffer, OperationId operationId, long offset, long size, - ReadOnlySpan inBuffer) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/src/LibHac/FsSrv/FsCreator/GameCardFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/GameCardFileSystemCreator.cs index 75749c63..cc7b363e 100644 --- a/src/LibHac/FsSrv/FsCreator/GameCardFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/GameCardFileSystemCreator.cs @@ -108,6 +108,7 @@ public class GameCardRootPartition : IDisposable return ResultFs.PartitionNotFound.Log(); ref readonly PartitionEntry entry = ref _partitionFsMeta.Get.GetEntry(entryIndex); + outEntry = new ReadOnlyRef(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(new SubStorage(in alignedRootStorage, - status.PartitionFsHeaderOffset, updateAndNormalPartitionSize)); + status.PartitionFsHeaderAddress, updateAndNormalPartitionSize)); if (!rootFsStorage.HasValue) return ResultFs.AllocationMemoryFailedInGameCardFileSystemCreatorD.Log(); diff --git a/src/LibHac/FsSrv/Impl/DeviceOperator.cs b/src/LibHac/FsSrv/Impl/DeviceOperator.cs index e07506ed..0ec8ea73 100644 --- a/src/LibHac/FsSrv/Impl/DeviceOperator.cs +++ b/src/LibHac/FsSrv/Impl/DeviceOperator.cs @@ -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; } diff --git a/src/LibHac/FsSrv/Storage/EmulatedStorageDeviceManagerFactory.cs b/src/LibHac/FsSrv/Storage/EmulatedStorageDeviceManagerFactory.cs index 283cd9f7..94ffcdfe 100644 --- a/src/LibHac/FsSrv/Storage/EmulatedStorageDeviceManagerFactory.cs +++ b/src/LibHac/FsSrv/Storage/EmulatedStorageDeviceManagerFactory.cs @@ -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; diff --git a/src/LibHac/FsSrv/Storage/GameCardService.cs b/src/LibHac/FsSrv/Storage/GameCardService.cs index cb0be461..861051da 100644 --- a/src/LibHac/FsSrv/Storage/GameCardService.cs +++ b/src/LibHac/FsSrv/Storage/GameCardService.cs @@ -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; } diff --git a/src/LibHac/Gc/GameCardDummy.cs b/src/LibHac/Gc/GameCardDummy.cs deleted file mode 100644 index 3c21c6ef..00000000 --- a/src/LibHac/Gc/GameCardDummy.cs +++ /dev/null @@ -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 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 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 gameCardKey, ReadOnlySpan gameCardCertificate) - { - throw new NotImplementedException(); - } - - public void Initialize(Memory workBuffer, ulong deviceBufferAddress) - { - throw new NotImplementedException(); - } - - public void FinalizeGc() - { - throw new NotImplementedException(); - } - - public void PowerOffGameCard() - { - throw new NotImplementedException(); - } - - public void RegisterDeviceVirtualAddress(Memory buffer, ulong deviceBufferAddress) - { - throw new NotImplementedException(); - } - - public void UnregisterDeviceVirtualAddress(Memory 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 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 destBuffer) - { - throw new NotImplementedException(); - } - - public Result GetCardDeviceCertificate(Span destBuffer) - { - throw new NotImplementedException(); - } - - public Result ChallengeCardExistence(Span responseBuffer, ReadOnlySpan challengeSeedBuffer, - ReadOnlySpan challengeValueBuffer) - { - throw new NotImplementedException(); - } - - public Result GetCardImageHash(Span destBuffer) - { - throw new NotImplementedException(); - } - - public Result GetGameCardIdSet(out GameCardIdSet outGcIdSet) - { - throw new NotImplementedException(); - } - - public void RegisterDetectionEventCallback(Action function, object args) - { - throw new NotImplementedException(); - } - - public void UnregisterDetectionEventCallback() - { - throw new NotImplementedException(); - } - - public Result GetCardHeader(Span destBuffer) - { - throw new NotImplementedException(); - } - - public Result GetErrorInfo(out GameCardErrorReportInfo outErrorReportInfo) - { - throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/src/LibHac/Gc/GameCardEmulated.cs b/src/LibHac/Gc/GameCardEmulated.cs new file mode 100644 index 00000000..5db1c734 --- /dev/null +++ b/src/LibHac/Gc/GameCardEmulated.cs @@ -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 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 _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 _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 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 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 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 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 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 gameCardKey, ReadOnlySpan gameCardCertificate) + { + // ... + } + + public void Initialize(Memory workBuffer, ulong deviceBufferAddress) + { + _initialized = true; + } + + public void FinalizeGc() + { + _initialized = false; + } + + public void PowerOffGameCard() + { + // ... + } + + public void RegisterDeviceVirtualAddress(Memory buffer, ulong deviceBufferAddress) + { + // ... + } + + public void UnregisterDeviceVirtualAddress(Memory 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 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 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 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 responseBuffer, ReadOnlySpan challengeSeedBuffer, + ReadOnlySpan challengeValueBuffer) + { + Abort.DoAbortUnless(_initialized, LibNotInitializedMessage); + return CheckCardReady().Ret(); + } + + public Result GetCardImageHash(Span 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 function, object args) + { + // ... + } + + public void UnregisterDetectionEventCallback() + { + // ... + } + + public Result GetCardHeader(Span destBuffer) + { + Abort.DoAbortUnless(_initialized, LibNotInitializedMessage); + return Result.Success; + } + + public Result GetErrorInfo(out GameCardErrorReportInfo outErrorReportInfo) + { + Abort.DoAbortUnless(_initialized, LibNotInitializedMessage); + + outErrorReportInfo = default; + return Result.Success; + } +} \ No newline at end of file diff --git a/src/LibHac/Gc/GameCardTypes.cs b/src/LibHac/Gc/GameCardTypes.cs index c4ef2521..9ebd4e77 100644 --- a/src/LibHac/Gc/GameCardTypes.cs +++ b/src/LibHac/Gc/GameCardTypes.cs @@ -6,18 +6,18 @@ namespace LibHac.Gc; public struct GameCardStatus { public Array32 PartitionFsHeaderHash; - public ulong PackageId; - public long Size; - public long PartitionFsHeaderOffset; + public Array8 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 Reserved61; - public byte GameCardAttribute; - public Array11 Reserved65; + public Array3 Reserved1; + public byte Flags; + public Array11 Reserved2; } public struct RmaInformation diff --git a/src/LibHac/Gc/GameCardValues.cs b/src/LibHac/Gc/GameCardValues.cs index e25d869a..066a6757 100644 --- a/src/LibHac/Gc/GameCardValues.cs +++ b/src/LibHac/Gc/GameCardValues.cs @@ -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; } \ No newline at end of file diff --git a/src/LibHac/Gc/Impl/GameCardImplTypes.cs b/src/LibHac/Gc/Impl/GameCardImplTypes.cs index 377a7ce8..b9863201 100644 --- a/src/LibHac/Gc/Impl/GameCardImplTypes.cs +++ b/src/LibHac/Gc/Impl/GameCardImplTypes.cs @@ -118,8 +118,8 @@ public struct CardHeader public uint ValidDataEndPage; public Array4 Reserved11C; public Array16 Iv; - public ulong PartitionFsHeaderAddress; - public ulong PartitionFsHeaderSize; + public long PartitionFsHeaderAddress; + public long PartitionFsHeaderSize; public Array32 PartitionFsHeaderHash; public Array32 InitialDataHash; public uint SelSec; diff --git a/src/LibHac/GcSrv/GameCardDetectionEventManager.cs b/src/LibHac/GcSrv/GameCardDetectionEventManager.cs index f081aaa5..0a927714 100644 --- a/src/LibHac/GcSrv/GameCardDetectionEventManager.cs +++ b/src/LibHac/GcSrv/GameCardDetectionEventManager.cs @@ -9,9 +9,9 @@ namespace LibHac.GcSrv; /// Based on nnSdk 14.3.0 (FS 14.1.0) internal class GameCardDetectionEventManager : CardDeviceDetectionEventManager { - private GameCardDummy _gc; + private GameCardEmulated _gc; - public GameCardDetectionEventManager(GameCardDummy gc) + public GameCardDetectionEventManager(GameCardEmulated gc) { _gc = gc; diff --git a/src/LibHac/GcSrv/GameCardDeviceOperator.cs b/src/LibHac/GcSrv/GameCardDeviceOperator.cs index 1088f72c..797f1f75 100644 --- a/src/LibHac/GcSrv/GameCardDeviceOperator.cs +++ b/src/LibHac/GcSrv/GameCardDeviceOperator.cs @@ -15,14 +15,14 @@ internal class GameCardDeviceOperator : IStorageDeviceOperator private SharedRef _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 storageDevice, GameCardDummy gc) + public GameCardDeviceOperator(ref SharedRef storageDevice, GameCardEmulated gc) { _storageDevice = SharedRef.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; } diff --git a/src/LibHac/GcSrv/GameCardManager.cs b/src/LibHac/GcSrv/GameCardManager.cs index 1b4f037b..75aa5cdd 100644 --- a/src/LibHac/GcSrv/GameCardManager.cs +++ b/src/LibHac/GcSrv/GameCardManager.cs @@ -39,9 +39,9 @@ public class GameCardManager : IStorageDeviceManager, IStorageDeviceOperator, IG // LibHac additions private WeakRef _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 CreateShared(GameCardDummy gc, FileSystemServer fsServer) + public static SharedRef 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 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 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); diff --git a/src/LibHac/GcSrv/GameCardStorage.cs b/src/LibHac/GcSrv/GameCardStorage.cs index 0e2cec6f..0af56a7f 100644 --- a/src/LibHac/GcSrv/GameCardStorage.cs +++ b/src/LibHac/GcSrv/GameCardStorage.cs @@ -20,9 +20,9 @@ internal class ReadOnlyGameCardStorage : IStorage private SharedRef _deviceManager; // LibHac additions - private readonly GameCardDummy _gc; + private readonly GameCardEmulated _gc; - public ReadOnlyGameCardStorage(ref SharedRef deviceManger, GameCardDummy gc) + public ReadOnlyGameCardStorage(ref SharedRef deviceManger, GameCardEmulated gc) { _deviceManager = SharedRef.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 _deviceManager; // LibHac additions - private readonly GameCardDummy _gc; + private readonly GameCardEmulated _gc; - public WriteOnlyGameCardStorage(ref SharedRef deviceManger, GameCardDummy gc) + public WriteOnlyGameCardStorage(ref SharedRef deviceManger, GameCardEmulated gc) { _deviceManager = SharedRef.CreateMove(ref deviceManger); _gc = gc; diff --git a/src/LibHac/GcSrv/GameCardStorageDevice.cs b/src/LibHac/GcSrv/GameCardStorageDevice.cs index e40eaca2..16c35927 100644 --- a/src/LibHac/GcSrv/GameCardStorageDevice.cs +++ b/src/LibHac/GcSrv/GameCardStorageDevice.cs @@ -20,9 +20,9 @@ internal class GameCardStorageDevice : GameCardStorageInterfaceAdapter, IStorage // LibHac additions private WeakRef _selfReference; - private readonly GameCardDummy _gc; + private readonly GameCardEmulated _gc; - private GameCardStorageDevice(GameCardDummy gc, ref SharedRef manager, + private GameCardStorageDevice(GameCardEmulated gc, ref SharedRef manager, in SharedRef baseStorage, GameCardHandle handle) : base(in baseStorage) { _manager = SharedRef.CreateMove(ref manager); @@ -32,7 +32,7 @@ internal class GameCardStorageDevice : GameCardStorageInterfaceAdapter, IStorage _gc = gc; } - private GameCardStorageDevice(GameCardDummy gc, ref SharedRef manager, + private GameCardStorageDevice(GameCardEmulated gc, ref SharedRef manager, in SharedRef baseStorage, GameCardHandle handle, bool isSecure, ReadOnlySpan cardDeviceId, ReadOnlySpan cardImageHash) : base(in baseStorage) @@ -50,7 +50,7 @@ internal class GameCardStorageDevice : GameCardStorageInterfaceAdapter, IStorage _gc = gc; } - public static SharedRef CreateShared(GameCardDummy gc, + public static SharedRef CreateShared(GameCardEmulated gc, ref SharedRef manager, in SharedRef baseStorage, GameCardHandle handle) { var storageDevice = new GameCardStorageDevice(gc, ref manager, in baseStorage, handle); @@ -61,7 +61,7 @@ internal class GameCardStorageDevice : GameCardStorageInterfaceAdapter, IStorage return SharedRef.CreateMove(ref sharedStorageDevice.Ref); } - public static SharedRef CreateShared(GameCardDummy gc, + public static SharedRef CreateShared(GameCardEmulated gc, ref SharedRef manager, in SharedRef baseStorage, GameCardHandle handle, bool isSecure, ReadOnlySpan cardDeviceId, ReadOnlySpan cardImageHash) { diff --git a/src/LibHac/Os/SharedLock.cs b/src/LibHac/Os/SharedLock.cs index 62a82383..72631e06 100644 --- a/src/LibHac/Os/SharedLock.cs +++ b/src/LibHac/Os/SharedLock.cs @@ -205,7 +205,7 @@ public struct SharedLock : IDisposable where TMutex : class, ISharedMute public void Unlock() { - if (_ownsLock) + if (!_ownsLock) throw new SynchronizationLockException("SharedLock.Unlock: Not locked"); _mutex.UnlockShared(); diff --git a/tests/LibHac.Tests/Gc/TypeLayoutTests.cs b/tests/LibHac.Tests/Gc/TypeLayoutTests.cs index bfe30c60..4d78ab5d 100644 --- a/tests/LibHac.Tests/Gc/TypeLayoutTests.cs +++ b/tests/LibHac.Tests/Gc/TypeLayoutTests.cs @@ -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]