diff --git a/build/CodeGen/results.csv b/build/CodeGen/results.csv index f9c257ec..6df4f75b 100644 --- a/build/CodeGen/results.csv +++ b/build/CodeGen/results.csv @@ -310,8 +310,8 @@ Module,DescriptionStart,DescriptionEnd,Flags,Namespace,Name,Summary 2,2955,,,,GameCardFsCheckHandleInGetDeviceCertFailure, 2,2956,,,,GameCardFsCheckHandleInGetCardImageHashFailure, 2,2957,,,,GameCardFsCheckHandleInChallengeCardExistence, -2,2958,,,,GameCardFsCheckHandleInOnAcquireLock, -2,2959,,,,GameCardFsCheckModeInOnAcquireSecureLock, +2,2958,,,,GameCardFsCheckHandleInAcquireReadLock, +2,2959,,,,GameCardFsCheckModeInAcquireSecureLock, 2,2960,,,,GameCardFsCheckHandleInCreateReadOnlyFailure, 2,2961,,,,GameCardFsCheckHandleInCreateSecureReadOnlyFailure, 2,2962,,,,GameCardFsInvalidCompatibilityType, diff --git a/src/LibHac/Fs/GameCard.cs b/src/LibHac/Fs/GameCard.cs index 3cf956fb..ba94d7c2 100644 --- a/src/LibHac/Fs/GameCard.cs +++ b/src/LibHac/Fs/GameCard.cs @@ -45,6 +45,12 @@ public enum GameCardSizeInternal : byte Size32Gb = 0xE2 } +public enum GameCardClockRate +{ + ClockRate25 = 25, + ClockRate50 = 50 +} + [Flags] public enum GameCardAttribute : byte { diff --git a/src/LibHac/Fs/ResultFs.cs b/src/LibHac/Fs/ResultFs.cs index fb10f0dc..b9d97bcb 100644 --- a/src/LibHac/Fs/ResultFs.cs +++ b/src/LibHac/Fs/ResultFs.cs @@ -518,9 +518,9 @@ public static class ResultFs /// Error code: 2002-2957; Inner value: 0x171a02 public static Result.Base GameCardFsCheckHandleInChallengeCardExistence => new Result.Base(ModuleFs, 2957); /// Error code: 2002-2958; Inner value: 0x171c02 - public static Result.Base GameCardFsCheckHandleInOnAcquireLock => new Result.Base(ModuleFs, 2958); + public static Result.Base GameCardFsCheckHandleInAcquireReadLock => new Result.Base(ModuleFs, 2958); /// Error code: 2002-2959; Inner value: 0x171e02 - public static Result.Base GameCardFsCheckModeInOnAcquireSecureLock => new Result.Base(ModuleFs, 2959); + public static Result.Base GameCardFsCheckModeInAcquireSecureLock => new Result.Base(ModuleFs, 2959); /// Error code: 2002-2960; Inner value: 0x172002 public static Result.Base GameCardFsCheckHandleInCreateReadOnlyFailure => new Result.Base(ModuleFs, 2960); /// Error code: 2002-2961; Inner value: 0x172202 diff --git a/src/LibHac/FsSrv/BaseStorageService.cs b/src/LibHac/FsSrv/BaseStorageService.cs index ee1dae2c..bc030150 100644 --- a/src/LibHac/FsSrv/BaseStorageService.cs +++ b/src/LibHac/FsSrv/BaseStorageService.cs @@ -136,8 +136,11 @@ public readonly struct BaseStorageService Result res = GetProgramInfo(out ProgramInfo programInfo); if (res.IsFailure()) return res.Miss(); - res = _serviceImpl.OpenDeviceOperator(ref outDeviceOperator, programInfo.AccessControl); - if (res.IsFailure()) return res.Miss(); + using var deviceOperator = + new SharedRef(new DeviceOperator(_serviceImpl.FsServer, programInfo.AccessControl, + _processId)); + + outDeviceOperator.SetByMove(ref deviceOperator.Ref); return Result.Success; } @@ -180,13 +183,11 @@ public class BaseStorageServiceImpl { private Configuration _config; - // LibHac addition - private SharedRef _deviceOperator; + internal FileSystemServer FsServer => _config.FsServer; public BaseStorageServiceImpl(in Configuration configuration) { _config = configuration; - _deviceOperator = new SharedRef(configuration.DeviceOperator); } public struct Configuration @@ -196,8 +197,6 @@ public class BaseStorageServiceImpl // LibHac additions public FileSystemServer FsServer; - // Todo: The DeviceOperator in FS uses mostly global state. Decide how to handle this. - public IDeviceOperator DeviceOperator; } internal Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) @@ -231,12 +230,4 @@ public class BaseStorageServiceImpl return ResultFs.InvalidArgument.Log(); } } - - // LibHac addition - internal Result OpenDeviceOperator(ref SharedRef outDeviceOperator, - AccessControl accessControl) - { - outDeviceOperator.SetByCopy(in _deviceOperator); - return Result.Success; - } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/DefaultFsServerObjects.cs b/src/LibHac/FsSrv/DefaultFsServerObjects.cs index 9f780f16..7adf2fc6 100644 --- a/src/LibHac/FsSrv/DefaultFsServerObjects.cs +++ b/src/LibHac/FsSrv/DefaultFsServerObjects.cs @@ -1,7 +1,7 @@ using LibHac.Common; using LibHac.Common.Keys; using LibHac.FsSrv.FsCreator; -using LibHac.FsSrv.Sf; +using LibHac.FsSrv.Storage; using LibHac.FsSystem; using IFileSystem = LibHac.Fs.Fsa.IFileSystem; @@ -10,9 +10,9 @@ namespace LibHac.FsSrv; public class DefaultFsServerObjects { public FileSystemCreatorInterfaces FsCreators { get; set; } - public IDeviceOperator DeviceOperator { get; set; } public EmulatedGameCard GameCard { get; set; } public EmulatedSdCard SdCard { get; set; } + public EmulatedStorageDeviceManagerFactory StorageDeviceManagerFactory { get; set; } public static DefaultFsServerObjects GetDefaultEmulatedCreators(IFileSystem rootFileSystem, KeySet keySet, FileSystemServer fsServer, RandomDataGenerator randomGenerator) @@ -39,14 +39,14 @@ public class DefaultFsServerObjects creators.BuiltInStorageFileSystemCreator = new EmulatedBisFileSystemCreator(ref sharedRootFileSystem.Ref); creators.SdCardFileSystemCreator = new EmulatedSdCardFileSystemCreator(sdCard, ref sharedRootFileSystemCopy.Ref); - var deviceOperator = new EmulatedDeviceOperator(gameCard, sdCard); + var storageDeviceManagerFactory = new EmulatedStorageDeviceManagerFactory(fsServer, true); return new DefaultFsServerObjects { FsCreators = creators, - DeviceOperator = deviceOperator, GameCard = gameCard, - SdCard = sdCard + SdCard = sdCard, + StorageDeviceManagerFactory = storageDeviceManagerFactory }; } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/FileSystemServerInitializer.cs b/src/LibHac/FsSrv/FileSystemServerInitializer.cs index f461cb19..ea543f8e 100644 --- a/src/LibHac/FsSrv/FileSystemServerInitializer.cs +++ b/src/LibHac/FsSrv/FileSystemServerInitializer.cs @@ -31,11 +31,11 @@ public static class FileSystemServerInitializer if (config.FsCreators == null) throw new ArgumentException("FsCreators must not be null"); - if (config.DeviceOperator == null) - throw new ArgumentException("DeviceOperator must not be null"); + if (config.StorageDeviceManagerFactory == null) + throw new ArgumentException("StorageDeviceManagerFactory must not be null"); server.SetDebugFlagEnabled(false); - server.Storage.InitializeStorageDeviceManagerFactory(null); + server.Storage.InitializeStorageDeviceManagerFactory(config.StorageDeviceManagerFactory); FileSystemProxyConfiguration fspConfig = InitializeFileSystemProxy(server, config); @@ -55,6 +55,9 @@ public static class FileSystemServerInitializer saveService.FixSaveData().IgnoreResult(); saveService.RecoverMultiCommit().IgnoreResult(); + config.StorageDeviceManagerFactory.SetReady(StorageDevicePortId.SdCard, null); + config.StorageDeviceManagerFactory.SetReady(StorageDevicePortId.GameCard, null); + // NS usually takes care of this if (client.Fs.IsSdCardInserted()) client.Fs.SetSdCardAccessibility(true); @@ -86,7 +89,6 @@ public static class FileSystemServerInitializer baseStorageConfig.BisStorageCreator = config.FsCreators.BuiltInStorageCreator; baseStorageConfig.GameCardStorageCreator = config.FsCreators.GameCardStorageCreator; baseStorageConfig.FsServer = server; - baseStorageConfig.DeviceOperator = config.DeviceOperator; var baseStorageService = new BaseStorageServiceImpl(in baseStorageConfig); var timeService = new TimeServiceImpl(server); @@ -263,9 +265,9 @@ public class FileSystemServerConfig public FileSystemCreatorInterfaces FsCreators { get; set; } /// - /// An for managing the gamecard and SD card. + /// An for managing the gamecard and SD card. /// - public IDeviceOperator DeviceOperator { get; set; } + public IStorageDeviceManagerFactory StorageDeviceManagerFactory { get; set; } /// /// A keyset containing rights IDs and title keys. diff --git a/src/LibHac/FsSrv/Storage/EmulatedStorageDeviceManagerFactory.cs b/src/LibHac/FsSrv/Storage/EmulatedStorageDeviceManagerFactory.cs new file mode 100644 index 00000000..2e3a1902 --- /dev/null +++ b/src/LibHac/FsSrv/Storage/EmulatedStorageDeviceManagerFactory.cs @@ -0,0 +1,150 @@ +using LibHac.Common; +using LibHac.Fs; +using LibHac.FsSrv.Storage.Sf; +using LibHac.GcSrv; +using LibHac.Os; +using LibHac.SdmmcSrv; +using LibHac.Sf; + +namespace LibHac.FsSrv.Storage; + +public class EmulatedStorageDeviceManagerFactory : IStorageDeviceManagerFactory +{ + private SdkMutexType _gameCardDeviceMutex; + private SdkMutexType _sdCardDeviceMutex; + private SdkMutexType _mmcDeviceMutex; + + private SharedRef _sdCardDeviceManager; + private SharedRef _dummyGameCardDeviceManager; + private SharedRef _gameCardDeviceManager; + private SharedRef _mmcDeviceManager; + + private readonly bool _hasGameCard; + + private readonly FileSystemServer _fsServer; + + public EmulatedStorageDeviceManagerFactory(FileSystemServer fsServer, bool hasGameCard) + { + _fsServer = fsServer; + _hasGameCard = hasGameCard; + + _gameCardDeviceMutex = new SdkMutexType(); + _sdCardDeviceMutex = new SdkMutexType(); + } + + public void Dispose() + { + _sdCardDeviceManager.Destroy(); + _dummyGameCardDeviceManager.Destroy(); + _gameCardDeviceManager.Destroy(); + _mmcDeviceManager.Destroy(); + } + + public Result Create(ref SharedRef outDeviceManager, StorageDevicePortId portId) + { + switch (portId) + { + case StorageDevicePortId.Mmc: + EnsureMmcReady(); + outDeviceManager.SetByCopy(in _mmcDeviceManager); + break; + case StorageDevicePortId.SdCard: + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _sdCardDeviceMutex); + + if (!_sdCardDeviceManager.HasValue) + return ResultFs.StorageDeviceNotReady.Log(); + + outDeviceManager.SetByCopy(in _sdCardDeviceManager); + break; + } + case StorageDevicePortId.GameCard: + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _gameCardDeviceMutex); + + if (!_dummyGameCardDeviceManager.HasValue && !_gameCardDeviceManager.HasValue) + return ResultFs.StorageDeviceNotReady.Log(); + + if (_hasGameCard) + { + outDeviceManager.SetByCopy(in _gameCardDeviceManager); + } + else + { + outDeviceManager.SetByCopy(in _dummyGameCardDeviceManager); + } + + break; + } + default: + return ResultFs.StorageDeviceInvalidOperation.Log(); + } + + return Result.Success; + } + + public Result SetReady(StorageDevicePortId portId, NativeHandle handle) + { + switch (portId) + { + case StorageDevicePortId.Mmc: + EnsureMmcReady(); + break; + case StorageDevicePortId.SdCard: + EnsureSdCardReady(); + break; + case StorageDevicePortId.GameCard: + EnsureGameCardReady(); + break; + default: + return ResultFs.StorageDeviceInvalidOperation.Log(); + } + + return Result.Success; + } + + public Result UnsetReady(StorageDevicePortId portId) + { + return ResultFs.StorageDeviceInvalidOperation.Log(); + } + + private void EnsureMmcReady() + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mmcDeviceMutex); + + if (!_mmcDeviceManager.HasValue) + { + _mmcDeviceManager.Reset(new MmcManager()); + } + } + + private void EnsureSdCardReady() + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _sdCardDeviceMutex); + + if (!_sdCardDeviceManager.HasValue) + { + _sdCardDeviceManager.Reset(new SdCardManager()); + + // Todo: BuiltInStorageFileSystemCreator::SetSdCardPortReady + } + } + + private void EnsureGameCardReady() + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _gameCardDeviceMutex); + + if (!_dummyGameCardDeviceManager.HasValue && !_gameCardDeviceManager.HasValue) + { + if (_hasGameCard) + { + using SharedRef manger = GameCardManager.CreateShared(_fsServer); + _gameCardDeviceManager.SetByMove(ref manger.Ref); + } + else + { + _dummyGameCardDeviceManager.Reset(new DummyGameCardManager()); + } + } + } +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/Storage/Sf/IStorageDeviceManager.cs b/src/LibHac/FsSrv/Storage/Sf/IStorageDeviceManager.cs index 1564a270..39f69df6 100644 --- a/src/LibHac/FsSrv/Storage/Sf/IStorageDeviceManager.cs +++ b/src/LibHac/FsSrv/Storage/Sf/IStorageDeviceManager.cs @@ -8,8 +8,8 @@ namespace LibHac.FsSrv.Storage.Sf; public interface IStorageDeviceManager : IDisposable { Result IsInserted(out bool isInserted); - Result IsHandleValid(out bool isValid, uint handle); - Result OpenDetectionEvent(ref SharedRef outEventNotifier); + Result IsHandleValid(out bool isValid, GameCardHandle handle); + Result OpenDetectionEvent(ref SharedRef outDetectionEvent); Result OpenOperator(ref SharedRef outDeviceOperator); Result OpenDevice(ref SharedRef outStorageDevice, ulong attribute); Result OpenStorage(ref SharedRef outStorage, ulong attribute); diff --git a/src/LibHac/FsSrv/Storage/StorageDeviceManagerFactory.cs b/src/LibHac/FsSrv/Storage/StorageDeviceManagerFactory.cs index d19650c5..13684b10 100644 --- a/src/LibHac/FsSrv/Storage/StorageDeviceManagerFactory.cs +++ b/src/LibHac/FsSrv/Storage/StorageDeviceManagerFactory.cs @@ -33,7 +33,7 @@ internal static class StorageDeviceManagerFactory IStorageDeviceManagerFactory factory = storage.GetStorageDeviceManagerFactory(null); Assert.SdkNotNull(factory); - return factory.Create(ref outDeviceManager, portId); + return factory.Create(ref outDeviceManager, portId).Ret(); } public static IStorageDeviceManagerFactory GetStorageDeviceManagerFactory(this StorageService storage, diff --git a/src/LibHac/FsSystem/CardDeviceDetectionEvent.cs b/src/LibHac/FsSystem/CardDeviceDetectionEvent.cs new file mode 100644 index 00000000..df86e661 --- /dev/null +++ b/src/LibHac/FsSystem/CardDeviceDetectionEvent.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using LibHac.Common; +using LibHac.FsSrv.Sf; +using LibHac.Os; +using LibHac.Sf; + +namespace LibHac.FsSystem; + +internal class CardDeviceDetectionEventManager : IDisposable +{ + private LinkedList _events; + private SdkMutex _mutex; + protected CallbackArguments CallbackArgs; + + protected class CallbackArguments + { + public CardDeviceDetectionEventManager EventManager; + public SdkMutex Mutex; + public Sdmmc.Port Port; + } + + public CardDeviceDetectionEventManager() + { + _events = new LinkedList(); + _mutex = new SdkMutex(); + + CallbackArgs = new CallbackArguments { EventManager = this, Mutex = _mutex }; + } + + public virtual void Dispose() + { + throw new NotImplementedException(); + } + + public Result CreateDetectionEvent(ref SharedRef outDetectionEvent) + { + throw new NotImplementedException(); + } + + public void Unlink(CardDeviceDetectionEvent detectionEvent) + { + throw new NotImplementedException(); + } + + public void SignalAll() + { + throw new NotImplementedException(); + } + + protected static void DetectionEventCallback(object args) + { + throw new NotImplementedException(); + } +} + +internal class CardDeviceDetectionEvent : IEventNotifier +{ + private CardDeviceDetectionEventManager _eventManager; + // Todo: SystemEvent + + public CardDeviceDetectionEvent(CardDeviceDetectionEventManager eventManager) + { + _eventManager = eventManager; + } + + public void Dispose() + { + throw new NotImplementedException(); + } + + public Result GetEventHandle(out NativeHandle handle) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/LibHac/Gc/GameCardDummy.cs b/src/LibHac/Gc/GameCardDummy.cs new file mode 100644 index 00000000..3c21c6ef --- /dev/null +++ b/src/LibHac/Gc/GameCardDummy.cs @@ -0,0 +1,200 @@ +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/Impl/GameCardImplTypes.cs b/src/LibHac/Gc/Impl/GameCardImplTypes.cs index fd2d159d..377a7ce8 100644 --- a/src/LibHac/Gc/Impl/GameCardImplTypes.cs +++ b/src/LibHac/Gc/Impl/GameCardImplTypes.cs @@ -22,6 +22,11 @@ public struct CardId3 public Array4 Reserved; } +public struct DevCardParameter +{ + public Array512 Data; +} + public struct CardInitialDataPayload { public Array8 PackageId; diff --git a/src/LibHac/Gc/Writer/WriterTypes.cs b/src/LibHac/Gc/Writer/WriterTypes.cs new file mode 100644 index 00000000..7b232fd0 --- /dev/null +++ b/src/LibHac/Gc/Writer/WriterTypes.cs @@ -0,0 +1,19 @@ +namespace LibHac.Gc.Writer; + +public enum AsicMode : byte +{ + Read = 0, + Write = 1 +} + +public enum MemorySize +{ + // ReSharper disable InconsistentNaming + Size1GB = 1, + Size2GB = 2, + Size4GB = 4, + Size8GB = 8, + Size16GB = 16, + Size32GB = 32 + // ReSharper restore InconsistentNaming +} \ No newline at end of file diff --git a/src/LibHac/GcSrv/DummyGameCardManager.cs b/src/LibHac/GcSrv/DummyGameCardManager.cs new file mode 100644 index 00000000..46d5e63a --- /dev/null +++ b/src/LibHac/GcSrv/DummyGameCardManager.cs @@ -0,0 +1,189 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Fs; +using LibHac.FsSrv.Sf; +using LibHac.FsSrv.Storage.Sf; +using LibHac.Sf; +using IStorageSf = LibHac.FsSrv.Sf.IStorage; + +namespace LibHac.GcSrv; + +public class DummyGameCardManager : IStorageDeviceManager, IStorageDeviceOperator +{ + public DummyGameCardManager() + { + throw new NotImplementedException(); + } + + public void Dispose() + { + throw new NotImplementedException(); + } + + public Result IsInserted(out bool isInserted) + { + isInserted = false; + return Result.Success; + } + + public Result IsHandleValid(out bool isValid, uint handle) + { + isValid = false; + return Result.Success; + } + + public Result OpenDetectionEvent(ref SharedRef outDetectionEvent) + { + throw new NotImplementedException(); + } + + public Result OpenOperator(ref SharedRef outDeviceOperator) + { + throw new NotImplementedException(); + } + + public Result OpenDevice(ref SharedRef outStorageDevice, ulong attribute) + { + return ResultFs.GameCardCardNotInserted.Log(); + } + + public Result OpenStorage(ref SharedRef outStorage, ulong attribute) + { + return ResultFs.GameCardCardNotInserted.Log(); + } + + public Result Invalidate() + { + return Result.Success; + } + + public Result Operate(int operationId) + { + var operation = (GameCardManagerOperationIdValue)operationId; + + switch (operation) + { + case GameCardManagerOperationIdValue.Finalize: + return Result.Success; + + case GameCardManagerOperationIdValue.GetInitializationResult: + return Result.Success; + + case GameCardManagerOperationIdValue.ForceErase: + return ResultFs.GameCardCardNotInserted.Log(); + + case GameCardManagerOperationIdValue.SimulateDetectionEventSignaled: + return ResultFs.GameCardNotSupportedOnDeviceModel.Log(); + + default: + return ResultFs.InvalidArgument.Log(); + } + } + + public Result OperateIn(InBuffer buffer, long offset, long size, int operationId) + { + var operation = (GameCardManagerOperationIdValue)operationId; + + switch (operation) + { + case GameCardManagerOperationIdValue.SetVerifyEnableFlag: + return Result.Success; + + case GameCardManagerOperationIdValue.EraseAndWriteParamDirectly: + return ResultFs.GameCardCardNotInserted.Log(); + + default: + return ResultFs.InvalidArgument.Log(); + } + } + + public Result OperateOut(out long bytesWritten, OutBuffer buffer, int operationId) + { + var operation = (GameCardManagerOperationIdValue)operationId; + bytesWritten = 0; + + switch (operation) + { + case GameCardManagerOperationIdValue.GetHandle: + { + return ResultFs.GameCardCardNotInserted.Log(); + } + case GameCardManagerOperationIdValue.GetGameCardErrorInfo: + { + if (buffer.Size < Unsafe.SizeOf()) + return ResultFs.InvalidArgument.Log(); + + buffer.As() = default; + bytesWritten = Unsafe.SizeOf(); + + return Result.Success; + } + case GameCardManagerOperationIdValue.GetGameCardErrorReportInfo: + { + if (buffer.Size < Unsafe.SizeOf()) + return ResultFs.InvalidArgument.Log(); + + buffer.As() = default; + bytesWritten = Unsafe.SizeOf(); + + return Result.Success; + } + case GameCardManagerOperationIdValue.ReadParamDirectly: + { + return ResultFs.GameCardCardNotInserted.Log(); + } + default: + return ResultFs.InvalidArgument.Log(); + } + } + + public Result OperateOut2(out long bytesWrittenBuffer1, OutBuffer buffer1, out long bytesWrittenBuffer2, + OutBuffer buffer2, int operationId) + { + bytesWrittenBuffer1 = 0; + bytesWrittenBuffer2 = 0; + + return ResultFs.NotImplemented.Log(); + } + + public Result OperateInOut(out long bytesWritten, OutBuffer outBuffer, InBuffer inBuffer, long offset, long size, + int operationId) + { + var operation = (GameCardManagerOperationIdValue)operationId; + bytesWritten = 0; + + switch (operation) + { + case GameCardManagerOperationIdValue.IsGameCardActivationValid: + { + if (outBuffer.Size < Unsafe.SizeOf()) + return ResultFs.InvalidArgument.Log(); + + outBuffer.As() = false; + bytesWritten = sizeof(bool); + + return Result.Success; + } + case GameCardManagerOperationIdValue.GetGameCardAsicInfo: + return ResultFs.GameCardAccessFailed.Log(); + + case GameCardManagerOperationIdValue.GetGameCardDeviceIdForProdCard: + return ResultFs.GameCardCardNotInserted.Log(); + + case GameCardManagerOperationIdValue.WriteToGameCardDirectly: + return ResultFs.GameCardCardNotInserted.Log(); + + default: + return ResultFs.InvalidArgument.Log(); + } + } + + public Result OperateIn2Out(out long bytesWritten, OutBuffer outBuffer, InBuffer inBuffer1, InBuffer inBuffer2, + long offset, long size, int operationId) + { + bytesWritten = 0; + + return ResultFs.NotImplemented.Log(); + } +} \ No newline at end of file diff --git a/src/LibHac/GcSrv/GameCardDeviceDetectionEventManager.cs b/src/LibHac/GcSrv/GameCardDeviceDetectionEventManager.cs new file mode 100644 index 00000000..48cddc3d --- /dev/null +++ b/src/LibHac/GcSrv/GameCardDeviceDetectionEventManager.cs @@ -0,0 +1,17 @@ +using System; +using LibHac.FsSystem; + +namespace LibHac.GcSrv; + +internal class GameCardDeviceDetectionEventManager : CardDeviceDetectionEventManager +{ + public GameCardDeviceDetectionEventManager() + { + throw new NotImplementedException(); + } + + public override void Dispose() + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/LibHac/GcSrv/GameCardManager.cs b/src/LibHac/GcSrv/GameCardManager.cs new file mode 100644 index 00000000..846b5525 --- /dev/null +++ b/src/LibHac/GcSrv/GameCardManager.cs @@ -0,0 +1,897 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Diag; +using LibHac.Fs; +using LibHac.FsSrv; +using LibHac.FsSrv.Sf; +using LibHac.FsSrv.Storage.Sf; +using LibHac.FsSystem; +using LibHac.Gc; +using LibHac.Gc.Impl; +using LibHac.Gc.Writer; +using LibHac.Os; +using LibHac.Sf; +using IStorage = LibHac.FsSrv.Sf.IStorage; + +namespace LibHac.GcSrv; + +public class GameCardManager : IStorageDeviceManager, IStorageDeviceOperator, IGameCardDeviceManager +{ + private enum CardState + { + Initial = 0, + Normal = 1, + Secure = 2, + Write = 3 + } + + private ReaderWriterLock _rwLock; + private bool _isInitialized; + private bool _isFinalized; + private CardState _state; + private GameCardHandle _currentHandle; + private GameCardDeviceDetectionEventManager _detectionEventManager; + + // LibHac additions + private WeakRef _selfReference; + private readonly FileSystemServer _fsServer; + private readonly GameCardDummy _gc; + + private GameCardManager(FileSystemServer fsServer) + { + _rwLock = new ReaderWriterLock(fsServer.Hos.Os); + + _fsServer = fsServer; + } + + public static SharedRef CreateShared(FileSystemServer fsServer) + { + var manager = new GameCardManager(fsServer); + + using var sharedManager = new SharedRef(manager); + manager._selfReference.Set(in sharedManager); + + return SharedRef.CreateMove(ref sharedManager.Ref); + } + + public void Dispose() + { + _detectionEventManager?.Dispose(); + _detectionEventManager = null; + + _rwLock?.Dispose(); + _rwLock = null; + } + + private uint BytesToPages(long byteCount) + { + return (uint)((ulong)byteCount / (ulong)Values.GcPageSize); + } + + private void DeactivateAndChangeState() + { + _gc.Deactivate(); + _currentHandle++; + _state = CardState.Initial; + } + + private void CheckGameCardAndDeactivate() + { + if (_state != CardState.Initial && !_gc.IsCardActivationValid()) + { + DeactivateAndChangeState(); + } + } + + private Result ActivateGameCard() + { + Result res = HandleGameCardAccessResult(_gc.Activate()); + if (res.IsFailure()) return res.Miss(); + + return Result.Success; + } + + private Result ActivateGameCardForWriter() + { + return HandleGameCardAccessResult(_gc.Writer.ActivateForWriter()); + } + + private Result SetGameCardToSecureMode() + { + return HandleGameCardAccessResult(_gc.SetCardToSecureMode()); + } + + public Result IsInserted(out bool isInserted) + { + UnsafeHelpers.SkipParamInit(out isInserted); + + Result res = InitializeGcLibrary(); + if (res.IsFailure()) return res.Miss(); + + isInserted = _gc.IsCardInserted(); + + return Result.Success; + } + + public Result InitializeGcLibrary() + { + using var writeLock = new UniqueLock(_rwLock); + + if (_isFinalized) + return ResultFs.PreconditionViolation.Log(); + + if (_isInitialized) + return Result.Success; + + // Missing: Wait on settings-ready event + // Missing: Allocate work buffer and pass it to nn::gc::Initialize + _gc.Initialize(default, default); + // Missing: Register the device buffer + + _detectionEventManager = new GameCardDeviceDetectionEventManager(); + _isInitialized = true; + + return Result.Success; + } + + private Result EnsureGameCardNormalMode(out GameCardHandle outNewHandle) + { + UnsafeHelpers.SkipParamInit(out outNewHandle); + + if (_state == CardState.Normal) + CheckGameCardAndDeactivate(); + + switch (_state) + { + case CardState.Initial: + { + // Initial -> Normal + Result res = ActivateGameCard(); + if (res.IsFailure()) return res.Miss(); + _state = CardState.Normal; + + break; + } + case CardState.Normal: + { + outNewHandle = _currentHandle; + return Result.Success; + } + case CardState.Secure: + { + // Secure -> Initial -> Normal + DeactivateAndChangeState(); + + Result res = ActivateGameCard(); + if (res.IsFailure()) return res.Miss(); + _state = CardState.Normal; + + break; + } + case CardState.Write: + { + // Write -> Initial -> Normal + DeactivateAndChangeState(); + _gc.Writer.ChangeMode(AsicMode.Read); + + Result res = ActivateGameCard(); + if (res.IsFailure()) return res.Miss(); + _state = CardState.Normal; + + break; + } + default: + Abort.UnexpectedDefault(); + break; + } + + outNewHandle = _currentHandle; + return Result.Success; + } + + private Result EnsureGameCardSecureMode(out GameCardHandle outNewHandle) + { + UnsafeHelpers.SkipParamInit(out outNewHandle); + + if (_state == CardState.Secure) + CheckGameCardAndDeactivate(); + + switch (_state) + { + case CardState.Initial: + { + // Initial -> Normal -> Secure + Result res = ActivateGameCard(); + if (res.IsFailure()) return res.Miss(); + _state = CardState.Normal; + + res = SetGameCardToSecureMode(); + if (res.IsFailure()) return res.Miss(); + _state = CardState.Secure; + + break; + } + case CardState.Normal: + { + // Normal -> Secure + Result res = SetGameCardToSecureMode(); + if (res.IsFailure()) return res.Miss(); + _state = CardState.Secure; + + break; + } + case CardState.Secure: + { + outNewHandle = _currentHandle; + return Result.Success; + } + case CardState.Write: + { + // Write -> Initial -> Normal -> Secure + DeactivateAndChangeState(); + _gc.Writer.ChangeMode(AsicMode.Read); + + Result res = ActivateGameCard(); + if (res.IsFailure()) return res.Miss(); + _state = CardState.Normal; + + res = SetGameCardToSecureMode(); + if (res.IsFailure()) return res.Miss(); + _state = CardState.Secure; + + break; + } + default: + Abort.UnexpectedDefault(); + break; + } + + outNewHandle = _currentHandle; + return Result.Success; + } + + private Result EnsureGameCardWriteMode(out GameCardHandle outNewHandle) + { + UnsafeHelpers.SkipParamInit(out outNewHandle); + + switch (_state) + { + case CardState.Initial: + { + // Initial -> Write + _gc.Writer.ChangeMode(AsicMode.Write); + Result res = ActivateGameCardForWriter(); + if (res.IsFailure()) return res.Miss(); + _state = CardState.Write; + + break; + } + case CardState.Normal: + case CardState.Secure: + { + // Normal/Secure -> Initial -> Write + DeactivateAndChangeState(); + + _gc.Writer.ChangeMode(AsicMode.Write); + Result res = ActivateGameCardForWriter(); + if (res.IsFailure()) return res.Miss(); + _state = CardState.Write; + + break; + } + case CardState.Write: + { + outNewHandle = _currentHandle; + return Result.Success; + } + default: + Abort.UnexpectedDefault(); + break; + } + + outNewHandle = _currentHandle; + return Result.Success; + } + + public Result IsHandleValid(out bool isValid, GameCardHandle handle) + { + UnsafeHelpers.SkipParamInit(out isValid); + + Result res = InitializeGcLibrary(); + if (res.IsFailure()) return res.Miss(); + + using var readLock = new SharedLock(); + isValid = AcquireReadLock(ref readLock.Ref(), handle).IsSuccess(); + + return Result.Success; + } + + public Result OpenDetectionEvent(ref SharedRef outDetectionEvent) + { + Result res = InitializeGcLibrary(); + if (res.IsFailure()) return res.Miss(); + + res = _detectionEventManager.CreateDetectionEvent(ref outDetectionEvent); + if (res.IsFailure()) return res.Miss(); + + return Result.Success; + } + + public Result OpenOperator(ref SharedRef outDeviceOperator) + { + throw new NotImplementedException(); + } + + public Result OpenDevice(ref SharedRef outStorageDevice, ulong attribute) + { + throw new NotImplementedException(); + } + + public Result OpenStorage(ref SharedRef outStorage, ulong attribute) + { + throw new NotImplementedException(); + } + + public Result PutToSleep() + { + using var writeLock = new UniqueLock(_rwLock); + _gc.PutToSleep(); + + return Result.Success; + } + + public Result Awaken() + { + using var writeLock = new UniqueLock(_rwLock); + _gc.Awaken(); + + return Result.Success; + } + + public Result Shutdown() + { + using var writeLock = new UniqueLock(_rwLock); + _gc.PutToSleep(); + + return Result.Success; + } + + public Result Invalidate() + { + using var writeLock = new UniqueLock(_rwLock); + DeactivateAndChangeState(); + + return Result.Success; + } + + public Result Operate(int operationId) + { + var operation = (GameCardManagerOperationIdValue)operationId; + + Result res = InitializeGcLibrary(); + if (res.IsFailure()) return res.Miss(); + + switch (operation) + { + case GameCardManagerOperationIdValue.Finalize: + FinalizeGcLibrary(); + return Result.Success; + + case GameCardManagerOperationIdValue.GetInitializationResult: + return GetInitializationResult().Ret(); + + case GameCardManagerOperationIdValue.ForceErase: + return ForceEraseGameCard().Ret(); + + case GameCardManagerOperationIdValue.SimulateDetectionEventSignaled: + _detectionEventManager.SignalAll(); + return Result.Success; + + default: + return ResultFs.InvalidArgument.Log(); + } + } + + public Result OperateIn(InBuffer buffer, long offset, long size, int operationId) + { + var operation = (GameCardManagerOperationIdValue)operationId; + + Result res = InitializeGcLibrary(); + if (res.IsFailure()) return res.Miss(); + + switch (operation) + { + case GameCardManagerOperationIdValue.SetVerifyEnableFlag: + if (buffer.Size < sizeof(bool)) + return ResultFs.InvalidArgument.Log(); + + SetVerifyEnableFlag(buffer.As()); + return Result.Success; + + case GameCardManagerOperationIdValue.EraseAndWriteParamDirectly: + if (buffer.Size < Unsafe.SizeOf()) + return ResultFs.InvalidArgument.Log(); + + res = EraseAndWriteParamDirectly(buffer.Buffer); + if (res.IsFailure()) return res.Miss(); + + return Result.Success; + + default: + return ResultFs.InvalidArgument.Log(); + } + } + + public Result OperateOut(out long bytesWritten, OutBuffer buffer, int operationId) + { + var operation = (GameCardManagerOperationIdValue)operationId; + bytesWritten = 0; + + Result res = InitializeGcLibrary(); + if (res.IsFailure()) return res.Miss(); + + switch (operation) + { + case GameCardManagerOperationIdValue.GetHandle: + { + using var writeLock = new UniqueLock(_rwLock); + + if (buffer.Size < sizeof(GameCardHandle)) + return ResultFs.InvalidArgument.Log(); + + res = GetHandle(out buffer.As()); + if (res.IsFailure()) return res.Miss(); + + bytesWritten = sizeof(GameCardHandle); + return Result.Success; + } + case GameCardManagerOperationIdValue.GetGameCardErrorInfo: + if (buffer.Size < Unsafe.SizeOf()) + return ResultFs.InvalidArgument.Log(); + + res = GetGameCardErrorInfo(out buffer.As()); + if (res.IsFailure()) return res.Miss(); + + bytesWritten = Unsafe.SizeOf(); + return Result.Success; + + case GameCardManagerOperationIdValue.GetGameCardErrorReportInfo: + if (buffer.Size < Unsafe.SizeOf()) + return ResultFs.InvalidArgument.Log(); + + res = GetGameCardErrorReportInfo(out buffer.As()); + if (res.IsFailure()) return res.Miss(); + + bytesWritten = Unsafe.SizeOf(); + return Result.Success; + + case GameCardManagerOperationIdValue.ReadParamDirectly: + if (buffer.Size < Unsafe.SizeOf()) + return ResultFs.InvalidArgument.Log(); + + res = ReadParamDirectly(buffer.Buffer); + if (res.IsFailure()) return res.Miss(); + + bytesWritten = Unsafe.SizeOf(); + return Result.Success; + + default: + return ResultFs.InvalidArgument.Log(); + } + } + + public Result OperateOut2(out long bytesWrittenBuffer1, OutBuffer buffer1, out long bytesWrittenBuffer2, + OutBuffer buffer2, int operationId) + { + UnsafeHelpers.SkipParamInit(out bytesWrittenBuffer1, out bytesWrittenBuffer2); + + return ResultFs.NotImplemented.Log(); + } + + public Result OperateInOut(out long bytesWritten, OutBuffer outBuffer, InBuffer inBuffer, long offset, long size, + int operationId) + { + var operation = (GameCardManagerOperationIdValue)operationId; + bytesWritten = 0; + + Result res = InitializeGcLibrary(); + if (res.IsFailure()) return res.Miss(); + + switch (operation) + { + case GameCardManagerOperationIdValue.IsGameCardActivationValid: + if (inBuffer.Size != sizeof(GameCardHandle)) + return ResultFs.InvalidArgument.Log(); + + if (outBuffer.Size < sizeof(bool)) + return ResultFs.InvalidArgument.Log(); + + outBuffer.As() = IsGameCardActivationValid(inBuffer.As()); + bytesWritten = sizeof(bool); + + return Result.Success; + + case GameCardManagerOperationIdValue.GetGameCardAsicInfo: + if (inBuffer.Size != Values.GcAsicFirmwareSize) + return ResultFs.InvalidArgument.Log(); + + if (outBuffer.Size < Unsafe.SizeOf()) + return ResultFs.InvalidArgument.Log(); + + res = GetGameCardAsicInfo(out RmaInformation rmaInfo, inBuffer.Buffer); + if (res.IsFailure()) return res.Miss(); + + SpanHelpers.AsReadOnlyByteSpan(in rmaInfo).CopyTo(outBuffer.Buffer); + bytesWritten = Unsafe.SizeOf(); + + return Result.Success; + + case GameCardManagerOperationIdValue.GetGameCardDeviceIdForProdCard: + if (inBuffer.Size < Values.GcPageSize) + return ResultFs.InvalidArgument.Log(); + + if (outBuffer.Size < Values.GcPageSize) + return ResultFs.InvalidArgument.Log(); + + res = GetGameCardDeviceIdForProdCard(outBuffer.Buffer, inBuffer.Buffer); + if (res.IsFailure()) return res.Miss(); + + bytesWritten = Values.GcPageSize; + + return Result.Success; + + case GameCardManagerOperationIdValue.WriteToGameCardDirectly: + return WriteToGameCardDirectly(offset, outBuffer.Buffer.Slice(0, (int)size)).Ret(); + + default: + return ResultFs.InvalidArgument.Log(); + } + } + + public Result OperateIn2Out(out long bytesWritten, OutBuffer outBuffer, InBuffer inBuffer1, InBuffer inBuffer2, + long offset, long size, int operationId) + { + UnsafeHelpers.SkipParamInit(out bytesWritten); + + return ResultFs.NotImplemented.Log(); + } + + private void FinalizeGcLibrary() + { + using var writeLock = new UniqueLock(_rwLock); + + if (_isInitialized) + { + _gc.UnregisterDetectionEventCallback(); + _isFinalized = true; + _gc.FinalizeGc(); + // nn::gc::UnregisterDeviceVirtualAddress + } + } + + private bool IsGameCardActivationValid(GameCardHandle handle) + { + using var writeLock = new UniqueLock(_rwLock); + + return handle == _currentHandle && _gc.IsCardActivationValid(); + } + + private Result GetInitializationResult() + { + return _gc.GetInitializationResult(); + } + + private Result GetGameCardErrorInfo(out GameCardErrorInfo outErrorInfo) + { + outErrorInfo = default; + + Result res = _gc.GetErrorInfo(out GameCardErrorReportInfo errorInfo); + if (res.IsFailure()) return res.Miss(); + + outErrorInfo.GameCardCrcErrorCount = errorInfo.ErrorInfo.GameCardCrcErrorCount; + outErrorInfo.AsicCrcErrorCount = errorInfo.ErrorInfo.AsicCrcErrorCount; + outErrorInfo.RefreshCount = errorInfo.ErrorInfo.RefreshCount; + outErrorInfo.TimeoutRetryErrorCount = errorInfo.ErrorInfo.TimeoutRetryErrorCount; + outErrorInfo.ReadRetryCount = errorInfo.ErrorInfo.ReadRetryCount; + + return Result.Success; + } + + private Result GetGameCardErrorReportInfo(out GameCardErrorReportInfo outErrorInfo) + { + Result res = _gc.GetErrorInfo(out outErrorInfo); + if (res.IsFailure()) return res.Miss(); + + return Result.Success; + } + + private void SetVerifyEnableFlag(bool isEnabled) + { + _gc.Writer.SetVerifyEnableFlag(isEnabled); + } + + private Result GetGameCardAsicInfo(out RmaInformation outRmaInfo, ReadOnlySpan asicFirmwareBuffer) + { + UnsafeHelpers.SkipParamInit(out outRmaInfo); + + Assert.SdkRequiresEqual(asicFirmwareBuffer.Length, Values.GcAsicFirmwareSize); + + _gc.Writer.SetUserAsicFirmwareBuffer(asicFirmwareBuffer); + _gc.Writer.ChangeMode(AsicMode.Write); + + Result res = _gc.Writer.GetRmaInformation(out RmaInformation rmaInfo); + if (res.IsFailure()) return res.Miss(); + + outRmaInfo = rmaInfo; + return Result.Success; + } + + private Result GetGameCardDeviceIdForProdCard(Span outBuffer, ReadOnlySpan devHeaderBuffer) + { + Assert.SdkRequiresGreaterEqual(outBuffer.Length, Values.GcPageSize); + Assert.SdkRequiresGreaterEqual(devHeaderBuffer.Length, Values.GcPageSize); + + Result res = InitializeGcLibrary(); + if (res.IsFailure()) return res.Miss(); + + using var writeLock = new UniqueLock(_rwLock); + + int writeSize = Values.GcPageSize; + var pooledBuffer = new PooledBuffer(writeSize, writeSize); + Assert.SdkGreaterEqual(pooledBuffer.GetSize(), writeSize); + + // Read the current card header into a temporary buffer + _gc.Writer.ChangeMode(AsicMode.Read); + + Span tmpBuffer = stackalloc byte[writeSize]; + tmpBuffer.Clear(); + + _gc.GetCardHeader(pooledBuffer.GetBuffer()); + if (res.IsFailure()) return res.Miss(); + + pooledBuffer.GetBuffer().CopyTo(tmpBuffer); + + // Write the provided card header + _gc.Writer.ChangeMode(AsicMode.Write); + res = HandleGameCardAccessResult(_gc.Writer.ActivateForWriter()); + if (res.IsFailure()) return res.Miss(); + + devHeaderBuffer.CopyTo(pooledBuffer.GetBuffer()); + res = _gc.Writer.Write(pooledBuffer.GetBuffer(), 8, 1); + if (res.IsFailure()) return res.Miss(); + + // Read the cert area + _gc.Writer.ChangeMode(AsicMode.Read); + res = _gc.Activate(); + if (res.IsFailure()) return res.Miss(); + + res = _gc.Read(pooledBuffer.GetBuffer(), 0x38, 1); + if (res.IsFailure()) return res.Miss(); + + Span deviceCert = stackalloc byte[writeSize]; + pooledBuffer.GetBuffer().CopyTo(deviceCert); + + // Restore the original card header + _gc.Writer.ChangeMode(AsicMode.Write); + res = HandleGameCardAccessResult(_gc.Writer.ActivateForWriter()); + if (res.IsFailure()) return res.Miss(); + + tmpBuffer.CopyTo(pooledBuffer.GetBuffer()); + res = _gc.Writer.Write(pooledBuffer.GetBuffer(), 8, 1); + if (res.IsFailure()) return res.Miss(); + + deviceCert.CopyTo(outBuffer); + return Result.Success; + } + + private Result EraseAndWriteParamDirectly(ReadOnlySpan inBuffer) + { + Assert.SdkRequires(inBuffer.Length >= Unsafe.SizeOf()); + + Result res = InitializeGcLibrary(); + if (res.IsFailure()) return res.Miss(); + + using var writeLock = new UniqueLock(_rwLock); + + var devCardParam = SpanHelpers.AsReadOnlyStruct(inBuffer); + return _gc.Writer.WriteDevCardParam(in devCardParam).Ret(); + } + + private Result ReadParamDirectly(Span outBuffer) + { + Result res = InitializeGcLibrary(); + if (res.IsFailure()) return res.Miss(); + + using var writeLock = new UniqueLock(_rwLock); + + res = _gc.Writer.ReadDevCardParam(out DevCardParameter devCardParam); + if (res.IsFailure()) return res.Miss(); + + SpanHelpers.AsReadOnlyByteSpan(in devCardParam).CopyTo(outBuffer); + return Result.Success; + } + + private Result WriteToGameCardDirectly(long offset, Span buffer) + { + Result res; + + using (new SharedLock(_rwLock)) + { + if (buffer.Length == 0) + return Result.Success; + + res = _gc.Writer.Write(buffer, BytesToPages(offset), BytesToPages(buffer.Length)); + } + + if (res != Result.Success) + { + using var writeLock = new UniqueLock(_rwLock); + res = HandleGameCardAccessResult(res); + } + + if (res.IsFailure()) return res.Miss(); + + return Result.Success; + } + + private Result ForceEraseGameCard() + { + Result res = InitializeGcLibrary(); + if (res.IsFailure()) return res.Miss(); + + using var writeLock = new UniqueLock(_rwLock); + + _gc.Writer.ChangeMode(AsicMode.Write); + res = _gc.Writer.ForceErase(); + if (res.IsFailure()) return res.Miss(); + + return Result.Success; + } + + public Result AcquireReadLock(ref SharedLock outLock, GameCardHandle handle) + { + using var readLock = new SharedLock(_rwLock); + + if (_state != CardState.Initial && !_gc.IsCardActivationValid()) + { + readLock.Unlock(); + Invalidate().IgnoreResult(); + + return ResultFs.GameCardFsCheckHandleInAcquireReadLock.Log(); + } + + if (_currentHandle != handle) + return ResultFs.GameCardFsCheckHandleInAcquireReadLock.Log(); + + outLock.Set(ref readLock.Ref()); + return Result.Success; + } + + public Result AcquireSecureLock(ref SharedLock outLock, ref GameCardHandle handle, + ReadOnlySpan cardDeviceId, ReadOnlySpan cardImageHash) + { + using (var readLock = new SharedLock(_rwLock)) + { + if (!IsSecureMode()) + { + return ResultFs.GameCardFsCheckModeInAcquireSecureLock.Log(); + } + + if (_state != CardState.Initial && !_gc.IsCardActivationValid()) + { + readLock.Unlock(); + Invalidate().IgnoreResult(); + } + else if (_currentHandle == handle) + { + outLock.Set(ref readLock.Ref()); + return Result.Success; + } + } + + GameCardHandle newHandle; + + using (new UniqueLock(_rwLock)) + { + if (!IsSecureMode()) + { + return ResultFs.GameCardFsCheckModeInAcquireSecureLock.Log(); + } + + Span currentCardDeviceId = stackalloc byte[Values.GcCardDeviceIdSize]; + Span currentCardImageHash = stackalloc byte[Values.GcCardImageHashSize]; + + Result res = HandleGameCardAccessResult(_gc.GetCardDeviceId(currentCardDeviceId)); + if (res.IsFailure()) return res.Miss(); + + res = HandleGameCardAccessResult(_gc.GetCardImageHash(currentCardImageHash)); + if (res.IsFailure()) return res.Miss(); + + if (!Crypto.CryptoUtil.IsSameBytes(currentCardDeviceId, cardDeviceId, Values.GcCardDeviceIdSize) || + !Crypto.CryptoUtil.IsSameBytes(currentCardImageHash, cardImageHash, Values.GcCardImageHashSize)) + return ResultFs.GameCardFsCheckModeInAcquireSecureLock.Log(); + + res = GetHandle(out newHandle); + if (res.IsFailure()) return res.Miss(); + } + + using (var readLock = new SharedLock()) + { + Result res = AcquireReadLock(ref readLock.Ref(), newHandle); + if (res.IsFailure()) return res.Miss(); + + handle = newHandle; + outLock.Set(ref readLock.Ref()); + + return Result.Success; + } + } + + public Result AcquireWriteLock(ref UniqueLock outLock) + { + Result res = InitializeGcLibrary(); + if (res.IsFailure()) return res.Miss(); + + using var writeLock = new UniqueLock(_rwLock); + outLock.Set(ref writeLock.Ref()); + + return Result.Success; + } + + public Result HandleGameCardAccessResult(Result result) + { + if (result.IsFailure()) + { + DeactivateAndChangeState(); + } + + return result; + } + + public Result GetHandle(out GameCardHandle outHandle) + { + UnsafeHelpers.SkipParamInit(out outHandle); + + if (_state == CardState.Normal || _state == CardState.Secure) + { + CheckGameCardAndDeactivate(); + } + + switch (_state) + { + case CardState.Initial: + { + Result res = ActivateGameCard(); + if (res.IsFailure()) return res.Miss(); + + break; + } + case CardState.Normal: + case CardState.Secure: + break; + case CardState.Write: + { + DeactivateAndChangeState(); + _gc.Writer.ChangeMode(AsicMode.Read); + + Result res = ActivateGameCard(); + if (res.IsFailure()) return res.Miss(); + + _state = CardState.Normal; + break; + } + default: + Abort.UnexpectedDefault(); + break; + } + + outHandle = _currentHandle; + return Result.Success; + } + + public bool IsSecureMode() + { + return _state == CardState.Secure; + } +} \ No newline at end of file diff --git a/src/LibHac/GcSrv/IGameCardDeviceManager.cs b/src/LibHac/GcSrv/IGameCardDeviceManager.cs index 99479d73..446fb562 100644 --- a/src/LibHac/GcSrv/IGameCardDeviceManager.cs +++ b/src/LibHac/GcSrv/IGameCardDeviceManager.cs @@ -1,14 +1,14 @@ using System; -using LibHac.Fs.Impl; +using LibHac.Os; namespace LibHac.GcSrv; internal interface IGameCardDeviceManager { - Result AcquireReadLock(out UniqueLock locker, uint handle); - Result AcquireReadLockSecureMode(out UniqueLock locker, ref uint handle, ReadOnlySpan cardDeviceId, ReadOnlySpan cardImageHash); - Result AcquireWriteLock(out SharedLock locker); + Result AcquireReadLock(ref SharedLock outLock, GameCardHandle handle); + Result AcquireSecureLock(ref SharedLock outLock, ref GameCardHandle handle, ReadOnlySpan cardDeviceId, ReadOnlySpan cardImageHash); + Result AcquireWriteLock(ref UniqueLock outLock); Result HandleGameCardAccessResult(Result result); - Result GetHandle(out uint handle); + Result GetHandle(out GameCardHandle outHandle); bool IsSecureMode(); } \ No newline at end of file diff --git a/src/LibHac/HorizonFactory.cs b/src/LibHac/HorizonFactory.cs index 02085a0b..5a58d460 100644 --- a/src/LibHac/HorizonFactory.cs +++ b/src/LibHac/HorizonFactory.cs @@ -24,9 +24,9 @@ public static class HorizonFactory var fsServerConfig = new FileSystemServerConfig { - DeviceOperator = defaultObjects.DeviceOperator, ExternalKeySet = keySet.ExternalKeySet, FsCreators = defaultObjects.FsCreators, + StorageDeviceManagerFactory = defaultObjects.StorageDeviceManagerFactory, RandomGenerator = randomGenerator }; diff --git a/src/LibHac/SdmmcSrv/MmcManager.cs b/src/LibHac/SdmmcSrv/MmcManager.cs new file mode 100644 index 00000000..1505eb0b --- /dev/null +++ b/src/LibHac/SdmmcSrv/MmcManager.cs @@ -0,0 +1,54 @@ +using System; +using LibHac.Common; +using LibHac.FsSrv.Sf; +using LibHac.FsSrv.Storage.Sf; + +namespace LibHac.SdmmcSrv; + +public class MmcManager : IStorageDeviceManager +{ + public MmcManager() + { + throw new NotImplementedException(); + } + + public void Dispose() + { + throw new NotImplementedException(); + } + + public Result IsInserted(out bool isInserted) + { + throw new NotImplementedException(); + } + + public Result IsHandleValid(out bool isValid, uint handle) + { + throw new NotImplementedException(); + } + + public Result OpenDetectionEvent(ref SharedRef outDetectionEvent) + { + throw new NotImplementedException(); + } + + public Result OpenOperator(ref SharedRef outDeviceOperator) + { + throw new NotImplementedException(); + } + + public Result OpenDevice(ref SharedRef outStorageDevice, ulong attribute) + { + throw new NotImplementedException(); + } + + public Result OpenStorage(ref SharedRef outStorage, ulong attribute) + { + throw new NotImplementedException(); + } + + public Result Invalidate() + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/LibHac/SdmmcSrv/SdCardDeviceDetectionEvent.cs b/src/LibHac/SdmmcSrv/SdCardDeviceDetectionEvent.cs new file mode 100644 index 00000000..46c02f50 --- /dev/null +++ b/src/LibHac/SdmmcSrv/SdCardDeviceDetectionEvent.cs @@ -0,0 +1,19 @@ +using System; +using LibHac.FsSystem; + +namespace LibHac.SdmmcSrv; + +internal class SdCardDeviceDetectionEventManager : CardDeviceDetectionEventManager +{ + public SdCardDeviceDetectionEventManager(Sdmmc.Port port) + { + CallbackArgs.Port = port; + + throw new NotImplementedException(); + } + + public override void Dispose() + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/LibHac/SdmmcSrv/SdCardManager.cs b/src/LibHac/SdmmcSrv/SdCardManager.cs new file mode 100644 index 00000000..fc313aba --- /dev/null +++ b/src/LibHac/SdmmcSrv/SdCardManager.cs @@ -0,0 +1,54 @@ +using System; +using LibHac.Common; +using LibHac.FsSrv.Sf; +using LibHac.FsSrv.Storage.Sf; + +namespace LibHac.SdmmcSrv; + +public class SdCardManager : IStorageDeviceManager +{ + public SdCardManager() + { + + } + + public void Dispose() + { + throw new NotImplementedException(); + } + + public Result IsInserted(out bool isInserted) + { + throw new NotImplementedException(); + } + + public Result IsHandleValid(out bool isValid, uint handle) + { + throw new NotImplementedException(); + } + + public Result OpenDetectionEvent(ref SharedRef outDetectionEvent) + { + throw new NotImplementedException(); + } + + public Result OpenOperator(ref SharedRef outDeviceOperator) + { + throw new NotImplementedException(); + } + + public Result OpenDevice(ref SharedRef outStorageDevice, ulong attribute) + { + throw new NotImplementedException(); + } + + public Result OpenStorage(ref SharedRef outStorage, ulong attribute) + { + throw new NotImplementedException(); + } + + public Result Invalidate() + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/src/LibHac/Sf/Buffers.cs b/src/LibHac/Sf/Buffers.cs index 7a0ac8c3..78095c73 100644 --- a/src/LibHac/Sf/Buffers.cs +++ b/src/LibHac/Sf/Buffers.cs @@ -17,6 +17,11 @@ public readonly ref struct InBuffer _buffer = buffer; } + public ref readonly T As() where T : unmanaged + { + return ref SpanHelpers.AsReadOnlyStruct(_buffer); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static InBuffer FromSpan(ReadOnlySpan buffer) where T : unmanaged { @@ -42,6 +47,11 @@ public readonly ref struct OutBuffer _buffer = buffer; } + public ref T As() where T : unmanaged + { + return ref SpanHelpers.AsStruct(_buffer); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static OutBuffer FromSpan(Span buffer) where T : unmanaged { diff --git a/tests/LibHac.Tests/Fs/FileSystemClientTests/FileSystemServerFactory.cs b/tests/LibHac.Tests/Fs/FileSystemClientTests/FileSystemServerFactory.cs index 00b9f5de..9fa21fb6 100644 --- a/tests/LibHac.Tests/Fs/FileSystemClientTests/FileSystemServerFactory.cs +++ b/tests/LibHac.Tests/Fs/FileSystemClientTests/FileSystemServerFactory.cs @@ -46,7 +46,7 @@ public static class FileSystemServerFactory var config = new FileSystemServerConfig(); config.FsCreators = defaultObjects.FsCreators; - config.DeviceOperator = defaultObjects.DeviceOperator; + config.StorageDeviceManagerFactory = defaultObjects.StorageDeviceManagerFactory; config.ExternalKeySet = new ExternalKeySet(); config.RandomGenerator = randomGenerator; diff --git a/tests/LibHac.Tests/HorizonFactory.cs b/tests/LibHac.Tests/HorizonFactory.cs index 7eca909e..97b729ea 100644 --- a/tests/LibHac.Tests/HorizonFactory.cs +++ b/tests/LibHac.Tests/HorizonFactory.cs @@ -26,7 +26,7 @@ public static class HorizonFactory var config = new FileSystemServerConfig(); config.FsCreators = defaultObjects.FsCreators; - config.DeviceOperator = defaultObjects.DeviceOperator; + config.StorageDeviceManagerFactory = defaultObjects.StorageDeviceManagerFactory; config.ExternalKeySet = new ExternalKeySet(); config.RandomGenerator = randomGenerator;