mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-04-24 06:47:44 +02:00
misc: chore: Use explicit types in Horizon project
This commit is contained in:
parent
5eba42fa06
commit
69e0b79bd9
23 changed files with 71 additions and 66 deletions
|
@ -39,9 +39,9 @@ namespace Ryujinx.Horizon.Bcat.Ipc
|
||||||
[CmifCommand(1)]
|
[CmifCommand(1)]
|
||||||
public Result CreateDeliveryCacheStorageService(out IDeliveryCacheStorageService service, [ClientProcessId] ulong pid)
|
public Result CreateDeliveryCacheStorageService(out IDeliveryCacheStorageService service, [ClientProcessId] ulong pid)
|
||||||
{
|
{
|
||||||
using var libHacService = new SharedRef<LibHac.Bcat.Impl.Ipc.IDeliveryCacheStorageService>();
|
using SharedRef<LibHac.Bcat.Impl.Ipc.IDeliveryCacheStorageService> libHacService = new SharedRef<LibHac.Bcat.Impl.Ipc.IDeliveryCacheStorageService>();
|
||||||
|
|
||||||
var resultCode = _libHacService.Get.CreateDeliveryCacheStorageService(ref libHacService.Ref, pid);
|
LibHac.Result resultCode = _libHacService.Get.CreateDeliveryCacheStorageService(ref libHacService.Ref, pid);
|
||||||
|
|
||||||
if (resultCode.IsSuccess())
|
if (resultCode.IsSuccess())
|
||||||
{
|
{
|
||||||
|
@ -58,9 +58,9 @@ namespace Ryujinx.Horizon.Bcat.Ipc
|
||||||
[CmifCommand(2)]
|
[CmifCommand(2)]
|
||||||
public Result CreateDeliveryCacheStorageServiceWithApplicationId(out IDeliveryCacheStorageService service, ApplicationId applicationId)
|
public Result CreateDeliveryCacheStorageServiceWithApplicationId(out IDeliveryCacheStorageService service, ApplicationId applicationId)
|
||||||
{
|
{
|
||||||
using var libHacService = new SharedRef<LibHac.Bcat.Impl.Ipc.IDeliveryCacheStorageService>();
|
using SharedRef<LibHac.Bcat.Impl.Ipc.IDeliveryCacheStorageService> libHacService = new SharedRef<LibHac.Bcat.Impl.Ipc.IDeliveryCacheStorageService>();
|
||||||
|
|
||||||
var resultCode = _libHacService.Get.CreateDeliveryCacheStorageServiceWithApplicationId(ref libHacService.Ref, new LibHac.ApplicationId(applicationId.Id));
|
LibHac.Result resultCode = _libHacService.Get.CreateDeliveryCacheStorageServiceWithApplicationId(ref libHacService.Ref, new LibHac.ApplicationId(applicationId.Id));
|
||||||
|
|
||||||
if (resultCode.IsSuccess())
|
if (resultCode.IsSuccess())
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,9 +22,9 @@ namespace Ryujinx.Horizon.Bcat.Ipc
|
||||||
[CmifCommand(0)]
|
[CmifCommand(0)]
|
||||||
public Result CreateFileService(out IDeliveryCacheFileService service)
|
public Result CreateFileService(out IDeliveryCacheFileService service)
|
||||||
{
|
{
|
||||||
using var libHacService = new SharedRef<LibHac.Bcat.Impl.Ipc.IDeliveryCacheFileService>();
|
using SharedRef<LibHac.Bcat.Impl.Ipc.IDeliveryCacheFileService> libHacService = new SharedRef<LibHac.Bcat.Impl.Ipc.IDeliveryCacheFileService>();
|
||||||
|
|
||||||
var resultCode = _libHacService.Get.CreateFileService(ref libHacService.Ref);
|
LibHac.Result resultCode = _libHacService.Get.CreateFileService(ref libHacService.Ref);
|
||||||
|
|
||||||
if (resultCode.IsSuccess())
|
if (resultCode.IsSuccess())
|
||||||
{
|
{
|
||||||
|
@ -41,9 +41,9 @@ namespace Ryujinx.Horizon.Bcat.Ipc
|
||||||
[CmifCommand(1)]
|
[CmifCommand(1)]
|
||||||
public Result CreateDirectoryService(out IDeliveryCacheDirectoryService service)
|
public Result CreateDirectoryService(out IDeliveryCacheDirectoryService service)
|
||||||
{
|
{
|
||||||
using var libHacService = new SharedRef<LibHac.Bcat.Impl.Ipc.IDeliveryCacheDirectoryService>();
|
using SharedRef<LibHac.Bcat.Impl.Ipc.IDeliveryCacheDirectoryService> libHacService = new SharedRef<LibHac.Bcat.Impl.Ipc.IDeliveryCacheDirectoryService>();
|
||||||
|
|
||||||
var resultCode = _libHacService.Get.CreateDirectoryService(ref libHacService.Ref);
|
LibHac.Result resultCode = _libHacService.Get.CreateDirectoryService(ref libHacService.Ref);
|
||||||
|
|
||||||
if (resultCode.IsSuccess())
|
if (resultCode.IsSuccess())
|
||||||
{
|
{
|
||||||
|
|
|
@ -67,7 +67,7 @@ namespace Ryujinx.Horizon
|
||||||
{
|
{
|
||||||
for (int i = 0; i < _freeRanges.Count; i++)
|
for (int i = 0; i < _freeRanges.Count; i++)
|
||||||
{
|
{
|
||||||
var range = _freeRanges[i];
|
Range range = _freeRanges[i];
|
||||||
|
|
||||||
ulong alignedOffset = BitUtils.AlignUp(range.Offset, alignment);
|
ulong alignedOffset = BitUtils.AlignUp(range.Offset, alignment);
|
||||||
ulong sizeDelta = alignedOffset - range.Offset;
|
ulong sizeDelta = alignedOffset - range.Offset;
|
||||||
|
@ -103,7 +103,7 @@ namespace Ryujinx.Horizon
|
||||||
|
|
||||||
private void InsertFreeRange(ulong offset, ulong size)
|
private void InsertFreeRange(ulong offset, ulong size)
|
||||||
{
|
{
|
||||||
var range = new Range(offset, size);
|
Range range = new Range(offset, size);
|
||||||
int index = _freeRanges.BinarySearch(range);
|
int index = _freeRanges.BinarySearch(range);
|
||||||
if (index < 0)
|
if (index < 0)
|
||||||
{
|
{
|
||||||
|
@ -116,7 +116,7 @@ namespace Ryujinx.Horizon
|
||||||
private void InsertFreeRangeComingled(ulong offset, ulong size)
|
private void InsertFreeRangeComingled(ulong offset, ulong size)
|
||||||
{
|
{
|
||||||
ulong endOffset = offset + size;
|
ulong endOffset = offset + size;
|
||||||
var range = new Range(offset, size);
|
Range range = new Range(offset, size);
|
||||||
int index = _freeRanges.BinarySearch(range);
|
int index = _freeRanges.BinarySearch(range);
|
||||||
if (index < 0)
|
if (index < 0)
|
||||||
{
|
{
|
||||||
|
|
|
@ -25,7 +25,7 @@ namespace Ryujinx.Horizon.Sdk.Arp
|
||||||
{
|
{
|
||||||
if (_sessionHandle == 0)
|
if (_sessionHandle == 0)
|
||||||
{
|
{
|
||||||
using var smApi = new SmApi();
|
using SmApi smApi = new();
|
||||||
|
|
||||||
smApi.Initialize();
|
smApi.Initialize();
|
||||||
smApi.GetServiceHandle(out _sessionHandle, ServiceName.Encode(ArpRName)).AbortOnFailure();
|
smApi.GetServiceHandle(out _sessionHandle, ServiceName.Encode(ArpRName)).AbortOnFailure();
|
||||||
|
|
|
@ -5,6 +5,7 @@ using Ryujinx.Horizon.Common;
|
||||||
using Ryujinx.Horizon.Sdk.Applet;
|
using Ryujinx.Horizon.Sdk.Applet;
|
||||||
using Ryujinx.Horizon.Sdk.Sf;
|
using Ryujinx.Horizon.Sdk.Sf;
|
||||||
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
||||||
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.Horizon.Sdk.Audio.Detail
|
namespace Ryujinx.Horizon.Sdk.Audio.Detail
|
||||||
|
@ -49,7 +50,7 @@ namespace Ryujinx.Horizon.Sdk.Audio.Detail
|
||||||
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<DeviceName> name,
|
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<DeviceName> name,
|
||||||
[ClientProcessId] ulong pid)
|
[ClientProcessId] ulong pid)
|
||||||
{
|
{
|
||||||
var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle);
|
IVirtualMemoryManager clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle);
|
||||||
|
|
||||||
ResultCode rc = _impl.OpenAudioIn(
|
ResultCode rc = _impl.OpenAudioIn(
|
||||||
out string outputDeviceName,
|
out string outputDeviceName,
|
||||||
|
|
|
@ -5,6 +5,7 @@ using Ryujinx.Horizon.Common;
|
||||||
using Ryujinx.Horizon.Sdk.Applet;
|
using Ryujinx.Horizon.Sdk.Applet;
|
||||||
using Ryujinx.Horizon.Sdk.Sf;
|
using Ryujinx.Horizon.Sdk.Sf;
|
||||||
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
||||||
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.Horizon.Sdk.Audio.Detail
|
namespace Ryujinx.Horizon.Sdk.Audio.Detail
|
||||||
|
@ -49,7 +50,7 @@ namespace Ryujinx.Horizon.Sdk.Audio.Detail
|
||||||
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<DeviceName> name,
|
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<DeviceName> name,
|
||||||
[ClientProcessId] ulong pid)
|
[ClientProcessId] ulong pid)
|
||||||
{
|
{
|
||||||
var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle);
|
IVirtualMemoryManager clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle);
|
||||||
|
|
||||||
ResultCode rc = _impl.OpenAudioOut(
|
ResultCode rc = _impl.OpenAudioOut(
|
||||||
out string outputDeviceName,
|
out string outputDeviceName,
|
||||||
|
|
|
@ -4,6 +4,7 @@ using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Horizon.Common;
|
using Ryujinx.Horizon.Common;
|
||||||
using Ryujinx.Horizon.Sdk.Applet;
|
using Ryujinx.Horizon.Sdk.Applet;
|
||||||
using Ryujinx.Horizon.Sdk.Sf;
|
using Ryujinx.Horizon.Sdk.Sf;
|
||||||
|
using Ryujinx.Memory;
|
||||||
|
|
||||||
namespace Ryujinx.Horizon.Sdk.Audio.Detail
|
namespace Ryujinx.Horizon.Sdk.Audio.Detail
|
||||||
{
|
{
|
||||||
|
@ -30,11 +31,11 @@ namespace Ryujinx.Horizon.Sdk.Audio.Detail
|
||||||
AppletResourceUserId appletResourceId,
|
AppletResourceUserId appletResourceId,
|
||||||
[ClientProcessId] ulong pid)
|
[ClientProcessId] ulong pid)
|
||||||
{
|
{
|
||||||
var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle);
|
IVirtualMemoryManager clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle);
|
||||||
ulong workBufferAddress = HorizonStatic.Syscall.GetTransferMemoryAddress(workBufferHandle);
|
ulong workBufferAddress = HorizonStatic.Syscall.GetTransferMemoryAddress(workBufferHandle);
|
||||||
|
|
||||||
Result result = new Result((int)_impl.OpenAudioRenderer(
|
Result result = new Result((int)_impl.OpenAudioRenderer(
|
||||||
out var renderSystem,
|
out AudioRenderSystem renderSystem,
|
||||||
clientMemoryManager,
|
clientMemoryManager,
|
||||||
ref parameter.Configuration,
|
ref parameter.Configuration,
|
||||||
appletResourceId.Id,
|
appletResourceId.Id,
|
||||||
|
@ -96,10 +97,10 @@ namespace Ryujinx.Horizon.Sdk.Audio.Detail
|
||||||
AppletResourceUserId appletResourceId,
|
AppletResourceUserId appletResourceId,
|
||||||
[ClientProcessId] ulong pid)
|
[ClientProcessId] ulong pid)
|
||||||
{
|
{
|
||||||
var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle);
|
IVirtualMemoryManager clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle);
|
||||||
|
|
||||||
Result result = new Result((int)_impl.OpenAudioRenderer(
|
Result result = new Result((int)_impl.OpenAudioRenderer(
|
||||||
out var renderSystem,
|
out AudioRenderSystem renderSystem,
|
||||||
clientMemoryManager,
|
clientMemoryManager,
|
||||||
ref parameter.Configuration,
|
ref parameter.Configuration,
|
||||||
appletResourceId.Id,
|
appletResourceId.Id,
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace Ryujinx.Horizon.Sdk.Lbl
|
||||||
|
|
||||||
public LblApi()
|
public LblApi()
|
||||||
{
|
{
|
||||||
using var smApi = new SmApi();
|
using SmApi smApi = new SmApi();
|
||||||
|
|
||||||
smApi.Initialize();
|
smApi.Initialize();
|
||||||
smApi.GetServiceHandle(out _sessionHandle, ServiceName.Encode(LblName)).AbortOnFailure();
|
smApi.GetServiceHandle(out _sessionHandle, ServiceName.Encode(LblName)).AbortOnFailure();
|
||||||
|
|
|
@ -35,13 +35,13 @@ namespace Ryujinx.Horizon.Sdk.Ngc.Detail
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var ranges = GetBitfieldRanges();
|
ReadOnlySpan<BitfieldRange> ranges = GetBitfieldRanges();
|
||||||
|
|
||||||
int rangeBlockIndex = index / CompressedEntriesPerBlock;
|
int rangeBlockIndex = index / CompressedEntriesPerBlock;
|
||||||
|
|
||||||
if (rangeBlockIndex < ranges.Length)
|
if (rangeBlockIndex < ranges.Length)
|
||||||
{
|
{
|
||||||
var range = ranges[rangeBlockIndex];
|
BitfieldRange range = ranges[rangeBlockIndex];
|
||||||
|
|
||||||
int bitfieldLength = range.BitfieldLength;
|
int bitfieldLength = range.BitfieldLength;
|
||||||
int bitfieldOffset = (index % CompressedEntriesPerBlock) * bitfieldLength;
|
int bitfieldOffset = (index % CompressedEntriesPerBlock) * bitfieldLength;
|
||||||
|
|
|
@ -31,7 +31,7 @@ namespace Ryujinx.Horizon.Sdk.OsTypes
|
||||||
|
|
||||||
public static void DestroySystemEvent(ref SystemEventType sysEvent)
|
public static void DestroySystemEvent(ref SystemEventType sysEvent)
|
||||||
{
|
{
|
||||||
var oldState = sysEvent.State;
|
SystemEventType.InitializationState oldState = sysEvent.State;
|
||||||
sysEvent.State = SystemEventType.InitializationState.NotInitialized;
|
sysEvent.State = SystemEventType.InitializationState.NotInitialized;
|
||||||
|
|
||||||
switch (oldState)
|
switch (oldState)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using Ryujinx.Horizon.Common;
|
using Ryujinx.Horizon.Common;
|
||||||
using Ryujinx.Horizon.Sdk.Sf.Cmif;
|
using Ryujinx.Horizon.Sdk.Sf.Cmif;
|
||||||
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
||||||
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.Horizon.Sdk
|
namespace Ryujinx.Horizon.Sdk
|
||||||
|
@ -12,7 +13,7 @@ namespace Ryujinx.Horizon.Sdk
|
||||||
ulong tlsAddress = HorizonStatic.ThreadContext.TlsAddress;
|
ulong tlsAddress = HorizonStatic.ThreadContext.TlsAddress;
|
||||||
int tlsSize = Api.TlsMessageBufferSize;
|
int tlsSize = Api.TlsMessageBufferSize;
|
||||||
|
|
||||||
using (var tlsRegion = HorizonStatic.AddressSpace.GetWritableRegion(tlsAddress, tlsSize))
|
using (WritableRegion tlsRegion = HorizonStatic.AddressSpace.GetWritableRegion(tlsAddress, tlsSize))
|
||||||
{
|
{
|
||||||
CmifRequest request = CmifMessage.CreateRequest(tlsRegion.Memory.Span, new CmifRequestFormat
|
CmifRequest request = CmifMessage.CreateRequest(tlsRegion.Memory.Span, new CmifRequestFormat
|
||||||
{
|
{
|
||||||
|
@ -48,7 +49,7 @@ namespace Ryujinx.Horizon.Sdk
|
||||||
ulong tlsAddress = HorizonStatic.ThreadContext.TlsAddress;
|
ulong tlsAddress = HorizonStatic.ThreadContext.TlsAddress;
|
||||||
int tlsSize = Api.TlsMessageBufferSize;
|
int tlsSize = Api.TlsMessageBufferSize;
|
||||||
|
|
||||||
using (var tlsRegion = HorizonStatic.AddressSpace.GetWritableRegion(tlsAddress, tlsSize))
|
using (WritableRegion tlsRegion = HorizonStatic.AddressSpace.GetWritableRegion(tlsAddress, tlsSize))
|
||||||
{
|
{
|
||||||
CmifRequestFormat format = new()
|
CmifRequestFormat format = new()
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,7 +19,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
|
||||||
return SfResult.InvalidHeaderSize;
|
return SfResult.InvalidHeaderSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
var inHeader = MemoryMarshal.Cast<byte, CmifDomainInHeader>(inRawData)[0];
|
CmifDomainInHeader inHeader = MemoryMarshal.Cast<byte, CmifDomainInHeader>(inRawData)[0];
|
||||||
|
|
||||||
ReadOnlySpan<byte> inDomainRawData = inRawData[Unsafe.SizeOf<CmifDomainInHeader>()..];
|
ReadOnlySpan<byte> inDomainRawData = inRawData[Unsafe.SizeOf<CmifDomainInHeader>()..];
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
|
||||||
switch (inHeader.Type)
|
switch (inHeader.Type)
|
||||||
{
|
{
|
||||||
case CmifDomainRequestType.SendMessage:
|
case CmifDomainRequestType.SendMessage:
|
||||||
var targetObject = domain.GetObject(targetObjectId);
|
ServiceObjectHolder targetObject = domain.GetObject(targetObjectId);
|
||||||
if (targetObject == null)
|
if (targetObject == null)
|
||||||
{
|
{
|
||||||
return SfResult.TargetNotFound;
|
return SfResult.TargetNotFound;
|
||||||
|
@ -48,7 +48,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
|
||||||
|
|
||||||
int[] inObjectIds = new int[inHeader.ObjectsCount];
|
int[] inObjectIds = new int[inHeader.ObjectsCount];
|
||||||
|
|
||||||
var domainProcessor = new DomainServiceObjectProcessor(domain, inObjectIds);
|
DomainServiceObjectProcessor domainProcessor = new DomainServiceObjectProcessor(domain, inObjectIds);
|
||||||
|
|
||||||
if (context.Processor == null)
|
if (context.Processor == null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -44,7 +44,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
|
||||||
|
|
||||||
public override ServerMessageRuntimeMetadata GetRuntimeMetadata()
|
public override ServerMessageRuntimeMetadata GetRuntimeMetadata()
|
||||||
{
|
{
|
||||||
var runtimeMetadata = _implProcessor.GetRuntimeMetadata();
|
ServerMessageRuntimeMetadata runtimeMetadata = _implProcessor.GetRuntimeMetadata();
|
||||||
|
|
||||||
return new ServerMessageRuntimeMetadata(
|
return new ServerMessageRuntimeMetadata(
|
||||||
(ushort)(runtimeMetadata.InDataSize + runtimeMetadata.InObjectsCount * sizeof(int)),
|
(ushort)(runtimeMetadata.InDataSize + runtimeMetadata.InObjectsCount * sizeof(int)),
|
||||||
|
@ -84,7 +84,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
|
||||||
|
|
||||||
public override HipcMessageData PrepareForReply(scoped ref ServiceDispatchContext context, out Span<byte> outRawData, ServerMessageRuntimeMetadata runtimeMetadata)
|
public override HipcMessageData PrepareForReply(scoped ref ServiceDispatchContext context, out Span<byte> outRawData, ServerMessageRuntimeMetadata runtimeMetadata)
|
||||||
{
|
{
|
||||||
var response = _implProcessor.PrepareForReply(ref context, out outRawData, runtimeMetadata);
|
HipcMessageData response = _implProcessor.PrepareForReply(ref context, out outRawData, runtimeMetadata);
|
||||||
|
|
||||||
int outHeaderSize = Unsafe.SizeOf<CmifDomainOutHeader>();
|
int outHeaderSize = Unsafe.SizeOf<CmifDomainOutHeader>();
|
||||||
int implOutDataTotalSize = ImplOutDataTotalSize;
|
int implOutDataTotalSize = ImplOutDataTotalSize;
|
||||||
|
|
|
@ -45,7 +45,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var entry = _freeList.First.Value;
|
Entry entry = _freeList.First.Value;
|
||||||
_freeList.RemoveFirst();
|
_freeList.RemoveFirst();
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
|
||||||
|
|
||||||
public override ServiceObjectHolder GetObject(int id)
|
public override ServiceObjectHolder GetObject(int id)
|
||||||
{
|
{
|
||||||
var entry = _manager._entryManager.GetEntry(id);
|
EntryManager.Entry entry = _manager._entryManager.GetEntry(id);
|
||||||
if (entry == null)
|
if (entry == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
|
@ -116,7 +116,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
|
||||||
|
|
||||||
public override void RegisterObject(int id, ServiceObjectHolder obj)
|
public override void RegisterObject(int id, ServiceObjectHolder obj)
|
||||||
{
|
{
|
||||||
var entry = _manager._entryManager.GetEntry(id);
|
EntryManager.Entry entry = _manager._entryManager.GetEntry(id);
|
||||||
DebugUtil.Assert(entry != null);
|
DebugUtil.Assert(entry != null);
|
||||||
|
|
||||||
lock (_manager._entryOwnerLock)
|
lock (_manager._entryOwnerLock)
|
||||||
|
@ -133,7 +133,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
|
||||||
{
|
{
|
||||||
for (int i = 0; i < outIds.Length; i++)
|
for (int i = 0; i < outIds.Length; i++)
|
||||||
{
|
{
|
||||||
var entry = _manager._entryManager.AllocateEntry();
|
EntryManager.Entry entry = _manager._entryManager.AllocateEntry();
|
||||||
if (entry == null)
|
if (entry == null)
|
||||||
{
|
{
|
||||||
return SfResult.OutOfDomainEntries;
|
return SfResult.OutOfDomainEntries;
|
||||||
|
@ -149,7 +149,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
|
||||||
|
|
||||||
public override ServiceObjectHolder UnregisterObject(int id)
|
public override ServiceObjectHolder UnregisterObject(int id)
|
||||||
{
|
{
|
||||||
var entry = _manager._entryManager.GetEntry(id);
|
EntryManager.Entry entry = _manager._entryManager.GetEntry(id);
|
||||||
if (entry == null)
|
if (entry == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
|
@ -186,7 +186,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
|
||||||
{
|
{
|
||||||
for (int i = 0; i < ids.Length; i++)
|
for (int i = 0; i < ids.Length; i++)
|
||||||
{
|
{
|
||||||
var entry = _manager._entryManager.GetEntry(ids[i]);
|
EntryManager.Entry entry = _manager._entryManager.GetEntry(ids[i]);
|
||||||
|
|
||||||
DebugUtil.Assert(entry != null);
|
DebugUtil.Assert(entry != null);
|
||||||
DebugUtil.Assert(entry.Owner == null);
|
DebugUtil.Assert(entry.Owner == null);
|
||||||
|
@ -197,7 +197,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
foreach (var entry in _entries)
|
foreach (EntryManager.Entry entry in _entries)
|
||||||
{
|
{
|
||||||
if (entry.Obj.ServiceObject is IDisposable disposableObj)
|
if (entry.Obj.ServiceObject is IDisposable disposableObj)
|
||||||
{
|
{
|
||||||
|
@ -230,7 +230,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var domain = new Domain(this);
|
Domain domain = new Domain(this);
|
||||||
_domains.Add(domain);
|
_domains.Add(domain);
|
||||||
return domain;
|
return domain;
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,9 +35,9 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
|
||||||
ReadOnlySpan<byte> inMessageRawData = inRawData[Unsafe.SizeOf<CmifInHeader>()..];
|
ReadOnlySpan<byte> inMessageRawData = inRawData[Unsafe.SizeOf<CmifInHeader>()..];
|
||||||
uint commandId = inHeader.CommandId;
|
uint commandId = inHeader.CommandId;
|
||||||
|
|
||||||
var outHeader = Span<CmifOutHeader>.Empty;
|
Span<CmifOutHeader> outHeader = Span<CmifOutHeader>.Empty;
|
||||||
|
|
||||||
if (!entries.TryGetValue((int)commandId, out var commandHandler))
|
if (!entries.TryGetValue((int)commandId, out CommandHandler commandHandler))
|
||||||
{
|
{
|
||||||
if (HorizonStatic.Options.IgnoreMissingServices)
|
if (HorizonStatic.Options.IgnoreMissingServices)
|
||||||
{
|
{
|
||||||
|
@ -87,7 +87,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif
|
||||||
|
|
||||||
private static void PrepareForStubReply(scoped ref ServiceDispatchContext context, out Span<byte> outRawData)
|
private static void PrepareForStubReply(scoped ref ServiceDispatchContext context, out Span<byte> outRawData)
|
||||||
{
|
{
|
||||||
var response = HipcMessage.WriteResponse(context.OutMessageBuffer, 0, 0x20 / sizeof(uint), 0, 0);
|
HipcMessageData response = HipcMessage.WriteResponse(context.OutMessageBuffer, 0, 0x20 / sizeof(uint), 0, 0);
|
||||||
outRawData = MemoryMarshal.Cast<uint, byte>(response.DataWords);
|
outRawData = MemoryMarshal.Cast<uint, byte>(response.DataWords);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
|
||||||
context.Processor.SetImplementationProcessor(_processor);
|
context.Processor.SetImplementationProcessor(_processor);
|
||||||
}
|
}
|
||||||
|
|
||||||
var runtimeMetadata = context.Processor.GetRuntimeMetadata();
|
ServerMessageRuntimeMetadata runtimeMetadata = context.Processor.GetRuntimeMetadata();
|
||||||
Result result = context.Processor.PrepareForProcess(ref context, runtimeMetadata);
|
Result result = context.Processor.PrepareForProcess(ref context, runtimeMetadata);
|
||||||
|
|
||||||
return result.IsFailure ? result : _invoke(ref context, _processor, runtimeMetadata, inRawData, ref outHeader);
|
return result.IsFailure ? result : _invoke(ref context, _processor, runtimeMetadata, inRawData, ref outHeader);
|
||||||
|
|
|
@ -27,7 +27,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
|
||||||
|
|
||||||
public static ref T GetRef<T>(PointerAndSize bufferRange) where T : unmanaged
|
public static ref T GetRef<T>(PointerAndSize bufferRange) where T : unmanaged
|
||||||
{
|
{
|
||||||
var writableRegion = GetWritableRegion(bufferRange);
|
WritableRegion writableRegion = GetWritableRegion(bufferRange);
|
||||||
|
|
||||||
return ref MemoryMarshal.Cast<byte, T>(writableRegion.Memory.Span)[0];
|
return ref MemoryMarshal.Cast<byte, T>(writableRegion.Memory.Span)[0];
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
|
||||||
|
|
||||||
handles[0] = sessionHandle;
|
handles[0] = sessionHandle;
|
||||||
|
|
||||||
var tlsSpan = HorizonStatic.AddressSpace.GetSpan(HorizonStatic.ThreadContext.TlsAddress, TlsMessageBufferSize);
|
ReadOnlySpan<byte> tlsSpan = HorizonStatic.AddressSpace.GetSpan(HorizonStatic.ThreadContext.TlsAddress, TlsMessageBufferSize);
|
||||||
|
|
||||||
if (messageBuffer == tlsSpan)
|
if (messageBuffer == tlsSpan)
|
||||||
{
|
{
|
||||||
|
@ -56,7 +56,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
|
||||||
|
|
||||||
private static Result ReplyImpl(int sessionHandle, ReadOnlySpan<byte> messageBuffer)
|
private static Result ReplyImpl(int sessionHandle, ReadOnlySpan<byte> messageBuffer)
|
||||||
{
|
{
|
||||||
var tlsSpan = HorizonStatic.AddressSpace.GetSpan(HorizonStatic.ThreadContext.TlsAddress, TlsMessageBufferSize);
|
ReadOnlySpan<byte> tlsSpan = HorizonStatic.AddressSpace.GetSpan(HorizonStatic.ThreadContext.TlsAddress, TlsMessageBufferSize);
|
||||||
|
|
||||||
if (messageBuffer == tlsSpan)
|
if (messageBuffer == tlsSpan)
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
|
||||||
{
|
{
|
||||||
objectId = 0;
|
objectId = 0;
|
||||||
|
|
||||||
var domain = _manager.Domain.AllocateDomainServiceObject();
|
DomainServiceObject domain = _manager.Domain.AllocateDomainServiceObject();
|
||||||
if (domain == null)
|
if (domain == null)
|
||||||
{
|
{
|
||||||
return HipcResult.OutOfDomains;
|
return HipcResult.OutOfDomains;
|
||||||
|
@ -66,7 +66,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
|
||||||
return HipcResult.TargetNotDomain;
|
return HipcResult.TargetNotDomain;
|
||||||
}
|
}
|
||||||
|
|
||||||
var obj = domain.GetObject(objectId);
|
ServiceObjectHolder obj = domain.GetObject(objectId);
|
||||||
if (obj == null)
|
if (obj == null)
|
||||||
{
|
{
|
||||||
return HipcResult.DomainObjectNotFound;
|
return HipcResult.DomainObjectNotFound;
|
||||||
|
@ -100,7 +100,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
|
||||||
{
|
{
|
||||||
clientHandle = 0;
|
clientHandle = 0;
|
||||||
|
|
||||||
var clone = _session.ServiceObjectHolder.Clone();
|
ServiceObjectHolder clone = _session.ServiceObjectHolder.Clone();
|
||||||
if (clone == null)
|
if (clone == null)
|
||||||
{
|
{
|
||||||
return HipcResult.DomainObjectNotFound;
|
return HipcResult.DomainObjectNotFound;
|
||||||
|
|
|
@ -2,6 +2,7 @@ using Ryujinx.Horizon.Common;
|
||||||
using Ryujinx.Horizon.Sdk.OsTypes;
|
using Ryujinx.Horizon.Sdk.OsTypes;
|
||||||
using Ryujinx.Horizon.Sdk.Sf.Cmif;
|
using Ryujinx.Horizon.Sdk.Sf.Cmif;
|
||||||
using Ryujinx.Horizon.Sdk.Sm;
|
using Ryujinx.Horizon.Sdk.Sm;
|
||||||
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
@ -257,14 +258,14 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
|
||||||
|
|
||||||
ServerSession session = (ServerSession)holder;
|
ServerSession session = (ServerSession)holder;
|
||||||
|
|
||||||
using var tlsMessage = HorizonStatic.AddressSpace.GetWritableRegion(HorizonStatic.ThreadContext.TlsAddress, Api.TlsMessageBufferSize);
|
using WritableRegion tlsMessage = HorizonStatic.AddressSpace.GetWritableRegion(HorizonStatic.ThreadContext.TlsAddress, Api.TlsMessageBufferSize);
|
||||||
|
|
||||||
Result result;
|
Result result;
|
||||||
|
|
||||||
if (_canDeferInvokeRequest)
|
if (_canDeferInvokeRequest)
|
||||||
{
|
{
|
||||||
// If the request is deferred, we save the message on a temporary buffer to process it later.
|
// If the request is deferred, we save the message on a temporary buffer to process it later.
|
||||||
using var savedMessage = HorizonStatic.AddressSpace.GetWritableRegion(session.SavedMessage.Address, (int)session.SavedMessage.Size);
|
using WritableRegion savedMessage = HorizonStatic.AddressSpace.GetWritableRegion(session.SavedMessage.Address, (int)session.SavedMessage.Size);
|
||||||
|
|
||||||
DebugUtil.Assert(tlsMessage.Memory.Length == savedMessage.Memory.Length);
|
DebugUtil.Assert(tlsMessage.Memory.Length == savedMessage.Memory.Length);
|
||||||
|
|
||||||
|
|
|
@ -186,7 +186,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
|
||||||
{
|
{
|
||||||
CommandType commandType = GetCmifCommandType(inMessage);
|
CommandType commandType = GetCmifCommandType(inMessage);
|
||||||
|
|
||||||
using var _ = new ScopedInlineContextChange(GetInlineContext(commandType, inMessage));
|
using ScopedInlineContextChange _ = new ScopedInlineContextChange(GetInlineContext(commandType, inMessage));
|
||||||
|
|
||||||
return commandType switch
|
return commandType switch
|
||||||
{
|
{
|
||||||
|
@ -282,7 +282,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
|
||||||
return HipcResult.InvalidRequestSize;
|
return HipcResult.InvalidRequestSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
var dispatchCtx = new ServiceDispatchContext
|
ServiceDispatchContext dispatchCtx = new ServiceDispatchContext
|
||||||
{
|
{
|
||||||
ServiceObject = objectHolder.ServiceObject,
|
ServiceObject = objectHolder.ServiceObject,
|
||||||
Manager = this,
|
Manager = this,
|
||||||
|
@ -312,7 +312,7 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc
|
||||||
|
|
||||||
result = Api.Reply(session.SessionHandle, outMessage);
|
result = Api.Reply(session.SessionHandle, outMessage);
|
||||||
|
|
||||||
ref var handlesToClose = ref dispatchCtx.HandlesToClose;
|
ref HandlesToClose handlesToClose = ref dispatchCtx.HandlesToClose;
|
||||||
|
|
||||||
for (int i = 0; i < handlesToClose.Count; i++)
|
for (int i = 0; i < handlesToClose.Count; i++)
|
||||||
{
|
{
|
||||||
|
|
|
@ -46,7 +46,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
|
||||||
switch (argInfo.Type)
|
switch (argInfo.Type)
|
||||||
{
|
{
|
||||||
case CommandArgType.Buffer:
|
case CommandArgType.Buffer:
|
||||||
var flags = argInfo.BufferFlags;
|
HipcBufferFlags flags = argInfo.BufferFlags;
|
||||||
|
|
||||||
if (flags.HasFlag(HipcBufferFlags.In))
|
if (flags.HasFlag(HipcBufferFlags.In))
|
||||||
{
|
{
|
||||||
|
@ -146,7 +146,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var flags = _args[i].BufferFlags;
|
HipcBufferFlags flags = _args[i].BufferFlags;
|
||||||
bool isMapAlias;
|
bool isMapAlias;
|
||||||
|
|
||||||
if (flags.HasFlag(HipcBufferFlags.MapAlias))
|
if (flags.HasFlag(HipcBufferFlags.MapAlias))
|
||||||
|
@ -159,7 +159,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
|
||||||
}
|
}
|
||||||
else /* if (flags.HasFlag(HipcBufferFlags.HipcAutoSelect)) */
|
else /* if (flags.HasFlag(HipcBufferFlags.HipcAutoSelect)) */
|
||||||
{
|
{
|
||||||
var descriptor = flags.HasFlag(HipcBufferFlags.In)
|
HipcBufferDescriptor descriptor = flags.HasFlag(HipcBufferFlags.In)
|
||||||
? context.Request.Data.SendBuffers[sendMapAliasIndex]
|
? context.Request.Data.SendBuffers[sendMapAliasIndex]
|
||||||
: context.Request.Data.ReceiveBuffers[recvMapAliasIndex];
|
: context.Request.Data.ReceiveBuffers[recvMapAliasIndex];
|
||||||
|
|
||||||
|
@ -170,7 +170,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
|
||||||
|
|
||||||
if (isMapAlias)
|
if (isMapAlias)
|
||||||
{
|
{
|
||||||
var descriptor = flags.HasFlag(HipcBufferFlags.In)
|
HipcBufferDescriptor descriptor = flags.HasFlag(HipcBufferFlags.In)
|
||||||
? context.Request.Data.SendBuffers[sendMapAliasIndex++]
|
? context.Request.Data.SendBuffers[sendMapAliasIndex++]
|
||||||
: context.Request.Data.ReceiveBuffers[recvMapAliasIndex++];
|
: context.Request.Data.ReceiveBuffers[recvMapAliasIndex++];
|
||||||
|
|
||||||
|
@ -185,7 +185,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
|
||||||
{
|
{
|
||||||
if (flags.HasFlag(HipcBufferFlags.In))
|
if (flags.HasFlag(HipcBufferFlags.In))
|
||||||
{
|
{
|
||||||
var descriptor = context.Request.Data.SendStatics[sendPointerIndex++];
|
HipcStaticDescriptor descriptor = context.Request.Data.SendStatics[sendPointerIndex++];
|
||||||
ulong address = descriptor.Address;
|
ulong address = descriptor.Address;
|
||||||
ulong size = descriptor.Size;
|
ulong size = descriptor.Size;
|
||||||
|
|
||||||
|
@ -206,8 +206,8 @@ namespace Ryujinx.Horizon.Sdk.Sf
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var data = MemoryMarshal.Cast<uint, byte>(context.Request.Data.DataWordsPadded);
|
Span<byte> data = MemoryMarshal.Cast<uint, byte>(context.Request.Data.DataWordsPadded);
|
||||||
var recvPointerSizes = MemoryMarshal.Cast<byte, ushort>(data[runtimeMetadata.UnfixedOutPointerSizeOffset..]);
|
Span<ushort> recvPointerSizes = MemoryMarshal.Cast<byte, ushort>(data[runtimeMetadata.UnfixedOutPointerSizeOffset..]);
|
||||||
|
|
||||||
size = recvPointerSizes[unfixedRecvPointerIndex++];
|
size = recvPointerSizes[unfixedRecvPointerIndex++];
|
||||||
}
|
}
|
||||||
|
@ -257,13 +257,13 @@ namespace Ryujinx.Horizon.Sdk.Sf
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var flags = _args[i].BufferFlags;
|
HipcBufferFlags flags = _args[i].BufferFlags;
|
||||||
if (!flags.HasFlag(HipcBufferFlags.Out))
|
if (!flags.HasFlag(HipcBufferFlags.Out))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var buffer = _bufferRanges[i];
|
PointerAndSize buffer = _bufferRanges[i];
|
||||||
|
|
||||||
if (flags.HasFlag(HipcBufferFlags.Pointer))
|
if (flags.HasFlag(HipcBufferFlags.Pointer))
|
||||||
{
|
{
|
||||||
|
@ -303,7 +303,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
|
||||||
|
|
||||||
public override Result PrepareForProcess(ref ServiceDispatchContext context, ServerMessageRuntimeMetadata runtimeMetadata)
|
public override Result PrepareForProcess(ref ServiceDispatchContext context, ServerMessageRuntimeMetadata runtimeMetadata)
|
||||||
{
|
{
|
||||||
ref var meta = ref context.Request.Meta;
|
ref HipcMetadata meta = ref context.Request.Meta;
|
||||||
bool requestValid = true;
|
bool requestValid = true;
|
||||||
requestValid &= meta.SendPid == _hasInProcessIdHolder;
|
requestValid &= meta.SendPid == _hasInProcessIdHolder;
|
||||||
requestValid &= meta.SendStaticsCount == _inPointerBuffersCount;
|
requestValid &= meta.SendStaticsCount == _inPointerBuffersCount;
|
||||||
|
@ -346,7 +346,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
|
||||||
}
|
}
|
||||||
|
|
||||||
int index = inObjectIndex++;
|
int index = inObjectIndex++;
|
||||||
var inObject = inObjects[index];
|
ServiceObjectHolder inObject = inObjects[index];
|
||||||
|
|
||||||
objects[index] = inObject?.ServiceObject;
|
objects[index] = inObject?.ServiceObject;
|
||||||
}
|
}
|
||||||
|
@ -362,7 +362,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
|
||||||
public override HipcMessageData PrepareForReply(scoped ref ServiceDispatchContext context, out Span<byte> outRawData, ServerMessageRuntimeMetadata runtimeMetadata)
|
public override HipcMessageData PrepareForReply(scoped ref ServiceDispatchContext context, out Span<byte> outRawData, ServerMessageRuntimeMetadata runtimeMetadata)
|
||||||
{
|
{
|
||||||
int rawDataSize = OutRawDataSize + runtimeMetadata.OutHeadersSize;
|
int rawDataSize = OutRawDataSize + runtimeMetadata.OutHeadersSize;
|
||||||
var response = HipcMessage.WriteResponse(
|
HipcMessageData response = HipcMessage.WriteResponse(
|
||||||
context.OutMessageBuffer,
|
context.OutMessageBuffer,
|
||||||
_outPointerBuffersCount,
|
_outPointerBuffersCount,
|
||||||
(BitUtils.AlignUp(rawDataSize, 4) + 0x10) / sizeof(uint),
|
(BitUtils.AlignUp(rawDataSize, 4) + 0x10) / sizeof(uint),
|
||||||
|
@ -376,7 +376,7 @@ namespace Ryujinx.Horizon.Sdk.Sf
|
||||||
public override void PrepareForErrorReply(scoped ref ServiceDispatchContext context, out Span<byte> outRawData, ServerMessageRuntimeMetadata runtimeMetadata)
|
public override void PrepareForErrorReply(scoped ref ServiceDispatchContext context, out Span<byte> outRawData, ServerMessageRuntimeMetadata runtimeMetadata)
|
||||||
{
|
{
|
||||||
int rawDataSize = runtimeMetadata.OutHeadersSize;
|
int rawDataSize = runtimeMetadata.OutHeadersSize;
|
||||||
var response = HipcMessage.WriteResponse(
|
HipcMessageData response = HipcMessage.WriteResponse(
|
||||||
context.OutMessageBuffer,
|
context.OutMessageBuffer,
|
||||||
0,
|
0,
|
||||||
(BitUtils.AlignUp(rawDataSize, 4) + 0x10) / sizeof(uint),
|
(BitUtils.AlignUp(rawDataSize, 4) + 0x10) / sizeof(uint),
|
||||||
|
|
|
@ -120,7 +120,7 @@ namespace Ryujinx.Horizon.Sm.Impl
|
||||||
return SmResult.NotRegistered;
|
return SmResult.NotRegistered;
|
||||||
}
|
}
|
||||||
|
|
||||||
ref var serviceInfo = ref _services[serviceIndex];
|
ref ServiceInfo serviceInfo = ref _services[serviceIndex];
|
||||||
if (serviceInfo.OwnerProcessId != processId)
|
if (serviceInfo.OwnerProcessId != processId)
|
||||||
{
|
{
|
||||||
return SmResult.NotAllowed;
|
return SmResult.NotAllowed;
|
||||||
|
|
Loading…
Add table
Reference in a new issue