aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Core/OsHle
diff options
context:
space:
mode:
authorgdkchan <gab.dark.100@gmail.com>2018-05-07 15:53:23 -0300
committerGitHub <noreply@github.com>2018-05-07 15:53:23 -0300
commit34037701c708cb70bbf44dea71ee0912f7b4102b (patch)
treeca4cf2bde85dea48af12033b8d0446f17b611f4f /Ryujinx.Core/OsHle
parent4419e8d6b43432eae94a3a9304f7df22b34738a8 (diff)
NvServices refactoring (#120)
* Initial implementation of NvMap/NvHostCtrl * More work on NvHostCtrl * Refactoring of nvservices, move GPU Vmm, make Vmm per-process, refactor most gpu devices, move Gpu to Core, fix CbBind * Implement GetGpuTime, support CancelSynchronization, fix issue on InsertWaitingMutex, proper double buffering support (again, not working properly for commercial games, only hb) * Try to fix perf regression reading/writing textures, moved syncpts and events to a UserCtx class, delete global state when the process exits, other minor tweaks * Remove now unused code, add comment about probably wrong result codes
Diffstat (limited to 'Ryujinx.Core/OsHle')
-rw-r--r--Ryujinx.Core/OsHle/Horizon.cs4
-rw-r--r--Ryujinx.Core/OsHle/Ipc/IpcHandler.cs4
-rw-r--r--Ryujinx.Core/OsHle/Ipc/IpcMessage.cs25
-rw-r--r--Ryujinx.Core/OsHle/Kernel/KernelErr.cs2
-rw-r--r--Ryujinx.Core/OsHle/Kernel/SvcHandler.cs21
-rw-r--r--Ryujinx.Core/OsHle/Kernel/SvcSystem.cs85
-rw-r--r--Ryujinx.Core/OsHle/Kernel/SvcThreadSync.cs6
-rw-r--r--Ryujinx.Core/OsHle/MemoryAllocator.cs12
-rw-r--r--Ryujinx.Core/OsHle/Process.cs6
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/INvDrvServices.cs728
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvChNvMap.cs31
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvGpuAS/NvGpuASAllocSpace.cs11
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvGpuAS/NvGpuASIoctl.cs245
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvGpuAS/NvGpuASMapBufferEx.cs13
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvGpuAS/NvGpuASRemap.cs12
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvGpuAS/NvGpuASUnmapBuffer.cs7
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuGetCharacteristics.cs43
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuIoctl.cs155
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvHelper.cs10
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs130
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvHostChannel/NvHostChannelSubmitGpfifo.cs11
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs355
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtRead.cs8
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWait.cs9
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWaitEx.cs10
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlUserCtx.cs19
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostEvent.cs10
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostEventState.cs10
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostSyncPt.cs107
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvMap.cs20
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapAlloc.cs12
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapCreate.cs8
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapFree.cs11
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapFromId.cs8
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapGetId.cs8
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapHandle.cs37
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapHandleParam.cs12
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapIoctl.cs302
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapParam.cs9
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvMapFb.cs40
-rw-r--r--Ryujinx.Core/OsHle/Services/Nv/NvResult.cs13
-rw-r--r--Ryujinx.Core/OsHle/Services/Vi/NvFlinger.cs42
-rw-r--r--Ryujinx.Core/OsHle/Utilities/IntUtils.cs15
-rw-r--r--Ryujinx.Core/OsHle/Utilities/MemReader.cs44
-rw-r--r--Ryujinx.Core/OsHle/Utilities/MemWriter.cs38
45 files changed, 1779 insertions, 929 deletions
diff --git a/Ryujinx.Core/OsHle/Horizon.cs b/Ryujinx.Core/OsHle/Horizon.cs
index 0cc3d815..1d4098c7 100644
--- a/Ryujinx.Core/OsHle/Horizon.cs
+++ b/Ryujinx.Core/OsHle/Horizon.cs
@@ -20,6 +20,8 @@ namespace Ryujinx.Core.OsHle
public SystemStateMgr SystemState { get; private set; }
+ internal MemoryAllocator Allocator { get; private set; }
+
internal HSharedMem HidSharedMem { get; private set; }
internal HSharedMem FontSharedMem { get; private set; }
@@ -35,6 +37,8 @@ namespace Ryujinx.Core.OsHle
SystemState = new SystemStateMgr();
+ Allocator = new MemoryAllocator();
+
HidSharedMem = new HSharedMem();
FontSharedMem = new HSharedMem();
diff --git a/Ryujinx.Core/OsHle/Ipc/IpcHandler.cs b/Ryujinx.Core/OsHle/Ipc/IpcHandler.cs
index 42322d41..420890eb 100644
--- a/Ryujinx.Core/OsHle/Ipc/IpcHandler.cs
+++ b/Ryujinx.Core/OsHle/Ipc/IpcHandler.cs
@@ -7,7 +7,7 @@ namespace Ryujinx.Core.OsHle.Ipc
{
static class IpcHandler
{
- public static void IpcCall(
+ public static long IpcCall(
Switch Ns,
Process Process,
AMemory Memory,
@@ -94,6 +94,8 @@ namespace Ryujinx.Core.OsHle.Ipc
AMemoryHelper.WriteBytes(Memory, CmdPtr, Response.GetBytes(CmdPtr));
}
+
+ return 0;
}
private static IpcMessage FillResponse(IpcMessage Response, long Result, params int[] Values)
diff --git a/Ryujinx.Core/OsHle/Ipc/IpcMessage.cs b/Ryujinx.Core/OsHle/Ipc/IpcMessage.cs
index d81f44bd..afcbb3a4 100644
--- a/Ryujinx.Core/OsHle/Ipc/IpcMessage.cs
+++ b/Ryujinx.Core/OsHle/Ipc/IpcMessage.cs
@@ -174,31 +174,6 @@ namespace Ryujinx.Core.OsHle.Ipc
return 0;
}
- public long GetSendBuffPtr()
- {
- if (SendBuff.Count > 0 && SendBuff[0].Size != 0)
- {
- return SendBuff[0].Position;
- }
-
- if (PtrBuff.Count > 0 && PtrBuff[0].Size != 0)
- {
- return PtrBuff[0].Position;
- }
-
- if (ReceiveBuff.Count > 0 && ReceiveBuff[0].Size != 0)
- {
- return ReceiveBuff[0].Position;
- }
-
- if (RecvListBuff.Count > 0 && RecvListBuff[0].Size != 0)
- {
- return RecvListBuff[0].Position;
- }
-
- return -1;
- }
-
public long GetBufferType0x21Position()
{
if (PtrBuff.Count > 0 && PtrBuff[0].Position != 0)
diff --git a/Ryujinx.Core/OsHle/Kernel/KernelErr.cs b/Ryujinx.Core/OsHle/Kernel/KernelErr.cs
index b568405b..87f9cf3b 100644
--- a/Ryujinx.Core/OsHle/Kernel/KernelErr.cs
+++ b/Ryujinx.Core/OsHle/Kernel/KernelErr.cs
@@ -7,6 +7,8 @@ namespace Ryujinx.Core.OsHle.Kernel
public const int InvalidMemRange = 110;
public const int InvalidHandle = 114;
public const int Timeout = 117;
+ public const int Canceled = 118;
+ public const int CountOutOfRange = 119;
public const int InvalidInfo = 120;
}
} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Kernel/SvcHandler.cs b/Ryujinx.Core/OsHle/Kernel/SvcHandler.cs
index e855b77d..1874360b 100644
--- a/Ryujinx.Core/OsHle/Kernel/SvcHandler.cs
+++ b/Ryujinx.Core/OsHle/Kernel/SvcHandler.cs
@@ -5,6 +5,8 @@ using Ryujinx.Core.Logging;
using Ryujinx.Core.OsHle.Handles;
using System;
using System.Collections.Generic;
+using System.Collections.Concurrent;
+using System.Threading;
namespace Ryujinx.Core.OsHle.Kernel
{
@@ -18,12 +20,16 @@ namespace Ryujinx.Core.OsHle.Kernel
private Process Process;
private AMemory Memory;
+ private ConcurrentDictionary<KThread, AutoResetEvent> SyncWaits;
+
private object CondVarLock;
private HashSet<(HSharedMem, long)> MappedSharedMems;
private ulong CurrentHeapSize;
+ private const uint SelfHandle = 0xffff8001;
+
private static Random Rng;
public SvcHandler(Switch Ns, Process Process)
@@ -51,6 +57,7 @@ namespace Ryujinx.Core.OsHle.Kernel
{ 0x16, SvcCloseHandle },
{ 0x17, SvcResetSignal },
{ 0x18, SvcWaitSynchronization },
+ { 0x19, SvcCancelSynchronization },
{ 0x1a, SvcArbitrateLock },
{ 0x1b, SvcArbitrateUnlock },
{ 0x1c, SvcWaitProcessWideKeyAtomic },
@@ -70,6 +77,8 @@ namespace Ryujinx.Core.OsHle.Kernel
this.Process = Process;
this.Memory = Process.Memory;
+ SyncWaits = new ConcurrentDictionary<KThread, AutoResetEvent>();
+
CondVarLock = new object();
MappedSharedMems = new HashSet<(HSharedMem, long)>();
@@ -100,6 +109,18 @@ namespace Ryujinx.Core.OsHle.Kernel
}
}
+ private KThread GetThread(long Tpidr, int Handle)
+ {
+ if ((uint)Handle == SelfHandle)
+ {
+ return Process.GetThread(Tpidr);
+ }
+ else
+ {
+ return Process.HandleTable.GetData<KThread>(Handle);
+ }
+ }
+
public void Dispose()
{
Dispose(true);
diff --git a/Ryujinx.Core/OsHle/Kernel/SvcSystem.cs b/Ryujinx.Core/OsHle/Kernel/SvcSystem.cs
index 601b211c..e5b080a8 100644
--- a/Ryujinx.Core/OsHle/Kernel/SvcSystem.cs
+++ b/Ryujinx.Core/OsHle/Kernel/SvcSystem.cs
@@ -40,7 +40,7 @@ namespace Ryujinx.Core.OsHle.Kernel
if (Obj == null)
{
- Ns.Log.PrintWarning(LogClass.KernelSvc, $"Tried to CloseHandle on invalid handle 0x{Handle:x8}!");
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid handle 0x{Handle:x8}!");
ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
@@ -88,9 +88,21 @@ namespace Ryujinx.Core.OsHle.Kernel
int HandlesCount = (int)ThreadState.X2;
ulong Timeout = ThreadState.X3;
+ Ns.Log.PrintDebug(LogClass.KernelSvc,
+ "HandlesPtr = " + HandlesPtr .ToString("x16") + ", " +
+ "HandlesCount = " + HandlesCount.ToString("x8") + ", " +
+ "Timeout = " + Timeout .ToString("x16"));
+
+ if ((uint)HandlesCount > 0x40)
+ {
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.CountOutOfRange);
+
+ return;
+ }
+
KThread CurrThread = Process.GetThread(ThreadState.Tpidr);
- WaitHandle[] Handles = new WaitHandle[HandlesCount];
+ WaitHandle[] Handles = new WaitHandle[HandlesCount + 1];
for (int Index = 0; Index < HandlesCount; Index++)
{
@@ -110,34 +122,73 @@ namespace Ryujinx.Core.OsHle.Kernel
Handles[Index] = SyncObj.WaitEvent;
}
- Process.Scheduler.Suspend(CurrThread.ProcessorId);
+ using (AutoResetEvent WaitEvent = new AutoResetEvent(false))
+ {
+ if (!SyncWaits.TryAdd(CurrThread, WaitEvent))
+ {
+ throw new InvalidOperationException();
+ }
+
+ Handles[HandlesCount] = WaitEvent;
- int HandleIndex;
+ Process.Scheduler.Suspend(CurrThread.ProcessorId);
- ulong Result = 0;
+ int HandleIndex;
- if (Timeout != ulong.MaxValue)
- {
- HandleIndex = WaitHandle.WaitAny(Handles, NsTimeConverter.GetTimeMs(Timeout));
+ ulong Result = 0;
+
+ if (Timeout != ulong.MaxValue)
+ {
+ HandleIndex = WaitHandle.WaitAny(Handles, NsTimeConverter.GetTimeMs(Timeout));
+ }
+ else
+ {
+ HandleIndex = WaitHandle.WaitAny(Handles);
+ }
if (HandleIndex == WaitHandle.WaitTimeout)
{
Result = MakeError(ErrorModule.Kernel, KernelErr.Timeout);
}
+ else if (HandleIndex == HandlesCount)
+ {
+ Result = MakeError(ErrorModule.Kernel, KernelErr.Canceled);
+ }
+
+ SyncWaits.TryRemove(CurrThread, out _);
+
+ Process.Scheduler.Resume(CurrThread);
+
+ ThreadState.X0 = Result;
+
+ if (Result == 0)
+ {
+ ThreadState.X1 = (ulong)HandleIndex;
+ }
}
- else
+ }
+
+ private void SvcCancelSynchronization(AThreadState ThreadState)
+ {
+ int ThreadHandle = (int)ThreadState.X0;
+
+ KThread Thread = GetThread(ThreadState.Tpidr, ThreadHandle);
+
+ if (Thread == null)
{
- HandleIndex = WaitHandle.WaitAny(Handles);
- }
+ Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid thread handle 0x{ThreadHandle:x8}!");
- Process.Scheduler.Resume(CurrThread);
+ ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
- ThreadState.X0 = Result;
+ return;
+ }
- if (Result == 0)
+ if (SyncWaits.TryRemove(Thread, out AutoResetEvent WaitEvent))
{
- ThreadState.X1 = (ulong)HandleIndex;
+ WaitEvent.Set();
}
+
+ ThreadState.X0 = 0;
}
private void SvcGetSystemTick(AThreadState ThreadState)
@@ -190,13 +241,13 @@ namespace Ryujinx.Core.OsHle.Kernel
IpcMessage Cmd = new IpcMessage(CmdData, CmdPtr);
- IpcHandler.IpcCall(Ns, Process, Memory, Session, Cmd, CmdPtr);
+ long Result = IpcHandler.IpcCall(Ns, Process, Memory, Session, Cmd, CmdPtr);
Thread.Yield();
Process.Scheduler.Resume(CurrThread);
- ThreadState.X0 = 0;
+ ThreadState.X0 = (ulong)Result;
}
else
{
diff --git a/Ryujinx.Core/OsHle/Kernel/SvcThreadSync.cs b/Ryujinx.Core/OsHle/Kernel/SvcThreadSync.cs
index e382cf75..57608cda 100644
--- a/Ryujinx.Core/OsHle/Kernel/SvcThreadSync.cs
+++ b/Ryujinx.Core/OsHle/Kernel/SvcThreadSync.cs
@@ -191,6 +191,8 @@ namespace Ryujinx.Core.OsHle.Kernel
InsertWaitingMutexThread(OwnerThreadHandle, WaitThread);
+ Ns.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state...");
+
Process.Scheduler.EnterWait(CurrThread);
}
@@ -297,6 +299,8 @@ namespace Ryujinx.Core.OsHle.Kernel
}
}
+ Ns.Log.PrintDebug(LogClass.KernelSvc, "Entering wait state...");
+
if (Timeout != ulong.MaxValue)
{
return Process.Scheduler.EnterWait(WaitThread, NsTimeConverter.GetTimeMs(Timeout));
@@ -407,7 +411,7 @@ namespace Ryujinx.Core.OsHle.Kernel
if (CurrThread != WaitThread)
{
- if (WaitThread.NextCondVarThread != null)
+ if (WaitThread.NextMutexThread != null)
{
throw new InvalidOperationException();
}
diff --git a/Ryujinx.Core/OsHle/MemoryAllocator.cs b/Ryujinx.Core/OsHle/MemoryAllocator.cs
new file mode 100644
index 00000000..e57f5264
--- /dev/null
+++ b/Ryujinx.Core/OsHle/MemoryAllocator.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Ryujinx.Core.OsHle
+{
+ class MemoryAllocator
+ {
+ public bool TryAllocate(long Size, out long Address)
+ {
+ throw new NotImplementedException();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Process.cs b/Ryujinx.Core/OsHle/Process.cs
index b8c08856..3ccbc016 100644
--- a/Ryujinx.Core/OsHle/Process.cs
+++ b/Ryujinx.Core/OsHle/Process.cs
@@ -410,11 +410,7 @@ namespace Ryujinx.Core.OsHle
}
}
- INvDrvServices.Fds.DeleteProcess(this);
-
- INvDrvServices.NvMaps .DeleteProcess(this);
- INvDrvServices.NvMapsById.DeleteProcess(this);
- INvDrvServices.NvMapsFb .DeleteProcess(this);
+ INvDrvServices.UnloadProcess(this);
AppletState.Dispose();
diff --git a/Ryujinx.Core/OsHle/Services/Nv/INvDrvServices.cs b/Ryujinx.Core/OsHle/Services/Nv/INvDrvServices.cs
index 2652724d..00209f9d 100644
--- a/Ryujinx.Core/OsHle/Services/Nv/INvDrvServices.cs
+++ b/Ryujinx.Core/OsHle/Services/Nv/INvDrvServices.cs
@@ -2,30 +2,36 @@ using ChocolArm64.Memory;
using Ryujinx.Core.Logging;
using Ryujinx.Core.OsHle.Handles;
using Ryujinx.Core.OsHle.Ipc;
-using Ryujinx.Core.OsHle.Utilities;
-using Ryujinx.Graphics.Gpu;
+using Ryujinx.Core.OsHle.Services.Nv.NvGpuAS;
+using Ryujinx.Core.OsHle.Services.Nv.NvGpuGpu;
+using Ryujinx.Core.OsHle.Services.Nv.NvHostChannel;
+using Ryujinx.Core.OsHle.Services.Nv.NvHostCtrl;
+using Ryujinx.Core.OsHle.Services.Nv.NvMap;
using System;
using System.Collections.Generic;
-using System.IO;
namespace Ryujinx.Core.OsHle.Services.Nv
{
class INvDrvServices : IpcService, IDisposable
{
- private delegate long ServiceProcessIoctl(ServiceCtx Context);
+ private delegate int IoctlProcessor(ServiceCtx Context, int Cmd);
private Dictionary<int, ServiceProcessRequest> m_Commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => m_Commands;
- private Dictionary<(string, int), ServiceProcessIoctl> IoctlCmds;
+ private static Dictionary<string, IoctlProcessor> IoctlProcessors =
+ new Dictionary<string, IoctlProcessor>()
+ {
+ { "/dev/nvhost-as-gpu", ProcessIoctlNvGpuAS },
+ { "/dev/nvhost-ctrl", ProcessIoctlNvHostCtrl },
+ { "/dev/nvhost-ctrl-gpu", ProcessIoctlNvGpuGpu },
+ { "/dev/nvhost-gpu", ProcessIoctlNvHostChannel },
+ { "/dev/nvmap", ProcessIoctlNvMap }
+ };
public static GlobalStateTable Fds { get; private set; }
- public static GlobalStateTable NvMaps { get; private set; }
- public static GlobalStateTable NvMapsById { get; private set; }
- public static GlobalStateTable NvMapsFb { get; private set; }
-
private KEvent Event;
public INvDrvServices()
@@ -37,42 +43,7 @@ namespace Ryujinx.Core.OsHle.Services.Nv
{ 2, Close },
{ 3, Initialize },
{ 4, QueryEvent },
- { 8, SetClientPid },
- };
-
- IoctlCmds = new Dictionary<(string, int), ServiceProcessIoctl>()
- {
- { ("/dev/nvhost-as-gpu", 0x4101), NvGpuAsIoctlBindChannel },
- { ("/dev/nvhost-as-gpu", 0x4102), NvGpuAsIoctlAllocSpace },
- { ("/dev/nvhost-as-gpu", 0x4105), NvGpuAsIoctlUnmap },
- { ("/dev/nvhost-as-gpu", 0x4106), NvGpuAsIoctlMapBufferEx },
- { ("/dev/nvhost-as-gpu", 0x4108), NvGpuAsIoctlGetVaRegions },
- { ("/dev/nvhost-as-gpu", 0x4109), NvGpuAsIoctlInitializeEx },
- { ("/dev/nvhost-as-gpu", 0x4114), NvGpuAsIoctlRemap },
- { ("/dev/nvhost-ctrl", 0x001b), NvHostIoctlCtrlGetConfig },
- { ("/dev/nvhost-ctrl", 0x001d), NvHostIoctlCtrlEventWait },
- { ("/dev/nvhost-ctrl", 0x001e), NvHostIoctlCtrlEventWaitAsync },
- { ("/dev/nvhost-ctrl", 0x001f), NvHostIoctlCtrlEventRegister },
- { ("/dev/nvhost-ctrl-gpu", 0x4701), NvGpuIoctlZcullGetCtxSize },
- { ("/dev/nvhost-ctrl-gpu", 0x4702), NvGpuIoctlZcullGetInfo },
- { ("/dev/nvhost-ctrl-gpu", 0x4703), NvGpuIoctlZbcSetTable },
- { ("/dev/nvhost-ctrl-gpu", 0x4705), NvGpuIoctlGetCharacteristics },
- { ("/dev/nvhost-ctrl-gpu", 0x4706), NvGpuIoctlGetTpcMasks },
- { ("/dev/nvhost-ctrl-gpu", 0x4714), NvGpuIoctlZbcGetActiveSlotMask },
- { ("/dev/nvhost-gpu", 0x4714), NvMapIoctlChannelSetUserData },
- { ("/dev/nvhost-gpu", 0x4801), NvMapIoctlChannelSetNvMap },
- { ("/dev/nvhost-gpu", 0x4808), NvMapIoctlChannelSubmitGpFifo },
- { ("/dev/nvhost-gpu", 0x4809), NvMapIoctlChannelAllocObjCtx },
- { ("/dev/nvhost-gpu", 0x480b), NvMapIoctlChannelZcullBind },
- { ("/dev/nvhost-gpu", 0x480c), NvMapIoctlChannelSetErrorNotifier },
- { ("/dev/nvhost-gpu", 0x480d), NvMapIoctlChannelSetPriority },
- { ("/dev/nvhost-gpu", 0x481a), NvMapIoctlChannelAllocGpFifoEx2 },
- { ("/dev/nvmap", 0x0101), NvMapIocCreate },
- { ("/dev/nvmap", 0x0103), NvMapIocFromId },
- { ("/dev/nvmap", 0x0104), NvMapIocAlloc },
- { ("/dev/nvmap", 0x0105), NvMapIocFree },
- { ("/dev/nvmap", 0x0109), NvMapIocParam },
- { ("/dev/nvmap", 0x010e), NvMapIocGetId },
+ { 8, SetClientPid }
};
Event = new KEvent();
@@ -81,10 +52,6 @@ namespace Ryujinx.Core.OsHle.Services.Nv
static INvDrvServices()
{
Fds = new GlobalStateTable();
-
- NvMaps = new GlobalStateTable();
- NvMapsById = new GlobalStateTable();
- NvMapsFb = new GlobalStateTable();
}
public long Open(ServiceCtx Context)
@@ -104,22 +71,25 @@ namespace Ryujinx.Core.OsHle.Services.Nv
public long Ioctl(ServiceCtx Context)
{
int Fd = Context.RequestData.ReadInt32();
- int Cmd = Context.RequestData.ReadInt32() & 0xffff;
+ int Cmd = Context.RequestData.ReadInt32();
NvFd FdData = Fds.GetData<NvFd>(Context.Process, Fd);
- long Position = Context.Request.GetSendBuffPtr();
-
- Context.ResponseData.Write(0);
+ int Result;
- if (IoctlCmds.TryGetValue((FdData.Name, Cmd), out ServiceProcessIoctl ProcReq))
+ if (IoctlProcessors.TryGetValue(FdData.Name, out IoctlProcessor Process))
{
- return ProcReq(Context);
+ Result = Process(Context, Cmd);
}
else
{
throw new NotImplementedException($"{FdData.Name} {Cmd:x4}");
}
+
+ //TODO: Verify if the error codes needs to be translated.
+ Context.ResponseData.Write(Result);
+
+ return 0;
}
public long Close(ServiceCtx Context)
@@ -138,9 +108,9 @@ namespace Ryujinx.Core.OsHle.Services.Nv
long TransferMemSize = Context.RequestData.ReadInt64();
int TransferMemHandle = Context.Request.HandleDesc.ToCopy[0];
- Context.ResponseData.Write(0);
+ NvMapIoctl.InitializeNvMap(Context);
- NvMapsFb.Add(Context.Process, 0, new NvMapFb());
+ Context.ResponseData.Write(0);
return 0;
}
@@ -169,659 +139,69 @@ namespace Ryujinx.Core.OsHle.Services.Nv
return 0;
}
- private long NvGpuAsIoctlBindChannel(ServiceCtx Context)
- {
- long Position = Context.Request.GetSendBuffPtr();
-
- int Fd = Context.Memory.ReadInt32(Position);
-
- return 0;
- }
-
- private long NvGpuAsIoctlAllocSpace(ServiceCtx Context)
- {
- long Position = Context.Request.GetSendBuffPtr();
-
- MemReader Reader = new MemReader(Context.Memory, Position);
-
- int Pages = Reader.ReadInt32();
- int PageSize = Reader.ReadInt32();
- int Flags = Reader.ReadInt32();
- int Padding = Reader.ReadInt32();
- long Align = Reader.ReadInt64();
-
- if ((Flags & 1) != 0)
- {
- Align = Context.Ns.Gpu.ReserveMemory(Align, (long)Pages * PageSize, 1);
- }
- else
- {
- Align = Context.Ns.Gpu.ReserveMemory((long)Pages * PageSize, Align);
- }
-
- Context.Memory.WriteInt64(Position + 0x10, Align);
-
- return 0;
- }
-
- private long NvGpuAsIoctlUnmap(ServiceCtx Context)
- {
- long Position = Context.Request.GetSendBuffPtr();
-
- MemReader Reader = new MemReader(Context.Memory, Position);
-
- long Offset = Reader.ReadInt64();
-
- Context.Ns.Gpu.MemoryMgr.Unmap(Offset, 0x10000);
-
- return 0;
- }
-
- private long NvGpuAsIoctlMapBufferEx(ServiceCtx Context)
- {
- long Position = Context.Request.GetSendBuffPtr();
-
- MemReader Reader = new MemReader(Context.Memory, Position);
-
- int Flags = Reader.ReadInt32();
- int Kind = Reader.ReadInt32();
- int Handle = Reader.ReadInt32();
- int PageSize = Reader.ReadInt32();
- long BuffAddr = Reader.ReadInt64();
- long MapSize = Reader.ReadInt64();
- long Offset = Reader.ReadInt64();
-
- if (Handle == 0)
- {
- //This is used to store offsets for the Framebuffer(s);
- NvMapFb MapFb = (NvMapFb)NvMapsFb.GetData(Context.Process, 0);
-
- MapFb.AddBufferOffset(BuffAddr);
-
- return 0;
- }
-
- NvMap Map = NvMaps.GetData<NvMap>(Context.Process, Handle);
-
- if (Map == null)
- {
- Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"invalid NvMap Handle {Handle}!");
-
- return -1; //TODO: Corrent error code.
- }
-
- if ((Flags & 1) != 0)
- {
- Offset = Context.Ns.Gpu.MapMemory(Map.CpuAddress, Offset, Map.Size);
- }
- else
- {
- Offset = Context.Ns.Gpu.MapMemory(Map.CpuAddress, Map.Size);
- }
-
- Context.Memory.WriteInt64(Position + 0x20, Offset);
-
- Map.GpuAddress = Offset;
-
- return 0;
- }
-
- private long NvGpuAsIoctlGetVaRegions(ServiceCtx Context)
- {
- long Position = Context.Request.GetSendBuffPtr();
-
- MemReader Reader = new MemReader(Context.Memory, Position);
- MemWriter Writer = new MemWriter(Context.Memory, Position);
-
- long Unused = Reader.ReadInt64();
- int BuffSize = Reader.ReadInt32();
- int Padding = Reader.ReadInt32();
-
- BuffSize = 0x30;
-
- Writer.WriteInt64(Unused);
- Writer.WriteInt32(BuffSize);
- Writer.WriteInt32(Padding);
-
- Writer.WriteInt64(0);
- Writer.WriteInt32(0);
- Writer.WriteInt32(0);
- Writer.WriteInt64(0);
-
- Writer.WriteInt64(0);
- Writer.WriteInt32(0);
- Writer.WriteInt32(0);
- Writer.WriteInt64(0);
-
- return 0;
- }
-
- private long NvGpuAsIoctlInitializeEx(ServiceCtx Context)
- {
- long Position = Context.Request.GetSendBuffPtr();
-
- MemReader Reader = new MemReader(Context.Memory, Position);
-
- int BigPageSize = Reader.ReadInt32();
- int AsFd = Reader.ReadInt32();
- int Flags = Reader.ReadInt32();
- int Reserved = Reader.ReadInt32();
- long Unknown10 = Reader.ReadInt64();
- long Unknown18 = Reader.ReadInt64();
- long Unknown20 = Reader.ReadInt64();
-
- return 0;
- }
-
- private long NvGpuAsIoctlRemap(ServiceCtx Context)
- {
- Context.RequestData.BaseStream.Seek(-4, SeekOrigin.Current);
-
- int Cmd = Context.RequestData.ReadInt32();
-
- int Size = (Cmd >> 16) & 0xff;
-
- int Count = Size / 0x18;
-
- long Position = Context.Request.GetSendBuffPtr();
-
- MemReader Reader = new MemReader(Context.Memory, Position);
-
- for (int Index = 0; Index < Count; Index++)
- {
- int Flags = Reader.ReadInt32();
- int Kind = Reader.ReadInt32();
- int Handle = Reader.ReadInt32();
- int Padding = Reader.ReadInt32();
- int Offset = Reader.ReadInt32();
- int Pages = Reader.ReadInt32();
-
- NvMap Map = NvMaps.GetData<NvMap>(Context.Process, Handle);
-
- if (Map == null)
- {
- Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"invalid NvMap Handle {Handle}!");
-
- return -1; //TODO: Corrent error code.
- }
-
- Context.Ns.Gpu.MapMemory(Map.CpuAddress,
- (long)(uint)Offset << 16,
- (long)(uint)Pages << 16);
- }
-
- //TODO
-
- return 0;
- }
-
- private long NvHostIoctlCtrlGetConfig(ServiceCtx Context)
- {
- long Position = Context.Request.GetSendBuffPtr();
-
- MemReader Reader = new MemReader(Context.Memory, Position);
- MemWriter Writer = new MemWriter(Context.Memory, Position + 0x82);
-
- for (int Index = 0; Index < 0x101; Index++)
- {
- Writer.WriteByte(0);
- }
-
- return 0;
- }
-
- private long NvHostIoctlCtrlEventWait(ServiceCtx Context)
- {
- long Position = Context.Request.GetSendBuffPtr();
-
- MemReader Reader = new MemReader(Context.Memory, Position);
-
- int SyncPtId = Reader.ReadInt32();
- int Threshold = Reader.ReadInt32();
- int Timeout = Reader.ReadInt32();
- int Value = Reader.ReadInt32();
-
- Context.Memory.WriteInt32(Position + 0xc, 0xcafe);
-
- return 0;
- }
-
- private long NvHostIoctlCtrlEventWaitAsync(ServiceCtx Context)
- {
- long Position = Context.Request.GetSendBuffPtr();
-
- MemReader Reader = new MemReader(Context.Memory, Position);
-
- int SyncPtId = Reader.ReadInt32();
- int Threshold = Reader.ReadInt32();
- int Timeout = Reader.ReadInt32();
- int Value = Reader.ReadInt32();
-
- Context.Memory.WriteInt32(Position + 0xc, 0xcafe);
-
- return 0;
- }
-
- private long NvHostIoctlCtrlEventRegister(ServiceCtx Context)
- {
- long Position = Context.Request.GetSendBuffPtr();
-
- MemReader Reader = new MemReader(Context.Memory, Position);
-
- int UserEventId = Reader.ReadInt32();
-
- return 0;
- }
-
- private long NvGpuIoctlZcullGetCtxSize(ServiceCtx Context)
- {
- long Position = Context.Request.GetSendBuffPtr();
-
- Context.Memory.WriteInt32(Position, 1);
-
- return 0;
- }
-
- private long NvGpuIoctlZcullGetInfo(ServiceCtx Context)
- {
- long Position = Context.Request.GetSendBuffPtr();
-
- MemWriter Writer = new MemWriter(Context.Memory, Position);
-
- Writer.WriteInt32(0);
- Writer.WriteInt32(0);
- Writer.WriteInt32(0);
- Writer.WriteInt32(0);
- Writer.WriteInt32(0);
- Writer.WriteInt32(0);
- Writer.WriteInt32(0);
- Writer.WriteInt32(0);
- Writer.WriteInt32(0);
- Writer.WriteInt32(0);
-
- return 0;
- }
-
- private long NvGpuIoctlZbcSetTable(ServiceCtx Context)
- {
- long Position = Context.Request.GetSendBuffPtr();
-
- MemReader Reader = new MemReader(Context.Memory, Position);
-
- int[] ColorDs = new int[4];
- int[] ColorL2 = new int[4];
-
- ColorDs[0] = Reader.ReadInt32();
- ColorDs[1] = Reader.ReadInt32();
- ColorDs[2] = Reader.ReadInt32();
- ColorDs[3] = Reader.ReadInt32();
-
- ColorL2[0] = Reader.ReadInt32();
- ColorL2[1] = Reader.ReadInt32();
- ColorL2[2] = Reader.ReadInt32();
- ColorL2[3] = Reader.ReadInt32();
-
- int Depth = Reader.ReadInt32();
- int Format = Reader.ReadInt32();
- int Type = Reader.ReadInt32();
-
- return 0;
- }
-
- private long NvGpuIoctlGetCharacteristics(ServiceCtx Context)
- {
- long Position = Context.Request.GetSendBuffPtr();
-
- MemReader Reader = new MemReader(Context.Memory, Position);
- MemWriter Writer = new MemWriter(Context.Memory, Position);
-
- //Note: We should just ignore the BuffAddr, because official code
- //does __memcpy_device from Position + 0x10 to BuffAddr.
- long BuffSize = Reader.ReadInt64();
- long BuffAddr = Reader.ReadInt64();
-
- BuffSize = 0xa0;
-
- Writer.WriteInt64(BuffSize);
- Writer.WriteInt64(BuffAddr);
- Writer.WriteInt32(0x120); //NVGPU_GPU_ARCH_GM200
- Writer.WriteInt32(0xb); //NVGPU_GPU_IMPL_GM20B
- Writer.WriteInt32(0xa1);
- Writer.WriteInt32(1);
- Writer.WriteInt64(0x40000);
- Writer.WriteInt64(0);
- Writer.WriteInt32(2);
- Writer.WriteInt32(0x20); //NVGPU_GPU_BUS_TYPE_AXI
- Writer.WriteInt32(0x20000);
- Writer.WriteInt32(0x20000);
- Writer.WriteInt32(0x1b);
- Writer.WriteInt32(0x30000);
- Writer.WriteInt32(1);
- Writer.WriteInt32(0x503);
- Writer.WriteInt32(0x503);
- Writer.WriteInt32(0x80);
- Writer.WriteInt32(0x28);
- Writer.WriteInt32(0);
- Writer.WriteInt64(0x55);
- Writer.WriteInt32(0x902d); //FERMI_TWOD_A
- Writer.WriteInt32(0xb197); //MAXWELL_B
- Writer.WriteInt32(0xb1c0); //MAXWELL_COMPUTE_B
- Writer.WriteInt32(0xb06f); //MAXWELL_CHANNEL_GPFIFO_A
- Writer.WriteInt32(0xa140); //KEPLER_INLINE_TO_MEMORY_B
- Writer.WriteInt32(0xb0b5); //MAXWELL_DMA_COPY_A
- Writer.WriteInt32(1);
- Writer.WriteInt32(0);
- Writer.WriteInt32(2);
- Writer.WriteInt32(1);
- Writer.WriteInt32(0);
- Writer.WriteInt32(1);
- Writer.WriteInt32(0x21d70);
- Writer.WriteInt32(0);
- Writer.WriteByte((byte)'g');
- Writer.WriteByte((byte)'m');
- Writer.WriteByte((byte)'2');
- Writer.WriteByte((byte)'0');
- Writer.WriteByte((byte)'b');
- Writer.WriteByte((byte)'\0');
- Writer.WriteByte((byte)'\0');
- Writer.WriteByte((byte)'\0');
- Writer.WriteInt64(0);
-
- return 0;
- }
-
- private long NvGpuIoctlGetTpcMasks(ServiceCtx Context)
- {
- long Position = Context.Request.GetSendBuffPtr();
-
- MemReader Reader = new MemReader(Context.Memory, Position);
-
- int MaskBuffSize = Reader.ReadInt32();
- int Reserved = Reader.ReadInt32();
- long MaskBuffAddr = Reader.ReadInt64();
- long Unknown = Reader.ReadInt64();
-
- return 0;
- }
-
- private long NvGpuIoctlZbcGetActiveSlotMask(ServiceCtx Context)
- {
- long Position = Context.Request.GetSendBuffPtr();
-
- Context.Memory.WriteInt32(Position + 0, 7);
- Context.Memory.WriteInt32(Position + 4, 1);
-
- return 0;
- }
-
- private long NvMapIoctlChannelSetUserData(ServiceCtx Context)
- {
- long Position = Context.Request.GetSendBuffPtr();
-
- return 0;
- }
-
- private long NvMapIoctlChannelSetNvMap(ServiceCtx Context)
- {
- long Position = Context.Request.GetSendBuffPtr();
-
- int Fd = Context.Memory.ReadInt32(Position);
-
- return 0;
- }
-
- private long NvMapIoctlChannelSubmitGpFifo(ServiceCtx Context)
- {
- long Position = Context.Request.GetSendBuffPtr();
-
- MemReader Reader = new MemReader(Context.Memory, Position);
- MemWriter Writer = new MemWriter(Context.Memory, Position + 0x10);
-
- long GpFifo = Reader.ReadInt64();
- int Count = Reader.ReadInt32();
- int Flags = Reader.ReadInt32();
- int FenceId = Reader.ReadInt32();
- int FenceVal = Reader.ReadInt32();
-
- for (int Index = 0; Index < Count; Index++)
- {
- long GpFifoHdr = Reader.ReadInt64();
-
- long GpuAddr = GpFifoHdr & 0xffffffffff;
-
- int Size = (int)(GpFifoHdr >> 40) & 0x7ffffc;
-
- long CpuAddr = Context.Ns.Gpu.GetCpuAddr(GpuAddr);
-
- if (CpuAddr != -1)
- {
- byte[] Data = AMemoryHelper.ReadBytes(Context.Memory, CpuAddr, Size);
-
- NsGpuPBEntry[] PushBuffer = NvGpuPushBuffer.Decode(Data);
-
- Context.Ns.Gpu.Fifo.PushBuffer(Context.Memory, PushBuffer);
- }
- }
-
- Writer.WriteInt32(0);
- Writer.WriteInt32(0);
-
- return 0;
- }
-
- private long NvMapIoctlChannelAllocObjCtx(ServiceCtx Context)
- {
- long Position = Context.Request.GetSendBuffPtr();
-
- int ClassNum = Context.Memory.ReadInt32(Position + 0);
- int Flags = Context.Memory.ReadInt32(Position + 4);
-
- Context.Memory.WriteInt32(Position + 8, 0);
-
- return 0;
- }
-
- private long NvMapIoctlChannelZcullBind(ServiceCtx Context)
+ private static int ProcessIoctlNvGpuAS(ServiceCtx Context, int Cmd)
{
- long Position = Context.Request.GetSendBuffPtr();
-
- MemReader Reader = new MemReader(Context.Memory, Position);
-
- long GpuVa = Reader.ReadInt64();
- int Mode = Reader.ReadInt32();
- int Padding = Reader.ReadInt32();
-
- return 0;
+ return ProcessIoctl(Context, Cmd, NvGpuASIoctl.ProcessIoctl);
}
- private long NvMapIoctlChannelSetErrorNotifier(ServiceCtx Context)
+ private static int ProcessIoctlNvHostCtrl(ServiceCtx Context, int Cmd)
{
- long Position = Context.Request.GetSendBuffPtr();
-
- MemReader Reader = new MemReader(Context.Memory, Position);
-
- long Offset = Reader.ReadInt64();
- long Size = Reader.ReadInt64();
- int Mem = Reader.ReadInt32();
- int Padding = Reader.ReadInt32();
-
- return 0;
+ return ProcessIoctl(Context, Cmd, NvHostCtrlIoctl.ProcessIoctl);
}
- private long NvMapIoctlChannelSetPriority(ServiceCtx Context)
+ private static int ProcessIoctlNvGpuGpu(ServiceCtx Context, int Cmd)
{
- long Position = Context.Request.GetSendBuffPtr();
-
- int Priority = Context.Memory.ReadInt32(Position);
-
- return 0;
+ return ProcessIoctl(Context, Cmd, NvGpuGpuIoctl.ProcessIoctl);
}
- private long NvMapIoctlChannelAllocGpFifoEx2(ServiceCtx Context)
+ private static int ProcessIoctlNvHostChannel(ServiceCtx Context, int Cmd)
{
- long Position = Context.Request.GetSendBuffPtr();
-
- MemReader Reader = new MemReader(Context.Memory, Position);
- MemWriter Writer = new MemWriter(Context.Memory, Position + 0xc);
-
- int Count = Reader.ReadInt32();
- int Flags = Reader.ReadInt32();
- int Unknown8 = Reader.ReadInt32();
- long Fence = Reader.ReadInt64();
- int Unknown14 = Reader.ReadInt32();
- int Unknown18 = Reader.ReadInt32();
-
- Writer.WriteInt32(0);
- Writer.WriteInt32(0);
-
- return 0;
+ return ProcessIoctl(Context, Cmd, NvHostChannelIoctl.ProcessIoctl);
}
- private long NvMapIocCreate(ServiceCtx Context)
+ private static int ProcessIoctlNvMap(ServiceCtx Context, int Cmd)
{
- long Position = Context.Request.GetSendBuffPtr();
-
- int Size = Context.Memory.ReadInt32(Position);
-
- NvMap Map = new NvMap() { Size = Size };
-
- Map.Handle = NvMaps.Add(Context.Process, Map);
-
- Map.Id = NvMapsById.Add(Context.Process, Map);
-
- Context.Memory.WriteInt32(Position + 4, Map.Handle);
-
- Context.Ns.Log.PrintInfo(LogClass.ServiceNv, $"NvMap {Map.Id} created with size {Size:x8}!");
-
- return 0;
+ return ProcessIoctl(Context, Cmd, NvMapIoctl.ProcessIoctl);
}
- private long NvMapIocFromId(ServiceCtx Context)
+ private static int ProcessIoctl(ServiceCtx Context, int Cmd, IoctlProcessor Processor)
{
- long Position = Context.Request.GetSendBuffPtr();
-
- int Id = Context.Memory.ReadInt32(Position);
-
- NvMap Map = NvMapsById.GetData<NvMap>(Context.Process, Id);
-
- if (Map == null)
+ if (CmdIn(Cmd) && Context.Request.GetBufferType0x21Position() == 0)
{
- Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap Id {Id}!");
+ Context.Ns.Log.PrintError(LogClass.ServiceNv, "Input buffer is null!");
- return -1; //TODO: Corrent error code.
+ return NvResult.InvalidInput;
}
- Context.Memory.WriteInt32(Position + 4, Map.Handle);
-
- return 0;
- }
-
- private long NvMapIocAlloc(ServiceCtx Context)
- {
- long Position = Context.Request.GetSendBuffPtr();
-
- MemReader Reader = new MemReader(Context.Memory, Position);
-
- int Handle = Reader.ReadInt32();
- int HeapMask = Reader.ReadInt32();
- int Flags = Reader.ReadInt32();
- int Align = Reader.ReadInt32();
- byte Kind = (byte)Reader.ReadInt64();
- long Addr = Reader.ReadInt64();
-
- NvMap Map = NvMaps.GetData<NvMap>(Context.Process, Handle);
-
- if (Map == null)
+ if (CmdOut(Cmd) && Context.Request.GetBufferType0x22Position() == 0)
{
- Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap Handle {Handle}!");
+ Context.Ns.Log.PrintError(LogClass.ServiceNv, "Output buffer is null!");
- return -1; //TODO: Corrent error code.
+ return NvResult.InvalidInput;
}
- Map.CpuAddress = Addr;
- Map.Align = Align;
- Map.Kind = Kind;
-
- return 0;
+ return Processor(Context, Cmd);
}
- private long NvMapIocFree(ServiceCtx Context)
+ private static bool CmdIn(int Cmd)
{
- long Position = Context.Request.GetSendBuffPtr();
-
- MemReader Reader = new MemReader(Context.Memory, Position);
- MemWriter Writer = new MemWriter(Context.Memory, Position + 8);
-
- int Handle = Reader.ReadInt32();
- int Padding = Reader.ReadInt32();
-
- NvMap Map = NvMaps.GetData<NvMap>(Context.Process, Handle);
-
- if (Map == null)
- {
- Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap Handle {Handle}!");
-
- return -1; //TODO: Corrent error code.
- }
-
- Writer.WriteInt64(0);
- Writer.WriteInt32(Map.Size);
- Writer.WriteInt32(0);
-
- return 0;
+ return ((Cmd >> 30) & 1) != 0;
}
- private long NvMapIocParam(ServiceCtx Context)
+ private static bool CmdOut(int Cmd)
{
- long Position = Context.Request.GetSendBuffPtr();
-
- MemReader Reader = new MemReader(Context.Memory, Position);
-
- int Handle = Reader.ReadInt32();
- int Param = Reader.ReadInt32();
-
- NvMap Map = NvMaps.GetData<NvMap>(Context.Process, Handle);
-
- if (Map == null)
- {
- Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap Handle {Handle}!");
-
- return -1; //TODO: Corrent error code.
- }
-
- int Response = 0;
-
- switch (Param)
- {
- case 1: Response = Map.Size; break;
- case 2: Response = Map.Align; break;
- case 4: Response = 0x40000000; break;
- case 5: Response = Map.Kind; break;
- }
-
- Context.Memory.WriteInt32(Position + 8, Response);
-
- return 0;
+ return ((Cmd >> 31) & 1) != 0;
}
- private long NvMapIocGetId(ServiceCtx Context)
+ public static void UnloadProcess(Process Process)
{
- long Position = Context.Request.GetSendBuffPtr();
-
- int Handle = Context.Memory.ReadInt32(Position + 4);
-
- NvMap Map = NvMaps.GetData<NvMap>(Context.Process, Handle);
+ Fds.DeleteProcess(Process);
- if (Map == null)
- {
- Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap Handle {Handle}!");
-
- return -1; //TODO: Corrent error code.
- }
+ NvGpuASIoctl.UnloadProcess(Process);
- Context.Memory.WriteInt32(Position, Map.Id);
+ NvHostCtrlIoctl.UnloadProcess(Process);
- return 0;
+ NvMapIoctl.UnloadProcess(Process);
}
public void Dispose()
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvChNvMap.cs b/Ryujinx.Core/OsHle/Services/Nv/NvChNvMap.cs
deleted file mode 100644
index 9ea3ae6e..00000000
--- a/Ryujinx.Core/OsHle/Services/Nv/NvChNvMap.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using System.Collections.Concurrent;
-
-namespace Ryujinx.Core.OsHle.Services.Nv
-{
- class NvChNvMap
- {
- private static ConcurrentDictionary<Process, IdDictionary> NvMaps;
-
- public void Create(ServiceCtx Context)
- {
- long InputPosition = Context.Request.GetBufferType0x21Position();
- long OutputPosition = Context.Request.GetBufferType0x22Position();
-
- int Size = Context.Memory.ReadInt32(InputPosition);
-
- int Handle = AddNvMap(Context, new NvMap(Size));
-
- Context.Memory.WriteInt32(OutputPosition, Handle);
- }
-
- private int AddNvMap(ServiceCtx Context, NvMap Map)
- {
- return NvMaps[Context.Process].Add(Map);
- }
-
- public NvMap GetNvMap(ServiceCtx Context, int Handle)
- {
- return NvMaps[Context.Process].GetData<NvMap>(Handle);
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvGpuAS/NvGpuASAllocSpace.cs b/Ryujinx.Core/OsHle/Services/Nv/NvGpuAS/NvGpuASAllocSpace.cs
new file mode 100644
index 00000000..7e652d1f
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvGpuAS/NvGpuASAllocSpace.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Core.OsHle.Services.Nv.NvGpuAS
+{
+ struct NvGpuASAllocSpace
+ {
+ public int Pages;
+ public int PageSize;
+ public int Flags;
+ public int Padding;
+ public long Offset;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvGpuAS/NvGpuASIoctl.cs b/Ryujinx.Core/OsHle/Services/Nv/NvGpuAS/NvGpuASIoctl.cs
new file mode 100644
index 00000000..6be45d5c
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvGpuAS/NvGpuASIoctl.cs
@@ -0,0 +1,245 @@
+using ChocolArm64.Memory;
+using Ryujinx.Core.Gpu;
+using Ryujinx.Core.Logging;
+using Ryujinx.Core.OsHle.Services.Nv.NvMap;
+using System;
+using System.Collections.Concurrent;
+
+namespace Ryujinx.Core.OsHle.Services.Nv.NvGpuAS
+{
+ class NvGpuASIoctl
+ {
+ private const int FlagFixedOffset = 1;
+
+ private static ConcurrentDictionary<Process, NvGpuVmm> Vmms;
+
+ static NvGpuASIoctl()
+ {
+ Vmms = new ConcurrentDictionary<Process, NvGpuVmm>();
+ }
+
+ public static int ProcessIoctl(ServiceCtx Context, int Cmd)
+ {
+ switch (Cmd & 0xffff)
+ {
+ case 0x4101: return BindChannel (Context);
+ case 0x4102: return AllocSpace (Context);
+ case 0x4103: return FreeSpace (Context);
+ case 0x4105: return UnmapBuffer (Context);
+ case 0x4106: return MapBufferEx (Context);
+ case 0x4108: return GetVaRegions(Context);
+ case 0x4109: return InitializeEx(Context);
+ case 0x4114: return Remap (Context);
+ }
+
+ throw new NotImplementedException(Cmd.ToString("x8"));
+ }
+
+ private static int BindChannel(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int AllocSpace(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ NvGpuASAllocSpace Args = AMemoryHelper.Read<NvGpuASAllocSpace>(Context.Memory, InputPosition);
+
+ NvGpuVmm Vmm = GetVmm(Context);
+
+ ulong Size = (ulong)Args.Pages *
+ (ulong)Args.PageSize;
+
+ if ((Args.Flags & FlagFixedOffset) != 0)
+ {
+ Args.Offset = Vmm.Reserve(Args.Offset, (long)Size, 1);
+ }
+ else
+ {
+ Args.Offset = Vmm.Reserve((long)Size, 1);
+ }
+
+ int Result = NvResult.Success;
+
+ if (Args.Offset < 0)
+ {
+ Args.Offset = 0;
+
+ Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"No memory to allocate size {Size:x16}!");
+
+ Result = NvResult.OutOfMemory;
+ }
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ return Result;
+ }
+
+ private static int FreeSpace(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ NvGpuASAllocSpace Args = AMemoryHelper.Read<NvGpuASAllocSpace>(Context.Memory, InputPosition);
+
+ NvGpuVmm Vmm = GetVmm(Context);
+
+ ulong Size = (ulong)Args.Pages *
+ (ulong)Args.PageSize;
+
+ Vmm.Free(Args.Offset, (long)Size);
+
+ return NvResult.Success;
+ }
+
+ private static int UnmapBuffer(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ NvGpuASUnmapBuffer Args = AMemoryHelper.Read<NvGpuASUnmapBuffer>(Context.Memory, InputPosition);
+
+ NvGpuVmm Vmm = GetVmm(Context);
+
+ if (!Vmm.Unmap(Args.Offset))
+ {
+ Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid buffer offset {Args.Offset:x16}!");
+ }
+
+ return NvResult.Success;
+ }
+
+ private static int MapBufferEx(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ NvGpuASMapBufferEx Args = AMemoryHelper.Read<NvGpuASMapBufferEx>(Context.Memory, InputPosition);
+
+ NvGpuVmm Vmm = GetVmm(Context);
+
+ NvMapHandle Map = NvMapIoctl.GetNvMapWithFb(Context, Args.NvMapHandle);
+
+ if (Map == null)
+ {
+ Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap handle 0x{Args.NvMapHandle:x8}!");
+
+ return NvResult.InvalidInput;
+ }
+
+ long PA = Map.Address + Args.BufferOffset;
+
+ long Size = Args.MappingSize;
+
+ if (Size == 0)
+ {
+ Size = Map.Size;
+ }
+
+ Size = Map.Size;
+
+ int Result = NvResult.Success;
+
+ //Note: When the fixed offset flag is not set,
+ //the Offset field holds the alignment size instead.
+ if ((Args.Flags & FlagFixedOffset) != 0)
+ {
+ long MapEnd = Args.Offset + Args.MappingSize;
+
+ if ((ulong)MapEnd <= (ulong)Args.Offset)
+ {
+ Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Offset 0x{Args.Offset:x16} and size 0x{Args.MappingSize:x16} results in a overflow!");
+
+ return NvResult.InvalidInput;
+ }
+
+ if ((Args.Offset & NvGpuVmm.PageMask) != 0)
+ {
+ Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Offset 0x{Args.Offset:x16} is not page aligned!");
+
+ return NvResult.InvalidInput;
+ }
+
+ Args.Offset = Vmm.Map(PA, Args.Offset, Size);
+ }
+ else
+ {
+ Args.Offset = Vmm.Map(PA, Size);
+
+ if (Args.Offset < 0)
+ {
+ Args.Offset = 0;
+
+ Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"No memory to map size {Args.MappingSize:x16}!");
+
+ Result = NvResult.InvalidInput;
+ }
+ }
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ return Result;
+ }
+
+ private static int GetVaRegions(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int InitializeEx(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int Remap(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+
+ NvGpuASRemap Args = AMemoryHelper.Read<NvGpuASRemap>(Context.Memory, InputPosition);
+
+ NvGpuVmm Vmm = GetVmm(Context);
+
+ NvMapHandle Map = NvMapIoctl.GetNvMapWithFb(Context, Args.NvMapHandle);
+
+ if (Map == null)
+ {
+ Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap handle 0x{Args.NvMapHandle:x8}!");
+
+ return NvResult.InvalidInput;
+ }
+
+ //FIXME: This is most likely wrong...
+ Vmm.Map(Map.Address, (long)(uint)Args.Offset << 16,
+ (long)(uint)Args.Pages << 16);
+
+ return NvResult.Success;
+ }
+
+ public static NvGpuVmm GetVmm(ServiceCtx Context)
+ {
+ return Vmms.GetOrAdd(Context.Process, (Key) => new NvGpuVmm(Context.Memory));
+ }
+
+ public static void UnloadProcess(Process Process)
+ {
+ Vmms.TryRemove(Process, out _);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvGpuAS/NvGpuASMapBufferEx.cs b/Ryujinx.Core/OsHle/Services/Nv/NvGpuAS/NvGpuASMapBufferEx.cs
new file mode 100644
index 00000000..b8bdd706
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvGpuAS/NvGpuASMapBufferEx.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.Core.OsHle.Services.Nv.NvGpuAS
+{
+ struct NvGpuASMapBufferEx
+ {
+ public int Flags;
+ public int Kind;
+ public int NvMapHandle;
+ public int PageSize;
+ public long BufferOffset;
+ public long MappingSize;
+ public long Offset;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvGpuAS/NvGpuASRemap.cs b/Ryujinx.Core/OsHle/Services/Nv/NvGpuAS/NvGpuASRemap.cs
new file mode 100644
index 00000000..363ae687
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvGpuAS/NvGpuASRemap.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Core.OsHle.Services.Nv.NvGpuAS
+{
+ struct NvGpuASRemap
+ {
+ public short Flags;
+ public short Kind;
+ public int NvMapHandle;
+ public int Padding;
+ public int Offset;
+ public int Pages;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvGpuAS/NvGpuASUnmapBuffer.cs b/Ryujinx.Core/OsHle/Services/Nv/NvGpuAS/NvGpuASUnmapBuffer.cs
new file mode 100644
index 00000000..8b627511
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvGpuAS/NvGpuASUnmapBuffer.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.Core.OsHle.Services.Nv.NvGpuAS
+{
+ struct NvGpuASUnmapBuffer
+ {
+ public long Offset;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuGetCharacteristics.cs b/Ryujinx.Core/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuGetCharacteristics.cs
new file mode 100644
index 00000000..5d92b508
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuGetCharacteristics.cs
@@ -0,0 +1,43 @@
+namespace Ryujinx.Core.OsHle.Services.Nv.NvGpuGpu
+{
+ struct NvGpuGpuGetCharacteristics
+ {
+ public long BufferSize;
+ public long BufferAddress;
+ public int Arch;
+ public int Impl;
+ public int Rev;
+ public int NumGpc;
+ public long L2CacheSize;
+ public long OnBoardVideoMemorySize;
+ public int NumTpcPerGpc;
+ public int BusType;
+ public int BigPageSize;
+ public int CompressionPageSize;
+ public int PdeCoverageBitCount;
+ public int AvailableBigPageSizes;
+ public int GpcMask;
+ public int SmArchSmVersion;
+ public int SmArchSpaVersion;
+ public int SmArchWarpCount;
+ public int GpuVaBitCount;
+ public int Reserved;
+ public long Flags;
+ public int TwodClass;
+ public int ThreedClass;
+ public int ComputeClass;
+ public int GpfifoClass;
+ public int InlineToMemoryClass;
+ public int DmaCopyClass;
+ public int MaxFbpsCount;
+ public int FbpEnMask;
+ public int MaxLtcPerFbp;
+ public int MaxLtsPerLtc;
+ public int MaxTexPerTpc;
+ public int MaxGpcCount;
+ public int RopL2EnMask0;
+ public int RopL2EnMask1;
+ public long ChipName;
+ public long GrCompbitStoreBaseHw;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuIoctl.cs b/Ryujinx.Core/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuIoctl.cs
new file mode 100644
index 00000000..772b6786
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvGpuGpu/NvGpuGpuIoctl.cs
@@ -0,0 +1,155 @@
+using ChocolArm64.Memory;
+using Ryujinx.Core.Logging;
+using System;
+using System.Diagnostics;
+
+namespace Ryujinx.Core.OsHle.Services.Nv.NvGpuGpu
+{
+ class NvGpuGpuIoctl
+ {
+ private static Stopwatch PTimer;
+
+ private static double TicksToNs;
+
+ static NvGpuGpuIoctl()
+ {
+ PTimer = new Stopwatch();
+
+ PTimer.Start();
+
+ TicksToNs = (1.0 / Stopwatch.Frequency) * 1_000_000_000;
+ }
+
+ public static int ProcessIoctl(ServiceCtx Context, int Cmd)
+ {
+ switch (Cmd & 0xffff)
+ {
+ case 0x4701: return ZcullGetCtxSize (Context);
+ case 0x4702: return ZcullGetInfo (Context);
+ case 0x4703: return ZbcSetTable (Context);
+ case 0x4705: return GetCharacteristics(Context);
+ case 0x4706: return GetTpcMasks (Context);
+ case 0x4714: return GetActiveSlotMask (Context);
+ case 0x471c: return GetGpuTime (Context);
+ }
+
+ throw new NotImplementedException(Cmd.ToString("x8"));
+ }
+
+ private static int ZcullGetCtxSize(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int ZcullGetInfo(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int ZbcSetTable(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int GetCharacteristics(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ NvGpuGpuGetCharacteristics Args = AMemoryHelper.Read<NvGpuGpuGetCharacteristics>(Context.Memory, InputPosition);
+
+ Args.BufferSize = 0xa0;
+
+ Args.Arch = 0x120;
+ Args.Impl = 0xb;
+ Args.Rev = 0xa1;
+ Args.NumGpc = 0x1;
+ Args.L2CacheSize = 0x40000;
+ Args.OnBoardVideoMemorySize = 0x0;
+ Args.NumTpcPerGpc = 0x2;
+ Args.BusType = 0x20;
+ Args.BigPageSize = 0x20000;
+ Args.CompressionPageSize = 0x20000;
+ Args.PdeCoverageBitCount = 0x1b;
+ Args.AvailableBigPageSizes = 0x30000;
+ Args.GpcMask = 0x1;
+ Args.SmArchSmVersion = 0x503;
+ Args.SmArchSpaVersion = 0x503;
+ Args.SmArchWarpCount = 0x80;
+ Args.GpuVaBitCount = 0x28;
+ Args.Reserved = 0x0;
+ Args.Flags = 0x55;
+ Args.TwodClass = 0x902d;
+ Args.ThreedClass = 0xb197;
+ Args.ComputeClass = 0xb1c0;
+ Args.GpfifoClass = 0xb06f;
+ Args.InlineToMemoryClass = 0xa140;
+ Args.DmaCopyClass = 0xb0b5;
+ Args.MaxFbpsCount = 0x1;
+ Args.FbpEnMask = 0x0;
+ Args.MaxLtcPerFbp = 0x2;
+ Args.MaxLtsPerLtc = 0x1;
+ Args.MaxTexPerTpc = 0x0;
+ Args.MaxGpcCount = 0x1;
+ Args.RopL2EnMask0 = 0x21d70;
+ Args.RopL2EnMask1 = 0x0;
+ Args.ChipName = 0x6230326d67;
+ Args.GrCompbitStoreBaseHw = 0x0;
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ return NvResult.Success;
+ }
+
+ private static int GetTpcMasks(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int GetActiveSlotMask(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int GetGpuTime(ServiceCtx Context)
+ {
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ Context.Memory.WriteInt64(OutputPosition, GetPTimerNanoSeconds());
+
+ return NvResult.Success;
+ }
+
+ private static long GetPTimerNanoSeconds()
+ {
+ double Ticks = PTimer.ElapsedTicks;
+
+ return (long)(Ticks * TicksToNs) & 0xff_ffff_ffff_ffff;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvHelper.cs b/Ryujinx.Core/OsHle/Services/Nv/NvHelper.cs
new file mode 100644
index 00000000..c5cee361
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvHelper.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Core.OsHle.Services.Nv
+{
+ static class NvHelper
+ {
+ public static void Crash()
+ {
+
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs b/Ryujinx.Core/OsHle/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs
new file mode 100644
index 00000000..85b47533
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvHostChannel/NvHostChannelIoctl.cs
@@ -0,0 +1,130 @@
+using ChocolArm64.Memory;
+using Ryujinx.Core.Logging;
+using Ryujinx.Core.OsHle.Services.Nv.NvGpuAS;
+using Ryujinx.Core.Gpu;
+using System;
+
+namespace Ryujinx.Core.OsHle.Services.Nv.NvHostChannel
+{
+ class NvHostChannelIoctl
+ {
+ public static int ProcessIoctl(ServiceCtx Context, int Cmd)
+ {
+ switch (Cmd & 0xffff)
+ {
+ case 0x4714: return SetUserData (Context);
+ case 0x4801: return SetNvMap (Context);
+ case 0x4808: return SubmitGpfifo (Context);
+ case 0x4809: return AllocObjCtx (Context);
+ case 0x480b: return ZcullBind (Context);
+ case 0x480c: return SetErrorNotifier(Context);
+ case 0x480d: return SetPriority (Context);
+ case 0x481a: return AllocGpfifoEx2 (Context);
+ }
+
+ throw new NotImplementedException(Cmd.ToString("x8"));
+ }
+
+ private static int SetUserData(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int SetNvMap(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int SubmitGpfifo(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ NvHostChannelSubmitGpfifo Args = AMemoryHelper.Read<NvHostChannelSubmitGpfifo>(Context.Memory, InputPosition);
+
+ NvGpuVmm Vmm = NvGpuASIoctl.GetVmm(Context);
+
+ for (int Index = 0; Index < Args.NumEntries; Index++)
+ {
+ long Gpfifo = Context.Memory.ReadInt64(InputPosition + 0x18 + Index * 8);
+
+ long VA = Gpfifo & 0xff_ffff_ffff;
+
+ int Size = (int)(Gpfifo >> 40) & 0x7ffffc;
+
+ byte[] Data = Vmm.ReadBytes(VA, Size);
+
+ NvGpuPBEntry[] PushBuffer = NvGpuPushBuffer.Decode(Data);
+
+ Context.Ns.Gpu.Fifo.PushBuffer(Vmm, PushBuffer);
+ }
+
+ Args.SyncptId = 0;
+ Args.SyncptValue = 0;
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ return NvResult.Success;
+ }
+
+ private static int AllocObjCtx(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int ZcullBind(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int SetErrorNotifier(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int SetPriority(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int AllocGpfifoEx2(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvHostChannel/NvHostChannelSubmitGpfifo.cs b/Ryujinx.Core/OsHle/Services/Nv/NvHostChannel/NvHostChannelSubmitGpfifo.cs
new file mode 100644
index 00000000..4698a3da
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvHostChannel/NvHostChannelSubmitGpfifo.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Core.OsHle.Services.Nv.NvHostChannel
+{
+ struct NvHostChannelSubmitGpfifo
+ {
+ public long Gpfifo;
+ public int NumEntries;
+ public int Flags;
+ public int SyncptId;
+ public int SyncptValue;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs b/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs
new file mode 100644
index 00000000..8c705d7f
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs
@@ -0,0 +1,355 @@
+using ChocolArm64.Memory;
+using Ryujinx.Core.Logging;
+using System;
+using System.Collections.Concurrent;
+using System.Threading;
+
+namespace Ryujinx.Core.OsHle.Services.Nv.NvHostCtrl
+{
+ class NvHostCtrlIoctl
+ {
+ private static ConcurrentDictionary<Process, NvHostCtrlUserCtx> UserCtxs;
+
+ static NvHostCtrlIoctl()
+ {
+ UserCtxs = new ConcurrentDictionary<Process, NvHostCtrlUserCtx>();
+ }
+
+ public static int ProcessIoctl(ServiceCtx Context, int Cmd)
+ {
+ switch (Cmd & 0xffff)
+ {
+ case 0x0014: return SyncptRead (Context);
+ case 0x0015: return SyncptIncr (Context);
+ case 0x0016: return SyncptWait (Context);
+ case 0x0019: return SyncptWaitEx (Context);
+ case 0x001a: return SyncptReadMax (Context);
+ case 0x001b: return GetConfig (Context);
+ case 0x001d: return EventWait (Context);
+ case 0x001e: return EventWaitAsync(Context);
+ case 0x001f: return EventRegister (Context);
+ }
+
+ throw new NotImplementedException(Cmd.ToString("x8"));
+ }
+
+ private static int SyncptRead(ServiceCtx Context)
+ {
+ return SyncptReadMinOrMax(Context, Max: false);
+ }
+
+ private static int SyncptIncr(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+
+ int Id = Context.Memory.ReadInt32(InputPosition);
+
+ if ((uint)Id >= NvHostSyncpt.SyncptsCount)
+ {
+ return NvResult.InvalidInput;
+ }
+
+ GetUserCtx(Context).Syncpt.Increment(Id);
+
+ return NvResult.Success;
+ }
+
+ private static int SyncptWait(ServiceCtx Context)
+ {
+ return SyncptWait(Context, Extended: false);
+ }
+
+ private static int SyncptWaitEx(ServiceCtx Context)
+ {
+ return SyncptWait(Context, Extended: true);
+ }
+
+ private static int SyncptReadMax(ServiceCtx Context)
+ {
+ return SyncptReadMinOrMax(Context, Max: true);
+ }
+
+ private static int GetConfig(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ string Nv = AMemoryHelper.ReadAsciiString(Context.Memory, InputPosition + 0, 0x41);
+ string Name = AMemoryHelper.ReadAsciiString(Context.Memory, InputPosition + 0x41, 0x41);
+
+ Context.Memory.WriteByte(OutputPosition + 0x82, 0);
+
+ Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int EventWait(ServiceCtx Context)
+ {
+ return EventWait(Context, Async: false);
+ }
+
+ private static int EventWaitAsync(ServiceCtx Context)
+ {
+ return EventWait(Context, Async: true);
+ }
+
+ private static int EventRegister(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ int EventId = Context.Memory.ReadInt32(InputPosition);
+
+ Context.Ns.Log.PrintStub(LogClass.ServiceNv, "Stubbed.");
+
+ return NvResult.Success;
+ }
+
+ private static int SyncptReadMinOrMax(ServiceCtx Context, bool Max)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ NvHostCtrlSyncptRead Args = AMemoryHelper.Read<NvHostCtrlSyncptRead>(Context.Memory, InputPosition);
+
+ if ((uint)Args.Id >= NvHostSyncpt.SyncptsCount)
+ {
+ return NvResult.InvalidInput;
+ }
+
+ if (Max)
+ {
+ Args.Value = GetUserCtx(Context).Syncpt.GetMax(Args.Id);
+ }
+ else
+ {
+ Args.Value = GetUserCtx(Context).Syncpt.GetMin(Args.Id);
+ }
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ return NvResult.Success;
+ }
+
+ private static int SyncptWait(ServiceCtx Context, bool Extended)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ NvHostCtrlSyncptWait Args = AMemoryHelper.Read<NvHostCtrlSyncptWait>(Context.Memory, InputPosition);
+
+ NvHostSyncpt Syncpt = GetUserCtx(Context).Syncpt;
+
+ if ((uint)Args.Id >= NvHostSyncpt.SyncptsCount)
+ {
+ return NvResult.InvalidInput;
+ }
+
+ int Result;
+
+ if (Syncpt.MinCompare(Args.Id, Args.Thresh))
+ {
+ Result = NvResult.Success;
+ }
+ else if (Args.Timeout == 0)
+ {
+ Result = NvResult.TryAgain;
+ }
+ else
+ {
+ Context.Ns.Log.PrintDebug(LogClass.ServiceNv, "Waiting syncpt with timeout of " + Args.Timeout + "ms...");
+
+ using (ManualResetEvent WaitEvent = new ManualResetEvent(false))
+ {
+ Syncpt.AddWaiter(Args.Thresh, WaitEvent);
+
+ //Note: Negative (> INT_MAX) timeouts aren't valid on .NET,
+ //in this case we just use the maximum timeout possible.
+ int Timeout = Args.Timeout;
+
+ if (Timeout < -1)
+ {
+ Timeout = int.MaxValue;
+ }
+
+ if (Timeout == -1)
+ {
+ WaitEvent.WaitOne();
+
+ Result = NvResult.Success;
+ }
+ else if (WaitEvent.WaitOne(Timeout))
+ {
+ Result = NvResult.Success;
+ }
+ else
+ {
+ Result = NvResult.TimedOut;
+ }
+ }
+
+ Context.Ns.Log.PrintDebug(LogClass.ServiceNv, "Resuming...");
+ }
+
+ if (Extended)
+ {
+ Context.Memory.WriteInt32(OutputPosition + 0xc, Syncpt.GetMin(Args.Id));
+ }
+
+ return Result;
+ }
+
+ private static int EventWait(ServiceCtx Context, bool Async)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ NvHostCtrlSyncptWaitEx Args = AMemoryHelper.Read<NvHostCtrlSyncptWaitEx>(Context.Memory, InputPosition);
+
+ if ((uint)Args.Id >= NvHostSyncpt.SyncptsCount)
+ {
+ return NvResult.InvalidInput;
+ }
+
+ void WriteArgs()
+ {
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+ }
+
+ NvHostSyncpt Syncpt = GetUserCtx(Context).Syncpt;
+
+ if (Syncpt.MinCompare(Args.Id, Args.Thresh))
+ {
+ Args.Value = Syncpt.GetMin(Args.Id);
+
+ WriteArgs();
+
+ return NvResult.Success;
+ }
+
+ if (!Async)
+ {
+ Args.Value = 0;
+ }
+
+ if (Args.Timeout == 0)
+ {
+ WriteArgs();
+
+ return NvResult.TryAgain;
+ }
+
+ NvHostEvent Event;
+
+ int Result, EventIndex;
+
+ if (Async)
+ {
+ EventIndex = Args.Value;
+
+ if ((uint)EventIndex >= NvHostCtrlUserCtx.EventsCount)
+ {
+ return NvResult.InvalidInput;
+ }
+
+ Event = GetUserCtx(Context).Events[EventIndex];
+ }
+ else
+ {
+ Event = GetFreeEvent(Context, Syncpt, Args.Id, out EventIndex);
+ }
+
+ if (Event != null &&
+ (Event.State == NvHostEventState.Registered ||
+ Event.State == NvHostEventState.Free))
+ {
+ Event.Id = Args.Id;
+ Event.Thresh = Args.Thresh;
+
+ Event.State = NvHostEventState.Waiting;
+
+ if (!Async)
+ {
+ Args.Value = ((Args.Id & 0xfff) << 16) | 0x10000000;
+ }
+ else
+ {
+ Args.Value = Args.Id << 4;
+ }
+
+ Args.Value |= EventIndex;
+
+ Result = NvResult.TryAgain;
+ }
+ else
+ {
+ Result = NvResult.InvalidInput;
+ }
+
+ WriteArgs();
+
+ return Result;
+ }
+
+ private static NvHostEvent GetFreeEvent(
+ ServiceCtx Context,
+ NvHostSyncpt Syncpt,
+ int Id,
+ out int EventIndex)
+ {
+ NvHostEvent[] Events = GetUserCtx(Context).Events;
+
+ EventIndex = NvHostCtrlUserCtx.EventsCount;
+
+ int NullIndex = NvHostCtrlUserCtx.EventsCount;
+
+ for (int Index = 0; Index < NvHostCtrlUserCtx.EventsCount; Index++)
+ {
+ NvHostEvent Event = Events[Index];
+
+ if (Event != null)
+ {
+ if (Event.State == NvHostEventState.Registered ||
+ Event.State == NvHostEventState.Free)
+ {
+ EventIndex = Index;
+
+ if (Event.Id == Id)
+ {
+ return Event;
+ }
+ }
+ }
+ else if (NullIndex == NvHostCtrlUserCtx.EventsCount)
+ {
+ NullIndex = Index;
+ }
+ }
+
+ if (NullIndex < NvHostCtrlUserCtx.EventsCount)
+ {
+ EventIndex = NullIndex;
+
+ return Events[NullIndex] = new NvHostEvent();
+ }
+
+ if (EventIndex < NvHostCtrlUserCtx.EventsCount)
+ {
+ return Events[EventIndex];
+ }
+
+ return null;
+ }
+
+ public static NvHostCtrlUserCtx GetUserCtx(ServiceCtx Context)
+ {
+ return UserCtxs.GetOrAdd(Context.Process, (Key) => new NvHostCtrlUserCtx());
+ }
+
+ public static void UnloadProcess(Process Process)
+ {
+ UserCtxs.TryRemove(Process, out _);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtRead.cs b/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtRead.cs
new file mode 100644
index 00000000..0b05e63a
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtRead.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Core.OsHle.Services.Nv.NvHostCtrl
+{
+ struct NvHostCtrlSyncptRead
+ {
+ public int Id;
+ public int Value;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWait.cs b/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWait.cs
new file mode 100644
index 00000000..6746090a
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWait.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Core.OsHle.Services.Nv.NvHostCtrl
+{
+ struct NvHostCtrlSyncptWait
+ {
+ public int Id;
+ public int Thresh;
+ public int Timeout;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWaitEx.cs b/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWaitEx.cs
new file mode 100644
index 00000000..21ba3783
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWaitEx.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Core.OsHle.Services.Nv.NvHostCtrl
+{
+ struct NvHostCtrlSyncptWaitEx
+ {
+ public int Id;
+ public int Thresh;
+ public int Timeout;
+ public int Value;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlUserCtx.cs b/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlUserCtx.cs
new file mode 100644
index 00000000..be71b225
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlUserCtx.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.Core.OsHle.Services.Nv.NvHostCtrl
+{
+ class NvHostCtrlUserCtx
+ {
+ public const int LocksCount = 16;
+ public const int EventsCount = 64;
+
+ public NvHostSyncpt Syncpt { get; private set; }
+
+ public NvHostEvent[] Events { get; private set; }
+
+ public NvHostCtrlUserCtx()
+ {
+ Syncpt = new NvHostSyncpt();
+
+ Events = new NvHostEvent[EventsCount];
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostEvent.cs b/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostEvent.cs
new file mode 100644
index 00000000..027e25b0
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostEvent.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Core.OsHle.Services.Nv.NvHostCtrl
+{
+ class NvHostEvent
+ {
+ public int Id;
+ public int Thresh;
+
+ public NvHostEventState State;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostEventState.cs b/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostEventState.cs
new file mode 100644
index 00000000..a4bd2cb6
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostEventState.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Core.OsHle.Services.Nv.NvHostCtrl
+{
+ enum NvHostEventState
+ {
+ Registered = 0,
+ Waiting = 1,
+ Busy = 2,
+ Free = 5
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostSyncPt.cs b/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostSyncPt.cs
new file mode 100644
index 00000000..96ae16a3
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostSyncPt.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Ryujinx.Core.OsHle.Services.Nv.NvHostCtrl
+{
+ class NvHostSyncpt
+ {
+ public const int SyncptsCount = 192;
+
+ private int[] CounterMin;
+ private int[] CounterMax;
+
+ private long EventMask;
+
+ private ConcurrentDictionary<EventWaitHandle, int> Waiters;
+
+ public NvHostSyncpt()
+ {
+ CounterMin = new int[SyncptsCount];
+ CounterMax = new int[SyncptsCount];
+
+ Waiters = new ConcurrentDictionary<EventWaitHandle, int>();
+ }
+
+ public int GetMin(int Id)
+ {
+ return CounterMin[Id];
+ }
+
+ public int GetMax(int Id)
+ {
+ return CounterMax[Id];
+ }
+
+ public int Increment(int Id)
+ {
+ if (((EventMask >> Id) & 1) != 0)
+ {
+ Interlocked.Increment(ref CounterMax[Id]);
+ }
+
+ return IncrementMin(Id);
+ }
+
+ public int IncrementMin(int Id)
+ {
+ int Value = Interlocked.Increment(ref CounterMin[Id]);
+
+ WakeUpWaiters(Id, Value);
+
+ return Value;
+ }
+
+ public int IncrementMax(int Id)
+ {
+ return Interlocked.Increment(ref CounterMax[Id]);
+ }
+
+ public void AddWaiter(int Threshold, EventWaitHandle WaitEvent)
+ {
+ if (!Waiters.TryAdd(WaitEvent, Threshold))
+ {
+ throw new InvalidOperationException();
+ }
+ }
+
+ public bool RemoveWaiter(EventWaitHandle WaitEvent)
+ {
+ return Waiters.TryRemove(WaitEvent, out _);
+ }
+
+ private void WakeUpWaiters(int Id, int NewValue)
+ {
+ foreach (KeyValuePair<EventWaitHandle, int> KV in Waiters)
+ {
+ if (MinCompare(Id, NewValue, CounterMax[Id], KV.Value))
+ {
+ KV.Key.Set();
+
+ Waiters.TryRemove(KV.Key, out _);
+ }
+ }
+ }
+
+ public bool MinCompare(int Id, int Threshold)
+ {
+ return MinCompare(Id, CounterMin[Id], CounterMax[Id], Threshold);
+ }
+
+ private bool MinCompare(int Id, int Min, int Max, int Threshold)
+ {
+ int MinDiff = Min - Threshold;
+ int MaxDiff = Max - Threshold;
+
+ if (((EventMask >> Id) & 1) != 0)
+ {
+ return MinDiff >= 0;
+ }
+ else
+ {
+ return (uint)MaxDiff >= (uint)MinDiff;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvMap.cs b/Ryujinx.Core/OsHle/Services/Nv/NvMap.cs
deleted file mode 100644
index 570cef68..00000000
--- a/Ryujinx.Core/OsHle/Services/Nv/NvMap.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-namespace Ryujinx.Core.OsHle.Services.Nv
-{
- class NvMap
- {
- public int Handle;
- public int Id;
- public int Size;
- public int Align;
- public int Kind;
- public long CpuAddress;
- public long GpuAddress;
-
- public NvMap() { }
-
- public NvMap(int Size)
- {
- this.Size = Size;
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapAlloc.cs b/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapAlloc.cs
new file mode 100644
index 00000000..86e4c238
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapAlloc.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Core.OsHle.Services.Nv.NvMap
+{
+ struct NvMapAlloc
+ {
+ public int Handle;
+ public int HeapMask;
+ public int Flags;
+ public int Align;
+ public long Kind;
+ public long Address;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapCreate.cs b/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapCreate.cs
new file mode 100644
index 00000000..7d35731c
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapCreate.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Core.OsHle.Services.Nv.NvMap
+{
+ struct NvMapCreate
+ {
+ public int Size;
+ public int Handle;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapFree.cs b/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapFree.cs
new file mode 100644
index 00000000..ee8bc618
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapFree.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Core.OsHle.Services.Nv.NvMap
+{
+ struct NvMapFree
+ {
+ public int Handle;
+ public int Padding;
+ public long RefCount;
+ public int Size;
+ public int Flags;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapFromId.cs b/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapFromId.cs
new file mode 100644
index 00000000..377eaa7f
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapFromId.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Core.OsHle.Services.Nv.NvMap
+{
+ struct NvMapFromId
+ {
+ public int Id;
+ public int Handle;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapGetId.cs b/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapGetId.cs
new file mode 100644
index 00000000..639a5fb4
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapGetId.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Core.OsHle.Services.Nv.NvMap
+{
+ struct NvMapGetId
+ {
+ public int Id;
+ public int Handle;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapHandle.cs b/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapHandle.cs
new file mode 100644
index 00000000..4a021f6f
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapHandle.cs
@@ -0,0 +1,37 @@
+using System.Threading;
+
+namespace Ryujinx.Core.OsHle.Services.Nv.NvMap
+{
+ class NvMapHandle
+ {
+ public int Handle;
+ public int Id;
+ public int Size;
+ public int Align;
+ public int Kind;
+ public long Address;
+ public bool Allocated;
+
+ private long Dupes;
+
+ public NvMapHandle()
+ {
+ Dupes = 1;
+ }
+
+ public NvMapHandle(int Size) : this()
+ {
+ this.Size = Size;
+ }
+
+ public long IncrementRefCount()
+ {
+ return Interlocked.Increment(ref Dupes);
+ }
+
+ public long DecrementRefCount()
+ {
+ return Interlocked.Decrement(ref Dupes);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapHandleParam.cs b/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapHandleParam.cs
new file mode 100644
index 00000000..80ff4c03
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapHandleParam.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Core.OsHle.Services.Nv.NvMap
+{
+ enum NvMapHandleParam
+ {
+ Size = 1,
+ Align = 2,
+ Base = 3,
+ Heap = 4,
+ Kind = 5,
+ Compr = 6
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapIoctl.cs b/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapIoctl.cs
new file mode 100644
index 00000000..f9c1564a
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapIoctl.cs
@@ -0,0 +1,302 @@
+using ChocolArm64.Memory;
+using Ryujinx.Core.Logging;
+using Ryujinx.Core.OsHle.Utilities;
+using Ryujinx.Core.Gpu;
+using System.Collections.Concurrent;
+
+namespace Ryujinx.Core.OsHle.Services.Nv.NvMap
+{
+ class NvMapIoctl
+ {
+ private const int FlagNotFreedYet = 1;
+
+ private static ConcurrentDictionary<Process, IdDictionary> Maps;
+
+ static NvMapIoctl()
+ {
+ Maps = new ConcurrentDictionary<Process, IdDictionary>();
+ }
+
+ public static int ProcessIoctl(ServiceCtx Context, int Cmd)
+ {
+ switch (Cmd & 0xffff)
+ {
+ case 0x0101: return Create(Context);
+ case 0x0103: return FromId(Context);
+ case 0x0104: return Alloc (Context);
+ case 0x0105: return Free (Context);
+ case 0x0109: return Param (Context);
+ case 0x010e: return GetId (Context);
+ }
+
+ Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Unsupported Ioctl command 0x{Cmd:x8}!");
+
+ return NvResult.NotSupported;
+ }
+
+ private static int Create(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ NvMapCreate Args = AMemoryHelper.Read<NvMapCreate>(Context.Memory, InputPosition);
+
+ if (Args.Size == 0)
+ {
+ Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid size 0x{Args.Size:x8}!");
+
+ return NvResult.InvalidInput;
+ }
+
+ int Size = IntUtils.RoundUp(Args.Size, NvGpuVmm.PageSize);
+
+ Args.Handle = AddNvMap(Context, new NvMapHandle(Size));
+
+ Context.Ns.Log.PrintInfo(LogClass.ServiceNv, $"Created map {Args.Handle} with size 0x{Size:x8}!");
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ return NvResult.Success;
+ }
+
+ private static int FromId(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ NvMapFromId Args = AMemoryHelper.Read<NvMapFromId>(Context.Memory, InputPosition);
+
+ NvMapHandle Map = GetNvMap(Context, Args.Id);
+
+ if (Map == null)
+ {
+ Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!");
+
+ return NvResult.InvalidInput;
+ }
+
+ Map.IncrementRefCount();
+
+ Args.Handle = Args.Id;
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ return NvResult.Success;
+ }
+
+ private static int Alloc(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ NvMapAlloc Args = AMemoryHelper.Read<NvMapAlloc>(Context.Memory, InputPosition);
+
+ NvMapHandle Map = GetNvMap(Context, Args.Handle);
+
+ if (Map == null)
+ {
+ Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!");
+
+ return NvResult.InvalidInput;
+ }
+
+ if ((Args.Align & (Args.Align - 1)) != 0)
+ {
+ Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid alignment 0x{Args.Align:x8}!");
+
+ return NvResult.InvalidInput;
+ }
+
+ if ((uint)Args.Align < NvGpuVmm.PageSize)
+ {
+ Args.Align = NvGpuVmm.PageSize;
+ }
+
+ int Result = NvResult.Success;
+
+ if (!Map.Allocated)
+ {
+ Map.Allocated = true;
+
+ Map.Align = Args.Align;
+ Map.Kind = (byte)Args.Kind;
+
+ int Size = IntUtils.RoundUp(Map.Size, NvGpuVmm.PageSize);
+
+ long Address = Args.Address;
+
+ if (Address == 0)
+ {
+ //When the address is zero, we need to allocate
+ //our own backing memory for the NvMap.
+ if (!Context.Ns.Os.Allocator.TryAllocate((uint)Size, out Address))
+ {
+ Result = NvResult.OutOfMemory;
+ }
+ }
+
+ if (Result == NvResult.Success)
+ {
+ Map.Size = Size;
+ Map.Address = Address;
+ }
+ }
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ return Result;
+ }
+
+ private static int Free(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ NvMapFree Args = AMemoryHelper.Read<NvMapFree>(Context.Memory, InputPosition);
+
+ NvMapHandle Map = GetNvMap(Context, Args.Handle);
+
+ if (Map == null)
+ {
+ Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!");
+
+ return NvResult.InvalidInput;
+ }
+
+ long RefCount = Map.DecrementRefCount();
+
+ if (RefCount <= 0)
+ {
+ DeleteNvMap(Context, Args.Handle);
+
+ Context.Ns.Log.PrintInfo(LogClass.ServiceNv, $"Deleted map {Args.Handle}!");
+
+ Args.Flags = 0;
+ }
+ else
+ {
+ Args.Flags = FlagNotFreedYet;
+ }
+
+ Args.RefCount = RefCount;
+ Args.Size = Map.Size;
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ return NvResult.Success;
+ }
+
+ private static int Param(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ NvMapParam Args = AMemoryHelper.Read<NvMapParam>(Context.Memory, InputPosition);
+
+ NvMapHandle Map = GetNvMap(Context, Args.Handle);
+
+ if (Map == null)
+ {
+ Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!");
+
+ return NvResult.InvalidInput;
+ }
+
+ switch ((NvMapHandleParam)Args.Param)
+ {
+ case NvMapHandleParam.Size: Args.Result = Map.Size; break;
+ case NvMapHandleParam.Align: Args.Result = Map.Align; break;
+ case NvMapHandleParam.Heap: Args.Result = 0x40000000; break;
+ case NvMapHandleParam.Kind: Args.Result = Map.Kind; break;
+ case NvMapHandleParam.Compr: Args.Result = 0; break;
+
+ //Note: Base is not supported and returns an error.
+ //Any other value also returns an error.
+ default: return NvResult.InvalidInput;
+ }
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ return NvResult.Success;
+ }
+
+ private static int GetId(ServiceCtx Context)
+ {
+ long InputPosition = Context.Request.GetBufferType0x21Position();
+ long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+ NvMapGetId Args = AMemoryHelper.Read<NvMapGetId>(Context.Memory, InputPosition);
+
+ NvMapHandle Map = GetNvMap(Context, Args.Handle);
+
+ if (Map == null)
+ {
+ Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid handle 0x{Args.Handle:x8}!");
+
+ return NvResult.InvalidInput;
+ }
+
+ Args.Id = Args.Handle;
+
+ AMemoryHelper.Write(Context.Memory, OutputPosition, Args);
+
+ return NvResult.Success;
+ }
+
+ private static int AddNvMap(ServiceCtx Context, NvMapHandle Map)
+ {
+ IdDictionary Dict = Maps.GetOrAdd(Context.Process, (Key) =>
+ {
+ IdDictionary NewDict = new IdDictionary();
+
+ NewDict.Add(0, new NvMapHandle());
+
+ return NewDict;
+ });
+
+ return Dict.Add(Map);
+ }
+
+ private static bool DeleteNvMap(ServiceCtx Context, int Handle)
+ {
+ if (Maps.TryGetValue(Context.Process, out IdDictionary Dict))
+ {
+ return Dict.Delete(Handle) != null;
+ }
+
+ return false;
+ }
+
+ public static void InitializeNvMap(ServiceCtx Context)
+ {
+ IdDictionary Dict = Maps.GetOrAdd(Context.Process, (Key) =>new IdDictionary());
+
+ Dict.Add(0, new NvMapHandle());
+ }
+
+ public static NvMapHandle GetNvMapWithFb(ServiceCtx Context, int Handle)
+ {
+ if (Maps.TryGetValue(Context.Process, out IdDictionary Dict))
+ {
+ return Dict.GetData<NvMapHandle>(Handle);
+ }
+
+ return null;
+ }
+
+ public static NvMapHandle GetNvMap(ServiceCtx Context, int Handle)
+ {
+ if (Handle != 0 && Maps.TryGetValue(Context.Process, out IdDictionary Dict))
+ {
+ return Dict.GetData<NvMapHandle>(Handle);
+ }
+
+ return null;
+ }
+
+ public static void UnloadProcess(Process Process)
+ {
+ Maps.TryRemove(Process, out _);
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapParam.cs b/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapParam.cs
new file mode 100644
index 00000000..196ef6ab
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvMap/NvMapParam.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Core.OsHle.Services.Nv.NvMap
+{
+ struct NvMapParam
+ {
+ public int Handle;
+ public int Param;
+ public int Result;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvMapFb.cs b/Ryujinx.Core/OsHle/Services/Nv/NvMapFb.cs
deleted file mode 100644
index d8a47418..00000000
--- a/Ryujinx.Core/OsHle/Services/Nv/NvMapFb.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace Ryujinx.Core.OsHle.Services.Nv
-{
- class NvMapFb
- {
- private List<long> BufferOffs;
-
- public NvMapFb()
- {
- BufferOffs = new List<long>();
- }
-
- public void AddBufferOffset(long Offset)
- {
- BufferOffs.Add(Offset);
- }
-
- public bool HasBufferOffset(int Index)
- {
- if ((uint)Index >= BufferOffs.Count)
- {
- return false;
- }
-
- return true;
- }
-
- public long GetBufferOffset(int Index)
- {
- if ((uint)Index >= BufferOffs.Count)
- {
- throw new ArgumentOutOfRangeException(nameof(Index));
- }
-
- return BufferOffs[Index];
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvResult.cs b/Ryujinx.Core/OsHle/Services/Nv/NvResult.cs
new file mode 100644
index 00000000..5a419165
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvResult.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.Core.OsHle.Services.Nv
+{
+ static class NvResult
+ {
+ public const int Success = 0;
+ public const int TryAgain = -11;
+ public const int OutOfMemory = -12;
+ public const int InvalidInput = -22;
+ public const int NotSupported = -25;
+ public const int Restart = -85;
+ public const int TimedOut = -110;
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Vi/NvFlinger.cs b/Ryujinx.Core/OsHle/Services/Vi/NvFlinger.cs
index 5aa3c3d5..db2f7fa2 100644
--- a/Ryujinx.Core/OsHle/Services/Vi/NvFlinger.cs
+++ b/Ryujinx.Core/OsHle/Services/Vi/NvFlinger.cs
@@ -1,9 +1,9 @@
using ChocolArm64.Memory;
+using Ryujinx.Core.Gpu;
using Ryujinx.Core.Logging;
using Ryujinx.Core.OsHle.Handles;
-using Ryujinx.Core.OsHle.Services.Nv;
+using Ryujinx.Core.OsHle.Services.Nv.NvMap;
using Ryujinx.Graphics.Gal;
-using Ryujinx.Graphics.Gpu;
using System;
using System.Collections.Generic;
using System.IO;
@@ -282,20 +282,12 @@ namespace Ryujinx.Core.OsHle.Services.Android
int FbWidth = 1280;
int FbHeight = 720;
- NvMap Map = GetNvMap(Context, Slot);
+ int NvMapHandle = BitConverter.ToInt32(BufferQueue[Slot].Data.RawData, 0x4c);
+ int BufferOffset = BitConverter.ToInt32(BufferQueue[Slot].Data.RawData, 0x50);
- NvMapFb MapFb = (NvMapFb)INvDrvServices.NvMapsFb.GetData(Context.Process, 0);
+ NvMapHandle Map = NvMapIoctl.GetNvMap(Context, NvMapHandle);;
- long CpuAddr = Map.CpuAddress;
- long GpuAddr = Map.GpuAddress;
-
- if (MapFb.HasBufferOffset(Slot))
- {
- CpuAddr += MapFb.GetBufferOffset(Slot);
-
- //TODO: Enable once the frame buffers problems are fixed.
- //GpuAddr += MapFb.GetBufferOffset(Slot);
- }
+ long FbAddr = Map.Address + BufferOffset;
BufferQueue[Slot].State = BufferState.Acquired;
@@ -352,17 +344,17 @@ namespace Ryujinx.Core.OsHle.Services.Android
//TODO: Support double buffering here aswell, it is broken for GPU
//frame buffers because it seems to be completely out of sync.
- if (Context.Ns.Gpu.Engine3d.IsFrameBufferPosition(GpuAddr))
+ if (Context.Ns.Gpu.Engine3d.IsFrameBufferPosition(FbAddr))
{
//Frame buffer is rendered to by the GPU, we can just
//bind the frame buffer texture, it's not necessary to read anything.
- Renderer.SetFrameBuffer(GpuAddr);
+ Renderer.SetFrameBuffer(FbAddr);
}
else
{
//Frame buffer is not set on the GPU registers, in this case
//assume that the app is manually writing to it.
- Texture Texture = new Texture(CpuAddr, FbWidth, FbHeight);
+ Texture Texture = new Texture(FbAddr, FbWidth, FbHeight);
byte[] Data = TextureReader.Read(Context.Memory, Texture);
@@ -372,22 +364,6 @@ namespace Ryujinx.Core.OsHle.Services.Android
Context.Ns.Gpu.Renderer.QueueAction(() => ReleaseBuffer(Slot));
}
- private NvMap GetNvMap(ServiceCtx Context, int Slot)
- {
- int NvMapHandle = BitConverter.ToInt32(BufferQueue[Slot].Data.RawData, 0x4c);
-
- if (!BitConverter.IsLittleEndian)
- {
- byte[] RawValue = BitConverter.GetBytes(NvMapHandle);
-
- Array.Reverse(RawValue);
-
- NvMapHandle = BitConverter.ToInt32(RawValue, 0);
- }
-
- return INvDrvServices.NvMaps.GetData<NvMap>(Context.Process, NvMapHandle);
- }
-
private void ReleaseBuffer(int Slot)
{
BufferQueue[Slot].State = BufferState.Free;
diff --git a/Ryujinx.Core/OsHle/Utilities/IntUtils.cs b/Ryujinx.Core/OsHle/Utilities/IntUtils.cs
new file mode 100644
index 00000000..4a522465
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Utilities/IntUtils.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.Core.OsHle.Utilities
+{
+ static class IntUtils
+ {
+ public static int RoundUp(int Value, int Size)
+ {
+ return (Value + (Size - 1)) & ~(Size - 1);
+ }
+
+ public static long RoundUp(long Value, int Size)
+ {
+ return (Value + (Size - 1)) & ~((long)Size - 1);
+ }
+ }
+}
diff --git a/Ryujinx.Core/OsHle/Utilities/MemReader.cs b/Ryujinx.Core/OsHle/Utilities/MemReader.cs
deleted file mode 100644
index fe92f68f..00000000
--- a/Ryujinx.Core/OsHle/Utilities/MemReader.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using ChocolArm64.Memory;
-
-namespace Ryujinx.Core.OsHle.Utilities
-{
- class MemReader
- {
- private AMemory Memory;
-
- public long Position { get; private set; }
-
- public MemReader(AMemory Memory, long Position)
- {
- this.Memory = Memory;
- this.Position = Position;
- }
-
- public byte ReadByte()
- {
- byte Value = Memory.ReadByte(Position);
-
- Position++;
-
- return Value;
- }
-
- public int ReadInt32()
- {
- int Value = Memory.ReadInt32(Position);
-
- Position += 4;
-
- return Value;
- }
-
- public long ReadInt64()
- {
- long Value = Memory.ReadInt64(Position);
-
- Position += 8;
-
- return Value;
- }
- }
-} \ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Utilities/MemWriter.cs b/Ryujinx.Core/OsHle/Utilities/MemWriter.cs
deleted file mode 100644
index 21b6a3b6..00000000
--- a/Ryujinx.Core/OsHle/Utilities/MemWriter.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using ChocolArm64.Memory;
-
-namespace Ryujinx.Core.OsHle.Utilities
-{
- class MemWriter
- {
- private AMemory Memory;
-
- public long Position { get; private set; }
-
- public MemWriter(AMemory Memory, long Position)
- {
- this.Memory = Memory;
- this.Position = Position;
- }
-
- public void WriteByte(byte Value)
- {
- Memory.WriteByte(Position, Value);
-
- Position++;
- }
-
- public void WriteInt32(int Value)
- {
- Memory.WriteInt32(Position, Value);
-
- Position += 4;
- }
-
- public void WriteInt64(long Value)
- {
- Memory.WriteInt64(Position, Value);
-
- Position += 8;
- }
- }
-} \ No newline at end of file