diff --git a/ChocolArm64/Memory/AMemory.cs b/ChocolArm64/Memory/AMemory.cs
index d7e11189f6..6625966f1a 100644
--- a/ChocolArm64/Memory/AMemory.cs
+++ b/ChocolArm64/Memory/AMemory.cs
@@ -6,7 +6,7 @@ using System.Runtime.InteropServices;
 
 namespace ChocolArm64.Memory
 {
-    public unsafe class AMemory : IDisposable
+    public unsafe class AMemory : IAMemory, IDisposable
     {
         private const long ErgMask = (4 << AThreadState.ErgSizeLog2) - 1;
 
diff --git a/ChocolArm64/Memory/IAMemory.cs b/ChocolArm64/Memory/IAMemory.cs
new file mode 100644
index 0000000000..5b7d17bb8b
--- /dev/null
+++ b/ChocolArm64/Memory/IAMemory.cs
@@ -0,0 +1,37 @@
+namespace ChocolArm64.Memory
+{
+    public interface IAMemory
+    {
+        sbyte ReadSByte(long Position);
+
+        short ReadInt16(long Position);
+
+        int ReadInt32(long Position);
+
+        long ReadInt64(long Position);
+
+        byte ReadByte(long Position);
+
+        ushort ReadUInt16(long Position);
+
+        uint ReadUInt32(long Position);
+
+        ulong ReadUInt64(long Position);
+
+        void WriteSByte(long Position, sbyte Value);
+
+        void WriteInt16(long Position, short Value);
+
+        void WriteInt32(long Position, int Value);
+
+        void WriteInt64(long Position, long Value);
+
+        void WriteByte(long Position, byte Value);
+
+        void WriteUInt16(long Position, ushort Value);
+
+        void WriteUInt32(long Position, uint Value);
+
+        void WriteUInt64(long Position, ulong Value);
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/BlockLinearSwizzle.cs b/Ryujinx.Core/Gpu/BlockLinearSwizzle.cs
similarity index 97%
rename from Ryujinx.Graphics/Gpu/BlockLinearSwizzle.cs
rename to Ryujinx.Core/Gpu/BlockLinearSwizzle.cs
index d2cbb14432..eb41ea3001 100644
--- a/Ryujinx.Graphics/Gpu/BlockLinearSwizzle.cs
+++ b/Ryujinx.Core/Gpu/BlockLinearSwizzle.cs
@@ -1,4 +1,4 @@
-namespace Ryujinx.Graphics.Gpu
+namespace Ryujinx.Core.Gpu
 {
     class BlockLinearSwizzle : ISwizzle
     {
diff --git a/Ryujinx.Core/Gpu/INvGpuEngine.cs b/Ryujinx.Core/Gpu/INvGpuEngine.cs
new file mode 100644
index 0000000000..865ea8ba89
--- /dev/null
+++ b/Ryujinx.Core/Gpu/INvGpuEngine.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Core.Gpu
+{
+    interface INvGpuEngine
+    {
+        int[] Registers { get; }
+
+        void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry);
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/ISwizzle.cs b/Ryujinx.Core/Gpu/ISwizzle.cs
similarity index 72%
rename from Ryujinx.Graphics/Gpu/ISwizzle.cs
rename to Ryujinx.Core/Gpu/ISwizzle.cs
index 755051d0c4..f475be6efc 100644
--- a/Ryujinx.Graphics/Gpu/ISwizzle.cs
+++ b/Ryujinx.Core/Gpu/ISwizzle.cs
@@ -1,4 +1,4 @@
-namespace Ryujinx.Graphics.Gpu
+namespace Ryujinx.Core.Gpu
 {
     interface ISwizzle
     {
diff --git a/Ryujinx.Graphics/Gpu/LinearSwizzle.cs b/Ryujinx.Core/Gpu/LinearSwizzle.cs
similarity index 91%
rename from Ryujinx.Graphics/Gpu/LinearSwizzle.cs
rename to Ryujinx.Core/Gpu/LinearSwizzle.cs
index c7a6b304e8..5f8dfddee5 100644
--- a/Ryujinx.Graphics/Gpu/LinearSwizzle.cs
+++ b/Ryujinx.Core/Gpu/LinearSwizzle.cs
@@ -1,4 +1,4 @@
-namespace Ryujinx.Graphics.Gpu
+namespace Ryujinx.Core.Gpu
 {
     class LinearSwizzle : ISwizzle
     {
diff --git a/Ryujinx.Graphics/Gpu/MacroInterpreter.cs b/Ryujinx.Core/Gpu/MacroInterpreter.cs
similarity index 92%
rename from Ryujinx.Graphics/Gpu/MacroInterpreter.cs
rename to Ryujinx.Core/Gpu/MacroInterpreter.cs
index 233baac8bd..b799f98fa6 100644
--- a/Ryujinx.Graphics/Gpu/MacroInterpreter.cs
+++ b/Ryujinx.Core/Gpu/MacroInterpreter.cs
@@ -1,8 +1,7 @@
-using ChocolArm64.Memory;
 using System;
 using System.Collections.Generic;
 
-namespace Ryujinx.Graphics.Gpu
+namespace Ryujinx.Core.Gpu
 {
     class MacroInterpreter
     {
@@ -69,7 +68,7 @@ namespace Ryujinx.Graphics.Gpu
             Gprs = new int[8];
         }
 
-        public void Execute(AMemory Memory, long Position, int Param)
+        public void Execute(NvGpuVmm Vmm, long Position, int Param)
         {
             Reset();
 
@@ -77,13 +76,13 @@ namespace Ryujinx.Graphics.Gpu
 
             Pc = Position;
 
-            FetchOpCode(Memory);
+            FetchOpCode(Vmm);
 
-            while (Step(Memory));
+            while (Step(Vmm));
 
             //Due to the delay slot, we still need to execute
             //one more instruction before we actually exit.
-            Step(Memory);
+            Step(Vmm);
         }
 
         private void Reset()
@@ -99,11 +98,11 @@ namespace Ryujinx.Graphics.Gpu
             Carry = false;
         }
 
-        private bool Step(AMemory Memory)
+        private bool Step(NvGpuVmm Vmm)
         {
             long BaseAddr = Pc - 4;
 
-            FetchOpCode(Memory);
+            FetchOpCode(Vmm);
 
             if ((OpCode & 7) < 7)
             {
@@ -145,7 +144,7 @@ namespace Ryujinx.Graphics.Gpu
                     {
                         SetDstGpr(FetchParam());
 
-                        Send(Memory, Result);
+                        Send(Vmm, Result);
 
                         break;
                     }
@@ -155,7 +154,7 @@ namespace Ryujinx.Graphics.Gpu
                     {
                         SetDstGpr(Result);
 
-                        Send(Memory, Result);
+                        Send(Vmm, Result);
 
                         break;
                     }
@@ -177,7 +176,7 @@ namespace Ryujinx.Graphics.Gpu
 
                         SetMethAddr(Result);
 
-                        Send(Memory, FetchParam());
+                        Send(Vmm, FetchParam());
 
                         break;
                     }
@@ -189,7 +188,7 @@ namespace Ryujinx.Graphics.Gpu
 
                         SetMethAddr(Result);
 
-                        Send(Memory, (Result >> 12) & 0x3f);
+                        Send(Vmm, (Result >> 12) & 0x3f);
 
                         break;
                     }
@@ -212,7 +211,7 @@ namespace Ryujinx.Graphics.Gpu
 
                     if (NoDelays)
                     {
-                        FetchOpCode(Memory);
+                        FetchOpCode(Vmm);
                     }
 
                     return true;
@@ -224,11 +223,11 @@ namespace Ryujinx.Graphics.Gpu
             return !Exit;
         }
 
-        private void FetchOpCode(AMemory Memory)
+        private void FetchOpCode(NvGpuVmm Vmm)
         {
             OpCode = PipeOp;
 
-            PipeOp = Memory.ReadInt32(Pc);
+            PipeOp = Vmm.ReadInt32(Pc);
 
             Pc += 4;
         }
@@ -408,11 +407,11 @@ namespace Ryujinx.Graphics.Gpu
             return Engine.Registers[Reg];
         }
 
-        private void Send(AMemory Memory, int Value)
+        private void Send(NvGpuVmm Vmm, int Value)
         {
-            NsGpuPBEntry PBEntry = new NsGpuPBEntry(MethAddr, 0, Value);
+            NvGpuPBEntry PBEntry = new NvGpuPBEntry(MethAddr, 0, Value);
 
-            Engine.CallMethod(Memory, PBEntry);
+            Engine.CallMethod(Vmm, PBEntry);
 
             MethAddr += MethIncr;
         }
diff --git a/Ryujinx.Graphics/Gpu/NsGpu.cs b/Ryujinx.Core/Gpu/NvGpu.cs
similarity index 50%
rename from Ryujinx.Graphics/Gpu/NsGpu.cs
rename to Ryujinx.Core/Gpu/NvGpu.cs
index e10889821e..71df76ff31 100644
--- a/Ryujinx.Graphics/Gpu/NsGpu.cs
+++ b/Ryujinx.Core/Gpu/NvGpu.cs
@@ -1,14 +1,12 @@
 using Ryujinx.Graphics.Gal;
 using System.Threading;
 
-namespace Ryujinx.Graphics.Gpu
+namespace Ryujinx.Core.Gpu
 {
-    public class NsGpu
+    public class NvGpu
     {
         public IGalRenderer Renderer { get; private set; }
 
-        public NsGpuMemoryMgr MemoryMgr { get; private set; }
-
         public NvGpuFifo Fifo { get; private set; }
 
         public NvGpuEngine2d Engine2d { get; private set; }
@@ -18,12 +16,10 @@ namespace Ryujinx.Graphics.Gpu
 
         private bool KeepRunning;
 
-        public NsGpu(IGalRenderer Renderer)
+        public NvGpu(IGalRenderer Renderer)
         {
             this.Renderer = Renderer;
 
-            MemoryMgr = new NsGpuMemoryMgr();
-
             Fifo = new NvGpuFifo(this);
 
             Engine2d = new NvGpuEngine2d(this);
@@ -36,31 +32,6 @@ namespace Ryujinx.Graphics.Gpu
             FifoProcessing.Start();
         }
 
-        public long GetCpuAddr(long Position)
-        {
-            return MemoryMgr.GetCpuAddr(Position);
-        }
-
-        public long MapMemory(long CpuAddr, long Size)
-        {
-            return MemoryMgr.Map(CpuAddr, Size);
-        }
-
-        public long MapMemory(long CpuAddr, long GpuAddr, long Size)
-        {
-            return MemoryMgr.Map(CpuAddr, GpuAddr, Size);
-        }
-
-        public long ReserveMemory(long Size, long Align)
-        {
-            return MemoryMgr.Reserve(Size, Align);
-        }
-
-        public long ReserveMemory(long GpuAddr, long Size, long Align)
-        {
-            return MemoryMgr.Reserve(GpuAddr, Size, Align);
-        }
-
         private void ProcessFifo()
         {
             while (KeepRunning)
diff --git a/Ryujinx.Graphics/Gpu/NvGpuEngine.cs b/Ryujinx.Core/Gpu/NvGpuEngine.cs
similarity index 84%
rename from Ryujinx.Graphics/Gpu/NvGpuEngine.cs
rename to Ryujinx.Core/Gpu/NvGpuEngine.cs
index 624915d0d4..ee0420f704 100644
--- a/Ryujinx.Graphics/Gpu/NvGpuEngine.cs
+++ b/Ryujinx.Core/Gpu/NvGpuEngine.cs
@@ -1,4 +1,4 @@
-namespace Ryujinx.Graphics.Gpu
+namespace Ryujinx.Core.Gpu
 {
     enum NvGpuEngine
     {
diff --git a/Ryujinx.Graphics/Gpu/NvGpuEngine2d.cs b/Ryujinx.Core/Gpu/NvGpuEngine2d.cs
similarity index 74%
rename from Ryujinx.Graphics/Gpu/NvGpuEngine2d.cs
rename to Ryujinx.Core/Gpu/NvGpuEngine2d.cs
index c2bee167d8..88395b7ab5 100644
--- a/Ryujinx.Graphics/Gpu/NvGpuEngine2d.cs
+++ b/Ryujinx.Core/Gpu/NvGpuEngine2d.cs
@@ -1,8 +1,7 @@
-using ChocolArm64.Memory;
 using Ryujinx.Graphics.Gal;
 using System.Collections.Generic;
 
-namespace Ryujinx.Graphics.Gpu
+namespace Ryujinx.Core.Gpu
 {
     public class NvGpuEngine2d : INvGpuEngine
     {
@@ -19,11 +18,11 @@ namespace Ryujinx.Graphics.Gpu
 
         public int[] Registers { get; private set; }
 
-        private NsGpu Gpu;
+        private NvGpu Gpu;
 
         private Dictionary<int, NvGpuMethod> Methods;
 
-        public NvGpuEngine2d(NsGpu Gpu)
+        public NvGpuEngine2d(NvGpu Gpu)
         {
             this.Gpu = Gpu;
 
@@ -44,11 +43,11 @@ namespace Ryujinx.Graphics.Gpu
             AddMethod(0xb5, 1, 1, TextureCopy);
         }
 
-        public void CallMethod(AMemory Memory, NsGpuPBEntry PBEntry)
+        public void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
         {
             if (Methods.TryGetValue(PBEntry.Method, out NvGpuMethod Method))
             {
-                Method(Memory, PBEntry);
+                Method(Vmm, PBEntry);
             }
             else
             {
@@ -56,7 +55,7 @@ namespace Ryujinx.Graphics.Gpu
             }
         }
 
-        private void TextureCopy(AMemory Memory, NsGpuPBEntry PBEntry)
+        private void TextureCopy(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
         {
             CopyOperation Operation = (CopyOperation)ReadRegister(NvGpuEngine2dReg.CopyOperation);
 
@@ -76,10 +75,10 @@ namespace Ryujinx.Graphics.Gpu
 
             int DstBlockHeight = 1 << ((DstBlkDim >> 4) & 0xf);
 
-            long Tag = MakeInt64From2xInt32(NvGpuEngine2dReg.SrcAddress);
+            long Tag = Vmm.GetPhysicalAddress(MakeInt64From2xInt32(NvGpuEngine2dReg.SrcAddress));
 
-            TryGetCpuAddr(NvGpuEngine2dReg.SrcAddress, out long SrcAddress);
-            TryGetCpuAddr(NvGpuEngine2dReg.DstAddress, out long DstAddress);
+            long SrcAddress = MakeInt64From2xInt32(NvGpuEngine2dReg.SrcAddress);
+            long DstAddress = MakeInt64From2xInt32(NvGpuEngine2dReg.DstAddress);
 
             bool IsFbTexture = Gpu.Engine3d.IsFrameBufferPosition(Tag);
 
@@ -101,31 +100,22 @@ namespace Ryujinx.Graphics.Gpu
             {
                 Gpu.Renderer.GetFrameBufferData(Tag, (byte[] Buffer) =>
                 {
-                    CopyTexture(Memory, DstTexture, Buffer);
+                    CopyTexture(Vmm, DstTexture, Buffer);
                 });
             }
             else
             {
                 long Size = SrcWidth * SrcHeight * 4;
 
-                byte[] Buffer = AMemoryHelper.ReadBytes(Memory, SrcAddress, Size);
+                byte[] Buffer = Vmm.ReadBytes(SrcAddress, Size);
 
-                CopyTexture(Memory, DstTexture, Buffer);
+                CopyTexture(Vmm, DstTexture, Buffer);
             }
         }
 
-        private void CopyTexture(AMemory Memory, Texture Texture, byte[] Buffer)
+        private void CopyTexture(NvGpuVmm Vmm, Texture Texture, byte[] Buffer)
         {
-            TextureWriter.Write(Memory, Texture, Buffer);
-        }
-
-        private bool TryGetCpuAddr(NvGpuEngine2dReg Reg, out long Position)
-        {
-            Position = MakeInt64From2xInt32(Reg);
-
-            Position = Gpu.GetCpuAddr(Position);
-
-            return Position != -1;
+            TextureWriter.Write(Vmm, Texture, Buffer);
         }
 
         private long MakeInt64From2xInt32(NvGpuEngine2dReg Reg)
@@ -135,7 +125,7 @@ namespace Ryujinx.Graphics.Gpu
                 (uint)Registers[(int)Reg + 1];
         }
 
-        private void WriteRegister(NsGpuPBEntry PBEntry)
+        private void WriteRegister(NvGpuPBEntry PBEntry)
         {
             int ArgsCount = PBEntry.Arguments.Count;
 
diff --git a/Ryujinx.Graphics/Gpu/NvGpuEngine2dReg.cs b/Ryujinx.Core/Gpu/NvGpuEngine2dReg.cs
similarity index 95%
rename from Ryujinx.Graphics/Gpu/NvGpuEngine2dReg.cs
rename to Ryujinx.Core/Gpu/NvGpuEngine2dReg.cs
index 903baca87c..b4abad00da 100644
--- a/Ryujinx.Graphics/Gpu/NvGpuEngine2dReg.cs
+++ b/Ryujinx.Core/Gpu/NvGpuEngine2dReg.cs
@@ -1,4 +1,4 @@
-namespace Ryujinx.Graphics.Gpu
+namespace Ryujinx.Core.Gpu
 {
     enum NvGpuEngine2dReg
     {
diff --git a/Ryujinx.Graphics/Gpu/NvGpuEngine3d.cs b/Ryujinx.Core/Gpu/NvGpuEngine3d.cs
similarity index 73%
rename from Ryujinx.Graphics/Gpu/NvGpuEngine3d.cs
rename to Ryujinx.Core/Gpu/NvGpuEngine3d.cs
index a6696650b6..b08b949617 100644
--- a/Ryujinx.Graphics/Gpu/NvGpuEngine3d.cs
+++ b/Ryujinx.Core/Gpu/NvGpuEngine3d.cs
@@ -1,15 +1,14 @@
-using ChocolArm64.Memory;
 using Ryujinx.Graphics.Gal;
 using System;
 using System.Collections.Generic;
 
-namespace Ryujinx.Graphics.Gpu
+namespace Ryujinx.Core.Gpu
 {
     public class NvGpuEngine3d : INvGpuEngine
     {
         public int[] Registers { get; private set; }
 
-        private NsGpu Gpu;
+        private NvGpu Gpu;
 
         private Dictionary<int, NvGpuMethod> Methods;
 
@@ -20,11 +19,11 @@ namespace Ryujinx.Graphics.Gpu
             public int  Size;
         }
 
-        private ConstBuffer[] ConstBuffers;
+        private ConstBuffer[][] ConstBuffers;
 
         private HashSet<long> FrameBuffers;
 
-        public NvGpuEngine3d(NsGpu Gpu)
+        public NvGpuEngine3d(NvGpu Gpu)
         {
             this.Gpu = Gpu;
 
@@ -46,18 +45,23 @@ namespace Ryujinx.Graphics.Gpu
             AddMethod(0x674,  1, 1, ClearBuffers);
             AddMethod(0x6c3,  1, 1, QueryControl);
             AddMethod(0x8e4, 16, 1, CbData);
-            AddMethod(0x904,  1, 1, CbBind);
+            AddMethod(0x904,  5, 8, CbBind);
 
-            ConstBuffers = new ConstBuffer[18];
+            ConstBuffers = new ConstBuffer[6][];
+
+            for (int Index = 0; Index < ConstBuffers.Length; Index++)
+            {
+                ConstBuffers[Index] = new ConstBuffer[18];
+            }
 
             FrameBuffers = new HashSet<long>();
         }
 
-        public void CallMethod(AMemory Memory, NsGpuPBEntry PBEntry)
+        public void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
         {
             if (Methods.TryGetValue(PBEntry.Method, out NvGpuMethod Method))
             {
-                Method(Memory, PBEntry);
+                Method(Vmm, PBEntry);
             }
             else
             {
@@ -65,22 +69,22 @@ namespace Ryujinx.Graphics.Gpu
             }
         }
 
-        private void VertexEndGl(AMemory Memory, NsGpuPBEntry PBEntry)
+        private void VertexEndGl(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
         {
-            SetFrameBuffer(0);
+            SetFrameBuffer(Vmm, 0);
 
-            long[] Tags = UploadShaders(Memory);
+            long[] Tags = UploadShaders(Vmm);
 
             Gpu.Renderer.BindProgram();
 
             SetAlphaBlending();
 
-            UploadTextures(Memory, Tags);
-            UploadUniforms(Memory);
-            UploadVertexArrays(Memory);
+            UploadTextures(Vmm, Tags);
+            UploadUniforms(Vmm);
+            UploadVertexArrays(Vmm);
         }
 
-        private void ClearBuffers(AMemory Memory, NsGpuPBEntry PBEntry)
+        private void ClearBuffers(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
         {
             int Arg0 = PBEntry.Arguments[0];
 
@@ -90,28 +94,30 @@ namespace Ryujinx.Graphics.Gpu
 
             GalClearBufferFlags Flags = (GalClearBufferFlags)(Arg0 & 0x3f);
 
-            SetFrameBuffer(0);
+            SetFrameBuffer(Vmm, 0);
 
             //TODO: Enable this once the frame buffer problems are fixed.
             //Gpu.Renderer.ClearBuffers(Layer, Flags);
         }
 
-        private void SetFrameBuffer(int FbIndex)
+        private void SetFrameBuffer(NvGpuVmm Vmm, int FbIndex)
         {
-            long Address = MakeInt64From2xInt32(NvGpuEngine3dReg.FrameBufferNAddress + FbIndex * 0x10);
+            long VA = MakeInt64From2xInt32(NvGpuEngine3dReg.FrameBufferNAddress + FbIndex * 0x10);
 
-            FrameBuffers.Add(Address);
+            long PA = Vmm.GetPhysicalAddress(VA);
+
+            FrameBuffers.Add(PA);
 
             int Width  = ReadRegister(NvGpuEngine3dReg.FrameBufferNWidth  + FbIndex * 0x10);
             int Height = ReadRegister(NvGpuEngine3dReg.FrameBufferNHeight + FbIndex * 0x10);
 
             //Note: Using the Width/Height results seems to give incorrect results.
             //Maybe the size of all frame buffers is hardcoded to screen size? This seems unlikely.
-            Gpu.Renderer.CreateFrameBuffer(Address, 1280, 720);
-            Gpu.Renderer.BindFrameBuffer(Address);
+            Gpu.Renderer.CreateFrameBuffer(PA, 1280, 720);
+            Gpu.Renderer.BindFrameBuffer(PA);
         }
 
-        private long[] UploadShaders(AMemory Memory)
+        private long[] UploadShaders(NvGpuVmm Vmm)
         {
             long[] Tags = new long[5];
 
@@ -132,12 +138,10 @@ namespace Ryujinx.Graphics.Gpu
 
                 long Tag = BasePosition + (uint)Offset;
 
-                long Position = Gpu.GetCpuAddr(Tag);
-
                 //TODO: Find a better way to calculate the size.
                 int Size = 0x20000;
 
-                byte[] Code = AMemoryHelper.ReadBytes(Memory, Position, (uint)Size);
+                byte[] Code = Vmm.ReadBytes(Tag, Size);
 
                 GalShaderType ShaderType = GetTypeFromProgram(Index);
 
@@ -211,16 +215,12 @@ namespace Ryujinx.Graphics.Gpu
             }
         }
 
-        private void UploadTextures(AMemory Memory, long[] Tags)
+        private void UploadTextures(NvGpuVmm Vmm, long[] Tags)
         {
             long BaseShPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.ShaderAddress);
 
             int TextureCbIndex = ReadRegister(NvGpuEngine3dReg.TextureCbIndex);
 
-            long BasePosition = ConstBuffers[TextureCbIndex].Position;
-
-            long Size = (uint)ConstBuffers[TextureCbIndex].Size;
-
             //Note: On the emulator renderer, Texture Unit 0 is
             //reserved for drawing the frame buffer.
             int TexIndex = 1;
@@ -229,9 +229,9 @@ namespace Ryujinx.Graphics.Gpu
             {
                 foreach (ShaderDeclInfo DeclInfo in Gpu.Renderer.GetTextureUsage(Tags[Index]))
                 {
-                    long Position = BasePosition + Index * Size;
+                    long Position = ConstBuffers[Index][TextureCbIndex].Position;
 
-                    UploadTexture(Memory, Position, TexIndex, DeclInfo.Index);
+                    UploadTexture(Vmm, Position, TexIndex, DeclInfo.Index);
 
                     Gpu.Renderer.SetUniform1(DeclInfo.Name, TexIndex);
 
@@ -240,26 +240,28 @@ namespace Ryujinx.Graphics.Gpu
             }
         }
 
-        private void UploadTexture(AMemory Memory, long BasePosition, int TexIndex, int HndIndex)
+        private void UploadTexture(NvGpuVmm Vmm, long BasePosition, int TexIndex, int HndIndex)
         {
             long Position = BasePosition + HndIndex * 4;
 
-            int TextureHandle = Memory.ReadInt32(Position);
+            int TextureHandle = Vmm.ReadInt32(Position);
 
             int TicIndex = (TextureHandle >>  0) & 0xfffff;
             int TscIndex = (TextureHandle >> 20) & 0xfff;
 
-            TryGetCpuAddr(NvGpuEngine3dReg.TexHeaderPoolOffset,  out long TicPosition);
-            TryGetCpuAddr(NvGpuEngine3dReg.TexSamplerPoolOffset, out long TscPosition);
+            long TicPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.TexHeaderPoolOffset);
+            long TscPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.TexSamplerPoolOffset);
 
             TicPosition += TicIndex * 0x20;
             TscPosition += TscIndex * 0x20;
 
-            GalTextureSampler Sampler = TextureFactory.MakeSampler(Gpu, Memory, TscPosition);
+            GalTextureSampler Sampler = TextureFactory.MakeSampler(Gpu, Vmm, TscPosition);
 
-            long TextureAddress = Memory.ReadInt64(TicPosition + 4) & 0xffffffffffff;
+            long TextureAddress = Vmm.ReadInt64(TicPosition + 4) & 0xffffffffffff;
 
-            if (FrameBuffers.Contains(TextureAddress))
+            TextureAddress = Vmm.GetPhysicalAddress(TextureAddress);
+
+            if (IsFrameBufferPosition(TextureAddress))
             {
                 //This texture is a frame buffer texture,
                 //we shouldn't read anything from memory and bind
@@ -269,14 +271,14 @@ namespace Ryujinx.Graphics.Gpu
             }
             else
             {
-                GalTexture Texture = TextureFactory.MakeTexture(Gpu, Memory, TicPosition);
+                GalTexture Texture = TextureFactory.MakeTexture(Gpu, Vmm, TicPosition);
 
                 Gpu.Renderer.SetTextureAndSampler(TexIndex, Texture, Sampler);
                 Gpu.Renderer.BindTexture(TexIndex);
             }
         }
 
-        private void UploadUniforms(AMemory Memory)
+        private void UploadUniforms(NvGpuVmm Vmm)
         {
             long BasePosition = MakeInt64From2xInt32(NvGpuEngine3dReg.ShaderAddress);
 
@@ -295,13 +297,11 @@ namespace Ryujinx.Graphics.Gpu
 
                 for (int Cbuf = 0; Cbuf < ConstBuffers.Length; Cbuf++)
                 {
-                    ConstBuffer Cb = ConstBuffers[Cbuf];
+                    ConstBuffer Cb = ConstBuffers[Index][Cbuf];
 
                     if (Cb.Enabled)
                     {
-                        long CbPosition = Cb.Position + Index * Cb.Size;
-
-                        byte[] Data = AMemoryHelper.ReadBytes(Memory, CbPosition, (uint)Cb.Size);
+                        byte[] Data = Vmm.ReadBytes(Cb.Position, (uint)Cb.Size);
 
                         Gpu.Renderer.SetConstBuffer(BasePosition + (uint)Offset, Cbuf, Data);
                     }
@@ -309,7 +309,7 @@ namespace Ryujinx.Graphics.Gpu
             }
         }
 
-        private void UploadVertexArrays(AMemory Memory)
+        private void UploadVertexArrays(NvGpuVmm Vmm)
         {
             long IndexPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.IndexArrayAddress);
 
@@ -328,11 +328,9 @@ namespace Ryujinx.Graphics.Gpu
 
             if (IndexSize != 0)
             {
-                IndexPosition = Gpu.GetCpuAddr(IndexPosition);
-
                 int BufferSize = IndexCount * IndexSize;
 
-                byte[] Data = AMemoryHelper.ReadBytes(Memory, IndexPosition, BufferSize);
+                byte[] Data = Vmm.ReadBytes(IndexPosition, BufferSize);
 
                 Gpu.Renderer.SetIndexArray(Data, IndexFormat);
             }
@@ -382,7 +380,7 @@ namespace Ryujinx.Graphics.Gpu
                 if (IndexCount != 0)
                 {
                     Size = GetVertexCountFromIndexBuffer(
-                        Memory,
+                        Vmm,
                         IndexPosition,
                         IndexCount,
                         IndexSize);
@@ -396,9 +394,7 @@ namespace Ryujinx.Graphics.Gpu
                 //In this case, we need to use the size of the attribute.
                 Size *= Stride;
 
-                VertexPosition = Gpu.GetCpuAddr(VertexPosition);
-
-                byte[] Data = AMemoryHelper.ReadBytes(Memory, VertexPosition, Size);
+                byte[] Data = Vmm.ReadBytes(VertexPosition, Size);
 
                 GalVertexAttrib[] AttribArray = Attribs[Index]?.ToArray() ?? new GalVertexAttrib[0];
 
@@ -420,10 +416,10 @@ namespace Ryujinx.Graphics.Gpu
         }
 
         private int GetVertexCountFromIndexBuffer(
-            AMemory Memory,
-            long    IndexPosition,
-            int     IndexCount,
-            int     IndexSize)
+            NvGpuVmm Vmm,
+            long     IndexPosition,
+            int      IndexCount,
+            int      IndexSize)
         {
             int MaxIndex = -1;
 
@@ -431,7 +427,7 @@ namespace Ryujinx.Graphics.Gpu
             {
                 while (IndexCount -- > 0)
                 {
-                    ushort Value = Memory.ReadUInt16(IndexPosition);
+                    ushort Value = Vmm.ReadUInt16(IndexPosition);
 
                     IndexPosition += 2;
 
@@ -445,7 +441,7 @@ namespace Ryujinx.Graphics.Gpu
             {
                 while (IndexCount -- > 0)
                 {
-                    byte Value = Memory.ReadByte(IndexPosition++);
+                    byte Value = Vmm.ReadByte(IndexPosition++);
 
                     if (MaxIndex < Value)
                     {
@@ -457,7 +453,7 @@ namespace Ryujinx.Graphics.Gpu
             {
                 while (IndexCount -- > 0)
                 {
-                    uint Value = Memory.ReadUInt32(IndexPosition);
+                    uint Value = Vmm.ReadUInt32(IndexPosition);
 
                     IndexPosition += 2;
 
@@ -475,75 +471,56 @@ namespace Ryujinx.Graphics.Gpu
             return MaxIndex + 1;
         }
 
-        private void QueryControl(AMemory Memory, NsGpuPBEntry PBEntry)
+        private void QueryControl(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
         {
-            if (TryGetCpuAddr(NvGpuEngine3dReg.QueryAddress, out long Position))
+            long Position = MakeInt64From2xInt32(NvGpuEngine3dReg.QueryAddress);
+
+            int Seq  = Registers[(int)NvGpuEngine3dReg.QuerySequence];
+            int Ctrl = Registers[(int)NvGpuEngine3dReg.QueryControl];
+
+            int Mode = Ctrl & 3;
+
+            if (Mode == 0)
             {
-                int Seq  = Registers[(int)NvGpuEngine3dReg.QuerySequence];
-                int Ctrl = Registers[(int)NvGpuEngine3dReg.QueryControl];
-
-                int Mode = Ctrl & 3;
-
-                if (Mode == 0)
-                {
-                    //Write mode.
-                    Memory.WriteInt32(Position, Seq);
-                }
+                //Write mode.
+                Vmm.WriteInt32(Position, Seq);
             }
 
             WriteRegister(PBEntry);
         }
 
-        private void CbData(AMemory Memory, NsGpuPBEntry PBEntry)
+        private void CbData(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
         {
-            if (TryGetCpuAddr(NvGpuEngine3dReg.ConstBufferNAddress, out long Position))
+            long Position = MakeInt64From2xInt32(NvGpuEngine3dReg.ConstBufferAddress);
+
+            int Offset = ReadRegister(NvGpuEngine3dReg.ConstBufferOffset);
+
+            foreach (int Arg in PBEntry.Arguments)
             {
-                int Offset = ReadRegister(NvGpuEngine3dReg.ConstBufferNOffset);
+                Vmm.WriteInt32(Position + Offset, Arg);
 
-                foreach (int Arg in PBEntry.Arguments)
-                {
-                    Memory.WriteInt32(Position + Offset, Arg);
-
-                    Offset += 4;
-                }
-
-                WriteRegister(NvGpuEngine3dReg.ConstBufferNOffset, Offset);
+                Offset += 4;
             }
+
+            WriteRegister(NvGpuEngine3dReg.ConstBufferOffset, Offset);
         }
 
-        private void CbBind(AMemory Memory, NsGpuPBEntry PBEntry)
+        private void CbBind(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
         {
+            int Stage = (PBEntry.Method - 0x904) >> 3;
+
             int Index = PBEntry.Arguments[0];
 
             bool Enabled = (Index & 1) != 0;
 
             Index = (Index >> 4) & 0x1f;
 
-            if (TryGetCpuAddr(NvGpuEngine3dReg.ConstBufferNAddress, out long Position))
-            {
-                ConstBuffers[Index].Position = Position;
-                ConstBuffers[Index].Enabled  = Enabled;
+            long Position = MakeInt64From2xInt32(NvGpuEngine3dReg.ConstBufferAddress);
 
-                ConstBuffers[Index].Size = ReadRegister(NvGpuEngine3dReg.ConstBufferNSize);
-            }
-        }
+            ConstBuffers[Stage][Index].Position = Position;
+            ConstBuffers[Stage][Index].Enabled  = Enabled;
 
-        private int ReadCb(AMemory Memory, int Cbuf, int Offset)
-        {
-            long Position = ConstBuffers[Cbuf].Position;
-
-            int Value = Memory.ReadInt32(Position + Offset);
-
-            return Value;
-        }
-
-        private bool TryGetCpuAddr(NvGpuEngine3dReg Reg, out long Position)
-        {
-            Position = MakeInt64From2xInt32(Reg);
-
-            Position = Gpu.GetCpuAddr(Position);
-
-            return Position != -1;
+            ConstBuffers[Stage][Index].Size = ReadRegister(NvGpuEngine3dReg.ConstBufferSize);
         }
 
         private long MakeInt64From2xInt32(NvGpuEngine3dReg Reg)
@@ -553,7 +530,7 @@ namespace Ryujinx.Graphics.Gpu
                 (uint)Registers[(int)Reg + 1];
         }
 
-        private void WriteRegister(NsGpuPBEntry PBEntry)
+        private void WriteRegister(NvGpuPBEntry PBEntry)
         {
             int ArgsCount = PBEntry.Arguments.Count;
 
diff --git a/Ryujinx.Graphics/Gpu/NvGpuEngine3dReg.cs b/Ryujinx.Core/Gpu/NvGpuEngine3dReg.cs
similarity index 93%
rename from Ryujinx.Graphics/Gpu/NvGpuEngine3dReg.cs
rename to Ryujinx.Core/Gpu/NvGpuEngine3dReg.cs
index 0d995619d7..823885ffd3 100644
--- a/Ryujinx.Graphics/Gpu/NvGpuEngine3dReg.cs
+++ b/Ryujinx.Core/Gpu/NvGpuEngine3dReg.cs
@@ -1,4 +1,4 @@
-namespace Ryujinx.Graphics.Gpu
+namespace Ryujinx.Core.Gpu
 {
     enum NvGpuEngine3dReg
     {
@@ -53,9 +53,9 @@ namespace Ryujinx.Graphics.Gpu
         ShaderNOffset        = 0x801,
         ShaderNMaxGprs       = 0x803,
         ShaderNType          = 0x804,
-        ConstBufferNSize     = 0x8e0,
-        ConstBufferNAddress  = 0x8e1,
-        ConstBufferNOffset   = 0x8e3,
+        ConstBufferSize      = 0x8e0,
+        ConstBufferAddress   = 0x8e1,
+        ConstBufferOffset    = 0x8e3,
         TextureCbIndex       = 0x982
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/NvGpuFifo.cs b/Ryujinx.Core/Gpu/NvGpuFifo.cs
similarity index 70%
rename from Ryujinx.Graphics/Gpu/NvGpuFifo.cs
rename to Ryujinx.Core/Gpu/NvGpuFifo.cs
index 68c2902a53..d0e6fc14a0 100644
--- a/Ryujinx.Graphics/Gpu/NvGpuFifo.cs
+++ b/Ryujinx.Core/Gpu/NvGpuFifo.cs
@@ -1,16 +1,15 @@
-using ChocolArm64.Memory;
 using System.Collections.Concurrent;
 
-namespace Ryujinx.Graphics.Gpu
+namespace Ryujinx.Core.Gpu
 {
     public class NvGpuFifo
     {
         private const int MacrosCount    = 0x80;
         private const int MacroIndexMask = MacrosCount - 1;
 
-        private NsGpu Gpu;
+        private NvGpu Gpu;
 
-        private ConcurrentQueue<(AMemory, NsGpuPBEntry)> BufferQueue;
+        private ConcurrentQueue<(NvGpuVmm, NvGpuPBEntry)> BufferQueue;
 
         private NvGpuEngine[] SubChannels;
 
@@ -32,9 +31,9 @@ namespace Ryujinx.Graphics.Gpu
                 Interpreter?.Fifo.Enqueue(Param);
             }
 
-            public void Execute(AMemory Memory, int Param)
+            public void Execute(NvGpuVmm Vmm, int Param)
             {
-                Interpreter?.Execute(Memory, Position, Param);
+                Interpreter?.Execute(Vmm, Position, Param);
             }
         }
 
@@ -43,22 +42,22 @@ namespace Ryujinx.Graphics.Gpu
 
         private CachedMacro[] Macros;
 
-        public NvGpuFifo(NsGpu Gpu)
+        public NvGpuFifo(NvGpu Gpu)
         {
             this.Gpu = Gpu;
 
-            BufferQueue = new ConcurrentQueue<(AMemory, NsGpuPBEntry)>();
+            BufferQueue = new ConcurrentQueue<(NvGpuVmm, NvGpuPBEntry)>();
 
             SubChannels = new NvGpuEngine[8];
 
             Macros = new CachedMacro[MacrosCount];
         }
 
-        public void PushBuffer(AMemory Memory, NsGpuPBEntry[] Buffer)
+        public void PushBuffer(NvGpuVmm Vmm, NvGpuPBEntry[] Buffer)
         {
-            foreach (NsGpuPBEntry PBEntry in Buffer)
+            foreach (NvGpuPBEntry PBEntry in Buffer)
             {
-                BufferQueue.Enqueue((Memory, PBEntry));
+                BufferQueue.Enqueue((Vmm, PBEntry));
             }
         }
 
@@ -69,9 +68,9 @@ namespace Ryujinx.Graphics.Gpu
 
         public bool Step()
         {
-            if (BufferQueue.TryDequeue(out (AMemory Memory, NsGpuPBEntry PBEntry) Tuple))
+            if (BufferQueue.TryDequeue(out (NvGpuVmm Vmm, NvGpuPBEntry PBEntry) Tuple))
             {
-                CallMethod(Tuple.Memory, Tuple.PBEntry);
+                CallMethod(Tuple.Vmm, Tuple.PBEntry);
 
                 return true;
             }
@@ -79,7 +78,7 @@ namespace Ryujinx.Graphics.Gpu
             return false;
         }
 
-        private void CallMethod(AMemory Memory, NsGpuPBEntry PBEntry)
+        private void CallMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
         {
             if (PBEntry.Method < 0x80)
             {
@@ -103,11 +102,11 @@ namespace Ryujinx.Graphics.Gpu
 
                     case NvGpuFifoMeth.SendMacroCodeData:
                     {
-                        long Position = Gpu.GetCpuAddr(CurrMacroPosition);
+                        long Position = CurrMacroPosition;
 
                         foreach (int Arg in PBEntry.Arguments)
                         {
-                            Memory.WriteInt32(Position, Arg);
+                            Vmm.WriteInt32(Position, Arg);
 
                             CurrMacroPosition += 4;
 
@@ -127,8 +126,6 @@ namespace Ryujinx.Graphics.Gpu
                     {
                         long Position = (long)((ulong)PBEntry.Arguments[0] << 2);
 
-                        Position = Gpu.GetCpuAddr(Position);
-
                         Macros[CurrMacroBindIndex] = new CachedMacro(this, Gpu.Engine3d, Position);
 
                         break;
@@ -139,22 +136,22 @@ namespace Ryujinx.Graphics.Gpu
             {
                 switch (SubChannels[PBEntry.SubChannel])
                 {
-                    case NvGpuEngine._2d: Call2dMethod(Memory, PBEntry); break;
-                    case NvGpuEngine._3d: Call3dMethod(Memory, PBEntry); break;
+                    case NvGpuEngine._2d: Call2dMethod(Vmm, PBEntry); break;
+                    case NvGpuEngine._3d: Call3dMethod(Vmm, PBEntry); break;
                 }
             }
         }
 
-        private void Call2dMethod(AMemory Memory, NsGpuPBEntry PBEntry)
+        private void Call2dMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
         {
-            Gpu.Engine2d.CallMethod(Memory, PBEntry);
+            Gpu.Engine2d.CallMethod(Vmm, PBEntry);
         }
 
-        private void Call3dMethod(AMemory Memory, NsGpuPBEntry PBEntry)
+        private void Call3dMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry)
         {
             if (PBEntry.Method < 0xe00)
             {
-                Gpu.Engine3d.CallMethod(Memory, PBEntry);
+                Gpu.Engine3d.CallMethod(Vmm, PBEntry);
             }
             else
             {
@@ -169,7 +166,7 @@ namespace Ryujinx.Graphics.Gpu
                 }
                 else
                 {
-                    Macros[MacroIndex].Execute(Memory, PBEntry.Arguments[0]);
+                    Macros[MacroIndex].Execute(Vmm, PBEntry.Arguments[0]);
                 }
             }
         }
diff --git a/Ryujinx.Graphics/Gpu/NvGpuFifoMeth.cs b/Ryujinx.Core/Gpu/NvGpuFifoMeth.cs
similarity index 87%
rename from Ryujinx.Graphics/Gpu/NvGpuFifoMeth.cs
rename to Ryujinx.Core/Gpu/NvGpuFifoMeth.cs
index 4287e25009..78ec908020 100644
--- a/Ryujinx.Graphics/Gpu/NvGpuFifoMeth.cs
+++ b/Ryujinx.Core/Gpu/NvGpuFifoMeth.cs
@@ -1,4 +1,4 @@
-namespace Ryujinx.Graphics.Gpu
+namespace Ryujinx.Core.Gpu
 {
     enum NvGpuFifoMeth
     {
diff --git a/Ryujinx.Core/Gpu/NvGpuMethod.cs b/Ryujinx.Core/Gpu/NvGpuMethod.cs
new file mode 100644
index 0000000000..42e1b5537f
--- /dev/null
+++ b/Ryujinx.Core/Gpu/NvGpuMethod.cs
@@ -0,0 +1,4 @@
+namespace Ryujinx.Core.Gpu
+{
+    delegate void NvGpuMethod(NvGpuVmm Vmm, NvGpuPBEntry PBEntry);
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/NsGpuPBEntry.cs b/Ryujinx.Core/Gpu/NvGpuPBEntry.cs
similarity index 78%
rename from Ryujinx.Graphics/Gpu/NsGpuPBEntry.cs
rename to Ryujinx.Core/Gpu/NvGpuPBEntry.cs
index d405a93c6a..ebf35b9e42 100644
--- a/Ryujinx.Graphics/Gpu/NsGpuPBEntry.cs
+++ b/Ryujinx.Core/Gpu/NvGpuPBEntry.cs
@@ -1,9 +1,9 @@
 using System;
 using System.Collections.ObjectModel;
 
-namespace Ryujinx.Graphics.Gpu
+namespace Ryujinx.Core.Gpu
 {
-    public struct NsGpuPBEntry
+    public struct NvGpuPBEntry
     {
         public int Method { get; private set; }
 
@@ -13,7 +13,7 @@ namespace Ryujinx.Graphics.Gpu
 
         public ReadOnlyCollection<int> Arguments => Array.AsReadOnly(m_Arguments);
 
-        public NsGpuPBEntry(int Method, int SubChannel, params int[] Arguments)
+        public NvGpuPBEntry(int Method, int SubChannel, params int[] Arguments)
         {
             this.Method      = Method;
             this.SubChannel  = SubChannel;
diff --git a/Ryujinx.Graphics/Gpu/NvGpuPushBuffer.cs b/Ryujinx.Core/Gpu/NvGpuPushBuffer.cs
similarity index 85%
rename from Ryujinx.Graphics/Gpu/NvGpuPushBuffer.cs
rename to Ryujinx.Core/Gpu/NvGpuPushBuffer.cs
index 8cbb3288eb..d55886559e 100644
--- a/Ryujinx.Graphics/Gpu/NvGpuPushBuffer.cs
+++ b/Ryujinx.Core/Gpu/NvGpuPushBuffer.cs
@@ -1,7 +1,7 @@
 using System.Collections.Generic;
 using System.IO;
 
-namespace Ryujinx.Graphics.Gpu
+namespace Ryujinx.Core.Gpu
 {
     public static class NvGpuPushBuffer
     {
@@ -13,13 +13,13 @@ namespace Ryujinx.Graphics.Gpu
             IncrementOnce   = 5
         }
 
-        public static NsGpuPBEntry[] Decode(byte[] Data)
+        public static NvGpuPBEntry[] Decode(byte[] Data)
         {
             using (MemoryStream MS = new MemoryStream(Data))
             {
                 BinaryReader Reader = new BinaryReader(MS);
 
-                List<NsGpuPBEntry> PushBuffer = new List<NsGpuPBEntry>();
+                List<NvGpuPBEntry> PushBuffer = new List<NvGpuPBEntry>();
 
                 bool CanRead() => MS.Position + 4 <= MS.Length;
 
@@ -38,7 +38,7 @@ namespace Ryujinx.Graphics.Gpu
                         {
                             for (int Index = 0; Index < Args && CanRead(); Index++, Meth++)
                             {
-                                PushBuffer.Add(new NsGpuPBEntry(Meth, SubC, Reader.ReadInt32()));
+                                PushBuffer.Add(new NvGpuPBEntry(Meth, SubC, Reader.ReadInt32()));
                             }
 
                             break;
@@ -58,14 +58,14 @@ namespace Ryujinx.Graphics.Gpu
                                 Arguments[Index] = Reader.ReadInt32();
                             }
 
-                            PushBuffer.Add(new NsGpuPBEntry(Meth, SubC, Arguments));
+                            PushBuffer.Add(new NvGpuPBEntry(Meth, SubC, Arguments));
 
                             break;
                         }
 
                         case SubmissionMode.Immediate:
                         {
-                            PushBuffer.Add(new NsGpuPBEntry(Meth, SubC, Args));
+                            PushBuffer.Add(new NvGpuPBEntry(Meth, SubC, Args));
 
                             break;
                         }
@@ -74,9 +74,9 @@ namespace Ryujinx.Graphics.Gpu
                         {
                             if (CanRead())
                             {
-                                PushBuffer.Add(new NsGpuPBEntry(Meth, SubC, Reader.ReadInt32()));
+                                PushBuffer.Add(new NvGpuPBEntry(Meth, SubC, Reader.ReadInt32()));
                             }
-    
+
                             if (CanRead() && Args > 1)
                             {
                                 int[] Arguments = new int[Args - 1];
@@ -86,7 +86,7 @@ namespace Ryujinx.Graphics.Gpu
                                     Arguments[Index] = Reader.ReadInt32();
                                 }
 
-                                PushBuffer.Add(new NsGpuPBEntry(Meth + 1, SubC, Arguments));
+                                PushBuffer.Add(new NvGpuPBEntry(Meth + 1, SubC, Arguments));
                             }
 
                             break;
diff --git a/Ryujinx.Core/Gpu/NvGpuVmm.cs b/Ryujinx.Core/Gpu/NvGpuVmm.cs
new file mode 100644
index 0000000000..cf94a6c016
--- /dev/null
+++ b/Ryujinx.Core/Gpu/NvGpuVmm.cs
@@ -0,0 +1,398 @@
+using ChocolArm64.Memory;
+using System.Collections.Concurrent;
+
+namespace Ryujinx.Core.Gpu
+{
+    public class NvGpuVmm : IAMemory
+    {
+        public const long AddrSize = 1L << 40;
+
+        private const int  PTLvl0Bits = 14;
+        private const int  PTLvl1Bits = 14;
+        private const int  PTPageBits = 12;
+
+        private const int  PTLvl0Size = 1 << PTLvl0Bits;
+        private const int  PTLvl1Size = 1 << PTLvl1Bits;
+        public  const int  PageSize   = 1 << PTPageBits;
+
+        private const int  PTLvl0Mask = PTLvl0Size - 1;
+        private const int  PTLvl1Mask = PTLvl1Size - 1;
+        public  const int  PageMask   = PageSize   - 1;
+
+        private const int  PTLvl0Bit = PTPageBits + PTLvl1Bits;
+        private const int  PTLvl1Bit = PTPageBits;
+
+        public AMemory Memory { get; private set; }
+
+        private struct MappedMemory
+        {
+            public long Size;
+
+            public MappedMemory(long Size)
+            {
+                this.Size = Size;
+            }
+        }
+
+        private ConcurrentDictionary<long, MappedMemory> Maps;
+
+        private const long PteUnmapped = -1;
+        private const long PteReserved = -2;
+
+        private long[][] PageTable;
+
+        public NvGpuVmm(AMemory Memory)
+        {
+            this.Memory = Memory;
+
+            Maps = new ConcurrentDictionary<long, MappedMemory>();
+
+            PageTable = new long[PTLvl0Size][];
+        }
+
+        public long Map(long PA, long VA, long Size)
+        {
+            lock (PageTable)
+            {
+                for (long Offset = 0; Offset < Size; Offset += PageSize)
+                {
+                    if (GetPte(VA + Offset) != PteReserved)
+                    {
+                        return Map(PA, Size);
+                    }
+                }
+
+                for (long Offset = 0; Offset < Size; Offset += PageSize)
+                {
+                    SetPte(VA + Offset, PA + Offset);
+                }
+            }
+
+            return VA;
+        }
+
+        public long Map(long PA, long Size)
+        {
+            lock (PageTable)
+            {
+                long VA = GetFreePosition(Size);
+
+                if (VA != -1)
+                {
+                    MappedMemory Map = new MappedMemory(Size);
+
+                    Maps.AddOrUpdate(VA, Map, (Key, Old) => Map);
+
+                    for (long Offset = 0; Offset < Size; Offset += PageSize)
+                    {
+                        SetPte(VA + Offset, PA + Offset);
+                    }
+                }
+
+                return VA;
+            }
+        }
+
+        public bool Unmap(long VA)
+        {
+            if (Maps.TryRemove(VA, out MappedMemory Map))
+            {
+                Free(VA, Map.Size);
+
+                return true;
+            }
+
+            return false;
+        }
+
+        public long Reserve(long VA, long Size, long Align)
+        {
+            lock (PageTable)
+            {
+                for (long Offset = 0; Offset < Size; Offset += PageSize)
+                {
+                    if (IsPageInUse(VA + Offset))
+                    {
+                        return Reserve(Size, Align);
+                    }
+                }
+
+                for (long Offset = 0; Offset < Size; Offset += PageSize)
+                {
+                    SetPte(VA + Offset, PteReserved);
+                }
+            }
+
+            return VA;
+        }
+
+        public long Reserve(long Size, long Align)
+        {
+            lock (PageTable)
+            {
+                long Position = GetFreePosition(Size, Align);
+
+                if (Position != -1)
+                {
+                    for (long Offset = 0; Offset < Size; Offset += PageSize)
+                    {
+                        SetPte(Position + Offset, PteReserved);
+                    }
+                }
+
+                return Position;
+            }
+        }
+
+        public void Free(long VA, long Size)
+        {
+            lock (PageTable)
+            {
+                for (long Offset = 0; Offset < Size; Offset += PageSize)
+                {
+                    SetPte(VA + Offset, PteUnmapped);
+                }
+            }
+        }
+
+        private long GetFreePosition(long Size, long Align = 1)
+        {
+            long Position = 0;
+            long FreeSize = 0;
+
+            if (Align < 1)
+            {
+                Align = 1;
+            }
+
+            Align = (Align + PageMask) & ~PageMask;
+
+            while (Position + FreeSize < AddrSize)
+            {
+                if (!IsPageInUse(Position + FreeSize))
+                {
+                    FreeSize += PageSize;
+
+                    if (FreeSize >= Size)
+                    {
+                        return Position;
+                    }
+                }
+                else
+                {
+                    Position += FreeSize + PageSize;
+                    FreeSize  = 0;
+
+                    long Remainder = Position % Align;
+
+                    if (Remainder != 0)
+                    {
+                        Position = (Position - Remainder) + Align;
+                    }
+                }
+            }
+
+            return -1;
+        }
+
+        public long GetPhysicalAddress(long VA)
+        {
+            long BasePos = GetPte(VA);
+
+            if (BasePos < 0)
+            {
+                return -1;
+            }
+
+            return BasePos + (VA & PageMask);
+        }
+
+        public bool IsRegionFree(long VA, long Size)
+        {
+            for (long Offset = 0; Offset < Size; Offset += PageSize)
+            {
+                if (IsPageInUse(VA + Offset))
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        private bool IsPageInUse(long VA)
+        {
+            if (VA >> PTLvl0Bits + PTLvl1Bits + PTPageBits != 0)
+            {
+                return false;
+            }
+
+            long L0 = (VA >> PTLvl0Bit) & PTLvl0Mask;
+            long L1 = (VA >> PTLvl1Bit) & PTLvl1Mask;
+
+            if (PageTable[L0] == null)
+            {
+                return false;
+            }
+
+            return PageTable[L0][L1] != PteUnmapped;
+        }
+
+        private long GetPte(long Position)
+        {
+            long L0 = (Position >> PTLvl0Bit) & PTLvl0Mask;
+            long L1 = (Position >> PTLvl1Bit) & PTLvl1Mask;
+
+            if (PageTable[L0] == null)
+            {
+                return -1;
+            }
+
+            return PageTable[L0][L1];
+        }
+
+        private void SetPte(long Position, long TgtAddr)
+        {
+            long L0 = (Position >> PTLvl0Bit) & PTLvl0Mask;
+            long L1 = (Position >> PTLvl1Bit) & PTLvl1Mask;
+
+            if (PageTable[L0] == null)
+            {
+                PageTable[L0] = new long[PTLvl1Size];
+
+                for (int Index = 0; Index < PTLvl1Size; Index++)
+                {
+                    PageTable[L0][Index] = PteUnmapped;
+                }
+            }
+
+            PageTable[L0][L1] = TgtAddr;
+        }
+
+        public byte ReadByte(long Position)
+        {
+            Position = GetPhysicalAddress(Position);
+
+            return Memory.ReadByte(Position);
+        }
+
+        public ushort ReadUInt16(long Position)
+        {
+            Position = GetPhysicalAddress(Position);
+
+            return Memory.ReadUInt16(Position);
+        }
+
+        public uint ReadUInt32(long Position)
+        {
+            Position = GetPhysicalAddress(Position);
+
+            return Memory.ReadUInt32(Position);
+        }
+
+        public ulong ReadUInt64(long Position)
+        {
+            Position = GetPhysicalAddress(Position);
+
+            return Memory.ReadUInt64(Position);
+        }
+
+        public sbyte ReadSByte(long Position)
+        {
+            Position = GetPhysicalAddress(Position);
+
+            return Memory.ReadSByte(Position);
+        }
+
+        public short ReadInt16(long Position)
+        {
+            Position = GetPhysicalAddress(Position);
+
+            return Memory.ReadInt16(Position);
+        }
+
+        public int ReadInt32(long Position)
+        {
+            Position = GetPhysicalAddress(Position);
+
+            return Memory.ReadInt32(Position);
+        }
+
+        public long ReadInt64(long Position)
+        {
+            Position = GetPhysicalAddress(Position);
+
+            return Memory.ReadInt64(Position);
+        }
+
+        public byte[] ReadBytes(long Position, long Size)
+        {
+            Position = GetPhysicalAddress(Position);
+
+            return AMemoryHelper.ReadBytes(Memory, Position, Size);
+        }
+
+        public void WriteByte(long Position, byte Value)
+        {
+            Position = GetPhysicalAddress(Position);
+
+            Memory.WriteByte(Position, Value);
+        }
+
+        public void WriteUInt16(long Position, ushort Value)
+        {
+            Position = GetPhysicalAddress(Position);
+
+            Memory.WriteUInt16(Position, Value);
+        }
+
+        public void WriteUInt32(long Position, uint Value)
+        {
+            Position = GetPhysicalAddress(Position);
+
+            Memory.WriteUInt32(Position, Value);
+        }
+
+        public void WriteUInt64(long Position, ulong Value)
+        {
+            Position = GetPhysicalAddress(Position);
+
+            Memory.WriteUInt64(Position, Value);
+        }
+
+        public void WriteSByte(long Position, sbyte Value)
+        {
+            Position = GetPhysicalAddress(Position);
+
+            Memory.WriteSByte(Position, Value);
+        }
+
+        public void WriteInt16(long Position, short Value)
+        {
+            Position = GetPhysicalAddress(Position);
+
+            Memory.WriteInt16(Position, Value);
+        }
+
+        public void WriteInt32(long Position, int Value)
+        {
+            Position = GetPhysicalAddress(Position);
+
+            Memory.WriteInt32(Position, Value);
+        }
+
+        public void WriteInt64(long Position, long Value)
+        {
+            Position = GetPhysicalAddress(Position);
+
+            Memory.WriteInt64(Position, Value);
+        }
+
+        public void WriteBytes(long Position, byte[] Data)
+        {
+            Position = GetPhysicalAddress(Position);
+
+            AMemoryHelper.WriteBytes(Memory, Position, Data);
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/Texture.cs b/Ryujinx.Core/Gpu/Texture.cs
similarity index 97%
rename from Ryujinx.Graphics/Gpu/Texture.cs
rename to Ryujinx.Core/Gpu/Texture.cs
index cbfa683dc9..39a35059bc 100644
--- a/Ryujinx.Graphics/Gpu/Texture.cs
+++ b/Ryujinx.Core/Gpu/Texture.cs
@@ -1,6 +1,6 @@
 using Ryujinx.Graphics.Gal;
 
-namespace Ryujinx.Graphics.Gpu
+namespace Ryujinx.Core.Gpu
 {
     public struct Texture
     {
diff --git a/Ryujinx.Graphics/Gpu/TextureFactory.cs b/Ryujinx.Core/Gpu/TextureFactory.cs
similarity index 77%
rename from Ryujinx.Graphics/Gpu/TextureFactory.cs
rename to Ryujinx.Core/Gpu/TextureFactory.cs
index 7f8580d987..68b48a1fbc 100644
--- a/Ryujinx.Graphics/Gpu/TextureFactory.cs
+++ b/Ryujinx.Core/Gpu/TextureFactory.cs
@@ -1,14 +1,13 @@
-using ChocolArm64.Memory;
 using Ryujinx.Graphics.Gal;
 using System;
 
-namespace Ryujinx.Graphics.Gpu
+namespace Ryujinx.Core.Gpu
 {
     static class TextureFactory
     {
-        public static GalTexture MakeTexture(NsGpu Gpu, AMemory Memory, long TicPosition)
+        public static GalTexture MakeTexture(NvGpu Gpu, NvGpuVmm Vmm, long TicPosition)
         {
-            int[] Tic = ReadWords(Memory, TicPosition, 8);
+            int[] Tic = ReadWords(Vmm, TicPosition, 8);
 
             GalTextureFormat Format = (GalTextureFormat)(Tic[0] & 0x7f);
 
@@ -16,8 +15,6 @@ namespace Ryujinx.Graphics.Gpu
 
             TextureAddress |= (long)((ushort)Tic[2]) << 32;
 
-            TextureAddress = Gpu.GetCpuAddr(TextureAddress);
-
             TextureSwizzle Swizzle = (TextureSwizzle)((Tic[2] >> 21) & 7);
 
             int Pitch = (Tic[3] & 0xffff) << 5;
@@ -38,14 +35,14 @@ namespace Ryujinx.Graphics.Gpu
                 Swizzle,
                 Format);
 
-            byte[] Data = TextureReader.Read(Memory, Texture);
+            byte[] Data = TextureReader.Read(Vmm, Texture);
 
             return new GalTexture(Data, Width, Height, Format);
         }
 
-        public static GalTextureSampler MakeSampler(NsGpu Gpu, AMemory Memory, long TscPosition)
+        public static GalTextureSampler MakeSampler(NvGpu Gpu, NvGpuVmm Vmm, long TscPosition)
         {
-            int[] Tsc = ReadWords(Memory, TscPosition, 8);
+            int[] Tsc = ReadWords(Vmm, TscPosition, 8);
 
             GalTextureWrap AddressU = (GalTextureWrap)((Tsc[0] >> 0) & 7);
             GalTextureWrap AddressV = (GalTextureWrap)((Tsc[0] >> 3) & 7);
@@ -71,13 +68,13 @@ namespace Ryujinx.Graphics.Gpu
                 BorderColor);
         }
 
-        private static int[] ReadWords(AMemory Memory, long Position, int Count)
+        private static int[] ReadWords(NvGpuVmm Vmm, long Position, int Count)
         {
             int[] Words = new int[Count];
 
             for (int Index = 0; Index < Count; Index++, Position += 4)
             {
-                Words[Index] = Memory.ReadInt32(Position);
+                Words[Index] = Vmm.ReadInt32(Position);
             }
 
             return Words;
diff --git a/Ryujinx.Graphics/Gpu/TextureHelper.cs b/Ryujinx.Core/Gpu/TextureHelper.cs
similarity index 62%
rename from Ryujinx.Graphics/Gpu/TextureHelper.cs
rename to Ryujinx.Core/Gpu/TextureHelper.cs
index d3c2ac14b5..f0ebc1f043 100644
--- a/Ryujinx.Graphics/Gpu/TextureHelper.cs
+++ b/Ryujinx.Core/Gpu/TextureHelper.cs
@@ -1,6 +1,7 @@
+using ChocolArm64.Memory;
 using System;
 
-namespace Ryujinx.Graphics.Gpu
+namespace Ryujinx.Core.Gpu
 {
     static class TextureHelper
     {
@@ -19,5 +20,17 @@ namespace Ryujinx.Graphics.Gpu
 
             throw new NotImplementedException(Texture.Swizzle.ToString());
         }
+
+        public static (AMemory Memory, long Position) GetMemoryAndPosition(
+            IAMemory Memory,
+            long     Position)
+        {
+            if (Memory is NvGpuVmm Vmm)
+            {
+                return (Vmm.Memory, Vmm.GetPhysicalAddress(Position));
+            }
+
+            return ((AMemory)Memory, Position);
+        }
     }
 }
diff --git a/Ryujinx.Graphics/Gpu/TextureReader.cs b/Ryujinx.Core/Gpu/TextureReader.cs
similarity index 73%
rename from Ryujinx.Graphics/Gpu/TextureReader.cs
rename to Ryujinx.Core/Gpu/TextureReader.cs
index 17fd95c5c5..f3e41046ae 100644
--- a/Ryujinx.Graphics/Gpu/TextureReader.cs
+++ b/Ryujinx.Core/Gpu/TextureReader.cs
@@ -2,11 +2,11 @@ using ChocolArm64.Memory;
 using Ryujinx.Graphics.Gal;
 using System;
 
-namespace Ryujinx.Graphics.Gpu
+namespace Ryujinx.Core.Gpu
 {
     public static class TextureReader
     {
-        public static byte[] Read(AMemory Memory, Texture Texture)
+        public static byte[] Read(IAMemory Memory, Texture Texture)
         {
             switch (Texture.Format)
             {
@@ -23,7 +23,7 @@ namespace Ryujinx.Graphics.Gpu
             throw new NotImplementedException(Texture.Format.ToString());
         }
 
-        private unsafe static byte[] Read2Bpp(AMemory Memory, Texture Texture)
+        private unsafe static byte[] Read2Bpp(IAMemory Memory, Texture Texture)
         {
             int Width  = Texture.Width;
             int Height = Texture.Height;
@@ -32,6 +32,10 @@ namespace Ryujinx.Graphics.Gpu
 
             ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 2);
 
+            (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition(
+                Memory,
+                Texture.Position);
+
             fixed (byte* BuffPtr = Output)
             {
                 long OutOffs = 0;
@@ -41,7 +45,7 @@ namespace Ryujinx.Graphics.Gpu
                 {
                     long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y);
 
-                    short Pixel = Memory.ReadInt16Unchecked(Texture.Position + Offset);
+                    short Pixel = CpuMem.ReadInt16Unchecked(Position + Offset);
 
                     *(short*)(BuffPtr + OutOffs) = Pixel;
 
@@ -52,7 +56,7 @@ namespace Ryujinx.Graphics.Gpu
             return Output;
         }
 
-        private unsafe static byte[] Read4Bpp(AMemory Memory, Texture Texture)
+        private unsafe static byte[] Read4Bpp(IAMemory Memory, Texture Texture)
         {
             int Width  = Texture.Width;
             int Height = Texture.Height;
@@ -61,6 +65,10 @@ namespace Ryujinx.Graphics.Gpu
 
             ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 4);
 
+            (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition(
+                Memory,
+                Texture.Position);
+
             fixed (byte* BuffPtr = Output)
             {
                 long OutOffs = 0;
@@ -70,7 +78,7 @@ namespace Ryujinx.Graphics.Gpu
                 {
                     long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y);
 
-                    int Pixel = Memory.ReadInt32Unchecked(Texture.Position + Offset);
+                    int Pixel = CpuMem.ReadInt32Unchecked(Position + Offset);
 
                     *(int*)(BuffPtr + OutOffs) = Pixel;
 
@@ -81,7 +89,7 @@ namespace Ryujinx.Graphics.Gpu
             return Output;
         }
 
-        private unsafe static byte[] Read8Bpt4x4(AMemory Memory, Texture Texture)
+        private unsafe static byte[] Read8Bpt4x4(IAMemory Memory, Texture Texture)
         {
             int Width  = (Texture.Width  + 3) / 4;
             int Height = (Texture.Height + 3) / 4;
@@ -90,6 +98,10 @@ namespace Ryujinx.Graphics.Gpu
 
             ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 8);
 
+            (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition(
+                Memory,
+                Texture.Position);
+
             fixed (byte* BuffPtr = Output)
             {
                 long OutOffs = 0;
@@ -99,7 +111,7 @@ namespace Ryujinx.Graphics.Gpu
                 {
                     long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y);
 
-                    long Tile = Memory.ReadInt64Unchecked(Texture.Position + Offset);
+                    long Tile = CpuMem.ReadInt64Unchecked(Position + Offset);
 
                     *(long*)(BuffPtr + OutOffs) = Tile;
 
@@ -110,7 +122,7 @@ namespace Ryujinx.Graphics.Gpu
             return Output;
         }
 
-        private unsafe static byte[] Read16Bpt4x4(AMemory Memory, Texture Texture)
+        private unsafe static byte[] Read16Bpt4x4(IAMemory Memory, Texture Texture)
         {
             int Width  = (Texture.Width  + 3) / 4;
             int Height = (Texture.Height + 3) / 4;
@@ -119,6 +131,10 @@ namespace Ryujinx.Graphics.Gpu
 
             ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 16);
 
+            (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition(
+                Memory,
+                Texture.Position);
+
             fixed (byte* BuffPtr = Output)
             {
                 long OutOffs = 0;
@@ -128,8 +144,8 @@ namespace Ryujinx.Graphics.Gpu
                 {
                     long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y);
 
-                    long Tile0 = Memory.ReadInt64Unchecked(Texture.Position + Offset + 0);
-                    long Tile1 = Memory.ReadInt64Unchecked(Texture.Position + Offset + 8);
+                    long Tile0 = CpuMem.ReadInt64Unchecked(Position + Offset + 0);
+                    long Tile1 = CpuMem.ReadInt64Unchecked(Position + Offset + 8);
 
                     *(long*)(BuffPtr + OutOffs + 0) = Tile0;
                     *(long*)(BuffPtr + OutOffs + 8) = Tile1;
diff --git a/Ryujinx.Graphics/Gpu/TextureSwizzle.cs b/Ryujinx.Core/Gpu/TextureSwizzle.cs
similarity index 87%
rename from Ryujinx.Graphics/Gpu/TextureSwizzle.cs
rename to Ryujinx.Core/Gpu/TextureSwizzle.cs
index 7d99279cd4..3214f45f81 100644
--- a/Ryujinx.Graphics/Gpu/TextureSwizzle.cs
+++ b/Ryujinx.Core/Gpu/TextureSwizzle.cs
@@ -1,4 +1,4 @@
-namespace Ryujinx.Graphics.Gpu
+namespace Ryujinx.Core.Gpu
 {
     public enum TextureSwizzle
     {
diff --git a/Ryujinx.Graphics/Gpu/TextureWriter.cs b/Ryujinx.Core/Gpu/TextureWriter.cs
similarity index 63%
rename from Ryujinx.Graphics/Gpu/TextureWriter.cs
rename to Ryujinx.Core/Gpu/TextureWriter.cs
index 2f25de73fc..125bb8c4f5 100644
--- a/Ryujinx.Graphics/Gpu/TextureWriter.cs
+++ b/Ryujinx.Core/Gpu/TextureWriter.cs
@@ -2,28 +2,31 @@ using ChocolArm64.Memory;
 using Ryujinx.Graphics.Gal;
 using System;
 
-namespace Ryujinx.Graphics.Gpu
+namespace Ryujinx.Core.Gpu
 {
     public static class TextureWriter
     {
-        public static void Write(AMemory Memory, Texture Texture, byte[] Data)
+        public static void Write(IAMemory Memory, Texture Texture, byte[] Data)
         {
             switch (Texture.Format)
             {
                 case GalTextureFormat.A8B8G8R8: Write4Bpp(Memory, Texture, Data); break;
 
-                default:
-                    throw new NotImplementedException(Texture.Format.ToString());
+                default: throw new NotImplementedException(Texture.Format.ToString());
             }
         }
 
-        private unsafe static void Write4Bpp(AMemory Memory, Texture Texture, byte[] Data)
+        private unsafe static void Write4Bpp(IAMemory Memory, Texture Texture, byte[] Data)
         {
             int Width  = Texture.Width;
             int Height = Texture.Height;
 
             ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 4);
 
+            (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition(
+                Memory,
+                Texture.Position);
+
             fixed (byte* BuffPtr = Data)
             {
                 long InOffs = 0;
@@ -35,7 +38,7 @@ namespace Ryujinx.Graphics.Gpu
 
                     int Pixel = *(int*)(BuffPtr + InOffs);
 
-                    Memory.WriteInt32Unchecked(Texture.Position + Offset, Pixel);
+                    CpuMem.WriteInt32Unchecked(Position + Offset, Pixel);
 
                     InOffs += 4;
                 }
diff --git a/Ryujinx.Core/Loaders/Executable.cs b/Ryujinx.Core/Loaders/Executable.cs
index 39ee9618b7..ab7e1979db 100644
--- a/Ryujinx.Core/Loaders/Executable.cs
+++ b/Ryujinx.Core/Loaders/Executable.cs
@@ -32,9 +32,9 @@ namespace Ryujinx.Core.Loaders
             this.ImageBase = ImageBase;
             this.ImageEnd  = ImageBase;
 
-            WriteData(ImageBase + Exe.TextOffset, Exe.Text, MemoryType.CodeStatic, AMemoryPerm.RX);
-            WriteData(ImageBase + Exe.ROOffset,   Exe.RO,   MemoryType.Normal,     AMemoryPerm.Read);
-            WriteData(ImageBase + Exe.DataOffset, Exe.Data, MemoryType.Normal,     AMemoryPerm.RW);
+            WriteData(ImageBase + Exe.TextOffset, Exe.Text, MemoryType.CodeStatic,  AMemoryPerm.RX);
+            WriteData(ImageBase + Exe.ROOffset,   Exe.RO,   MemoryType.CodeMutable, AMemoryPerm.Read);
+            WriteData(ImageBase + Exe.DataOffset, Exe.Data, MemoryType.CodeMutable, AMemoryPerm.RW);
 
             if (Exe.Mod0Offset == 0)
             {
diff --git a/Ryujinx.Core/OsHle/Horizon.cs b/Ryujinx.Core/OsHle/Horizon.cs
index 0cc3d815ad..1d4098c7d4 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 42322d41a7..420890ebba 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 d81f44bd9b..afcbb3a439 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 b568405b7c..87f9cf3b34 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 e855b77df0..1874360b59 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 601b211c59..e5b080a88f 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);
-
-            int HandleIndex;
-
-            ulong Result = 0;
-
-            if (Timeout != ulong.MaxValue)
+            using (AutoResetEvent WaitEvent = new AutoResetEvent(false))
             {
-                HandleIndex = WaitHandle.WaitAny(Handles, NsTimeConverter.GetTimeMs(Timeout));
+                if (!SyncWaits.TryAdd(CurrThread, WaitEvent))
+                {
+                    throw new InvalidOperationException();
+                }
+
+                Handles[HandlesCount] = WaitEvent;
+
+                Process.Scheduler.Suspend(CurrThread.ProcessorId);
+
+                int HandleIndex;
+
+                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}!");
+
+                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidHandle);
+
+                return;
             }
 
-            Process.Scheduler.Resume(CurrThread);
-
-            ThreadState.X0 = Result;
-
-            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 e382cf7597..57608cda43 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 0000000000..e57f52640d
--- /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 b8c088563f..3ccbc01692 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 2652724d5b..00209f9db7 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();
+            int Result;
 
-            Context.ResponseData.Write(0);
-
-            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)
+        private static int ProcessIoctlNvGpuAS(ServiceCtx Context, int Cmd)
         {
-            long Position = Context.Request.GetSendBuffPtr();
-
-            int Fd = Context.Memory.ReadInt32(Position);
-
-            return 0;
+            return ProcessIoctl(Context, Cmd, NvGpuASIoctl.ProcessIoctl);
         }
 
-        private long NvGpuAsIoctlAllocSpace(ServiceCtx Context)
+        private static int ProcessIoctlNvHostCtrl(ServiceCtx Context, int Cmd)
         {
-            long Position = Context.Request.GetSendBuffPtr();
+            return ProcessIoctl(Context, Cmd, NvHostCtrlIoctl.ProcessIoctl);
+        }
 
-            MemReader Reader = new MemReader(Context.Memory, Position);
+        private static int ProcessIoctlNvGpuGpu(ServiceCtx Context, int Cmd)
+        {
+            return ProcessIoctl(Context, Cmd, NvGpuGpuIoctl.ProcessIoctl);
+        }
 
-            int  Pages    = Reader.ReadInt32();
-            int  PageSize = Reader.ReadInt32();
-            int  Flags    = Reader.ReadInt32();
-            int  Padding  = Reader.ReadInt32();
-            long Align    = Reader.ReadInt64();
+        private static int ProcessIoctlNvHostChannel(ServiceCtx Context, int Cmd)
+        {
+            return ProcessIoctl(Context, Cmd, NvHostChannelIoctl.ProcessIoctl);
+        }
 
-            if ((Flags & 1) != 0)
+        private static int ProcessIoctlNvMap(ServiceCtx Context, int Cmd)
+        {
+            return ProcessIoctl(Context, Cmd, NvMapIoctl.ProcessIoctl);
+        }
+
+        private static int ProcessIoctl(ServiceCtx Context, int Cmd, IoctlProcessor Processor)
+        {
+            if (CmdIn(Cmd) && Context.Request.GetBufferType0x21Position() == 0)
             {
-                Align = Context.Ns.Gpu.ReserveMemory(Align, (long)Pages * PageSize, 1);
-            }
-            else
-            {
-                Align = Context.Ns.Gpu.ReserveMemory((long)Pages * PageSize, Align);
+                Context.Ns.Log.PrintError(LogClass.ServiceNv, "Input buffer is null!");
+
+                return NvResult.InvalidInput;
             }
 
-            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)
+            if (CmdOut(Cmd) && Context.Request.GetBufferType0x22Position() == 0)
             {
-                //This is used to store offsets for the Framebuffer(s);
-                NvMapFb MapFb = (NvMapFb)NvMapsFb.GetData(Context.Process, 0);
+                Context.Ns.Log.PrintError(LogClass.ServiceNv, "Output buffer is null!");
 
-                MapFb.AddBufferOffset(BuffAddr);
-
-                return 0;
+                return NvResult.InvalidInput;
             }
 
-            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;
+            return Processor(Context, Cmd);
         }
 
-        private long NvGpuAsIoctlGetVaRegions(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);
-
-            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;
+            return ((Cmd >> 30) & 1) != 0;
         }
 
-        private long NvGpuAsIoctlInitializeEx(ServiceCtx Context)
+        private static bool CmdOut(int Cmd)
         {
-            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;
+            return ((Cmd >> 31) & 1) != 0;
         }
 
-        private long NvGpuAsIoctlRemap(ServiceCtx Context)
+        public static void UnloadProcess(Process Process)
         {
-            Context.RequestData.BaseStream.Seek(-4, SeekOrigin.Current);
+            Fds.DeleteProcess(Process);
 
-            int Cmd = Context.RequestData.ReadInt32();
+            NvGpuASIoctl.UnloadProcess(Process);
 
-            int Size = (Cmd >> 16) & 0xff;
+            NvHostCtrlIoctl.UnloadProcess(Process);
 
-            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)
-        {
-            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;
-        }
-
-        private long NvMapIoctlChannelSetErrorNotifier(ServiceCtx Context)
-        {
-            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;
-        }
-
-        private long NvMapIoctlChannelSetPriority(ServiceCtx Context)
-        {
-            long Position = Context.Request.GetSendBuffPtr();
-
-            int Priority = Context.Memory.ReadInt32(Position);
-
-            return 0;
-        }
-
-        private long NvMapIoctlChannelAllocGpFifoEx2(ServiceCtx Context)
-        {
-            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;
-        }
-
-        private long NvMapIocCreate(ServiceCtx Context)
-        {
-            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;
-        }
-
-        private long NvMapIocFromId(ServiceCtx Context)
-        {
-            long Position = Context.Request.GetSendBuffPtr();
-
-            int Id = Context.Memory.ReadInt32(Position);
-
-            NvMap Map = NvMapsById.GetData<NvMap>(Context.Process, Id);
-
-            if (Map == null)
-            {
-                Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap Id {Id}!");
-
-                return -1; //TODO: Corrent error code.
-            }
-
-            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)
-            {
-                Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"Invalid NvMap Handle {Handle}!");
-
-                return -1; //TODO: Corrent error code.
-            }
-
-            Map.CpuAddress = Addr;
-            Map.Align      = Align;
-            Map.Kind       = Kind;
-
-            return 0;
-        }
-
-        private long NvMapIocFree(ServiceCtx Context)
-        {
-            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;
-        }
-
-        private long NvMapIocParam(ServiceCtx Context)
-        {
-            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;
-        }
-
-        private long NvMapIocGetId(ServiceCtx Context)
-        {
-            long Position = Context.Request.GetSendBuffPtr();
-
-            int Handle = Context.Memory.ReadInt32(Position + 4);
-
-            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.Memory.WriteInt32(Position, Map.Id);
-
-            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 9ea3ae6e5c..0000000000
--- 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 0000000000..7e652d1fcb
--- /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 0000000000..6be45d5c17
--- /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 0000000000..b8bdd70691
--- /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 0000000000..363ae687eb
--- /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 0000000000..8b62751176
--- /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 0000000000..5d92b50837
--- /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 0000000000..772b6786eb
--- /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 0000000000..c5cee361ca
--- /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 0000000000..85b4753398
--- /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 0000000000..4698a3da25
--- /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 0000000000..8c705d7f03
--- /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 0000000000..0b05e63aa2
--- /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 0000000000..6746090ab9
--- /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 0000000000..21ba378321
--- /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 0000000000..be71b2252a
--- /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 0000000000..027e25b0c1
--- /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 0000000000..a4bd2cb6ba
--- /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 0000000000..96ae16a390
--- /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 570cef6827..0000000000
--- 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 0000000000..86e4c23811
--- /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 0000000000..7d35731ce5
--- /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 0000000000..ee8bc618a6
--- /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 0000000000..377eaa7f02
--- /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 0000000000..639a5fb4c6
--- /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 0000000000..4a021f6f35
--- /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 0000000000..80ff4c0347
--- /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 0000000000..f9c1564a5a
--- /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 0000000000..196ef6aba8
--- /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 d8a47418b9..0000000000
--- 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 0000000000..5a41916512
--- /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 5aa3c3d51e..db2f7fa265 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 0000000000..4a522465ac
--- /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 fe92f68fd2..0000000000
--- 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 21b6a3b653..0000000000
--- 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
diff --git a/Ryujinx.Core/Switch.cs b/Ryujinx.Core/Switch.cs
index 02fdc8b696..a755ea0c74 100644
--- a/Ryujinx.Core/Switch.cs
+++ b/Ryujinx.Core/Switch.cs
@@ -4,7 +4,7 @@ using Ryujinx.Core.Logging;
 using Ryujinx.Core.OsHle;
 using Ryujinx.Core.Settings;
 using Ryujinx.Graphics.Gal;
-using Ryujinx.Graphics.Gpu;
+using Ryujinx.Core.Gpu;
 using System;
 
 namespace Ryujinx.Core
@@ -15,7 +15,7 @@ namespace Ryujinx.Core
 
         public Logger Log { get; private set; }
 
-        internal NsGpu Gpu { get; private set; }
+        internal NvGpu Gpu { get; private set; }
 
         internal VirtualFileSystem VFs { get; private set; }
 
@@ -45,7 +45,7 @@ namespace Ryujinx.Core
 
             Log = new Logger();
 
-            Gpu = new NsGpu(Renderer);
+            Gpu = new NvGpu(Renderer);
 
             VFs = new VirtualFileSystem();
 
diff --git a/Ryujinx.Graphics/Gpu/INvGpuEngine.cs b/Ryujinx.Graphics/Gpu/INvGpuEngine.cs
deleted file mode 100644
index 17e9b435c8..0000000000
--- a/Ryujinx.Graphics/Gpu/INvGpuEngine.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using ChocolArm64.Memory;
-
-namespace Ryujinx.Graphics.Gpu
-{
-    interface INvGpuEngine
-    {
-        int[] Registers { get; }
-
-        void CallMethod(AMemory Memory, NsGpuPBEntry PBEntry);
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/NsGpuMemoryMgr.cs b/Ryujinx.Graphics/Gpu/NsGpuMemoryMgr.cs
deleted file mode 100644
index eff5178313..0000000000
--- a/Ryujinx.Graphics/Gpu/NsGpuMemoryMgr.cs
+++ /dev/null
@@ -1,212 +0,0 @@
-namespace Ryujinx.Graphics.Gpu
-{
-    public class NsGpuMemoryMgr
-    {
-        private const long AddrSize   = 1L << 40;
-
-        private const int  PTLvl0Bits = 14;
-        private const int  PTLvl1Bits = 14;
-        private const int  PTPageBits = 12;
-
-        private const int  PTLvl0Size = 1 << PTLvl0Bits;
-        private const int  PTLvl1Size = 1 << PTLvl1Bits;
-        private const int  PageSize   = 1 << PTPageBits;
-
-        private const int  PTLvl0Mask = PTLvl0Size - 1;
-        private const int  PTLvl1Mask = PTLvl1Size - 1;
-        private const int  PageMask   = PageSize   - 1;
-
-        private const int  PTLvl0Bit  = PTPageBits + PTLvl1Bits;
-        private const int  PTLvl1Bit  = PTPageBits;
-
-        private const long PteUnmapped = -1;
-        private const long PteReserved = -2;
-
-        private long[][] PageTable;
-
-        public NsGpuMemoryMgr()
-        {
-            PageTable = new long[PTLvl0Size][];
-        }
-
-        public long Map(long CpuAddr, long GpuAddr, long Size)
-        {
-            CpuAddr &= ~PageMask;
-            GpuAddr &= ~PageMask;
-
-            for (long Offset = 0; Offset < Size; Offset += PageSize)
-            {
-                if (GetPTAddr(GpuAddr + Offset) != PteReserved)
-                {
-                    return Map(CpuAddr, Size);
-                }
-            }
-
-            for (long Offset = 0; Offset < Size; Offset += PageSize)
-            {
-                SetPTAddr(GpuAddr + Offset, CpuAddr + Offset);
-            }
-
-            return GpuAddr;
-        }
-
-        public void Unmap(long Position, long Size)
-        {
-            for (long Offset = 0; Offset < Size; Offset += PageSize)
-            {
-                SetPTAddr(Position + Offset, PteUnmapped);
-            }
-        }
-
-        public long Map(long CpuAddr, long Size)
-        {
-            CpuAddr &= ~PageMask;
-
-            long Position = GetFreePosition(Size);
-
-            if (Position != -1)
-            {
-                for (long Offset = 0; Offset < Size; Offset += PageSize)
-                {
-                    SetPTAddr(Position + Offset, CpuAddr + Offset);
-                }
-            }
-
-            return Position;
-        }
-
-        public long Reserve(long GpuAddr, long Size, long Align)
-        {
-            for (long Offset = 0; Offset < Size; Offset += PageSize)
-            {
-                if (HasPTAddr(GpuAddr + Offset))
-                {
-                    return Reserve(Size, Align);
-                }
-            }
-
-            for (long Offset = 0; Offset < Size; Offset += PageSize)
-            {
-                SetPTAddr(GpuAddr + Offset, PteReserved);
-            }
-
-            return GpuAddr;
-        }
-
-        public long Reserve(long Size, long Align)
-        {
-            long Position = GetFreePosition(Size, Align);
-
-            if (Position != -1)
-            {
-                for (long Offset = 0; Offset < Size; Offset += PageSize)
-                {
-                    SetPTAddr(Position + Offset, PteReserved);
-                }
-            }
-
-            return Position;
-        }
-
-        private long GetFreePosition(long Size, long Align = 1)
-        {
-            long Position = 0;
-            long FreeSize = 0;
-
-            if (Align < 1)
-            {
-                Align = 1;
-            }
-
-            Align = (Align + PageMask) & ~PageMask;
-
-            while (Position + FreeSize < AddrSize)
-            {
-                if (!HasPTAddr(Position + FreeSize))
-                {
-                    FreeSize += PageSize;
-
-                    if (FreeSize >= Size)
-                    {
-                        return Position;
-                    }
-                }
-                else
-                {
-                    Position += FreeSize + PageSize;
-                    FreeSize  = 0;
-
-                    long Remainder = Position % Align;
-
-                    if (Remainder != 0)
-                    {
-                        Position = (Position - Remainder) + Align;
-                    }
-                }
-            }
-
-            return -1;
-        }
-
-        public long GetCpuAddr(long Position)
-        {
-            long BasePos = GetPTAddr(Position);
-
-            if (BasePos < 0)
-            {
-                return -1;
-            }
-
-            return BasePos + (Position & PageMask);
-        }
-
-        private bool HasPTAddr(long Position)
-        {
-            if (Position >> PTLvl0Bits + PTLvl1Bits + PTPageBits != 0)
-            {
-                return false;
-            }
-
-            long L0 = (Position >> PTLvl0Bit) & PTLvl0Mask;
-            long L1 = (Position >> PTLvl1Bit) & PTLvl1Mask;
-
-            if (PageTable[L0] == null)
-            {
-                return false;
-            }
-
-            return PageTable[L0][L1] != PteUnmapped;
-        }
-
-        private long GetPTAddr(long Position)
-        {
-            long L0 = (Position >> PTLvl0Bit) & PTLvl0Mask;
-            long L1 = (Position >> PTLvl1Bit) & PTLvl1Mask;
-
-            if (PageTable[L0] == null)
-            {
-                return -1;
-            }
-
-            return PageTable[L0][L1];
-        }
-
-        private void SetPTAddr(long Position, long TgtAddr)
-        {
-            long L0 = (Position >> PTLvl0Bit) & PTLvl0Mask;
-            long L1 = (Position >> PTLvl1Bit) & PTLvl1Mask;
-
-            if (PageTable[L0] == null)
-            {
-                PageTable[L0] = new long[PTLvl1Size];
-
-                for (int Index = 0; Index < PTLvl1Size; Index++)
-                {
-                    PageTable[L0][Index] = PteUnmapped;
-                }
-            }
-
-            PageTable[L0][L1] = TgtAddr;
-        }
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/NvGpuMethod.cs b/Ryujinx.Graphics/Gpu/NvGpuMethod.cs
deleted file mode 100644
index 2923ddff0c..0000000000
--- a/Ryujinx.Graphics/Gpu/NvGpuMethod.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-using ChocolArm64.Memory;
-
-namespace Ryujinx.Graphics.Gpu
-{
-    delegate void NvGpuMethod(AMemory Memory, NsGpuPBEntry PBEntry);
-}
\ No newline at end of file