Update ISaveDataFileSystemCreator.Create

Update method parameters and rewrite the method to use SaveDataSharedFileStorage.

SaveDataFileSystemCreator now requires a FileSystemServer at construction time, so DefaultFsServerObjects must be created after FileSystemServer construction but before FileSystemServer initialization.
This commit is contained in:
Alex Barney 2021-05-16 22:52:27 -07:00
parent d1a49b989a
commit 7294206116
9 changed files with 156 additions and 123 deletions

View File

@ -12,7 +12,8 @@ namespace LibHac.FsSrv
public EmulatedGameCard GameCard { get; set; }
public EmulatedSdCard SdCard { get; set; }
public static DefaultFsServerObjects GetDefaultEmulatedCreators(IFileSystem rootFileSystem, KeySet keySet)
public static DefaultFsServerObjects GetDefaultEmulatedCreators(IFileSystem rootFileSystem, KeySet keySet,
FileSystemServer fsServer)
{
var creators = new FileSystemCreatorInterfaces();
var gameCard = new EmulatedGameCard(keySet);
@ -25,7 +26,7 @@ namespace LibHac.FsSrv
creators.StorageOnNcaCreator = new StorageOnNcaCreator(keySet);
creators.TargetManagerFileSystemCreator = new TargetManagerFileSystemCreator();
creators.SubDirectoryFileSystemCreator = new SubDirectoryFileSystemCreator();
creators.SaveDataFileSystemCreator = new SaveDataFileSystemCreator(keySet, null, null);
creators.SaveDataFileSystemCreator = new SaveDataFileSystemCreator(fsServer, keySet, null, null);
creators.GameCardStorageCreator = gcStorageCreator;
creators.GameCardFileSystemCreator = new EmulatedGameCardFsCreator(gcStorageCreator, gameCard);
creators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator(keySet);

View File

@ -68,16 +68,12 @@ namespace LibHac.FsSrv
{
random.NextBytes(buffer);
return Result.Success;
};
};
var bufferManager = new FileSystemBufferManager();
Memory<byte> heapBuffer = GC.AllocateArray<byte>(BufferManagerHeapSize, true);
bufferManager.Initialize(BufferManagerCacheSize, heapBuffer, BufferManagerBlockSize);
// Todo: A non-hacky way of initializing the save data creator
config.FsCreators.SaveDataFileSystemCreator =
new SaveDataFileSystemCreator(config.KeySet, bufferManager, randomGenerator);
var saveDataIndexerManager = new SaveDataIndexerManager(server.Hos.Fs, Fs.SaveData.SaveIndexerId,
new ArrayPoolMemoryResource(), new SdHandleManager(), false);
@ -264,10 +260,5 @@ namespace LibHac.FsSrv
/// If null, an empty set will be created.
/// </summary>
public ExternalKeySet ExternalKeySet { get; set; }
/// <summary>
/// A keyset used for decrypting content.
/// </summary>
public KeySet KeySet { get; set; }
}
}

View File

@ -11,8 +11,13 @@ namespace LibHac.FsSrv.FsCreator
Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem,
out ReferenceCountedDisposable<ISaveDataExtraDataAccessor> extraDataAccessor,
ReferenceCountedDisposable<IFileSystem> sourceFileSystem, ulong saveDataId, bool allowDirectorySaveData,
bool useDeviceUniqueMac, SaveDataType type, ISaveDataCommitTimeStampGetter timeStampGetter);
ISaveDataFileSystemCacheManager cacheManager, ref ReferenceCountedDisposable<IFileSystem> baseFileSystem,
SaveDataSpaceId spaceId, ulong saveDataId, bool allowDirectorySaveData, bool useDeviceUniqueMac,
bool isJournalingSupported, bool isMultiCommitSupported, bool openReadOnly, bool openShared,
ISaveDataCommitTimeStampGetter timeStampGetter);
Result CreateExtraDataAccessor(out ReferenceCountedDisposable<ISaveDataExtraDataAccessor> extraDataAccessor,
ReferenceCountedDisposable<IFileSystem> sourceFileSystem);
void SetSdCardEncryptionSeed(ReadOnlySpan<byte> seed);
}

View File

@ -1,10 +1,14 @@
using System;
using LibHac.Common;
using LibHac.Common.Keys;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.FsSystem.Save;
using LibHac.Util;
using OpenType = LibHac.FsSrv.SaveDataOpenTypeSetFileStorage.OpenType;
namespace LibHac.FsSrv.FsCreator
{
@ -13,13 +17,16 @@ namespace LibHac.FsSrv.FsCreator
private IBufferManager _bufferManager;
private RandomDataGenerator _randomGenerator;
// LibHac Additions
private KeySet _keySet;
private FileSystemServer _fsServer;
public SaveDataFileSystemCreator(KeySet keySet, IBufferManager bufferManager,
public SaveDataFileSystemCreator(FileSystemServer fsServer, KeySet keySet, IBufferManager bufferManager,
RandomDataGenerator randomGenerator)
{
_bufferManager = bufferManager;
_randomGenerator = randomGenerator;
_fsServer = fsServer;
_keySet = keySet;
}
@ -30,68 +37,92 @@ namespace LibHac.FsSrv.FsCreator
public Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem,
out ReferenceCountedDisposable<ISaveDataExtraDataAccessor> extraDataAccessor,
ReferenceCountedDisposable<IFileSystem> sourceFileSystem, ulong saveDataId, bool allowDirectorySaveData,
bool useDeviceUniqueMac, SaveDataType type, ISaveDataCommitTimeStampGetter timeStampGetter)
ISaveDataFileSystemCacheManager cacheManager, ref ReferenceCountedDisposable<IFileSystem> baseFileSystem,
SaveDataSpaceId spaceId, ulong saveDataId, bool allowDirectorySaveData, bool useDeviceUniqueMac,
bool isJournalingSupported, bool isMultiCommitSupported, bool openReadOnly, bool openShared,
ISaveDataCommitTimeStampGetter timeStampGetter)
{
Span<byte> saveImageName = stackalloc byte[0x12];
UnsafeHelpers.SkipParamInit(out fileSystem, out extraDataAccessor);
var saveDataPath = $"/{saveDataId:x16}".ToU8String();
Assert.SdkRequiresNotNull(cacheManager);
var sb = new U8StringBuilder(saveImageName);
sb.Append((byte)'/').AppendFormat(saveDataId, 'x', 16);
Result rc = baseFileSystem.Target.GetEntryType(out DirectoryEntryType type, new U8Span(saveImageName));
Result rc = sourceFileSystem.Target.GetEntryType(out DirectoryEntryType entryType, saveDataPath);
if (rc.IsFailure())
{
return ResultFs.PathNotFound.Includes(rc) ? ResultFs.TargetNotFound.LogConverted(rc) : rc;
}
switch (entryType)
if (type == DirectoryEntryType.Directory)
{
case DirectoryEntryType.Directory:
if (!allowDirectorySaveData) return ResultFs.InvalidSaveDataEntryType.Log();
if (!allowDirectorySaveData)
return ResultFs.InvalidSaveDataEntryType.Log();
var subDirFs = new SubdirectoryFileSystem(ref sourceFileSystem);
SubdirectoryFileSystem subDirFs = null;
ReferenceCountedDisposable<DirectorySaveDataFileSystem> saveFs = null;
try
{
subDirFs = new SubdirectoryFileSystem(ref baseFileSystem);
rc = subDirFs.Initialize(saveDataPath);
if (rc.IsFailure())
{
subDirFs.Dispose();
return rc;
}
bool isPersistentSaveData = type != SaveDataType.Temporary;
bool isUserSaveData = type == SaveDataType.Account || type == SaveDataType.Device;
rc = DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, subDirFs,
timeStampGetter, _randomGenerator, isPersistentSaveData, isUserSaveData, true, null);
rc = subDirFs.Initialize(new U8Span(saveImageName));
if (rc.IsFailure()) return rc;
ReferenceCountedDisposable<DirectorySaveDataFileSystem> sharedSaveFs = null;
try
{
sharedSaveFs = new ReferenceCountedDisposable<DirectorySaveDataFileSystem>(saveFs);
fileSystem = sharedSaveFs.AddReference<IFileSystem>();
extraDataAccessor = sharedSaveFs.AddReference<ISaveDataExtraDataAccessor>();
saveFs = DirectorySaveDataFileSystem.CreateShared(Shared.Move(ref subDirFs), _fsServer.Hos.Fs);
return Result.Success;
}
finally
{
sharedSaveFs?.Dispose();
}
case DirectoryEntryType.File:
rc = sourceFileSystem.Target.OpenFile(out IFile saveDataFile, saveDataPath, OpenMode.ReadWrite);
rc = saveFs.Target.Initialize(timeStampGetter, _randomGenerator, isJournalingSupported,
isMultiCommitSupported, !openReadOnly);
if (rc.IsFailure()) return rc;
var saveDataStorage = new DisposingFileStorage(saveDataFile);
fileSystem = new ReferenceCountedDisposable<IFileSystem>(new SaveDataFileSystem(_keySet,
saveDataStorage, IntegrityCheckLevel.ErrorOnInvalid, false));
// Todo: ISaveDataExtraDataAccessor
fileSystem = saveFs.AddReference<IFileSystem>();
extraDataAccessor = saveFs.AddReference<ISaveDataExtraDataAccessor>();
return Result.Success;
default:
throw new ArgumentOutOfRangeException();
}
finally
{
subDirFs?.Dispose();
saveFs?.Dispose();
}
}
ReferenceCountedDisposable<IStorage> fileStorage = null;
try
{
Optional<OpenType> openType =
openShared ? new Optional<OpenType>(OpenType.Normal) : new Optional<OpenType>();
rc = _fsServer.OpenSaveDataStorage(out fileStorage, ref baseFileSystem, spaceId, saveDataId,
OpenMode.ReadWrite, openType);
if (rc.IsFailure()) return rc;
if (!isJournalingSupported)
{
throw new NotImplementedException();
}
// Todo: Properly handle shared storage
fileSystem = new ReferenceCountedDisposable<IFileSystem>(new SaveDataFileSystem(_keySet,
fileStorage.Target, IntegrityCheckLevel.ErrorOnInvalid, false));
// Todo: ISaveDataExtraDataAccessor
return Result.Success;
}
finally
{
fileStorage?.Dispose();
}
}
public Result CreateExtraDataAccessor(
out ReferenceCountedDisposable<ISaveDataExtraDataAccessor> extraDataAccessor,
ReferenceCountedDisposable<IFileSystem> sourceFileSystem)
{
throw new NotImplementedException();
}
public void SetSdCardEncryptionSeed(ReadOnlySpan<byte> seed)

View File

@ -167,11 +167,14 @@ namespace LibHac.FsSrv
{
using ScopedLock<SdkRecursiveMutexType> scopedLock = _extraDataCacheManager.GetScopedLock();
// Todo: Update ISaveDataFileSystemCreator
bool openShared = SaveDataProperties.IsSharedOpenNeeded(type);
bool isMultiCommitSupported = SaveDataProperties.IsMultiCommitSupported(type);
bool isJournalingSupported = SaveDataProperties.IsJournalingSupported(type);
bool useDeviceUniqueMac = IsDeviceUniqueMac(spaceId);
rc = _config.SaveFsCreator.Create(out saveFs, out extraDataAccessor, saveDirectoryFs,
saveDataId, allowDirectorySaveData, useDeviceUniqueMac, type, _timeStampGetter);
rc = _config.SaveFsCreator.Create(out saveFs, out extraDataAccessor, _saveDataFsCacheManager,
ref saveDirectoryFs, spaceId, saveDataId, allowDirectorySaveData, useDeviceUniqueMac,
isJournalingSupported, isMultiCommitSupported, openReadOnly, openShared, _timeStampGetter);
if (rc.IsFailure()) return rc;
saveDataFs = Shared.Move(ref saveFs);
@ -188,7 +191,6 @@ namespace LibHac.FsSrv
finally
{
saveFs?.Dispose();
cachedSaveDataFs?.Dispose();
extraDataAccessor?.Dispose();
}
}
@ -204,8 +206,6 @@ namespace LibHac.FsSrv
finally
{
saveDirectoryFs?.Dispose();
// ReSharper disable once ExpressionIsAlwaysNull
// ReSharper disable once ConstantConditionalAccessQualifier
cachedSaveDataFs?.Dispose();
saveDataFs?.Dispose();
}

View File

@ -47,9 +47,9 @@ namespace LibHac.FsSystem
// Todo: Unique file system for disposal
private int _openWritableFileCount;
private bool _isPersistentSaveData;
private bool _canCommitProvisionally;
private bool _useTransactions;
private bool _isJournalingSupported;
private bool _isMultiCommitSupported;
private bool _isJournalingEnabled;
// Additions to support extra data
private ISaveDataCommitTimeStampGetter _timeStampGetter;
@ -124,12 +124,12 @@ namespace LibHac.FsSystem
public static Result CreateNew(out DirectorySaveDataFileSystem created, IFileSystem baseFileSystem,
ISaveDataCommitTimeStampGetter timeStampGetter, RandomDataGenerator randomGenerator,
bool isPersistentSaveData, bool canCommitProvisionally, bool useTransactions,
bool isJournalingSupported, bool isMultiCommitSupported, bool isJournalingEnabled,
FileSystemClient fsClient)
{
var obj = new DirectorySaveDataFileSystem(baseFileSystem, fsClient);
Result rc = obj.Initialize(timeStampGetter, randomGenerator, isPersistentSaveData, canCommitProvisionally,
useTransactions);
Result rc = obj.Initialize(timeStampGetter, randomGenerator, isJournalingSupported, isMultiCommitSupported,
isJournalingEnabled);
if (rc.IsSuccess())
{
@ -143,10 +143,17 @@ namespace LibHac.FsSystem
}
public static Result CreateNew(out DirectorySaveDataFileSystem created, IFileSystem baseFileSystem,
bool isPersistentSaveData, bool canCommitProvisionally, bool useTransactions)
bool isJournalingSupported, bool isMultiCommitSupported, bool isJournalingEnabled)
{
return CreateNew(out created, baseFileSystem, null, null, isPersistentSaveData, canCommitProvisionally,
useTransactions, null);
return CreateNew(out created, baseFileSystem, null, null, isJournalingSupported, isMultiCommitSupported,
isJournalingEnabled, null);
}
public static ReferenceCountedDisposable<DirectorySaveDataFileSystem> CreateShared(IFileSystem baseFileSystem,
FileSystemClient fsClient)
{
var fileSystem = new DirectorySaveDataFileSystem(baseFileSystem, fsClient);
return new ReferenceCountedDisposable<DirectorySaveDataFileSystem>(fileSystem);
}
/// <summary>
@ -181,17 +188,17 @@ namespace LibHac.FsSystem
base.Dispose(disposing);
}
private Result Initialize(bool isPersistentSaveData, bool canCommitProvisionally, bool useTransactions)
public Result Initialize(bool isJournalingSupported, bool isMultiCommitSupported, bool isJournalingEnabled)
{
return Initialize(null, null, isPersistentSaveData, canCommitProvisionally, useTransactions);
return Initialize(null, null, isJournalingSupported, isMultiCommitSupported, isJournalingEnabled);
}
private Result Initialize(ISaveDataCommitTimeStampGetter timeStampGetter, RandomDataGenerator randomGenerator,
bool isPersistentSaveData, bool canCommitProvisionally, bool useTransactions)
public Result Initialize(ISaveDataCommitTimeStampGetter timeStampGetter, RandomDataGenerator randomGenerator,
bool isJournalingSupported, bool isMultiCommitSupported, bool isJournalingEnabled)
{
_isPersistentSaveData = isPersistentSaveData;
_canCommitProvisionally = canCommitProvisionally;
_useTransactions = useTransactions;
_isJournalingSupported = isJournalingSupported;
_isMultiCommitSupported = isMultiCommitSupported;
_isJournalingEnabled = isJournalingEnabled;
_timeStampGetter = timeStampGetter ?? _timeStampGetter;
_randomGenerator = randomGenerator ?? _randomGenerator;
@ -205,7 +212,7 @@ namespace LibHac.FsSystem
rc = _baseFs.CreateDirectory(WorkingDirectoryPath);
if (rc.IsFailure()) return rc;
if (_isPersistentSaveData)
if (_isJournalingSupported)
{
rc = _baseFs.CreateDirectory(CommittedDirectoryPath);
@ -215,15 +222,15 @@ namespace LibHac.FsSystem
}
}
// Only the working directory is needed for temporary savedata
if (!_isPersistentSaveData)
// Only the working directory is needed for non-journaling savedata
if (!_isJournalingSupported)
return Result.Success;
rc = _baseFs.GetEntryType(out _, CommittedDirectoryPath);
if (rc.IsSuccess())
{
if (!_useTransactions)
if (!_isJournalingEnabled)
return Result.Success;
rc = SynchronizeDirectory(WorkingDirectoryPath, CommittedDirectoryPath);
@ -254,7 +261,8 @@ namespace LibHac.FsSystem
if (StringUtils.GetLength(relativePath, PathTools.MaxPathLength + 1) > PathTools.MaxPathLength)
return ResultFs.TooLongPath.Log();
U8Span workingPath = !_useTransactions && _isPersistentSaveData
// Use the committed directory directly if journaling is supported but not enabled
U8Span workingPath = _isJournalingSupported && !_isJournalingEnabled
? CommittedDirectoryPath
: WorkingDirectoryPath;
@ -466,7 +474,7 @@ namespace LibHac.FsSystem
{
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
if (!_useTransactions || !_isPersistentSaveData)
if (!_isJournalingEnabled || !_isJournalingSupported)
return Result.Success;
if (_openWritableFileCount > 0)
@ -499,7 +507,7 @@ namespace LibHac.FsSystem
protected override Result DoCommitProvisionally(long counter)
{
if (!_canCommitProvisionally)
if (!_isMultiCommitSupported)
return ResultFs.UnsupportedCommitProvisionallyForDirectorySaveDataFileSystem.Log();
return Result.Success;
@ -507,11 +515,11 @@ namespace LibHac.FsSystem
protected override Result DoRollback()
{
// No old data is kept for temporary save data, so there's nothing to rollback to
if (!_isPersistentSaveData)
// No old data is kept for non-journaling save data, so there's nothing to rollback to
if (!_isJournalingSupported)
return Result.Success;
return Initialize(_isPersistentSaveData, _canCommitProvisionally, _useTransactions);
return Initialize(_isJournalingSupported, _isMultiCommitSupported, _isJournalingEnabled);
}
protected override Result DoGetFreeSpaceSize(out long freeSpace, U8Span path)
@ -589,7 +597,7 @@ namespace LibHac.FsSystem
rc = _baseFs.CreateFile(WorkingExtraDataPath, Unsafe.SizeOf<SaveDataExtraData>());
if (rc.IsFailure()) return rc;
if (_isPersistentSaveData)
if (_isJournalingSupported)
{
rc = _baseFs.CreateFile(CommittedExtraDataPath, Unsafe.SizeOf<SaveDataExtraData>());
if (rc.IsFailure() && !ResultFs.PathAlreadyExists.Includes(rc)) return rc;
@ -602,8 +610,8 @@ namespace LibHac.FsSystem
if (rc.IsFailure()) return rc;
}
// Only the working extra data is needed for temporary savedata
if (!_isPersistentSaveData)
// Only the working extra data is needed for non-journaling savedata
if (!_isJournalingSupported)
return Result.Success;
rc = _baseFs.GetEntryType(out _, CommittedExtraDataPath);
@ -613,7 +621,7 @@ namespace LibHac.FsSystem
rc = EnsureExtraDataSize(CommittedExtraDataPath);
if (rc.IsFailure()) return rc;
if (!_useTransactions)
if (!_isJournalingEnabled)
return Result.Success;
return SynchronizeExtraData(WorkingExtraDataPath, CommittedExtraDataPath);
@ -684,7 +692,7 @@ namespace LibHac.FsSystem
private U8Span GetExtraDataPath()
{
return !_useTransactions && _isPersistentSaveData
return _isJournalingSupported && !_isJournalingEnabled
? CommittedExtraDataPath
: WorkingExtraDataPath;
}
@ -757,7 +765,7 @@ namespace LibHac.FsSystem
{
Assert.SdkRequires(_mutex.IsLockedByCurrentThread());
if (!_useTransactions || !_isPersistentSaveData)
if (!_isJournalingSupported || !_isJournalingEnabled)
return Result.Success;
Result RenameCommittedFile() => _baseFs.RenameFile(CommittedExtraDataPath, SynchronizingExtraDataPath);

View File

@ -7,36 +7,21 @@ namespace LibHac
{
public static class HorizonFactory
{
public static Horizon CreateWithFsConfig(HorizonConfiguration config, FileSystemServerConfig fsServerConfig)
public static Horizon CreateWithDefaultFsConfig(HorizonConfiguration config, IFileSystem rootFileSystem,
KeySet keySet)
{
var horizon = new Horizon(config);
HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient();
var fsServer = new FileSystemServer(fsServerClient);
FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, fsServerConfig);
HorizonClient bcatServerClient = horizon.CreateHorizonClient();
_ = new BcatServer(bcatServerClient);
return horizon;
}
public static Horizon CreateWithDefaultFsConfig(HorizonConfiguration config, IFileSystem rootFileSystem, KeySet keySet)
{
var horizon = new Horizon(config);
var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFileSystem, keySet);
HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient();
var fsServer = new FileSystemServer(fsServerClient);
var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFileSystem, keySet, fsServer);
var fsServerConfig = new FileSystemServerConfig
{
DeviceOperator = defaultObjects.DeviceOperator,
ExternalKeySet = keySet.ExternalKeySet,
FsCreators = defaultObjects.FsCreators,
KeySet = keySet
};
FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, fsServerConfig);

View File

@ -11,8 +11,13 @@ namespace LibHac.Tests.Fs.FileSystemClientTests
{
rootFs = new InMemoryFileSystem();
var keySet = new KeySet();
var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFs, keySet);
var horizon = new Horizon(new HorizonConfiguration());
HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient();
var fsServer = new FileSystemServer(fsServerClient);
var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFs, keySet, fsServer);
defaultObjects.SdCard.SetSdCardInsertionStatus(sdCardInserted);
@ -20,9 +25,8 @@ namespace LibHac.Tests.Fs.FileSystemClientTests
config.FsCreators = defaultObjects.FsCreators;
config.DeviceOperator = defaultObjects.DeviceOperator;
config.ExternalKeySet = new ExternalKeySet();
config.KeySet = keySet;
Horizon horizon = LibHac.HorizonFactory.CreateWithFsConfig(new HorizonConfiguration(), config);
FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, config);
HorizonClient horizonClient = horizon.CreatePrivilegedHorizonClient();

View File

@ -1,4 +1,5 @@
using LibHac.Common.Keys;
using LibHac.Bcat;
using LibHac.Common.Keys;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSrv;
@ -12,15 +13,22 @@ namespace LibHac.Tests
IFileSystem rootFs = new InMemoryFileSystem();
var keySet = new KeySet();
var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFs, keySet);
var horizon = new Horizon(new HorizonConfiguration());
HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient();
var fsServer = new FileSystemServer(fsServerClient);
var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFs, keySet, fsServer);
var config = new FileSystemServerConfig();
config.FsCreators = defaultObjects.FsCreators;
config.DeviceOperator = defaultObjects.DeviceOperator;
config.ExternalKeySet = new ExternalKeySet();
config.KeySet = keySet;
Horizon horizon = LibHac.HorizonFactory.CreateWithFsConfig(new HorizonConfiguration(), config);
FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, config);
HorizonClient bcatServerClient = horizon.CreateHorizonClient();
_ = new BcatServer(bcatServerClient);
return horizon;
}