diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000..1ff0c42304
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln merge=binary
+#*.csproj merge=binary
+#*.vbproj merge=binary
+#*.vcxproj merge=binary
+#*.vcproj merge=binary
+#*.dbproj merge=binary
+#*.fsproj merge=binary
+#*.lsproj merge=binary
+#*.wixproj merge=binary
+#*.modelproj merge=binary
+#*.sqlproj merge=binary
+#*.wwaproj merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg binary
+#*.png binary
+#*.gif binary
+
+###############################################################################
+# diff behavior for common document formats
+#
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the
+# entries below.
+###############################################################################
+#*.doc diff=astextplain
+#*.DOC diff=astextplain
+#*.docx diff=astextplain
+#*.DOCX diff=astextplain
+#*.dot diff=astextplain
+#*.DOT diff=astextplain
+#*.pdf diff=astextplain
+#*.PDF diff=astextplain
+#*.rtf diff=astextplain
+#*.RTF diff=astextplain
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000..82d9719b59
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,160 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.sln.docstates
+.vs
+.vscode
+
+# Build results
+
+[Dd]ebug/
+[Rr]elease/
+x64/
+build/
+[Bb]in/
+[Oo]bj/
+
+# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
+!packages/*/build/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+*_i.c
+*_p.c
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.log
+*.scc
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opensdf
+*.sdf
+*.cachefile
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+*.ncrunch*
+.*crunch*.local.xml
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.Publish.xml
+
+# NuGet Packages Directory
+## TODO: If you have NuGet Package Restore enabled, uncomment the next line
+#packages/
+
+# Windows Azure Build Output
+csx
+*.build.csdef
+
+# Windows Store app package directory
+AppPackages/
+
+# Others
+sql/
+*.Cache
+ClientBin/
+[Ss]tyle[Cc]op.*
+~$*
+*~
+*.dbmdl
+*.[Pp]ublish.xml
+*.pfx
+*.publishsettings
+packages/*
+*.config
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file to a newer
+# Visual Studio version. Backup files are not needed, because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+App_Data/*.mdf
+App_Data/*.ldf
+
+
+#LightSwitch generated files
+GeneratedArtifacts/
+_Pvt_Extensions/
+ModelManifest.xml
+
+# =========================
+# Windows detritus
+# =========================
+
+# Windows image file caches
+Thumbs.db
+ehthumbs.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Mac desktop service store files
+.DS_Store
diff --git a/GLScreen.cs b/GLScreen.cs
new file mode 100644
index 0000000000..d757db8fce
--- /dev/null
+++ b/GLScreen.cs
@@ -0,0 +1,366 @@
+// This code was written for the OpenTK library and has been released
+// to the Public Domain.
+// It is provided "as is" without express or implied warranty of any kind.
+
+using Gal;
+using OpenTK;
+using OpenTK.Graphics;
+using OpenTK.Graphics.OpenGL;
+using System;
+
+namespace Ryujinx
+{
+ public class GLScreen : GameWindow
+ {
+ class ScreenTexture : IDisposable
+ {
+ private Switch Ns;
+ private IGalRenderer Renderer;
+
+ private int Width;
+ private int Height;
+ private int TexHandle;
+
+ private int[] Pixels;
+
+ public ScreenTexture(Switch Ns, IGalRenderer Renderer, int Width, int Height)
+ {
+ this.Ns = Ns;
+ this.Renderer = Renderer;
+ this.Width = Width;
+ this.Height = Height;
+
+ Pixels = new int[Width * Height];
+
+ TexHandle = GL.GenTexture();
+
+ GL.BindTexture(TextureTarget.Texture2D, TexHandle);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
+ GL.TexImage2D(TextureTarget.Texture2D,
+ 0,
+ PixelInternalFormat.Rgba,
+ Width,
+ Height,
+ 0,
+ PixelFormat.Rgba,
+ PixelType.UnsignedByte,
+ IntPtr.Zero);
+ }
+
+ public int Texture
+ {
+ get
+ {
+ UploadBitmap();
+
+ return TexHandle;
+ }
+ }
+
+ unsafe void UploadBitmap()
+ {
+ if (Renderer.FrameBufferPtr == 0)
+ {
+ return;
+ }
+
+ byte* SrcPtr = (byte*)IntPtr.Add(Ns.Ram, (int)Renderer.FrameBufferPtr);
+
+ for (int Y = 0; Y < Height; Y++)
+ {
+ for (int X = 0; X < Width; X++)
+ {
+ int SrcOffs = GetSwizzleOffset(X, Y, 4);
+
+ Pixels[X + Y * Width] = *((int*)(SrcPtr + SrcOffs));
+ }
+ }
+
+ GL.BindTexture(TextureTarget.Texture2D, TexHandle);
+ GL.TexSubImage2D(TextureTarget.Texture2D,
+ 0,
+ 0,
+ 0,
+ Width,
+ Height,
+ PixelFormat.Rgba,
+ PixelType.UnsignedByte,
+ Pixels);
+ }
+
+ private int GetSwizzleOffset(int X, int Y, int Bpp)
+ {
+ int Pos;
+
+ Pos = (Y & 0x7f) >> 4;
+ Pos += (X >> 4) << 3;
+ Pos += (Y >> 7) * ((Width >> 4) << 3);
+ Pos *= 1024;
+ Pos += ((Y & 0xf) >> 3) << 9;
+ Pos += ((X & 0xf) >> 3) << 8;
+ Pos += ((Y & 0x7) >> 1) << 6;
+ Pos += ((X & 0x7) >> 2) << 5;
+ Pos += ((Y & 0x1) >> 0) << 4;
+ Pos += ((X & 0x3) >> 0) << 2;
+
+ return Pos;
+ }
+
+ private bool disposed;
+
+ public void Dispose()
+ {
+ Dispose(true);
+
+ GC.SuppressFinalize(this);
+ }
+
+ void Dispose(bool disposing)
+ {
+ if (!disposed)
+ {
+ if (disposing)
+ {
+ GL.DeleteTexture(TexHandle);
+ }
+
+ disposed = true;
+ }
+ }
+ }
+
+ private string VtxShaderSource = @"
+#version 330 core
+
+precision highp float;
+
+layout(location = 0) in vec3 in_position;
+layout(location = 1) in vec4 in_color;
+layout(location = 2) in vec2 in_tex_coord;
+
+out vec4 color;
+out vec2 tex_coord;
+
+void main(void) {
+ color = in_color;
+ tex_coord = in_tex_coord;
+ gl_Position = vec4((in_position + vec3(-960, 270, 0)) / vec3(1920, 270, 1), 1);
+}";
+
+ private string FragShaderSource = @"
+#version 330 core
+
+precision highp float;
+
+uniform sampler2D tex;
+
+in vec4 color;
+in vec2 tex_coord;
+out vec4 out_frag_color;
+
+void main(void) {
+ out_frag_color = vec4(texture(tex, tex_coord).rgb, color.a);
+}";
+
+ private int VtxShaderHandle,
+ FragShaderHandle,
+ PrgShaderHandle;
+
+ private int VaoHandle;
+ private int VboHandle;
+
+ private Switch Ns;
+
+ private IGalRenderer Renderer;
+
+ private ScreenTexture ScreenTex;
+
+ public GLScreen(Switch Ns, IGalRenderer Renderer)
+ : base(1280, 720,
+ new GraphicsMode(), "Ryujinx", 0,
+ DisplayDevice.Default, 3, 3,
+ GraphicsContextFlags.ForwardCompatible)
+ {
+ this.Ns = Ns;
+ this.Renderer = Renderer;
+
+ ScreenTex = new ScreenTexture(Ns, Renderer, 1280, 720);
+ }
+
+ protected override void OnLoad (EventArgs e)
+ {
+ VSync = VSyncMode.On;
+
+ CreateShaders();
+ CreateVbo();
+
+ GL.Enable(EnableCap.Blend);
+ GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
+ }
+
+ protected override void OnUnload(EventArgs e)
+ {
+ ScreenTex.Dispose();
+
+ GL.DeleteVertexArray(VaoHandle);
+ GL.DeleteBuffer(VboHandle);
+ }
+
+ private void CreateVbo()
+ {
+ VaoHandle = GL.GenVertexArray();
+ VboHandle = GL.GenBuffer();
+
+ uint[] Buffer = new uint[]
+ {
+ 0xc4700000, 0x80000000, 0x00000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
+ 0x45340000, 0x80000000, 0x00000000, 0xffffffff, 0x00000000, 0x3f800000, 0x00000000,
+ 0xc4700000, 0xc4070000, 0x00000000, 0xffffffff, 0x00000000, 0x00000000, 0x3f800000,
+ 0x45340000, 0xc4070000, 0x00000000, 0xffffffff, 0x00000000, 0x3f800000, 0x3f800000
+ };
+
+ IntPtr Length = new IntPtr(Buffer.Length * 4);
+
+ GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle);
+ GL.BufferData(BufferTarget.ArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw);
+ GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
+
+ GL.BindVertexArray(VaoHandle);
+
+ GL.EnableVertexAttribArray(0);
+
+ GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle);
+
+ GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 28, 0);
+
+ GL.EnableVertexAttribArray(1);
+
+ GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle);
+
+ GL.VertexAttribPointer(1, 4, VertexAttribPointerType.UnsignedByte, false, 28, 12);
+
+ GL.EnableVertexAttribArray(2);
+
+ GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle);
+
+ GL.VertexAttribPointer(2, 2, VertexAttribPointerType.Float, false, 28, 20);
+
+ GL.BindVertexArray(0);
+ }
+
+ private void CreateShaders()
+ {
+ VtxShaderHandle = GL.CreateShader(ShaderType.VertexShader);
+ FragShaderHandle = GL.CreateShader(ShaderType.FragmentShader);
+
+ GL.ShaderSource(VtxShaderHandle, VtxShaderSource);
+ GL.ShaderSource(FragShaderHandle, FragShaderSource);
+ GL.CompileShader(VtxShaderHandle);
+ GL.CompileShader(FragShaderHandle);
+
+ PrgShaderHandle = GL.CreateProgram();
+
+ GL.AttachShader(PrgShaderHandle, VtxShaderHandle);
+ GL.AttachShader(PrgShaderHandle, FragShaderHandle);
+ GL.LinkProgram(PrgShaderHandle);
+ GL.UseProgram(PrgShaderHandle);
+
+ int TexLocation = GL.GetUniformLocation(PrgShaderHandle, "tex");
+
+ GL.Uniform1(TexLocation, 0);
+ }
+
+ protected override void OnUpdateFrame(FrameEventArgs e)
+ {
+ unsafe
+ {
+ byte* Ptr = (byte*)IntPtr.Add(Ns.Ram, (int)Ns.Os.HidOffset);
+
+ int State = 0;
+
+ if (Keyboard[OpenTK.Input.Key.Up])
+ {
+ State |= 0x2000;
+ }
+
+ if (Keyboard[OpenTK.Input.Key.Down])
+ {
+ State |= 0x8000;
+ }
+
+ if (Keyboard[OpenTK.Input.Key.Left])
+ {
+ State |= 0x1000;
+ }
+
+ if (Keyboard[OpenTK.Input.Key.Right])
+ {
+ State |= 0x4000;
+ }
+
+ if (Keyboard[OpenTK.Input.Key.A])
+ {
+ State |= 0x1;
+ }
+
+ if (Keyboard[OpenTK.Input.Key.S])
+ {
+ State |= 0x2;
+ }
+
+ if (Keyboard[OpenTK.Input.Key.Z])
+ {
+ State |= 0x4;
+ }
+
+ if (Keyboard[OpenTK.Input.Key.X])
+ {
+ State |= 0x8;
+ }
+
+ if (Keyboard[OpenTK.Input.Key.Enter])
+ {
+ State |= 0x400;
+ }
+
+ if (Keyboard[OpenTK.Input.Key.Tab])
+ {
+ State |= 0x800;
+ }
+
+ *((int*)(Ptr + 0xae38)) = (int)State;
+ }
+
+ if (Keyboard[OpenTK.Input.Key.Escape])
+ {
+ this.Exit();
+ }
+ }
+
+ protected override void OnRenderFrame(FrameEventArgs e)
+ {
+ GL.Viewport(0, 0, 1280, 720);
+
+ GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
+
+ RenderFb();
+
+ GL.UseProgram(PrgShaderHandle);
+
+ Renderer.RunActions();
+ Renderer.BindTexture(0);
+ Renderer.Render();
+
+ SwapBuffers();
+ }
+
+ void RenderFb()
+ {
+ GL.ActiveTexture(TextureUnit.Texture0);
+ GL.BindTexture(TextureTarget.Texture2D, ScreenTex.Texture);
+ GL.BindVertexArray(VaoHandle);
+ GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
+ }
+ }
+}
\ No newline at end of file
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000000..00d2e135a7
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to
\ No newline at end of file
diff --git a/Program.cs b/Program.cs
new file mode 100644
index 0000000000..44dc67c1fd
--- /dev/null
+++ b/Program.cs
@@ -0,0 +1,57 @@
+using Gal;
+using Gal.OpenGL;
+using System;
+using System.IO;
+
+namespace Ryujinx
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ IGalRenderer Renderer = new OpenGLRenderer();
+
+ Switch Ns = new Switch(Renderer);
+
+ if (args.Length == 1)
+ {
+ if (Directory.Exists(args[0]))
+ {
+ string[] RomFsFiles = Directory.GetFiles(args[0], "*.istorage");
+
+ if (RomFsFiles.Length > 0)
+ {
+ Console.WriteLine("Loading as cart with RomFS.");
+
+ Ns.Os.LoadCart(args[0], RomFsFiles[0]);
+ }
+ else
+ {
+ Console.WriteLine("Loading as cart WITHOUT RomFS.");
+
+ Ns.Os.LoadCart(args[0]);
+ }
+ }
+ else if (File.Exists(args[0]))
+ {
+ Console.WriteLine("Loading as homebrew.");
+
+ Ns.Os.LoadProgram(args[0]);
+ }
+ }
+ else
+ {
+ Console.WriteLine("Please specify the folder with the NSOs/IStorage or a NSO/NRO.");
+ }
+
+ using (GLScreen Screen = new GLScreen(Ns, Renderer))
+ {
+ Screen.Run(60.0);
+ }
+
+ Ns.Os.StopAllProcesses();
+
+ Ns.Dispose();
+ }
+ }
+}
diff --git a/Ryujinx.csproj b/Ryujinx.csproj
new file mode 100644
index 0000000000..2c25afc0ff
--- /dev/null
+++ b/Ryujinx.csproj
@@ -0,0 +1,12 @@
+
+
+ Exe
+ netcoreapp2.0
+ win10-x64
+ true
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Ryujinx.sln b/Ryujinx.sln
new file mode 100644
index 0000000000..c75364f712
--- /dev/null
+++ b/Ryujinx.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26730.8
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "Ryujinx.csproj", "{074045D4-3ED2-4711-9169-E385F2BFB5A0}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {074045D4-3ED2-4711-9169-E385F2BFB5A0}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {110169B3-3328-4730-8AB0-BA05BEF75C1A}
+ EndGlobalSection
+EndGlobal
diff --git a/Ryujinx/Cpu/ABitUtils.cs b/Ryujinx/Cpu/ABitUtils.cs
new file mode 100644
index 0000000000..357dd45d15
--- /dev/null
+++ b/Ryujinx/Cpu/ABitUtils.cs
@@ -0,0 +1,57 @@
+namespace ChocolArm64
+{
+ static class ABitUtils
+ {
+ public static int CountBitsSet(long Value)
+ {
+ int Count = 0;
+
+ for (int Bit = 0; Bit < 64; Bit++)
+ {
+ Count += (int)(Value >> Bit) & 1;
+ }
+
+ return Count;
+ }
+
+ public static int HighestBitSet32(int Value)
+ {
+ for (int Bit = 31; Bit >= 0; Bit--)
+ {
+ if (((Value >> Bit) & 1) != 0)
+ {
+ return Bit;
+ }
+ }
+
+ return -1;
+ }
+
+ public static long Replicate(long Bits, int Size)
+ {
+ long Output = 0;
+
+ for (int Bit = 0; Bit < 64; Bit += Size)
+ {
+ Output |= Bits << Bit;
+ }
+
+ return Output;
+ }
+
+ public static long FillWithOnes(int Bits)
+ {
+ return Bits == 64 ? -1L : (1L << Bits) - 1;
+ }
+
+ public static long RotateRight(long Bits, int Shift, int Size)
+ {
+ return (Bits >> Shift) | (Bits << (Size - Shift));
+ }
+
+ public static bool IsPow2(int Value)
+ {
+ return Value != 0 && (Value & (Value - 1)) == 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/AOpCodeTable.cs b/Ryujinx/Cpu/AOpCodeTable.cs
new file mode 100644
index 0000000000..d15d2e1230
--- /dev/null
+++ b/Ryujinx/Cpu/AOpCodeTable.cs
@@ -0,0 +1,374 @@
+using ChocolArm64.Decoder;
+using ChocolArm64.Instruction;
+using System;
+
+namespace ChocolArm64
+{
+ static class AOpCodeTable
+ {
+ static AOpCodeTable()
+ {
+ #region "OpCode Table"
+ //Integer
+ Set("x0010001xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Add, typeof(AOpCodeAluImm));
+ Set("x0001011xx0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Add, typeof(AOpCodeAluRs));
+ Set("x0001011001xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Add, typeof(AOpCodeAluRx));
+ Set("x0110001xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Adds, typeof(AOpCodeAluImm));
+ Set("x0101011xx0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Adds, typeof(AOpCodeAluRs));
+ Set("x0101011001xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Adds, typeof(AOpCodeAluRx));
+ Set("0xx10000xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Adr, typeof(AOpCodeAdr));
+ Set("1xx10000xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Adrp, typeof(AOpCodeAdr));
+ Set("x00100100xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.And, typeof(AOpCodeAluImm));
+ Set("x0001010xx0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.And, typeof(AOpCodeAluRs));
+ Set("x11100100xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ands, typeof(AOpCodeAluImm));
+ Set("x1101010xx0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ands, typeof(AOpCodeAluRs));
+ Set("x0011010110xxxxx001010xxxxxxxxxx", AInstEmit.Asrv, typeof(AOpCodeAluRs));
+ Set("000101xxxxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.B, typeof(AOpCodeBImmAl));
+ Set("01010100xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.B_Cond, typeof(AOpCodeBImmCond));
+ Set("x01100110xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Bfm, typeof(AOpCodeBfm));
+ Set("x0001010xx1xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Bic, typeof(AOpCodeAluRs));
+ Set("x1101010xx1xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Bics, typeof(AOpCodeAluRs));
+ Set("100101xxxxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Bl, typeof(AOpCodeBImmAl));
+ Set("11010110001xxxxx000000xxxxxxxxxx", AInstEmit.Blr, typeof(AOpCodeBReg));
+ Set("11010110000xxxxx000000xxxxxxxxxx", AInstEmit.Br, typeof(AOpCodeBReg));
+ Set("x0110101xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Cbnz, typeof(AOpCodeBImmCmp));
+ Set("x0110100xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Cbz, typeof(AOpCodeBImmCmp));
+ Set("x0111010010xxxxxxxxx10xxxxxxxxxx", AInstEmit.Ccmn, typeof(AOpCodeCcmpImm));
+ Set("x0111010010xxxxxxxxx00xxxxxxxxxx", AInstEmit.Ccmn, typeof(AOpCodeCcmpReg));
+ Set("x1111010010xxxxxxxxx10xxxxxxxxxx", AInstEmit.Ccmp, typeof(AOpCodeCcmpImm));
+ Set("x1111010010xxxxxxxxx00xxxxxxxxxx", AInstEmit.Ccmp, typeof(AOpCodeCcmpReg));
+ Set("11010101000000110011xxxx01011111", AInstEmit.Clrex, typeof(AOpCodeSystem));
+ Set("x101101011000000000100xxxxxxxxxx", AInstEmit.Clz, typeof(AOpCodeAlu));
+ Set("x0011010100xxxxxxxxx00xxxxxxxxxx", AInstEmit.Csel, typeof(AOpCodeCsel));
+ Set("x0011010100xxxxxxxxx01xxxxxxxxxx", AInstEmit.Csinc, typeof(AOpCodeCsel));
+ Set("x1011010100xxxxxxxxx00xxxxxxxxxx", AInstEmit.Csinv, typeof(AOpCodeCsel));
+ Set("x1011010100xxxxxxxxx01xxxxxxxxxx", AInstEmit.Csneg, typeof(AOpCodeCsel));
+ Set("11010101000000110011xxxx10111111", AInstEmit.Dmb, typeof(AOpCodeSystem));
+ Set("11010101000000110011xxxx10011111", AInstEmit.Dsb, typeof(AOpCodeSystem));
+ Set("x10100100xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Eor, typeof(AOpCodeAluImm));
+ Set("x1001010xx0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Eor, typeof(AOpCodeAluRs));
+ Set("x00100111x0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Extr, typeof(AOpCodeAluRs));
+ Set("xx001000110xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Ldar, typeof(AOpCodeMemEx));
+ Set("1x001000011xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Ldaxp, typeof(AOpCodeMemEx));
+ Set("xx001000010xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Ldaxr, typeof(AOpCodeMemEx));
+ Set("<<10100xx1xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldp, typeof(AOpCodeMemPair));
+ Set("xx111000010xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldr, typeof(AOpCodeMemImm));
+ Set("xx11100101xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldr, typeof(AOpCodeMemImm));
+ Set("xx111000011xxxxxxxxx10xxxxxxxxxx", AInstEmit.Ldr, typeof(AOpCodeMemReg));
+ Set("xx011000xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.LdrLit, typeof(AOpCodeMemLit));
+ Set("0x1110001x0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldrs, typeof(AOpCodeMemImm));
+ Set("0x1110011xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldrs, typeof(AOpCodeMemImm));
+ Set("10111000100xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldrs, typeof(AOpCodeMemImm));
+ Set("1011100110xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ldrs, typeof(AOpCodeMemImm));
+ Set("0x1110001x1xxxxxxxxx10xxxxxxxxxx", AInstEmit.Ldrs, typeof(AOpCodeMemReg));
+ Set("10111000101xxxxxxxxx10xxxxxxxxxx", AInstEmit.Ldrs, typeof(AOpCodeMemReg));
+ Set("xx001000010xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Ldxr, typeof(AOpCodeMemEx));
+ Set("1x001000011xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Ldxp, typeof(AOpCodeMemEx));
+ Set("x0011010110xxxxx001000xxxxxxxxxx", AInstEmit.Lslv, typeof(AOpCodeAluRs));
+ Set("x0011010110xxxxx001001xxxxxxxxxx", AInstEmit.Lsrv, typeof(AOpCodeAluRs));
+ Set("x0011011000xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Madd, typeof(AOpCodeMul));
+ Set("x11100101xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Movk, typeof(AOpCodeMov));
+ Set("x00100101xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Movn, typeof(AOpCodeMov));
+ Set("x10100101xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Movz, typeof(AOpCodeMov));
+ Set("110101010011xxxxxxxxxxxxxxxxxxxx", AInstEmit.Mrs, typeof(AOpCodeSystem));
+ Set("110101010001xxxxxxxxxxxxxxxxxxxx", AInstEmit.Msr, typeof(AOpCodeSystem));
+ Set("x0011011000xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Msub, typeof(AOpCodeMul));
+ Set("11010101000000110010000000011111", AInstEmit.Nop, typeof(AOpCodeSystem));
+ Set("x0101010xx1xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Orn, typeof(AOpCodeAluRs));
+ Set("x01100100xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Orr, typeof(AOpCodeAluImm));
+ Set("x0101010xx0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Orr, typeof(AOpCodeAluRs));
+ Set("1111100110xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Pfrm, typeof(AOpCodeMemImm));
+ Set("11011000xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Pfrm, typeof(AOpCodeMemLit));
+ Set("x101101011000000000000xxxxxxxxxx", AInstEmit.Rbit, typeof(AOpCodeAlu));
+ Set("11010110010xxxxx000000xxxxxxxxxx", AInstEmit.Ret, typeof(AOpCodeBReg));
+ Set("x101101011000000000001xxxxxxxxxx", AInstEmit.Rev16, typeof(AOpCodeAlu));
+ Set("x101101011000000000010xxxxxxxxxx", AInstEmit.Rev32, typeof(AOpCodeAlu));
+ Set("1101101011000000000011xxxxxxxxxx", AInstEmit.Rev64, typeof(AOpCodeAlu));
+ Set("x0011010110xxxxx001011xxxxxxxxxx", AInstEmit.Rorv, typeof(AOpCodeAluRs));
+ Set("x00100110xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Sbfm, typeof(AOpCodeBfm));
+ Set("x0011010110xxxxx000011xxxxxxxxxx", AInstEmit.Sdiv, typeof(AOpCodeAluRs));
+ Set("10011011001xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Smaddl, typeof(AOpCodeMul));
+ Set("10011011001xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Smsubl, typeof(AOpCodeMul));
+ Set("10011011010xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Smulh, typeof(AOpCodeMul));
+ Set("xx001000100xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Stlr, typeof(AOpCodeMemEx));
+ Set("1x001000001xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Stlxp, typeof(AOpCodeMemEx));
+ Set("xx001000000xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Stlxr, typeof(AOpCodeMemEx));
+ Set("x010100xx0xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Stp, typeof(AOpCodeMemPair));
+ Set("xx111000000xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeMemImm));
+ Set("xx11100100xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeMemImm));
+ Set("xx111000001xxxxxxxxx10xxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeMemReg));
+ Set("1x001000001xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Stxp, typeof(AOpCodeMemEx));
+ Set("xx001000000xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Stxr, typeof(AOpCodeMemEx));
+ Set("x1010001xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Sub, typeof(AOpCodeAluImm));
+ Set("x1001011xx0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Sub, typeof(AOpCodeAluRs));
+ Set("x1001011001xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Sub, typeof(AOpCodeAluRx));
+ Set("x1110001xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Subs, typeof(AOpCodeAluImm));
+ Set("x1101011xx0xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Subs, typeof(AOpCodeAluRs));
+ Set("x1101011001xxxxxxxxxxxxxxxxxxxxx", AInstEmit.Subs, typeof(AOpCodeAluRx));
+ Set("11010100000xxxxxxxxxxxxxxxx00001", AInstEmit.Svc, typeof(AOpCodeException));
+ Set("1101010100001xxxxxxxxxxxxxxxxxxx", AInstEmit.Sys, typeof(AOpCodeSystem));
+ Set("x0110111xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Tbnz, typeof(AOpCodeBImmTest));
+ Set("x0110110xxxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Tbz, typeof(AOpCodeBImmTest));
+ Set("x10100110xxxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Ubfm, typeof(AOpCodeBfm));
+ Set("x0011010110xxxxx000010xxxxxxxxxx", AInstEmit.Udiv, typeof(AOpCodeAluRs));
+ Set("10011011101xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Umaddl, typeof(AOpCodeMul));
+ Set("10011011101xxxxx1xxxxxxxxxxxxxxx", AInstEmit.Umsubl, typeof(AOpCodeMul));
+ Set("10011011110xxxxx0xxxxxxxxxxxxxxx", AInstEmit.Umulh, typeof(AOpCodeMul));
+
+ //Vector
+ Set("0x001110xx1xxxxx100001xxxxxxxxxx", AInstEmit.Add_V, typeof(AOpCodeSimdReg));
+ Set("01011110xx110001101110xxxxxxxxxx", AInstEmit.Addp_S, typeof(AOpCodeSimd));
+ Set("0x001110xx1xxxxx101111xxxxxxxxxx", AInstEmit.Addp_V, typeof(AOpCodeSimdReg));
+ Set("0x001110<<110001101110xxxxxxxxxx", AInstEmit.Addv_V, typeof(AOpCodeSimd));
+ Set("0x001110001xxxxx000111xxxxxxxxxx", AInstEmit.And_V, typeof(AOpCodeSimdReg));
+ Set("0x001110011xxxxx000111xxxxxxxxxx", AInstEmit.Bic_V, typeof(AOpCodeSimdReg));
+ Set("0x10111100000xxx<>xxxxx111111xxxxxxxxxx", AInstEmit.Fcvtzu_V_Fix, typeof(AOpCodeSimdShImm));
+ Set("x0011110xx011000xxxxxxxxxxxxxxxx", AInstEmit.Fcvtzs_Fix, typeof(AOpCodeSimdCvt));
+ Set("x0011110xx011001xxxxxxxxxxxxxxxx", AInstEmit.Fcvtzu_Fix, typeof(AOpCodeSimdCvt));
+ Set("00011110xx1xxxxx000110xxxxxxxxxx", AInstEmit.Fdiv_S, typeof(AOpCodeSimdReg));
+ Set("00011110xx1xxxxx010010xxxxxxxxxx", AInstEmit.Fmax_S, typeof(AOpCodeSimdReg));
+ Set("00011110xx1xxxxx011010xxxxxxxxxx", AInstEmit.Fmaxnm_S, typeof(AOpCodeSimdReg));
+ Set("00011110xx1xxxxx010110xxxxxxxxxx", AInstEmit.Fmin_S, typeof(AOpCodeSimdReg));
+ Set("00011110xx1xxxxx011110xxxxxxxxxx", AInstEmit.Fminnm_S, typeof(AOpCodeSimdReg));
+ Set("0x0011100x1xxxxx110011xxxxxxxxxx", AInstEmit.Fmla_V, typeof(AOpCodeSimdReg));
+ Set("0x0011111<>>>xxx010101xxxxxxxxxx", AInstEmit.Shl_S, typeof(AOpCodeSimdShImm));
+ Set("0x0011110>>>>xxx010101xxxxxxxxxx", AInstEmit.Shl_V, typeof(AOpCodeSimdShImm));
+ Set("0x001110<<1xxxxx011001xxxxxxxxxx", AInstEmit.Smax_V, typeof(AOpCodeSimdReg));
+ Set("0x001110<<1xxxxx011011xxxxxxxxxx", AInstEmit.Smin_V, typeof(AOpCodeSimdReg));
+ Set("0x00111100>>>xxx101001xxxxxxxxxx", AInstEmit.Sshll_V, typeof(AOpCodeSimdShImm));
+ Set("010111110>>>>xxx000001xxxxxxxxxx", AInstEmit.Sshr_S, typeof(AOpCodeSimdShImm));
+ Set("0x0011110>>>>xxx000001xxxxxxxxxx", AInstEmit.Sshr_V, typeof(AOpCodeSimdShImm));
+ Set("0x00110000000000xxxxxxxxxxxxxxxx", AInstEmit.St__V, typeof(AOpCodeSimdMemMult));
+ Set("0x001100100xxxxxxxxxxxxxxxxxxxxx", AInstEmit.St__V, typeof(AOpCodeSimdMemMult));
+ Set("xx10110xx0xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Stp, typeof(AOpCodeSimdMemPair));
+ Set("xx111100x00xxxxxxxxx00xxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeSimdMemImm));
+ Set("xx111100x00xxxxxxxxx01xxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeSimdMemImm));
+ Set("xx111100x00xxxxxxxxx11xxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeSimdMemImm));
+ Set("xx111101x0xxxxxxxxxxxxxxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeSimdMemImm));
+ Set("xx111100x01xxxxxxxxx10xxxxxxxxxx", AInstEmit.Str, typeof(AOpCodeSimdMemReg));
+ Set("01111110xx1xxxxx100001xxxxxxxxxx", AInstEmit.Sub_S, typeof(AOpCodeSimdReg));
+ Set("0x101110xx1xxxxx100001xxxxxxxxxx", AInstEmit.Sub_V, typeof(AOpCodeSimdReg));
+ Set("x0011110xx100010000000xxxxxxxxxx", AInstEmit.Scvtf_Gp, typeof(AOpCodeSimdCvt));
+ Set("010111100x100001110110xxxxxxxxxx", AInstEmit.Scvtf_S, typeof(AOpCodeSimd));
+ Set("0x001110000xxxxx0xx000xxxxxxxxxx", AInstEmit.Tbl_V, typeof(AOpCodeSimdTbl));
+ Set("001011100x110000001110xxxxxxxxxx", AInstEmit.Uaddlv_V, typeof(AOpCodeSimd));
+ Set("01101110<<110000001110xxxxxxxxxx", AInstEmit.Uaddlv_V, typeof(AOpCodeSimd));
+ Set("0x101110<<1xxxxx000100xxxxxxxxxx", AInstEmit.Uaddw_V, typeof(AOpCodeSimdReg));
+ Set("x0011110xx100011000000xxxxxxxxxx", AInstEmit.Ucvtf_Gp, typeof(AOpCodeSimdCvt));
+ Set("011111100x100001110110xxxxxxxxxx", AInstEmit.Ucvtf_V, typeof(AOpCodeSimdReg));
+ Set("0x001110000xxxxx001111xxxxxxxxxx", AInstEmit.Umov_S, typeof(AOpCodeSimdIns));
+ Set("0x101110xx1xxxxx010001xxxxxxxxxx", AInstEmit.Ushl_V, typeof(AOpCodeSimdReg));
+ Set("0x10111100>>>xxx101001xxxxxxxxxx", AInstEmit.Ushll_V, typeof(AOpCodeSimdShImm));
+ Set("0x1011110>>>>xxx000001xxxxxxxxxx", AInstEmit.Ushr_V, typeof(AOpCodeSimdShImm));
+ Set("0x1011110>>>>xxx000101xxxxxxxxxx", AInstEmit.Usra_V, typeof(AOpCodeSimdShImm));
+ Set("0x001110xx0xxxxx000110xxxxxxxxxx", AInstEmit.Uzp1_V, typeof(AOpCodeSimdReg));
+ Set("0x001110<<100001001010xxxxxxxxxx", AInstEmit.Xtn_V, typeof(AOpCodeSimd));
+#endregion
+ }
+
+ private class TreeNode
+ {
+ public int Mask;
+ public int Value;
+
+ public TreeNode Next;
+ public TreeNode Child;
+
+ public AInst Inst;
+
+ public TreeNode(int Mask, int Value, AInst Inst)
+ {
+ this.Mask = Mask;
+ this.Value = Value;
+ this.Inst = Inst;
+ }
+ }
+
+ private static TreeNode Root;
+
+ private static void Set(string Encoding, AInstEmitter Emitter, Type Type)
+ {
+ Set(Encoding, new AInst(Emitter, Type));
+ }
+
+ private static void Set(string Encoding, AInst Inst)
+ {
+ int Bit = Encoding.Length - 1;
+ int Value = 0;
+ int XMask = 0;
+ int ZCount = 0;
+ int OCount = 0;
+
+ int[] ZPos = new int[Encoding.Length];
+ int[] OPos = new int[Encoding.Length];
+
+ for (int Index = 0; Index < Encoding.Length; Index++, Bit--)
+ {
+ //Note: < and > are used on special encodings.
+ //The < means that we should never have ALL bits with the '<' set.
+ //So, when the encoding has <<, it means that 00, 01, and 10 are valid,
+ //but not 11. <<< is 000, 001, ..., 110 but NOT 111, and so on...
+ //For >, the invalid value is zero. So, for << 01, 10 and 11 are valid,
+ //but 00 isn't.
+ switch (Encoding[Index])
+ {
+ case '0': /* Do nothing. */ break;
+ case '1': Value |= 1 << Bit; break;
+ case 'x': XMask |= 1 << Bit; break;
+
+ case '<': OPos[OCount++] = Bit; break;
+ case '>': ZPos[ZCount++] = Bit; break;
+
+ default: throw new ArgumentException(nameof(Encoding));
+ }
+ }
+
+ if (ZCount + OCount == 0)
+ {
+ InsertTop(XMask, Value, Inst);
+ }
+ else if ((ZCount & OCount) != 0)
+ {
+ for (int OCtr = 0; (uint)OCtr < (1 << OCount) - 1; OCtr++)
+ {
+ int OVal = Value;
+
+ for (int O = 0; O < OCount; O++)
+ {
+ OVal |= ((OCtr >> O) & 1) << OPos[O];
+ }
+
+ InsertWithCtr(1, 1 << ZCount, ZCount, ZPos, XMask, OVal, Inst);
+ }
+ }
+ else if (ZCount != 0)
+ {
+ InsertWithCtr(1, 1 << ZCount, ZCount, ZPos, XMask, Value, Inst);
+ }
+ else if (OCount != 0)
+ {
+ InsertWithCtr(0, (1 << OCount) - 1, OCount, OPos, XMask, Value, Inst);
+ }
+ }
+
+ private static void InsertWithCtr(
+ int Start,
+ int End,
+ int Cnt,
+ int[] Pos,
+ int XMask,
+ int Value,
+ AInst Inst)
+ {
+ for (int Ctr = Start; (uint)Ctr < End; Ctr++)
+ {
+ int Val = Value;
+
+ for (int Index = 0; Index < Cnt; Index++)
+ {
+ Val |= ((Ctr >> Index) & 1) << Pos[Index];
+ }
+
+ InsertTop(XMask, Val, Inst);
+ }
+ }
+
+ private static void InsertTop(int XMask, int Value, AInst Inst)
+ {
+ TreeNode Next = Root;
+
+ Root = new TreeNode(~XMask, Value, Inst);
+
+ Root.Next = Next;
+ }
+
+ public static AInst GetInst(int OpCode)
+ {
+ TreeNode Node = Root;
+
+ do
+ {
+ if ((OpCode & Node.Mask) == Node.Value)
+ {
+ return Node.Inst;
+ }
+ }
+ while ((Node = Node.Next) != null);
+
+ return AInst.Undefined;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/AThread.cs b/Ryujinx/Cpu/AThread.cs
new file mode 100644
index 0000000000..2fafcdd53c
--- /dev/null
+++ b/Ryujinx/Cpu/AThread.cs
@@ -0,0 +1,74 @@
+using ChocolArm64.Memory;
+using ChocolArm64.State;
+using System;
+using System.Threading;
+
+namespace ChocolArm64
+{
+ class AThread
+ {
+ public ARegisters Registers { get; private set; }
+ public AMemory Memory { get; private set; }
+
+ private ATranslator Translator;
+ private Thread Work;
+
+ public event EventHandler WorkFinished;
+
+ public int ThreadId => Registers.ThreadId;
+
+ public bool IsAlive => Work.IsAlive;
+
+ public long EntryPoint { get; private set; }
+ public int Priority { get; private set; }
+
+ public AThread(AMemory Memory, long EntryPoint = 0, int Priority = 0)
+ {
+ this.Memory = Memory;
+ this.EntryPoint = EntryPoint;
+ this.Priority = Priority;
+
+ Registers = new ARegisters();
+ Translator = new ATranslator(this);
+ }
+
+ public void StopExecution() => Translator.StopExecution();
+
+ public void Execute() => Execute(EntryPoint);
+
+ public void Execute(long EntryPoint)
+ {
+ Work = new Thread(delegate()
+ {
+ Translator.ExecuteSubroutine(EntryPoint);
+
+ Memory.RemoveMonitor(ThreadId);
+
+ WorkFinished?.Invoke(this, EventArgs.Empty);
+ });
+
+ if (Priority < 12)
+ {
+ Work.Priority = ThreadPriority.Highest;
+ }
+ else if (Priority < 24)
+ {
+ Work.Priority = ThreadPriority.AboveNormal;
+ }
+ else if (Priority < 36)
+ {
+ Work.Priority = ThreadPriority.Normal;
+ }
+ else if (Priority < 48)
+ {
+ Work.Priority = ThreadPriority.BelowNormal;
+ }
+ else
+ {
+ Work.Priority = ThreadPriority.Lowest;
+ }
+
+ Work.Start();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/ATranslatedSub.cs b/Ryujinx/Cpu/ATranslatedSub.cs
new file mode 100644
index 0000000000..1a821f479f
--- /dev/null
+++ b/Ryujinx/Cpu/ATranslatedSub.cs
@@ -0,0 +1,104 @@
+using ChocolArm64.Memory;
+using ChocolArm64.State;
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace ChocolArm64
+{
+ class ATranslatedSub
+ {
+ private delegate long AA64Subroutine(ARegisters Register, AMemory Memory);
+
+ private AA64Subroutine ExecDelegate;
+
+ private bool HasDelegate;
+
+ public static Type[] FixedArgTypes { get; private set; }
+
+ public static int RegistersArgIdx { get; private set; }
+ public static int MemoryArgIdx { get; private set; }
+
+ public DynamicMethod Method { get; private set; }
+
+ public HashSet SubCalls { get; private set; }
+
+ public List Params { get; private set; }
+
+ public bool NeedsReJit { get; private set; }
+
+ public ATranslatedSub()
+ {
+ SubCalls = new HashSet();
+ }
+
+ public ATranslatedSub(DynamicMethod Method, List Params) : this()
+ {
+ if (Params == null)
+ {
+ throw new ArgumentNullException(nameof(Params));
+ }
+
+ this.Method = Method;
+ this.Params = Params;
+ }
+
+ static ATranslatedSub()
+ {
+ MethodInfo MthdInfo = typeof(AA64Subroutine).GetMethod("Invoke");
+
+ ParameterInfo[] Params = MthdInfo.GetParameters();
+
+ FixedArgTypes = new Type[Params.Length];
+
+ for (int Index = 0; Index < Params.Length; Index++)
+ {
+ Type ParamType = Params[Index].ParameterType;
+
+ FixedArgTypes[Index] = ParamType;
+
+ if (ParamType == typeof(ARegisters))
+ {
+ RegistersArgIdx = Index;
+ }
+ else if (ParamType == typeof(AMemory))
+ {
+ MemoryArgIdx = Index;
+ }
+ }
+ }
+
+ public long Execute(ARegisters Registers, AMemory Memory)
+ {
+ if (!HasDelegate)
+ {
+ string Name = $"{Method.Name}_Dispatch";
+
+ DynamicMethod Mthd = new DynamicMethod(Name, typeof(long), FixedArgTypes);
+
+ ILGenerator Generator = Mthd.GetILGenerator();
+
+ Generator.EmitLdargSeq(FixedArgTypes.Length);
+
+ foreach (ARegister Reg in Params)
+ {
+ Generator.EmitLdarg(RegistersArgIdx);
+
+ Generator.Emit(OpCodes.Ldfld, Reg.GetField());
+ }
+
+ Generator.Emit(OpCodes.Call, Method);
+ Generator.Emit(OpCodes.Ret);
+
+ ExecDelegate = (AA64Subroutine)Mthd.CreateDelegate(typeof(AA64Subroutine));
+
+ HasDelegate = true;
+ }
+
+ return ExecDelegate(Registers, Memory);
+ }
+
+ public void MarkForReJit() => NeedsReJit = true;
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/ATranslator.cs b/Ryujinx/Cpu/ATranslator.cs
new file mode 100644
index 0000000000..ba7f3df6fa
--- /dev/null
+++ b/Ryujinx/Cpu/ATranslator.cs
@@ -0,0 +1,106 @@
+using ChocolArm64.Decoder;
+using ChocolArm64.Instruction;
+using ChocolArm64.Translation;
+using System.Collections.Generic;
+using System.Reflection.Emit;
+
+namespace ChocolArm64
+{
+ class ATranslator
+ {
+ private Dictionary CachedSubs;
+
+ public AThread Thread { get; private set; }
+
+ private bool KeepRunning;
+
+ public ATranslator(AThread Parent)
+ {
+ this.Thread = Parent;
+
+ CachedSubs = new Dictionary();
+
+ KeepRunning = true;
+ }
+
+ public void StopExecution() => KeepRunning = false;
+
+ public void ExecuteSubroutine(long Position)
+ {
+ do
+ {
+ if (CachedSubs.TryGetValue(Position, out ATranslatedSub Sub) && !Sub.NeedsReJit)
+ {
+ Position = Sub.Execute(Thread.Registers, Thread.Memory);
+ }
+ else
+ {
+ Position = TranslateSubroutine(Position).Execute(Thread.Registers, Thread.Memory);
+ }
+ }
+ while (Position != 0 && KeepRunning);
+ }
+
+ public bool TryGetCachedSub(AOpCode OpCode, out ATranslatedSub Sub)
+ {
+ if (OpCode.Emitter != AInstEmit.Bl)
+ {
+ Sub = null;
+
+ return false;
+ }
+
+ return TryGetCachedSub(((AOpCodeBImmAl)OpCode).Imm, out Sub);
+ }
+
+ public bool TryGetCachedSub(long Position, out ATranslatedSub Sub)
+ {
+ return CachedSubs.TryGetValue(Position, out Sub);
+ }
+
+ public bool HasCachedSub(long Position)
+ {
+ return CachedSubs.ContainsKey(Position);
+ }
+
+ private ATranslatedSub TranslateSubroutine(long Position)
+ {
+ (ABlock[] Graph, ABlock Root) Cfg = ADecoder.DecodeSubroutine(this, Position);
+
+ AILEmitterCtx Context = new AILEmitterCtx(
+ this,
+ Cfg.Graph,
+ Cfg.Root);
+
+ if (Context.CurrBlock.Position != Position)
+ {
+ Context.Emit(OpCodes.Br, Context.GetLabel(Position));
+ }
+
+ do
+ {
+ Context.EmitOpCode();
+ }
+ while (Context.AdvanceOpCode());
+
+ //Mark all methods that calls this method for ReJiting,
+ //since we can now call it directly which is faster.
+ foreach (ATranslatedSub TS in CachedSubs.Values)
+ {
+ if (TS.SubCalls.Contains(Position))
+ {
+ TS.MarkForReJit();
+ }
+ }
+
+ ATranslatedSub Subroutine = Context.GetSubroutine();
+
+ if (!CachedSubs.TryAdd(Position, Subroutine))
+ {
+ CachedSubs[Position] = Subroutine;
+ }
+
+ return Subroutine;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/ABlock.cs b/Ryujinx/Cpu/Decoder/ABlock.cs
new file mode 100644
index 0000000000..32974c1abf
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/ABlock.cs
@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+
+namespace ChocolArm64.Decoder
+{
+ class ABlock
+ {
+ public long Position { get; set; }
+ public long EndPosition { get; set; }
+
+ public ABlock Next { get; set; }
+ public ABlock Branch { get; set; }
+
+ public List OpCodes { get; private set; }
+
+ public ABlock()
+ {
+ OpCodes = new List();
+ }
+
+ public ABlock(long Position) : this()
+ {
+ this.Position = Position;
+ }
+
+ public AOpCode GetLastOp()
+ {
+ if (OpCodes.Count > 0)
+ {
+ return OpCodes[OpCodes.Count - 1];
+ }
+
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/ACond.cs b/Ryujinx/Cpu/Decoder/ACond.cs
new file mode 100644
index 0000000000..f2da8bd29f
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/ACond.cs
@@ -0,0 +1,22 @@
+namespace ChocolArm64.Decoder
+{
+ enum ACond
+ {
+ Eq = 0,
+ Ne = 1,
+ Ge_Un = 2,
+ Lt_Un = 3,
+ Mi = 4,
+ Pl = 5,
+ Vs = 6,
+ Vc = 7,
+ Gt_Un = 8,
+ Le_Un = 9,
+ Ge = 10,
+ Lt = 11,
+ Gt = 12,
+ Le = 13,
+ Al = 14,
+ Nv = 15
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/ADataOp.cs b/Ryujinx/Cpu/Decoder/ADataOp.cs
new file mode 100644
index 0000000000..a5601a3abc
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/ADataOp.cs
@@ -0,0 +1,10 @@
+namespace ChocolArm64.Decoder
+{
+ enum ADataOp
+ {
+ Adr = 0,
+ Arithmetic = 1,
+ Logical = 2,
+ BitField = 3
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/ADecoder.cs b/Ryujinx/Cpu/Decoder/ADecoder.cs
new file mode 100644
index 0000000000..1d34003965
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/ADecoder.cs
@@ -0,0 +1,206 @@
+using ChocolArm64.Instruction;
+using ChocolArm64.Memory;
+using System;
+using System.Collections.Generic;
+using System.Reflection.Emit;
+
+namespace ChocolArm64.Decoder
+{
+ static class ADecoder
+ {
+ public static (ABlock[] Graph, ABlock Root) DecodeSubroutine(ATranslator Translator, long Start)
+ {
+ Dictionary Visited = new Dictionary();
+ Dictionary VisitedEnd = new Dictionary();
+
+ Queue Blocks = new Queue();
+
+ ABlock Enqueue(long Position)
+ {
+ if (!Visited.TryGetValue(Position, out ABlock Output))
+ {
+ Output = new ABlock(Position);
+
+ Blocks.Enqueue(Output);
+
+ Visited.Add(Position, Output);
+ }
+
+ return Output;
+ }
+
+ ABlock Root = Enqueue(Start);
+
+ while (Blocks.Count > 0)
+ {
+ ABlock Current = Blocks.Dequeue();
+
+ FillBlock(Translator.Thread.Memory, Current);
+
+ //Set child blocks. "Branch" is the block the branch instruction
+ //points to (when taken), "Next" is the block at the next address,
+ //executed when the branch is not taken. For Unconditional Branches
+ //(except BL/BLR that are sub calls) or end of executable, Next is null.
+ if (Current.OpCodes.Count > 0)
+ {
+ bool HasCachedSub = false;
+
+ AOpCode LastOp = Current.GetLastOp();
+
+ if (LastOp is AOpCodeBImm Op)
+ {
+ if (Op.Emitter == AInstEmit.Bl)
+ {
+ HasCachedSub = Translator.HasCachedSub(Op.Imm);
+ }
+ else
+ {
+ Current.Branch = Enqueue(Op.Imm);
+ }
+ }
+
+ if ((!(LastOp is AOpCodeBImmAl) &&
+ !(LastOp is AOpCodeBReg)) || HasCachedSub)
+ {
+ Current.Next = Enqueue(Current.EndPosition);
+ }
+ }
+
+ //If we have on the tree two blocks with the same end position,
+ //then we need to split the bigger block and have two small blocks,
+ //the end position of the bigger "Current" block should then be == to
+ //the position of the "Smaller" block.
+ while (VisitedEnd.TryGetValue(Current.EndPosition, out ABlock Smaller))
+ {
+ if (Current.Position > Smaller.Position)
+ {
+ ABlock Temp = Smaller;
+
+ Smaller = Current;
+ Current = Temp;
+ }
+
+ Current.EndPosition = Smaller.Position;
+ Current.Next = Smaller;
+ Current.Branch = null;
+
+ Current.OpCodes.RemoveRange(
+ Current.OpCodes.Count - Smaller.OpCodes.Count,
+ Smaller.OpCodes.Count);
+
+ VisitedEnd[Smaller.EndPosition] = Smaller;
+ }
+
+ VisitedEnd.Add(Current.EndPosition, Current);
+ }
+
+ //Make and sort Graph blocks array by position.
+ ABlock[] Graph = new ABlock[Visited.Count];
+
+ while (Visited.Count > 0)
+ {
+ ulong FirstPos = ulong.MaxValue;
+
+ foreach (ABlock Block in Visited.Values)
+ {
+ if (FirstPos > (ulong)Block.Position)
+ FirstPos = (ulong)Block.Position;
+ }
+
+ ABlock Current = Visited[(long)FirstPos];
+
+ do
+ {
+ Graph[Graph.Length - Visited.Count] = Current;
+
+ Visited.Remove(Current.Position);
+
+ Current = Current.Next;
+ }
+ while (Current != null);
+ }
+
+ return (Graph, Root);
+ }
+
+ private static void FillBlock(AMemory Memory, ABlock Block)
+ {
+ long Position = Block.Position;
+
+ AOpCode OpCode;
+
+ do
+ {
+ OpCode = DecodeOpCode(Memory, Position);
+
+ Block.OpCodes.Add(OpCode);
+
+ Position += 4;
+ }
+ while (!(IsBranch(OpCode) || IsException(OpCode)));
+
+ Block.EndPosition = Position;
+ }
+
+ private static bool IsBranch(AOpCode OpCode)
+ {
+ return OpCode is AOpCodeBImm ||
+ OpCode is AOpCodeBReg;
+ }
+
+ private static bool IsException(AOpCode OpCode)
+ {
+ return OpCode.Emitter == AInstEmit.Svc ||
+ OpCode.Emitter == AInstEmit.Und;
+ }
+
+ public static AOpCode DecodeOpCode(AMemory Memory, long Position)
+ {
+ int OpCode = Memory.ReadInt32(Position);
+
+ AInst Inst = AOpCodeTable.GetInst(OpCode);
+
+ AOpCode DecodedOpCode = new AOpCode(AInst.Undefined, Position);
+
+ if (Inst.Type != null)
+ {
+ DecodedOpCode = CreateOpCode(Inst.Type, Inst, Position, OpCode);
+ }
+
+ return DecodedOpCode;
+ }
+
+ private delegate object OpActivator(AInst Inst, long Position, int OpCode);
+
+ private static Dictionary Activators = new Dictionary();
+
+ private static AOpCode CreateOpCode(Type Type, AInst Inst, long Position, int OpCode)
+ {
+ if (Type == null)
+ {
+ throw new ArgumentNullException(nameof(Type));
+ }
+
+ if (!Activators.TryGetValue(Type, out OpActivator CreateInstance))
+ {
+ Type[] ArgTypes = new Type[] { typeof(AInst), typeof(long), typeof(int) };
+
+ DynamicMethod Mthd = new DynamicMethod($"{Type.Name}_Create", Type, ArgTypes);
+
+ ILGenerator Generator = Mthd.GetILGenerator();
+
+ Generator.Emit(OpCodes.Ldarg_0);
+ Generator.Emit(OpCodes.Ldarg_1);
+ Generator.Emit(OpCodes.Ldarg_2);
+ Generator.Emit(OpCodes.Newobj, Type.GetConstructor(ArgTypes));
+ Generator.Emit(OpCodes.Ret);
+
+ CreateInstance = (OpActivator)Mthd.CreateDelegate(typeof(OpActivator));
+
+ Activators.Add(Type, CreateInstance);
+ }
+
+ return (AOpCode)CreateInstance(Inst, Position, OpCode);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/ADecoderHelper.cs b/Ryujinx/Cpu/Decoder/ADecoderHelper.cs
new file mode 100644
index 0000000000..a2179f49e3
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/ADecoderHelper.cs
@@ -0,0 +1,107 @@
+using System;
+
+namespace ChocolArm64.Decoder
+{
+ static class ADecoderHelper
+ {
+ public struct BitMask
+ {
+ public long WMask;
+ public long TMask;
+ public int Pos;
+ public int Shift;
+ public bool IsUndefined;
+
+ public static BitMask Invalid => new BitMask { IsUndefined = true };
+ }
+
+ public static BitMask DecodeBitMask(int OpCode, bool Immediate)
+ {
+ int ImmS = (OpCode >> 10) & 0x3f;
+ int ImmR = (OpCode >> 16) & 0x3f;
+
+ int N = (OpCode >> 22) & 1;
+ int SF = (OpCode >> 31) & 1;
+
+ int Length = ABitUtils.HighestBitSet32((~ImmS & 0x3f) | (N << 6));
+
+ if (Length < 1 || (SF == 0 && N != 0))
+ {
+ return BitMask.Invalid;
+ }
+
+ int Size = 1 << Length;
+
+ int Levels = Size - 1;
+
+ int S = ImmS & Levels;
+ int R = ImmR & Levels;
+
+ if (Immediate && S == Levels)
+ {
+ return BitMask.Invalid;
+ }
+
+ long WMask = ABitUtils.FillWithOnes(S + 1);
+ long TMask = ABitUtils.FillWithOnes(((S - R) & Levels) + 1);
+
+ if (R > 0)
+ {
+ WMask = ABitUtils.RotateRight(WMask, R, Size);
+ WMask &= ABitUtils.FillWithOnes(Size);
+ }
+
+ return new BitMask()
+ {
+ WMask = ABitUtils.Replicate(WMask, Size),
+ TMask = ABitUtils.Replicate(TMask, Size),
+
+ Pos = ImmS,
+ Shift = ImmR
+ };
+ }
+
+ public static long DecodeImm8Float(long Imm, int Size)
+ {
+ int E = 0, F = 0;
+
+ switch (Size)
+ {
+ case 0: E = 8; F = 23; break;
+ case 1: E = 11; F = 52; break;
+
+ default: throw new ArgumentOutOfRangeException(nameof(Size));
+ }
+
+ long Value = (Imm & 0x3f) << F - 4;
+
+ long EBit = (Imm >> 6) & 1;
+ long SBit = (Imm >> 7) & 1;
+
+ if (EBit != 0)
+ {
+ Value |= (1L << E - 3) - 1 << F + 2;
+ }
+
+ Value |= (EBit ^ 1) << F + E - 1;
+ Value |= SBit << F + E;
+
+ return Value;
+ }
+
+ public static long DecodeImm26_2(int OpCode)
+ {
+ return ((long)OpCode << 38) >> 36;
+ }
+
+ public static long DecodeImmS19_2(int OpCode)
+ {
+ return (((long)OpCode << 40) >> 43) & ~3;
+ }
+
+ public static long DecodeImmS14_2(int OpCode)
+ {
+ return (((long)OpCode << 45) >> 48) & ~3;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AIntType.cs b/Ryujinx/Cpu/Decoder/AIntType.cs
new file mode 100644
index 0000000000..242fdada14
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AIntType.cs
@@ -0,0 +1,14 @@
+namespace ChocolArm64.Decoder
+{
+ enum AIntType
+ {
+ UInt8 = 0,
+ UInt16 = 1,
+ UInt32 = 2,
+ UInt64 = 3,
+ Int8 = 4,
+ Int16 = 5,
+ Int32 = 6,
+ Int64 = 7
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCode.cs b/Ryujinx/Cpu/Decoder/AOpCode.cs
new file mode 100644
index 0000000000..4e5a807007
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCode.cs
@@ -0,0 +1,36 @@
+using ChocolArm64.Instruction;
+using ChocolArm64.State;
+using System;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCode : IAOpCode
+ {
+ public long Position { get; private set; }
+
+ public AInstEmitter Emitter { get; protected set; }
+ public ARegisterSize RegisterSize { get; protected set; }
+
+ public AOpCode(AInst Inst, long Position)
+ {
+ this.Position = Position;
+
+ RegisterSize = ARegisterSize.Int64;
+
+ Emitter = Inst.Emitter;
+ }
+
+ public int GetBitsCount()
+ {
+ switch (RegisterSize)
+ {
+ case ARegisterSize.Int32: return 32;
+ case ARegisterSize.Int64: return 64;
+ case ARegisterSize.SIMD64: return 64;
+ case ARegisterSize.SIMD128: return 128;
+ }
+
+ throw new InvalidOperationException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeAdr.cs b/Ryujinx/Cpu/Decoder/AOpCodeAdr.cs
new file mode 100644
index 0000000000..49f756e721
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeAdr.cs
@@ -0,0 +1,18 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeAdr : AOpCode
+ {
+ public int Rd { get; private set; }
+ public long Imm { get; private set; }
+
+ public AOpCodeAdr(AInst Inst, long Position, int OpCode) : base(Inst, Position)
+ {
+ Rd = OpCode & 0x1f;
+
+ Imm = ADecoderHelper.DecodeImmS19_2(OpCode);
+ Imm |= ((long)OpCode >> 29) & 3;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeAlu.cs b/Ryujinx/Cpu/Decoder/AOpCodeAlu.cs
new file mode 100644
index 0000000000..981af800e5
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeAlu.cs
@@ -0,0 +1,24 @@
+using ChocolArm64.Instruction;
+using ChocolArm64.State;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeAlu : AOpCode, IAOpCodeAlu
+ {
+ public int Rd { get; protected set; }
+ public int Rn { get; private set; }
+
+ public ADataOp DataOp { get; private set; }
+
+ public AOpCodeAlu(AInst Inst, long Position, int OpCode) : base(Inst, Position)
+ {
+ Rd = (OpCode >> 0) & 0x1f;
+ Rn = (OpCode >> 5) & 0x1f;
+ DataOp = (ADataOp)((OpCode >> 24) & 0x3);
+
+ RegisterSize = (OpCode >> 31) != 0
+ ? ARegisterSize.Int64
+ : ARegisterSize.Int32;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeAluImm.cs b/Ryujinx/Cpu/Decoder/AOpCodeAluImm.cs
new file mode 100644
index 0000000000..6b0adbc25e
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeAluImm.cs
@@ -0,0 +1,41 @@
+using ChocolArm64.Instruction;
+using System;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeAluImm : AOpCodeAlu, IAOpCodeAluImm
+ {
+ public long Imm { get; private set; }
+
+ public AOpCodeAluImm(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode)
+ {
+ if (DataOp == ADataOp.Arithmetic)
+ {
+ Imm = (OpCode >> 10) & 0xfff;
+
+ int Shift = (OpCode >> 22) & 3;
+
+ //Assert Shift < 2
+
+ Imm <<= Shift * 12;
+ }
+ else if (DataOp == ADataOp.Logical)
+ {
+ var BM = ADecoderHelper.DecodeBitMask(OpCode, true);
+
+ if (BM.IsUndefined)
+ {
+ Emitter = AInstEmit.Und;
+
+ return;
+ }
+
+ Imm = BM.WMask;
+ }
+ else
+ {
+ throw new ArgumentException(nameof(OpCode));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeAluRs.cs b/Ryujinx/Cpu/Decoder/AOpCodeAluRs.cs
new file mode 100644
index 0000000000..8439df6f28
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeAluRs.cs
@@ -0,0 +1,21 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeAluRs : AOpCodeAlu, IAOpCodeAluRs
+ {
+ public int Shift { get; private set; }
+ public int Rm { get; private set; }
+
+ public AShiftType ShiftType { get; private set; }
+
+ public AOpCodeAluRs(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode)
+ {
+ Shift = (OpCode >> 10) & 0x3f;
+ Rm = (OpCode >> 16) & 0x1f;
+ ShiftType = (AShiftType)((OpCode >> 22) & 0x3);
+
+ //Assert ShiftType != 3
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeAluRx.cs b/Ryujinx/Cpu/Decoder/AOpCodeAluRx.cs
new file mode 100644
index 0000000000..7dd72a6842
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeAluRx.cs
@@ -0,0 +1,19 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeAluRx : AOpCodeAlu, IAOpCodeAluRx
+ {
+ public int Shift { get; private set; }
+ public int Rm { get; private set; }
+
+ public AIntType IntType { get; private set; }
+
+ public AOpCodeAluRx(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode)
+ {
+ Shift = (OpCode >> 10) & 0x7;
+ IntType = (AIntType)((OpCode >> 13) & 0x7);
+ Rm = (OpCode >> 16) & 0x1f;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeBImm.cs b/Ryujinx/Cpu/Decoder/AOpCodeBImm.cs
new file mode 100644
index 0000000000..2a56d4af49
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeBImm.cs
@@ -0,0 +1,11 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeBImm : AOpCode
+ {
+ public long Imm { get; protected set; }
+
+ public AOpCodeBImm(AInst Inst, long Position) : base(Inst, Position) { }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeBImmAl.cs b/Ryujinx/Cpu/Decoder/AOpCodeBImmAl.cs
new file mode 100644
index 0000000000..1b6a98e7c8
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeBImmAl.cs
@@ -0,0 +1,12 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeBImmAl : AOpCodeBImm
+ {
+ public AOpCodeBImmAl(AInst Inst, long Position, int OpCode) : base(Inst, Position)
+ {
+ Imm = Position + ADecoderHelper.DecodeImm26_2(OpCode);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeBImmCmp.cs b/Ryujinx/Cpu/Decoder/AOpCodeBImmCmp.cs
new file mode 100644
index 0000000000..e0ce57e386
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeBImmCmp.cs
@@ -0,0 +1,16 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeBImmCmp : AOpCodeBImm
+ {
+ public int Rt { get; private set; }
+
+ public AOpCodeBImmCmp(AInst Inst, long Position, int OpCode) : base(Inst, Position)
+ {
+ Rt = OpCode & 0x1f;
+
+ Imm = Position + ADecoderHelper.DecodeImmS19_2(OpCode);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeBImmCond.cs b/Ryujinx/Cpu/Decoder/AOpCodeBImmCond.cs
new file mode 100644
index 0000000000..e4ae845fe0
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeBImmCond.cs
@@ -0,0 +1,25 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeBImmCond : AOpCodeBImm, IAOpCodeCond
+ {
+ public ACond Cond { get; private set; }
+
+ public AOpCodeBImmCond(AInst Inst, long Position, int OpCode) : base(Inst, Position)
+ {
+ int O0 = (OpCode >> 4) & 1;
+
+ if (O0 != 0)
+ {
+ Emitter = AInstEmit.Und;
+
+ return;
+ }
+
+ Cond = (ACond)(OpCode & 0xf);
+
+ Imm = Position + ADecoderHelper.DecodeImmS19_2(OpCode);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeBImmTest.cs b/Ryujinx/Cpu/Decoder/AOpCodeBImmTest.cs
new file mode 100644
index 0000000000..6b8b966db8
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeBImmTest.cs
@@ -0,0 +1,20 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeBImmTest : AOpCodeBImm
+ {
+ public int Rt { get; private set; }
+ public int Pos { get; private set; }
+
+ public AOpCodeBImmTest(AInst Inst, long Position, int OpCode) : base(Inst, Position)
+ {
+ Rt = OpCode & 0x1f;
+
+ Imm = Position + ADecoderHelper.DecodeImmS14_2(OpCode);
+
+ Pos = (OpCode >> 19) & 0x1f;
+ Pos |= (OpCode >> 26) & 0x20;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeBReg.cs b/Ryujinx/Cpu/Decoder/AOpCodeBReg.cs
new file mode 100644
index 0000000000..a71fc338ae
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeBReg.cs
@@ -0,0 +1,24 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeBReg : AOpCode
+ {
+ public int Rn { get; private set; }
+
+ public AOpCodeBReg(AInst Inst, long Position, int OpCode) : base(Inst, Position)
+ {
+ int Op4 = (OpCode >> 0) & 0x1f;
+ int Op2 = (OpCode >> 16) & 0x1f;
+
+ if (Op2 != 0b11111 || Op4 != 0b00000)
+ {
+ Emitter = AInstEmit.Und;
+
+ return;
+ }
+
+ Rn = (OpCode >> 5) & 0x1f;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeBfm.cs b/Ryujinx/Cpu/Decoder/AOpCodeBfm.cs
new file mode 100644
index 0000000000..6498d8ec69
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeBfm.cs
@@ -0,0 +1,29 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeBfm : AOpCodeAlu
+ {
+ public long WMask { get; private set; }
+ public long TMask { get; private set; }
+ public int Pos { get; private set; }
+ public int Shift { get; private set; }
+
+ public AOpCodeBfm(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode)
+ {
+ var BM = ADecoderHelper.DecodeBitMask(OpCode, false);
+
+ if (BM.IsUndefined)
+ {
+ Emitter = AInstEmit.Und;
+
+ return;
+ }
+
+ WMask = BM.WMask;
+ TMask = BM.TMask;
+ Pos = BM.Pos;
+ Shift = BM.Shift;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeCcmp.cs b/Ryujinx/Cpu/Decoder/AOpCodeCcmp.cs
new file mode 100644
index 0000000000..ab7aa75456
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeCcmp.cs
@@ -0,0 +1,31 @@
+using ChocolArm64.Instruction;
+using ChocolArm64.State;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeCcmp : AOpCodeAlu, IAOpCodeCond
+ {
+ public int NZCV { get; private set; }
+ protected int RmImm;
+
+ public ACond Cond { get; private set; }
+
+ public AOpCodeCcmp(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode)
+ {
+ int O3 = (OpCode >> 4) & 1;
+
+ if (O3 != 0)
+ {
+ Emitter = AInstEmit.Und;
+
+ return;
+ }
+
+ NZCV = (OpCode >> 0) & 0xf;
+ Cond = (ACond)((OpCode >> 12) & 0xf);
+ RmImm = (OpCode >> 16) & 0x1f;
+
+ Rd = ARegisters.ZRIndex;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeCcmpImm.cs b/Ryujinx/Cpu/Decoder/AOpCodeCcmpImm.cs
new file mode 100644
index 0000000000..803eefc249
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeCcmpImm.cs
@@ -0,0 +1,11 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeCcmpImm : AOpCodeCcmp, IAOpCodeAluImm
+ {
+ public long Imm => RmImm;
+
+ public AOpCodeCcmpImm(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) { }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeCcmpReg.cs b/Ryujinx/Cpu/Decoder/AOpCodeCcmpReg.cs
new file mode 100644
index 0000000000..c364ae68b4
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeCcmpReg.cs
@@ -0,0 +1,15 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeCcmpReg : AOpCodeCcmp, IAOpCodeAluRs
+ {
+ public int Rm => RmImm;
+
+ public int Shift => 0;
+
+ public AShiftType ShiftType => AShiftType.Lsl;
+
+ public AOpCodeCcmpReg(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode) { }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeCsel.cs b/Ryujinx/Cpu/Decoder/AOpCodeCsel.cs
new file mode 100644
index 0000000000..cdef3e745c
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeCsel.cs
@@ -0,0 +1,17 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeCsel : AOpCodeAlu, IAOpCodeCond
+ {
+ public int Rm { get; private set; }
+
+ public ACond Cond { get; private set; }
+
+ public AOpCodeCsel(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode)
+ {
+ Rm = (OpCode >> 16) & 0x1f;
+ Cond = (ACond)((OpCode >> 12) & 0xf);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeException.cs b/Ryujinx/Cpu/Decoder/AOpCodeException.cs
new file mode 100644
index 0000000000..6d4a03861b
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeException.cs
@@ -0,0 +1,14 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeException : AOpCode
+ {
+ public int Id { get; private set; }
+
+ public AOpCodeException(AInst Inst, long Position, int OpCode) : base(Inst, Position)
+ {
+ Id = (OpCode >> 5) & 0xfff;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeMem.cs b/Ryujinx/Cpu/Decoder/AOpCodeMem.cs
new file mode 100644
index 0000000000..1950b28677
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeMem.cs
@@ -0,0 +1,19 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeMem : AOpCode
+ {
+ public int Rt { get; protected set; }
+ public int Rn { get; protected set; }
+ public int Size { get; protected set; }
+ public bool Extend64 { get; protected set; }
+
+ public AOpCodeMem(AInst Inst, long Position, int OpCode) : base(Inst, Position)
+ {
+ Rt = (OpCode >> 0) & 0x1f;
+ Rn = (OpCode >> 5) & 0x1f;
+ Size = (OpCode >> 30) & 0x3;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeMemEx.cs b/Ryujinx/Cpu/Decoder/AOpCodeMemEx.cs
new file mode 100644
index 0000000000..3a28cfd73a
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeMemEx.cs
@@ -0,0 +1,16 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeMemEx : AOpCodeMem
+ {
+ public int Rt2 { get; private set; }
+ public int Rs { get; private set; }
+
+ public AOpCodeMemEx(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode)
+ {
+ Rt2 = (OpCode >> 10) & 0x1f;
+ Rs = (OpCode >> 16) & 0x1f;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeMemImm.cs b/Ryujinx/Cpu/Decoder/AOpCodeMemImm.cs
new file mode 100644
index 0000000000..14edc51487
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeMemImm.cs
@@ -0,0 +1,53 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeMemImm : AOpCodeMem
+ {
+ public long Imm { get; protected set; }
+ public bool WBack { get; protected set; }
+ public bool PostIdx { get; protected set; }
+ protected bool Unscaled { get; private set; }
+
+ private enum MemOp
+ {
+ Unscaled = 0,
+ PostIndexed = 1,
+ Unprivileged = 2,
+ PreIndexed = 3,
+ Unsigned
+ }
+
+ public AOpCodeMemImm(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode)
+ {
+ Extend64 = ((OpCode >> 22) & 3) == 2;
+ WBack = ((OpCode >> 24) & 1) == 0;
+
+ //The type is not valid for the Unsigned Immediate 12-bits encoding,
+ //because the bits 11:10 are used for the larger Immediate offset.
+ MemOp Type = WBack ? (MemOp)((OpCode >> 10) & 3) : MemOp.Unsigned;
+
+ PostIdx = Type == MemOp.PostIndexed;
+ Unscaled = Type == MemOp.Unscaled ||
+ Type == MemOp.Unprivileged;
+
+ //Unscaled and Unprivileged doesn't write back,
+ //but they do use the 9-bits Signed Immediate.
+ if (Unscaled)
+ {
+ WBack = false;
+ }
+
+ if (WBack || Unscaled)
+ {
+ //9-bits Signed Immediate.
+ Imm = (OpCode << 43) >> 55;
+ }
+ else
+ {
+ //12-bits Unsigned Immediate.
+ Imm = ((OpCode >> 10) & 0xfff) << Size;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeMemLit.cs b/Ryujinx/Cpu/Decoder/AOpCodeMemLit.cs
new file mode 100644
index 0000000000..b942943531
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeMemLit.cs
@@ -0,0 +1,28 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeMemLit : AOpCode, IAOpCodeLit
+ {
+ public int Rt { get; private set; }
+ public long Imm { get; private set; }
+ public int Size { get; private set; }
+ public bool Signed { get; private set; }
+ public bool Prefetch { get; private set; }
+
+ public AOpCodeMemLit(AInst Inst, long Position, int OpCode) : base(Inst, Position)
+ {
+ Rt = OpCode & 0x1f;
+
+ Imm = Position + ADecoderHelper.DecodeImmS19_2(OpCode);
+
+ switch ((OpCode >> 30) & 3)
+ {
+ case 0: Size = 2; Signed = false; Prefetch = false; break;
+ case 1: Size = 3; Signed = false; Prefetch = false; break;
+ case 2: Size = 2; Signed = true; Prefetch = false; break;
+ case 3: Size = 0; Signed = false; Prefetch = true; break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeMemPair.cs b/Ryujinx/Cpu/Decoder/AOpCodeMemPair.cs
new file mode 100644
index 0000000000..ec866c84e5
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeMemPair.cs
@@ -0,0 +1,25 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeMemPair : AOpCodeMemImm
+ {
+ public int Rt2 { get; private set; }
+
+ public AOpCodeMemPair(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode)
+ {
+ Rt2 = (OpCode >> 10) & 0x1f;
+ WBack = ((OpCode >> 23) & 0x1) != 0;
+ PostIdx = ((OpCode >> 23) & 0x3) == 1;
+ Extend64 = ((OpCode >> 30) & 0x3) == 1;
+ Size = ((OpCode >> 31) & 0x1) | 2;
+
+ DecodeImm(OpCode);
+ }
+
+ protected void DecodeImm(int OpCode)
+ {
+ Imm = ((long)(OpCode >> 15) << 57) >> (57 - Size);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeMemReg.cs b/Ryujinx/Cpu/Decoder/AOpCodeMemReg.cs
new file mode 100644
index 0000000000..989271282f
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeMemReg.cs
@@ -0,0 +1,20 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeMemReg : AOpCodeMem
+ {
+ public bool Shift { get; private set; }
+ public int Rm { get; private set; }
+
+ public AIntType IntType { get; private set; }
+
+ public AOpCodeMemReg(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode)
+ {
+ Shift = ((OpCode >> 12) & 0x1) != 0;
+ IntType = (AIntType)((OpCode >> 13) & 0x7);
+ Rm = (OpCode >> 16) & 0x1f;
+ Extend64 = ((OpCode >> 22) & 0x3) == 2;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeMov.cs b/Ryujinx/Cpu/Decoder/AOpCodeMov.cs
new file mode 100644
index 0000000000..3d1431fb1d
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeMov.cs
@@ -0,0 +1,36 @@
+using ChocolArm64.Instruction;
+using ChocolArm64.State;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeMov : AOpCode
+ {
+ public int Rd { get; private set; }
+ public long Imm { get; private set; }
+ public int Pos { get; private set; }
+
+ public AOpCodeMov(AInst Inst, long Position, int OpCode) : base(Inst, Position)
+ {
+ int P1 = (OpCode >> 22) & 1;
+ int SF = (OpCode >> 31) & 1;
+
+ if (SF == 0 && P1 != 0)
+ {
+ Emitter = AInstEmit.Und;
+
+ return;
+ }
+
+ Rd = (OpCode >> 0) & 0x1f;
+ Imm = (OpCode >> 5) & 0xffff;
+ Pos = (OpCode >> 21) & 0x3;
+
+ Pos <<= 4;
+ Imm <<= Pos;
+
+ RegisterSize = (OpCode >> 31) != 0
+ ? ARegisterSize.Int64
+ : ARegisterSize.Int32;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeMul.cs b/Ryujinx/Cpu/Decoder/AOpCodeMul.cs
new file mode 100644
index 0000000000..ca2b0cdb38
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeMul.cs
@@ -0,0 +1,16 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeMul : AOpCodeAlu
+ {
+ public int Rm { get; private set; }
+ public int Ra { get; private set; }
+
+ public AOpCodeMul(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode)
+ {
+ Ra = (OpCode >> 10) & 0x1f;
+ Rm = (OpCode >> 16) & 0x1f;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimd.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimd.cs
new file mode 100644
index 0000000000..7961998451
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeSimd.cs
@@ -0,0 +1,27 @@
+using ChocolArm64.Instruction;
+using ChocolArm64.State;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeSimd : AOpCode, IAOpCodeSimd
+ {
+ public int Rd { get; private set; }
+ public int Rn { get; private set; }
+ public int Opc { get; private set; }
+ public int Size { get; protected set; }
+
+ public int SizeF => Size & 1;
+
+ public AOpCodeSimd(AInst Inst, long Position, int OpCode) : base(Inst, Position)
+ {
+ Rd = (OpCode >> 0) & 0x1f;
+ Rn = (OpCode >> 5) & 0x1f;
+ Opc = (OpCode >> 15) & 0x3;
+ Size = (OpCode >> 22) & 0x3;
+
+ RegisterSize = ((OpCode >> 30) & 1) != 0
+ ? ARegisterSize.SIMD128
+ : ARegisterSize.SIMD64;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdCvt.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdCvt.cs
new file mode 100644
index 0000000000..41f4d3b143
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdCvt.cs
@@ -0,0 +1,31 @@
+using ChocolArm64.Instruction;
+using ChocolArm64.State;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeSimdCvt : AOpCodeSimd
+ {
+ public int FBits { get; private set; }
+
+ public AOpCodeSimdCvt(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode)
+ {
+ //TODO:
+ //Und of Fixed Point variants.
+ int Scale = (OpCode >> 10) & 0x3f;
+ int SF = (OpCode >> 31) & 0x1;
+
+ /*if (Type != SF && !(Type == 2 && SF == 1))
+ {
+ Emitter = AInstEmit.Und;
+
+ return;
+ }*/
+
+ FBits = 64 - Scale;
+
+ RegisterSize = SF != 0
+ ? ARegisterSize.Int64
+ : ARegisterSize.Int32;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdFcond.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdFcond.cs
new file mode 100644
index 0000000000..e38e742473
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdFcond.cs
@@ -0,0 +1,17 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeSimdFcond : AOpCodeSimdReg, IAOpCodeCond
+ {
+ public int NZCV { get; private set; }
+
+ public ACond Cond { get; private set; }
+
+ public AOpCodeSimdFcond(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode)
+ {
+ NZCV = (OpCode >> 0) & 0xf;
+ Cond = (ACond)((OpCode >> 12) & 0xf);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdFmov.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdFmov.cs
new file mode 100644
index 0000000000..1047beffca
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdFmov.cs
@@ -0,0 +1,33 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeSimdFmov : AOpCode, IAOpCodeSimd
+ {
+ public int Rd { get; private set; }
+ public long Imm { get; private set; }
+ public int Size { get; private set; }
+
+ public AOpCodeSimdFmov(AInst Inst, long Position, int OpCode) : base(Inst, Position)
+ {
+ int Imm5 = (OpCode >> 5) & 0x1f;
+ int Type = (OpCode >> 22) & 0x3;
+
+ if (Imm5 != 0b00000 || Type > 1)
+ {
+ Emitter = AInstEmit.Und;
+
+ return;
+ }
+
+ Size = Type;
+
+ long Imm;
+
+ Rd = (OpCode >> 0) & 0x1f;
+ Imm = (OpCode >> 13) & 0xff;
+
+ this.Imm = ADecoderHelper.DecodeImm8Float(Imm, Type);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdImm.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdImm.cs
new file mode 100644
index 0000000000..3a08ce63ad
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdImm.cs
@@ -0,0 +1,94 @@
+using ChocolArm64.Instruction;
+using ChocolArm64.State;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeSimdImm : AOpCode, IAOpCodeSimd
+ {
+ public int Rd { get; private set; }
+ public long Imm { get; private set; }
+ public int Size { get; private set; }
+
+ public AOpCodeSimdImm(AInst Inst, long Position, int OpCode) : base(Inst, Position)
+ {
+ Rd = OpCode & 0x1f;
+
+ int CMode = (OpCode >> 12) & 0xf;
+ int Op = (OpCode >> 29) & 0x1;
+
+ int ModeLow = CMode & 1;
+ int ModeHigh = CMode >> 1;
+
+ long Imm;
+
+ Imm = ((uint)OpCode >> 5) & 0x1f;
+ Imm |= ((uint)OpCode >> 11) & 0xe0;
+
+ if (ModeHigh == 0b111)
+ {
+ Size = ModeLow != 0 ? Op : 3;
+
+ switch (Op | (ModeLow << 1))
+ {
+ case 0:
+ //64-bits Immediate.
+ //Transform abcd efgh into abcd efgh abcd efgh ...
+ Imm = (long)((ulong)Imm * 0x0101010101010101);
+ break;
+
+ case 1:
+ //64-bits Immediate.
+ //Transform abcd efgh into aaaa aaaa bbbb bbbb ...
+ Imm = (Imm & 0xf0) >> 4 | (Imm & 0x0f) << 4;
+ Imm = (Imm & 0xcc) >> 2 | (Imm & 0x33) << 2;
+ Imm = (Imm & 0xaa) >> 1 | (Imm & 0x55) << 1;
+
+ Imm = (long)((ulong)Imm * 0x8040201008040201);
+ Imm = (long)((ulong)Imm & 0x8080808080808080);
+
+ Imm |= Imm >> 4;
+ Imm |= Imm >> 2;
+ Imm |= Imm >> 1;
+ break;
+
+ case 2:
+ case 3:
+ //Floating point Immediate.
+ Imm = ADecoderHelper.DecodeImm8Float(Imm, Size);
+ break;
+ }
+ }
+ else if ((ModeHigh & 0b110) == 0b100)
+ {
+ //16-bits shifted Immediate.
+ Size = 1; Imm <<= (ModeHigh & 1) << 3;
+ }
+ else if ((ModeHigh & 0b100) == 0b000)
+ {
+ //32-bits shifted Immediate.
+ Size = 2; Imm <<= ModeHigh << 3;
+ }
+ else if ((ModeHigh & 0b111) == 0b110)
+ {
+ //32-bits shifted Immediate (fill with ones).
+ Size = 2; Imm = ShlOnes(Imm, 8 << ModeLow);
+ }
+ else
+ {
+ //8 bits without shift.
+ Size = 0;
+ }
+
+ this.Imm = Imm;
+
+ RegisterSize = ((OpCode >> 30) & 1) != 0
+ ? ARegisterSize.SIMD128
+ : ARegisterSize.SIMD64;
+ }
+
+ private static long ShlOnes(long Value, int Shift)
+ {
+ return Value << Shift | (long)(ulong.MaxValue >> (64 - Shift));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdIns.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdIns.cs
new file mode 100644
index 0000000000..0b60bbe837
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdIns.cs
@@ -0,0 +1,36 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeSimdIns : AOpCodeSimd
+ {
+ public int SrcIndex { get; private set; }
+ public int DstIndex { get; private set; }
+
+ public AOpCodeSimdIns(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode)
+ {
+ int Imm4 = (OpCode >> 11) & 0xf;
+ int Imm5 = (OpCode >> 16) & 0x1f;
+
+ if (Imm5 == 0b10000)
+ {
+ Emitter = AInstEmit.Und;
+
+ return;
+ }
+
+ Size = Imm5 & -Imm5;
+
+ switch (Size)
+ {
+ case 1: Size = 0; break;
+ case 2: Size = 1; break;
+ case 4: Size = 2; break;
+ case 8: Size = 3; break;
+ }
+
+ SrcIndex = Imm4 >> Size;
+ DstIndex = Imm5 >> (Size + 1);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdMemImm.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdMemImm.cs
new file mode 100644
index 0000000000..1ef19a5d67
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdMemImm.cs
@@ -0,0 +1,19 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeSimdMemImm : AOpCodeMemImm, IAOpCodeSimd
+ {
+ public AOpCodeSimdMemImm(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode)
+ {
+ Size |= (OpCode >> 21) & 4;
+
+ if (!WBack && !Unscaled && Size >= 4)
+ {
+ Imm <<= 4;
+ }
+
+ Extend64 = false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdMemLit.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdMemLit.cs
new file mode 100644
index 0000000000..cf6915f566
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdMemLit.cs
@@ -0,0 +1,31 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeSimdMemLit : AOpCode, IAOpCodeSimd, IAOpCodeLit
+ {
+ public int Rt { get; private set; }
+ public long Imm { get; private set; }
+ public int Size { get; private set; }
+ public bool Signed => false;
+ public bool Prefetch => false;
+
+ public AOpCodeSimdMemLit(AInst Inst, long Position, int OpCode) : base(Inst, Position)
+ {
+ int Opc = (OpCode >> 30) & 3;
+
+ if (Opc == 3)
+ {
+ Emitter = AInstEmit.Und;
+
+ return;
+ }
+
+ Rt = OpCode & 0x1f;
+
+ Imm = Position + ADecoderHelper.DecodeImmS19_2(OpCode);
+
+ Size = Opc + 2;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdMemMult.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdMemMult.cs
new file mode 100644
index 0000000000..9731c7e745
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdMemMult.cs
@@ -0,0 +1,54 @@
+using ChocolArm64.Instruction;
+using ChocolArm64.State;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeSimdMemMult : AOpCode, IAOpCodeSimd
+ {
+ public int Rt { get; private set; }
+ public int Rn { get; private set; }
+ public int Size { get; private set; }
+ public int Rm { get; private set; }
+ public int Reps { get; private set; }
+ public int SElems { get; private set; }
+ public int Elems { get; private set; }
+ public bool WBack { get; private set; }
+
+ public AOpCodeSimdMemMult(AInst Inst, long Position, int OpCode) : base(Inst, Position)
+ {
+ switch ((OpCode >> 12) & 0xf)
+ {
+ case 0b0000: Reps = 1; SElems = 4; break;
+ case 0b0010: Reps = 4; SElems = 1; break;
+ case 0b0100: Reps = 1; SElems = 3; break;
+ case 0b0110: Reps = 3; SElems = 1; break;
+ case 0b0111: Reps = 1; SElems = 1; break;
+ case 0b1000: Reps = 1; SElems = 2; break;
+ case 0b1010: Reps = 2; SElems = 1; break;
+
+ default: Inst = AInst.Undefined; return;
+ }
+
+ Rt = (OpCode >> 0) & 0x1f;
+ Rn = (OpCode >> 5) & 0x1f;
+ Size = (OpCode >> 10) & 0x3;
+ Rm = (OpCode >> 16) & 0x1f;
+ WBack = ((OpCode >> 23) & 0x1) != 0;
+
+ bool Q = ((OpCode >> 30) & 1) != 0;
+
+ if (!Q && Size == 3 && SElems != 1)
+ {
+ Inst = AInst.Undefined;
+
+ return;
+ }
+
+ RegisterSize = Q
+ ? ARegisterSize.SIMD128
+ : ARegisterSize.SIMD64;
+
+ Elems = (GetBitsCount() >> 3) >> Size;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdMemPair.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdMemPair.cs
new file mode 100644
index 0000000000..db99e3d44d
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdMemPair.cs
@@ -0,0 +1,16 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeSimdMemPair : AOpCodeMemPair, IAOpCodeSimd
+ {
+ public AOpCodeSimdMemPair(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode)
+ {
+ Size = ((OpCode >> 30) & 3) + 2;
+
+ Extend64 = false;
+
+ DecodeImm(OpCode);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdMemReg.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdMemReg.cs
new file mode 100644
index 0000000000..aabf484611
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdMemReg.cs
@@ -0,0 +1,14 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeSimdMemReg : AOpCodeMemReg, IAOpCodeSimd
+ {
+ public AOpCodeSimdMemReg(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode)
+ {
+ Size |= (OpCode >> 21) & 4;
+
+ Extend64 = false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdReg.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdReg.cs
new file mode 100644
index 0000000000..d3a8b76aae
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdReg.cs
@@ -0,0 +1,16 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeSimdReg : AOpCodeSimd
+ {
+ public int Rm { get; private set; }
+ public bool Bit3 { get; private set; }
+
+ public AOpCodeSimdReg(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode)
+ {
+ Rm = (OpCode >> 16) & 0x1f;
+ Bit3 = ((OpCode >> 3) & 0x1) != 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdRegElem.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdRegElem.cs
new file mode 100644
index 0000000000..828fe7884d
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdRegElem.cs
@@ -0,0 +1,26 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeSimdRegElem : AOpCodeSimd
+ {
+ public int Rm { get; private set; }
+ public int Index { get; private set; }
+
+ public AOpCodeSimdRegElem(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode)
+ {
+ Rm = (OpCode >> 16) & 0x1f;
+ Size = (OpCode >> 22) & 0x1;
+
+ if (Size != 0)
+ {
+ Index = (OpCode >> 11) & 1;
+ }
+ else
+ {
+ Index = (OpCode >> 21) & 1 |
+ (OpCode >> 10) & 2;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdShImm.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdShImm.cs
new file mode 100644
index 0000000000..a677e579b5
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdShImm.cs
@@ -0,0 +1,26 @@
+using ChocolArm64.Instruction;
+using ChocolArm64.State;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeSimdShImm : AOpCode, IAOpCodeSimd
+ {
+ public int Rd { get; private set; }
+ public int Rn { get; private set; }
+ public int Imm { get; private set; }
+ public int Size { get; private set; }
+
+ public AOpCodeSimdShImm(AInst Inst, long Position, int OpCode) : base(Inst, Position)
+ {
+ Rd = (OpCode >> 0) & 0x1f;
+ Rn = (OpCode >> 5) & 0x1f;
+ Imm = (OpCode >> 16) & 0x7f;
+
+ Size = ABitUtils.HighestBitSet32(Imm >> 3);
+
+ RegisterSize = ((OpCode >> 30) & 1) != 0
+ ? ARegisterSize.SIMD128
+ : ARegisterSize.SIMD64;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSimdTbl.cs b/Ryujinx/Cpu/Decoder/AOpCodeSimdTbl.cs
new file mode 100644
index 0000000000..c8ae5bac74
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeSimdTbl.cs
@@ -0,0 +1,12 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeSimdTbl : AOpCodeSimdReg
+ {
+ public AOpCodeSimdTbl(AInst Inst, long Position, int OpCode) : base(Inst, Position, OpCode)
+ {
+ Size = ((OpCode >> 13) & 3) + 1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AOpCodeSystem.cs b/Ryujinx/Cpu/Decoder/AOpCodeSystem.cs
new file mode 100644
index 0000000000..95b291001d
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AOpCodeSystem.cs
@@ -0,0 +1,24 @@
+using ChocolArm64.Instruction;
+
+namespace ChocolArm64.Decoder
+{
+ class AOpCodeSystem : AOpCode
+ {
+ public int Rt { get; private set; }
+ public int Op2 { get; private set; }
+ public int CRm { get; private set; }
+ public int CRn { get; private set; }
+ public int Op1 { get; private set; }
+ public int Op0 { get; private set; }
+
+ public AOpCodeSystem(AInst Inst, long Position, int OpCode) : base(Inst, Position)
+ {
+ Rt = (OpCode >> 0) & 0x1f;
+ Op2 = (OpCode >> 5) & 0x7;
+ CRm = (OpCode >> 8) & 0xf;
+ CRn = (OpCode >> 12) & 0xf;
+ Op1 = (OpCode >> 16) & 0x7;
+ Op0 = ((OpCode >> 19) & 0x1) | 2;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/AShiftType.cs b/Ryujinx/Cpu/Decoder/AShiftType.cs
new file mode 100644
index 0000000000..34ceea2087
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/AShiftType.cs
@@ -0,0 +1,10 @@
+namespace ChocolArm64.Decoder
+{
+ enum AShiftType
+ {
+ Lsl,
+ Lsr,
+ Asr,
+ Ror
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/IAOpCode.cs b/Ryujinx/Cpu/Decoder/IAOpCode.cs
new file mode 100644
index 0000000000..44bf9cb2f1
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/IAOpCode.cs
@@ -0,0 +1,13 @@
+using ChocolArm64.Instruction;
+using ChocolArm64.State;
+
+namespace ChocolArm64.Decoder
+{
+ interface IAOpCode
+ {
+ long Position { get; }
+
+ AInstEmitter Emitter { get; }
+ ARegisterSize RegisterSize { get; }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/IAOpCodeAlu.cs b/Ryujinx/Cpu/Decoder/IAOpCodeAlu.cs
new file mode 100644
index 0000000000..22af4c82d6
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/IAOpCodeAlu.cs
@@ -0,0 +1,10 @@
+namespace ChocolArm64.Decoder
+{
+ interface IAOpCodeAlu : IAOpCode
+ {
+ int Rd { get; }
+ int Rn { get; }
+
+ ADataOp DataOp { get; }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/IAOpCodeAluImm.cs b/Ryujinx/Cpu/Decoder/IAOpCodeAluImm.cs
new file mode 100644
index 0000000000..04b5c5f7d1
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/IAOpCodeAluImm.cs
@@ -0,0 +1,7 @@
+namespace ChocolArm64.Decoder
+{
+ interface IAOpCodeAluImm : IAOpCodeAlu
+ {
+ long Imm { get; }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/IAOpCodeAluRs.cs b/Ryujinx/Cpu/Decoder/IAOpCodeAluRs.cs
new file mode 100644
index 0000000000..5ca9de4032
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/IAOpCodeAluRs.cs
@@ -0,0 +1,10 @@
+namespace ChocolArm64.Decoder
+{
+ interface IAOpCodeAluRs : IAOpCodeAlu
+ {
+ int Shift { get; }
+ int Rm { get; }
+
+ AShiftType ShiftType { get; }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/IAOpCodeAluRx.cs b/Ryujinx/Cpu/Decoder/IAOpCodeAluRx.cs
new file mode 100644
index 0000000000..b49d5325a9
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/IAOpCodeAluRx.cs
@@ -0,0 +1,10 @@
+namespace ChocolArm64.Decoder
+{
+ interface IAOpCodeAluRx : IAOpCodeAlu
+ {
+ int Shift { get; }
+ int Rm { get; }
+
+ AIntType IntType { get; }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/IAOpCodeCond.cs b/Ryujinx/Cpu/Decoder/IAOpCodeCond.cs
new file mode 100644
index 0000000000..1655abaac0
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/IAOpCodeCond.cs
@@ -0,0 +1,7 @@
+namespace ChocolArm64.Decoder
+{
+ interface IAOpCodeCond : IAOpCode
+ {
+ ACond Cond { get; }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/IAOpCodeLit.cs b/Ryujinx/Cpu/Decoder/IAOpCodeLit.cs
new file mode 100644
index 0000000000..0f5092d076
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/IAOpCodeLit.cs
@@ -0,0 +1,11 @@
+namespace ChocolArm64.Decoder
+{
+ interface IAOpCodeLit : IAOpCode
+ {
+ int Rt { get; }
+ long Imm { get; }
+ int Size { get; }
+ bool Signed { get; }
+ bool Prefetch { get; }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Decoder/IAOpCodeSimd.cs b/Ryujinx/Cpu/Decoder/IAOpCodeSimd.cs
new file mode 100644
index 0000000000..19032ad940
--- /dev/null
+++ b/Ryujinx/Cpu/Decoder/IAOpCodeSimd.cs
@@ -0,0 +1,7 @@
+namespace ChocolArm64.Decoder
+{
+ interface IAOpCodeSimd : IAOpCode
+ {
+ int Size { get; }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Exceptions/VmmAccessViolationException.cs b/Ryujinx/Cpu/Exceptions/VmmAccessViolationException.cs
new file mode 100644
index 0000000000..dd9d7a646b
--- /dev/null
+++ b/Ryujinx/Cpu/Exceptions/VmmAccessViolationException.cs
@@ -0,0 +1,14 @@
+using ChocolArm64.Memory;
+using System;
+
+namespace ChocolArm64.Exceptions
+{
+ public class VmmAccessViolationException : Exception
+ {
+ private const string ExMsg = "Value at address 0x{0:x16} could not be \"{1}\"!";
+
+ public VmmAccessViolationException() { }
+
+ public VmmAccessViolationException(long Position, AMemoryPerm Perm) : base(string.Format(ExMsg, Position, Perm)) { }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Exceptions/VmmOutOfMemoryException.cs b/Ryujinx/Cpu/Exceptions/VmmOutOfMemoryException.cs
new file mode 100644
index 0000000000..c11384dae8
--- /dev/null
+++ b/Ryujinx/Cpu/Exceptions/VmmOutOfMemoryException.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace ChocolArm64.Exceptions
+{
+ public class VmmOutOfMemoryException : Exception
+ {
+ private const string ExMsg = "Failed to allocate {0} bytes of memory!";
+
+ public VmmOutOfMemoryException() { }
+
+ public VmmOutOfMemoryException(long Size) : base(string.Format(ExMsg, Size)) { }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Exceptions/VmmPageFaultException.cs b/Ryujinx/Cpu/Exceptions/VmmPageFaultException.cs
new file mode 100644
index 0000000000..d55c2c1ca9
--- /dev/null
+++ b/Ryujinx/Cpu/Exceptions/VmmPageFaultException.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace ChocolArm64.Exceptions
+{
+ public class VmmPageFaultException : Exception
+ {
+ private const string ExMsg = "Tried to access unmapped address 0x{0:x16}!";
+
+ public VmmPageFaultException() { }
+
+ public VmmPageFaultException(long Position) : base(string.Format(ExMsg, Position)) { }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Instruction/AInst.cs b/Ryujinx/Cpu/Instruction/AInst.cs
new file mode 100644
index 0000000000..cab597d656
--- /dev/null
+++ b/Ryujinx/Cpu/Instruction/AInst.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace ChocolArm64.Instruction
+{
+ struct AInst
+ {
+ public AInstEmitter Emitter { get; private set; }
+ public Type Type { get; private set; }
+
+ public static AInst Undefined => new AInst(AInstEmit.Und, null);
+
+ public AInst(AInstEmitter Emitter, Type Type)
+ {
+ this.Emitter = Emitter;
+ this.Type = Type;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Instruction/AInstEmitAlu.cs b/Ryujinx/Cpu/Instruction/AInstEmitAlu.cs
new file mode 100644
index 0000000000..c09b186322
--- /dev/null
+++ b/Ryujinx/Cpu/Instruction/AInstEmitAlu.cs
@@ -0,0 +1,296 @@
+using ChocolArm64.Decoder;
+using ChocolArm64.State;
+using ChocolArm64.Translation;
+using System.Reflection.Emit;
+
+using static ChocolArm64.Instruction.AInstEmitAluHelper;
+
+namespace ChocolArm64.Instruction
+{
+ static partial class AInstEmit
+ {
+ public static void Add(AILEmitterCtx Context) => EmitDataOp(Context, OpCodes.Add);
+
+ public static void Adds(AILEmitterCtx Context)
+ {
+ Context.TryOptMarkCondWithoutCmp();
+
+ EmitDataLoadOpers(Context);
+
+ Context.Emit(OpCodes.Add);
+
+ Context.EmitZNFlagCheck();
+
+ EmitAddsCCheck(Context);
+ EmitAddsVCheck(Context);
+ EmitDataStoreS(Context);
+ }
+
+ public static void And(AILEmitterCtx Context) => EmitDataOp(Context, OpCodes.And);
+
+ public static void Ands(AILEmitterCtx Context)
+ {
+ EmitDataLoadOpers(Context);
+
+ Context.Emit(OpCodes.And);
+
+ Context.EmitZNFlagCheck();
+
+ EmitDataStoreS(Context);
+ }
+
+ public static void Asrv(AILEmitterCtx Context) => EmitDataOpShift(Context, OpCodes.Shr);
+
+ public static void Bic(AILEmitterCtx Context) => EmitBic(Context, false);
+ public static void Bics(AILEmitterCtx Context) => EmitBic(Context, true);
+
+ private static void EmitBic(AILEmitterCtx Context, bool SetFlags)
+ {
+ EmitDataLoadOpers(Context);
+
+ Context.Emit(OpCodes.Not);
+ Context.Emit(OpCodes.And);
+
+ if (SetFlags)
+ {
+ Context.EmitZNFlagCheck();
+ }
+
+ EmitDataStore(Context, SetFlags);
+ }
+
+ public static void Clz(AILEmitterCtx Context)
+ {
+ AOpCodeAlu Op = (AOpCodeAlu)Context.CurrOp;
+
+ Context.EmitLdintzr(Op.Rn);
+
+ if (Op.RegisterSize == ARegisterSize.Int32)
+ {
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.CountLeadingZeros32));
+ }
+ else
+ {
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.CountLeadingZeros64));
+ }
+
+ Context.EmitStintzr(Op.Rd);
+ }
+
+ public static void Eor(AILEmitterCtx Context) => EmitDataOp(Context, OpCodes.Xor);
+
+ public static void Extr(AILEmitterCtx Context)
+ {
+ //TODO: Ensure that the Shift is valid for the Is64Bits.
+ AOpCodeAluRs Op = (AOpCodeAluRs)Context.CurrOp;
+
+ Context.EmitLdintzr(Op.Rm);
+
+ if (Op.Shift > 0)
+ {
+ Context.EmitLdc_I4(Op.Shift);
+
+ Context.Emit(OpCodes.Shr_Un);
+
+ Context.EmitLdintzr(Op.Rn);
+ Context.EmitLdc_I4(Op.GetBitsCount() - Op.Shift);
+
+ Context.Emit(OpCodes.Shl);
+ Context.Emit(OpCodes.Or);
+ }
+
+ EmitDataStore(Context);
+ }
+
+ public static void Lslv(AILEmitterCtx Context) => EmitDataOpShift(Context, OpCodes.Shl);
+ public static void Lsrv(AILEmitterCtx Context) => EmitDataOpShift(Context, OpCodes.Shr_Un);
+
+ public static void Sub(AILEmitterCtx Context) => EmitDataOp(Context, OpCodes.Sub);
+
+ public static void Subs(AILEmitterCtx Context)
+ {
+ Context.TryOptMarkCondWithoutCmp();
+
+ EmitDataLoadOpers(Context);
+
+ Context.Emit(OpCodes.Sub);
+
+ Context.EmitZNFlagCheck();
+
+ EmitSubsCCheck(Context);
+ EmitSubsVCheck(Context);
+ EmitDataStoreS(Context);
+ }
+
+ public static void Orn(AILEmitterCtx Context)
+ {
+ EmitDataLoadOpers(Context);
+
+ Context.Emit(OpCodes.Not);
+ Context.Emit(OpCodes.Or);
+
+ EmitDataStore(Context);
+ }
+
+ public static void Orr(AILEmitterCtx Context) => EmitDataOp(Context, OpCodes.Or);
+
+ public static void Rbit(AILEmitterCtx Context) => EmitFallback32_64(Context,
+ nameof(ASoftFallback.ReverseBits32),
+ nameof(ASoftFallback.ReverseBits64));
+
+ public static void Rev16(AILEmitterCtx Context) => EmitFallback32_64(Context,
+ nameof(ASoftFallback.ReverseBytes16_32),
+ nameof(ASoftFallback.ReverseBytes16_64));
+
+ public static void Rev32(AILEmitterCtx Context) => EmitFallback32_64(Context,
+ nameof(ASoftFallback.ReverseBytes32_32),
+ nameof(ASoftFallback.ReverseBytes32_64));
+
+ public static void EmitFallback32_64(AILEmitterCtx Context, string Name32, string Name64)
+ {
+ AOpCodeAlu Op = (AOpCodeAlu)Context.CurrOp;
+
+ Context.EmitLdintzr(Op.Rn);
+
+ if (Op.RegisterSize == ARegisterSize.Int32)
+ {
+ ASoftFallback.EmitCall(Context, Name32);
+ }
+ else
+ {
+ ASoftFallback.EmitCall(Context, Name64);
+ }
+
+ Context.EmitStintzr(Op.Rd);
+ }
+
+ public static void Rev64(AILEmitterCtx Context)
+ {
+ AOpCodeAlu Op = (AOpCodeAlu)Context.CurrOp;
+
+ Context.EmitLdintzr(Op.Rn);
+
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.ReverseBytes64));
+
+ Context.EmitStintzr(Op.Rd);
+ }
+
+ private static void EmitRev(AILEmitterCtx Context, string Name)
+ {
+ AOpCodeAlu Op = (AOpCodeAlu)Context.CurrOp;
+
+ Context.EmitLdintzr(Op.Rn);
+
+ ASoftFallback.EmitCall(Context, Name);
+
+ Context.EmitStintzr(Op.Rd);
+ }
+
+ public static void Rorv(AILEmitterCtx Context)
+ {
+ EmitDataLoadRn(Context);
+ EmitDataLoadShift(Context);
+
+ Context.Emit(OpCodes.Shr_Un);
+
+ EmitDataLoadRn(Context);
+
+ Context.EmitLdc_I4(Context.CurrOp.GetBitsCount());
+
+ EmitDataLoadShift(Context);
+
+ Context.Emit(OpCodes.Sub);
+ Context.Emit(OpCodes.Shl);
+ Context.Emit(OpCodes.Or);
+
+ EmitDataStore(Context);
+ }
+
+ public static void Sdiv(AILEmitterCtx Context) => EmitDiv(Context, OpCodes.Div);
+ public static void Udiv(AILEmitterCtx Context) => EmitDiv(Context, OpCodes.Div_Un);
+
+ private static void EmitDiv(AILEmitterCtx Context, OpCode ILOp)
+ {
+ //If Rm == 0, Rd = 0 (division by zero).
+ Context.EmitLdc_I(0);
+
+ EmitDataLoadRm(Context);
+
+ Context.EmitLdc_I(0);
+
+ AILLabel BadDiv = new AILLabel();
+
+ Context.Emit(OpCodes.Beq_S, BadDiv);
+ Context.Emit(OpCodes.Pop);
+
+ if (ILOp == OpCodes.Div)
+ {
+ //If Rn == INT_MIN && Rm == -1, Rd = INT_MIN (overflow).
+ long IntMin = 1L << (Context.CurrOp.GetBitsCount() - 1);
+
+ Context.EmitLdc_I(IntMin);
+
+ EmitDataLoadRn(Context);
+
+ Context.EmitLdc_I(IntMin);
+
+ Context.Emit(OpCodes.Ceq);
+
+ EmitDataLoadRm(Context);
+
+ Context.EmitLdc_I(-1);
+
+ Context.Emit(OpCodes.Ceq);
+ Context.Emit(OpCodes.And);
+ Context.Emit(OpCodes.Brtrue_S, BadDiv);
+ Context.Emit(OpCodes.Pop);
+ }
+
+ EmitDataLoadRn(Context);
+ EmitDataLoadRm(Context);
+
+ Context.Emit(ILOp);
+
+ Context.MarkLabel(BadDiv);
+
+ EmitDataStore(Context);
+ }
+
+ private static void EmitDataOp(AILEmitterCtx Context, OpCode ILOp)
+ {
+ EmitDataLoadOpers(Context);
+
+ Context.Emit(ILOp);
+
+ EmitDataStore(Context);
+ }
+
+ private static void EmitDataOpShift(AILEmitterCtx Context, OpCode ILOp)
+ {
+ EmitDataLoadRn(Context);
+ EmitDataLoadShift(Context);
+
+ Context.Emit(ILOp);
+
+ EmitDataStore(Context);
+ }
+
+ private static void EmitDataLoadShift(AILEmitterCtx Context)
+ {
+ EmitDataLoadRm(Context);
+
+ Context.EmitLdc_I(Context.CurrOp.GetBitsCount() - 1);
+
+ Context.Emit(OpCodes.And);
+
+ //Note: Only 32-bits shift values are valid, so when the value is 64-bits
+ //we need to cast it to a 32-bits integer. This is fine because we
+ //AND the value and only keep the lower 5 or 6 bits anyway -- it
+ //could very well fit on a byte.
+ if (Context.CurrOp.RegisterSize != ARegisterSize.Int32)
+ {
+ Context.Emit(OpCodes.Conv_I4);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Instruction/AInstEmitAluHelper.cs b/Ryujinx/Cpu/Instruction/AInstEmitAluHelper.cs
new file mode 100644
index 0000000000..03355ebad7
--- /dev/null
+++ b/Ryujinx/Cpu/Instruction/AInstEmitAluHelper.cs
@@ -0,0 +1,159 @@
+using ChocolArm64.Decoder;
+using ChocolArm64.State;
+using ChocolArm64.Translation;
+using System.Reflection.Emit;
+
+namespace ChocolArm64.Instruction
+{
+ static class AInstEmitAluHelper
+ {
+ public static void EmitAddsCCheck(AILEmitterCtx Context)
+ {
+ //C = Rd < Rn
+ Context.Emit(OpCodes.Dup);
+
+ EmitDataLoadRn(Context);
+
+ Context.Emit(OpCodes.Clt_Un);
+
+ Context.EmitStflg((int)APState.CBit);
+ }
+
+ public static void EmitAddsVCheck(AILEmitterCtx Context)
+ {
+ //V = (Rd ^ Rn) & (Rd ^ Rm) & ~(Rn ^ Rm) < 0
+ Context.EmitSttmp();
+ Context.EmitLdtmp();
+ Context.EmitLdtmp();
+
+ EmitDataLoadRn(Context);
+
+ Context.Emit(OpCodes.Xor);
+
+ Context.EmitLdtmp();
+
+ EmitDataLoadOper2(Context);
+
+ Context.Emit(OpCodes.Xor);
+ Context.Emit(OpCodes.And);
+
+ EmitDataLoadOpers(Context);
+
+ Context.Emit(OpCodes.Xor);
+ Context.Emit(OpCodes.Not);
+ Context.Emit(OpCodes.And);
+
+ Context.EmitLdc_I(0);
+
+ Context.Emit(OpCodes.Clt);
+
+ Context.EmitStflg((int)APState.VBit);
+ }
+
+ public static void EmitSubsCCheck(AILEmitterCtx Context)
+ {
+ //C = Rn == Rm || Rn > Rm
+ EmitDataLoadOpers(Context);
+
+ Context.Emit(OpCodes.Ceq);
+
+ EmitDataLoadOpers(Context);
+
+ Context.Emit(OpCodes.Cgt_Un);
+ Context.Emit(OpCodes.Or);
+
+ Context.EmitStflg((int)APState.CBit);
+ }
+
+ public static void EmitSubsVCheck(AILEmitterCtx Context)
+ {
+ //V = (Rd ^ Rn) & (Rn ^ Rm) < 0
+ Context.Emit(OpCodes.Dup);
+
+ EmitDataLoadRn(Context);
+
+ Context.Emit(OpCodes.Xor);
+
+ EmitDataLoadOpers(Context);
+
+ Context.Emit(OpCodes.Xor);
+ Context.Emit(OpCodes.And);
+
+ Context.EmitLdc_I(0);
+
+ Context.Emit(OpCodes.Clt);
+
+ Context.EmitStflg((int)APState.VBit);
+ }
+
+ public static void EmitDataLoadRm(AILEmitterCtx Context)
+ {
+ Context.EmitLdintzr(((IAOpCodeAluRs)Context.CurrOp).Rm);
+ }
+
+ public static void EmitDataLoadOpers(AILEmitterCtx Context)
+ {
+ EmitDataLoadRn(Context);
+ EmitDataLoadOper2(Context);
+ }
+
+ public static void EmitDataLoadRn(AILEmitterCtx Context)
+ {
+ IAOpCodeAlu Op = (IAOpCodeAlu)Context.CurrOp;
+
+ if (Op.DataOp == ADataOp.Logical || Op is IAOpCodeAluRs)
+ {
+ Context.EmitLdintzr(Op.Rn);
+ }
+ else
+ {
+ Context.EmitLdint(Op.Rn);
+ }
+ }
+
+ public static void EmitDataLoadOper2(AILEmitterCtx Context)
+ {
+ switch (Context.CurrOp)
+ {
+ case IAOpCodeAluImm Op:
+ Context.EmitLdc_I(Op.Imm);
+ break;
+
+ case IAOpCodeAluRs Op:
+ Context.EmitLdintzr(Op.Rm);
+
+ switch (Op.ShiftType)
+ {
+ case AShiftType.Lsl: Context.EmitLsl(Op.Shift); break;
+ case AShiftType.Lsr: Context.EmitLsr(Op.Shift); break;
+ case AShiftType.Asr: Context.EmitAsr(Op.Shift); break;
+ case AShiftType.Ror: Context.EmitRor(Op.Shift); break;
+ }
+ break;
+
+ case IAOpCodeAluRx Op:
+ Context.EmitLdintzr(Op.Rm);
+ Context.EmitCast(Op.IntType);
+ Context.EmitLsl(Op.Shift);
+ break;
+ }
+ }
+
+ public static void EmitDataStore(AILEmitterCtx Context) => EmitDataStore(Context, false);
+ public static void EmitDataStoreS(AILEmitterCtx Context) => EmitDataStore(Context, true);
+
+ public static void EmitDataStore(AILEmitterCtx Context, bool SetFlags)
+ {
+ IAOpCodeAlu Op = (IAOpCodeAlu)Context.CurrOp;
+
+ if (SetFlags || Op is IAOpCodeAluRs)
+ {
+ Context.EmitStintzr(Op.Rd);
+ }
+ else
+ {
+ Context.EmitStint(Op.Rd);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Instruction/AInstEmitBfm.cs b/Ryujinx/Cpu/Instruction/AInstEmitBfm.cs
new file mode 100644
index 0000000000..4eff013d16
--- /dev/null
+++ b/Ryujinx/Cpu/Instruction/AInstEmitBfm.cs
@@ -0,0 +1,208 @@
+using ChocolArm64.Decoder;
+using ChocolArm64.State;
+using ChocolArm64.Translation;
+using System.Reflection.Emit;
+
+namespace ChocolArm64.Instruction
+{
+ static partial class AInstEmit
+ {
+ public static void Bfm(AILEmitterCtx Context)
+ {
+ AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp;
+
+ EmitBfmLoadRn(Context);
+
+ Context.EmitLdintzr(Op.Rd);
+ Context.EmitLdc_I(~Op.WMask & Op.TMask);
+
+ Context.Emit(OpCodes.And);
+ Context.Emit(OpCodes.Or);
+
+ Context.EmitLdintzr(Op.Rd);
+ Context.EmitLdc_I(~Op.TMask);
+
+ Context.Emit(OpCodes.And);
+ Context.Emit(OpCodes.Or);
+
+ Context.EmitStintzr(Op.Rd);
+ }
+
+ public static void Sbfm(AILEmitterCtx Context)
+ {
+ AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp;
+
+ int BitsCount = Op.GetBitsCount();
+
+ if (Op.Pos + 1 == BitsCount)
+ {
+ EmitBfmShift(Context, OpCodes.Shr);
+ }
+ else if (Op.Pos < Op.Shift)
+ {
+ EmitSbfiz(Context);
+ }
+ else if (Op.Pos == 7 && Op.Shift == 0)
+ {
+ EmitSbfmCast(Context, OpCodes.Conv_I1);
+ }
+ else if (Op.Pos == 15 && Op.Shift == 0)
+ {
+ EmitSbfmCast(Context, OpCodes.Conv_I2);
+ }
+ else if (Op.Pos == 31 && Op.Shift == 0)
+ {
+ EmitSbfmCast(Context, OpCodes.Conv_I4);
+ }
+ else if (Op.Shift == 0)
+ {
+ Context.EmitLdintzr(Op.Rn);
+
+ Context.EmitLsl(BitsCount - 1 - Op.Pos);
+ Context.EmitAsr(BitsCount - 1);
+
+ Context.EmitStintzr(Op.Rd);
+ }
+ else
+ {
+ EmitBfmLoadRn(Context);
+
+ Context.EmitLdintzr(Op.Rn);
+
+ Context.EmitLsl(BitsCount - 1 - Op.Pos);
+ Context.EmitAsr(BitsCount - 1);
+
+ Context.EmitLdc_I(~Op.TMask);
+
+ Context.Emit(OpCodes.And);
+ Context.Emit(OpCodes.Or);
+
+ Context.EmitStintzr(Op.Rd);
+ }
+ }
+
+ public static void Ubfm(AILEmitterCtx Context)
+ {
+ AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp;
+
+ if (Op.Pos + 1 == Op.GetBitsCount())
+ {
+ EmitBfmShift(Context, OpCodes.Shr_Un);
+ }
+ else if (Op.Pos < Op.Shift)
+ {
+ EmitUbfiz(Context);
+ }
+ else if (Op.Pos + 1 == Op.Shift)
+ {
+ EmitBfmLsl(Context);
+ }
+ else if (Op.Pos == 7 && Op.Shift == 0)
+ {
+ EmitUbfmCast(Context, OpCodes.Conv_U1);
+ }
+ else if (Op.Pos == 15 && Op.Shift == 0)
+ {
+ EmitUbfmCast(Context, OpCodes.Conv_U2);
+ }
+ else
+ {
+ EmitBfmLoadRn(Context);
+
+ Context.EmitStintzr(Op.Rd);
+ }
+ }
+
+ private static void EmitSbfiz(AILEmitterCtx Context) => EmitBfiz(Context, true);
+ private static void EmitUbfiz(AILEmitterCtx Context) => EmitBfiz(Context, false);
+
+ private static void EmitBfiz(AILEmitterCtx Context, bool Signed)
+ {
+ AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp;
+
+ int Width = Op.Pos + 1;
+
+ Context.EmitLdintzr(Op.Rn);
+
+ Context.EmitLsl(Op.GetBitsCount() - Width);
+
+ if (Signed)
+ {
+ Context.EmitAsr(Op.Shift - Width);
+ }
+ else
+ {
+ Context.EmitLsr(Op.Shift - Width);
+ }
+
+ Context.EmitStintzr(Op.Rd);
+ }
+
+ private static void EmitSbfmCast(AILEmitterCtx Context, OpCode ILOp)
+ {
+ EmitBfmCast(Context, ILOp, true);
+ }
+
+ private static void EmitUbfmCast(AILEmitterCtx Context, OpCode ILOp)
+ {
+ EmitBfmCast(Context, ILOp, false);
+ }
+
+ private static void EmitBfmCast(AILEmitterCtx Context, OpCode ILOp, bool Signed)
+ {
+ AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp;
+
+ Context.EmitLdintzr(Op.Rn);
+
+ Context.Emit(ILOp);
+
+ if (Op.RegisterSize != ARegisterSize.Int32)
+ {
+ Context.Emit(Signed
+ ? OpCodes.Conv_I8
+ : OpCodes.Conv_U8);
+ }
+
+ Context.EmitStintzr(Op.Rd);
+ }
+
+ private static void EmitBfmShift(AILEmitterCtx Context, OpCode ILOp)
+ {
+ AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp;
+
+ if (Op.Shift > 0)
+ {
+ Context.EmitLdintzr(Op.Rn);
+ Context.EmitLdc_I4(Op.Shift);
+
+ Context.Emit(ILOp);
+
+ Context.EmitStintzr(Op.Rd);
+ }
+ }
+
+ private static void EmitBfmLsl(AILEmitterCtx Context)
+ {
+ AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp;
+
+ Context.EmitLdintzr(Op.Rn);
+
+ Context.EmitLsl(Op.GetBitsCount() - Op.Shift);
+
+ Context.EmitStintzr(Op.Rd);
+ }
+
+ private static void EmitBfmLoadRn(AILEmitterCtx Context)
+ {
+ AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp;
+
+ Context.EmitLdintzr(Op.Rn);
+
+ Context.EmitRor(Op.Shift);
+
+ Context.EmitLdc_I(Op.WMask & Op.TMask);
+
+ Context.Emit(OpCodes.And);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Instruction/AInstEmitCcmp.cs b/Ryujinx/Cpu/Instruction/AInstEmitCcmp.cs
new file mode 100644
index 0000000000..7153a6a0d1
--- /dev/null
+++ b/Ryujinx/Cpu/Instruction/AInstEmitCcmp.cs
@@ -0,0 +1,81 @@
+using ChocolArm64.Decoder;
+using ChocolArm64.State;
+using ChocolArm64.Translation;
+using System;
+using System.Reflection.Emit;
+
+using static ChocolArm64.Instruction.AInstEmitAluHelper;
+
+namespace ChocolArm64.Instruction
+{
+ static partial class AInstEmit
+ {
+ private enum CcmpOp
+ {
+ Cmp,
+ Cmn
+ }
+
+ public static void Ccmn(AILEmitterCtx Context) => EmitCcmp(Context, CcmpOp.Cmn);
+ public static void Ccmp(AILEmitterCtx Context) => EmitCcmp(Context, CcmpOp.Cmp);
+
+ private static void EmitCcmp(AILEmitterCtx Context, CcmpOp CmpOp)
+ {
+ AOpCodeCcmp Op = (AOpCodeCcmp)Context.CurrOp;
+
+ AILLabel LblTrue = new AILLabel();
+ AILLabel LblEnd = new AILLabel();
+
+ Context.EmitCondBranch(LblTrue, Op.Cond);
+
+ Context.EmitLdc_I4((Op.NZCV >> 0) & 1);
+
+ Context.EmitStflg((int)APState.VBit);
+
+ Context.EmitLdc_I4((Op.NZCV >> 1) & 1);
+
+ Context.EmitStflg((int)APState.CBit);
+
+ Context.EmitLdc_I4((Op.NZCV >> 2) & 1);
+
+ Context.EmitStflg((int)APState.ZBit);
+
+ Context.EmitLdc_I4((Op.NZCV >> 3) & 1);
+
+ Context.EmitStflg((int)APState.NBit);
+
+ Context.Emit(OpCodes.Br_S, LblEnd);
+
+ Context.MarkLabel(LblTrue);
+
+ EmitDataLoadOpers(Context);
+
+ if (CmpOp == CcmpOp.Cmp)
+ {
+ Context.Emit(OpCodes.Sub);
+
+ Context.EmitZNFlagCheck();
+
+ EmitSubsCCheck(Context);
+ EmitSubsVCheck(Context);
+ }
+ else if (CmpOp == CcmpOp.Cmn)
+ {
+ Context.Emit(OpCodes.Add);
+
+ Context.EmitZNFlagCheck();
+
+ EmitAddsCCheck(Context);
+ EmitAddsVCheck(Context);
+ }
+ else
+ {
+ throw new ArgumentException(nameof(CmpOp));
+ }
+
+ Context.Emit(OpCodes.Pop);
+
+ Context.MarkLabel(LblEnd);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Instruction/AInstEmitCsel.cs b/Ryujinx/Cpu/Instruction/AInstEmitCsel.cs
new file mode 100644
index 0000000000..330809806a
--- /dev/null
+++ b/Ryujinx/Cpu/Instruction/AInstEmitCsel.cs
@@ -0,0 +1,59 @@
+using ChocolArm64.Decoder;
+using ChocolArm64.Translation;
+using System.Reflection.Emit;
+
+namespace ChocolArm64.Instruction
+{
+ static partial class AInstEmit
+ {
+ private enum CselOperation
+ {
+ None,
+ Increment,
+ Invert,
+ Negate
+ }
+
+ public static void Csel(AILEmitterCtx Context) => EmitCsel(Context, CselOperation.None);
+ public static void Csinc(AILEmitterCtx Context) => EmitCsel(Context, CselOperation.Increment);
+ public static void Csinv(AILEmitterCtx Context) => EmitCsel(Context, CselOperation.Invert);
+ public static void Csneg(AILEmitterCtx Context) => EmitCsel(Context, CselOperation.Negate);
+
+ private static void EmitCsel(AILEmitterCtx Context, CselOperation CselOp)
+ {
+ AOpCodeCsel Op = (AOpCodeCsel)Context.CurrOp;
+
+ AILLabel LblTrue = new AILLabel();
+ AILLabel LblEnd = new AILLabel();
+
+ Context.EmitCondBranch(LblTrue, Op.Cond);
+ Context.EmitLdintzr(Op.Rm);
+
+ if (CselOp == CselOperation.Increment)
+ {
+ Context.EmitLdc_I(1);
+
+ Context.Emit(OpCodes.Add);
+ }
+ else if (CselOp == CselOperation.Invert)
+ {
+ Context.Emit(OpCodes.Not);
+ }
+ else if (CselOp == CselOperation.Negate)
+ {
+ Context.Emit(OpCodes.Neg);
+ }
+
+ Context.EmitStintzr(Op.Rd);
+
+ Context.Emit(OpCodes.Br_S, LblEnd);
+
+ Context.MarkLabel(LblTrue);
+
+ Context.EmitLdintzr(Op.Rn);
+ Context.EmitStintzr(Op.Rd);
+
+ Context.MarkLabel(LblEnd);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Instruction/AInstEmitException.cs b/Ryujinx/Cpu/Instruction/AInstEmitException.cs
new file mode 100644
index 0000000000..6e5665fbca
--- /dev/null
+++ b/Ryujinx/Cpu/Instruction/AInstEmitException.cs
@@ -0,0 +1,33 @@
+using ChocolArm64.Decoder;
+using ChocolArm64.State;
+using ChocolArm64.Translation;
+using System;
+
+namespace ChocolArm64.Instruction
+{
+ static partial class AInstEmit
+ {
+ public static void Svc(AILEmitterCtx Context)
+ {
+ AOpCodeException Op = (AOpCodeException)Context.CurrOp;
+
+ Context.EmitStoreState();
+
+ Context.EmitLdarg(ATranslatedSub.RegistersArgIdx);
+
+ Context.EmitLdc_I4(Op.Id);
+
+ Context.EmitCall(typeof(ARegisters), nameof(ARegisters.OnSvcCall));
+
+ if (Context.CurrBlock.Next != null)
+ {
+ Context.EmitLoadState(Context.CurrBlock.Next);
+ }
+ }
+
+ public static void Und(AILEmitterCtx Context)
+ {
+ throw new Exception("und inst! " + Context.CurrOp.Position.ToString("x8"));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Instruction/AInstEmitFlow.cs b/Ryujinx/Cpu/Instruction/AInstEmitFlow.cs
new file mode 100644
index 0000000000..6fa19c9fc0
--- /dev/null
+++ b/Ryujinx/Cpu/Instruction/AInstEmitFlow.cs
@@ -0,0 +1,124 @@
+using ChocolArm64.Decoder;
+using ChocolArm64.State;
+using ChocolArm64.Translation;
+using System.Reflection.Emit;
+
+namespace ChocolArm64.Instruction
+{
+ static partial class AInstEmit
+ {
+ public static void B(AILEmitterCtx Context)
+ {
+ AOpCodeBImmAl Op = (AOpCodeBImmAl)Context.CurrOp;
+
+ Context.Emit(OpCodes.Br, Context.GetLabel(Op.Imm));
+ }
+
+ public static void B_Cond(AILEmitterCtx Context)
+ {
+ AOpCodeBImmCond Op = (AOpCodeBImmCond)Context.CurrOp;
+
+ Context.EmitCondBranch(Context.GetLabel(Op.Imm), Op.Cond);
+ }
+
+ public static void Bl(AILEmitterCtx Context)
+ {
+ AOpCodeBImmAl Op = (AOpCodeBImmAl)Context.CurrOp;
+
+ Context.EmitLdc_I(Op.Position + 4);
+ Context.EmitStint(ARegisters.LRIndex);
+ Context.EmitStoreState();
+
+ if (Context.TryOptEmitSubroutineCall())
+ {
+ //Note: the return value of the called method will be placed
+ //at the Stack, the return value is always a Int64 with the
+ //return address of the function. We check if the address is
+ //correct, if it isn't we keep returning until we reach the dispatcher.
+ Context.Emit(OpCodes.Dup);
+
+ Context.EmitLdc_I8(Op.Position + 4);
+
+ AILLabel LblContinue = new AILLabel();
+
+ Context.Emit(OpCodes.Beq_S, LblContinue);
+ Context.Emit(OpCodes.Ret);
+
+ Context.MarkLabel(LblContinue);
+
+ Context.Emit(OpCodes.Pop);
+
+ if (Context.CurrBlock.Next != null)
+ {
+ Context.EmitLoadState(Context.CurrBlock.Next);
+ }
+ }
+ else
+ {
+ Context.EmitLdc_I8(Op.Imm);
+
+ Context.Emit(OpCodes.Ret);
+ }
+ }
+
+ public static void Blr(AILEmitterCtx Context)
+ {
+ AOpCodeBReg Op = (AOpCodeBReg)Context.CurrOp;
+
+ Context.EmitLdc_I(Op.Position + 4);
+ Context.EmitStint(ARegisters.LRIndex);
+ Context.EmitStoreState();
+ Context.EmitLdintzr(Op.Rn);
+
+ Context.Emit(OpCodes.Ret);
+ }
+
+ public static void Br(AILEmitterCtx Context)
+ {
+ AOpCodeBReg Op = (AOpCodeBReg)Context.CurrOp;
+
+ Context.EmitStoreState();
+ Context.EmitLdintzr(Op.Rn);
+
+ Context.Emit(OpCodes.Ret);
+ }
+
+ public static void Cbnz(AILEmitterCtx Context) => EmitCb(Context, OpCodes.Bne_Un);
+ public static void Cbz(AILEmitterCtx Context) => EmitCb(Context, OpCodes.Beq);
+
+ private static void EmitCb(AILEmitterCtx Context, OpCode ILOp)
+ {
+ AOpCodeBImmCmp Op = (AOpCodeBImmCmp)Context.CurrOp;
+
+ Context.EmitLdintzr(Op.Rt);
+ Context.EmitLdc_I(0);
+
+ Context.Emit(ILOp, Context.GetLabel(Op.Imm));
+ }
+
+ public static void Ret(AILEmitterCtx Context)
+ {
+ Context.EmitStoreState();
+ Context.EmitLdint(ARegisters.LRIndex);
+
+ Context.Emit(OpCodes.Ret);
+ }
+
+ public static void Tbnz(AILEmitterCtx Context) => EmitTb(Context, OpCodes.Bne_Un);
+ public static void Tbz(AILEmitterCtx Context) => EmitTb(Context, OpCodes.Beq);
+
+ private static void EmitTb(AILEmitterCtx Context, OpCode ILOp)
+ {
+ AOpCodeBImmTest Op = (AOpCodeBImmTest)Context.CurrOp;
+
+ Context.EmitLdintzr(Op.Rt);
+ Context.EmitLdc_I(1L << Op.Pos);
+
+ Context.Emit(OpCodes.And);
+
+ Context.EmitLdc_I(0);
+
+ Context.Emit(ILOp, Context.GetLabel(Op.Imm));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Instruction/AInstEmitMemory.cs b/Ryujinx/Cpu/Instruction/AInstEmitMemory.cs
new file mode 100644
index 0000000000..ca0c82a377
--- /dev/null
+++ b/Ryujinx/Cpu/Instruction/AInstEmitMemory.cs
@@ -0,0 +1,252 @@
+using ChocolArm64.Decoder;
+using ChocolArm64.Translation;
+using System.Reflection.Emit;
+
+using static ChocolArm64.Instruction.AInstEmitMemoryHelper;
+
+namespace ChocolArm64.Instruction
+{
+ static partial class AInstEmit
+ {
+ public static void Adr(AILEmitterCtx Context)
+ {
+ AOpCodeAdr Op = (AOpCodeAdr)Context.CurrOp;
+
+ Context.EmitLdc_I(Op.Position + Op.Imm);
+ Context.EmitStintzr(Op.Rd);
+ }
+
+ public static void Adrp(AILEmitterCtx Context)
+ {
+ AOpCodeAdr Op = (AOpCodeAdr)Context.CurrOp;
+
+ Context.EmitLdc_I((Op.Position & ~0xfff) + (Op.Imm << 12));
+ Context.EmitStintzr(Op.Rd);
+ }
+
+ public static void Ldr(AILEmitterCtx Context) => EmitLdr(Context, false);
+ public static void Ldrs(AILEmitterCtx Context) => EmitLdr(Context, true);
+
+ public static void EmitLdr(AILEmitterCtx Context, bool Signed)
+ {
+ AOpCodeMem Op = (AOpCodeMem)Context.CurrOp;
+
+ Context.EmitLdarg(ATranslatedSub.MemoryArgIdx);
+
+ EmitLoadAddress(Context);
+
+ if (Signed && Op.Extend64)
+ {
+ EmitReadSx64Call(Context, Op.Size);
+ }
+ else if (Signed)
+ {
+ EmitReadSx32Call(Context, Op.Size);
+ }
+ else
+ {
+ EmitReadZxCall(Context, Op.Size);
+ }
+
+ if (Op is IAOpCodeSimd)
+ {
+ Context.EmitStvec(Op.Rt);
+ }
+ else
+ {
+ Context.EmitStintzr(Op.Rt);
+ }
+
+ EmitWBackIfNeeded(Context);
+ }
+
+ public static void LdrLit(AILEmitterCtx Context)
+ {
+ IAOpCodeLit Op = (IAOpCodeLit)Context.CurrOp;
+
+ if (Op.Prefetch)
+ {
+ return;
+ }
+
+ Context.EmitLdarg(ATranslatedSub.MemoryArgIdx);
+ Context.EmitLdc_I8(Op.Imm);
+
+ if (Op.Signed)
+ {
+ EmitReadSx64Call(Context, Op.Size);
+ }
+ else
+ {
+ EmitReadZxCall(Context, Op.Size);
+ }
+
+ if (Op is IAOpCodeSimd)
+ {
+ Context.EmitStvec(Op.Rt);
+ }
+ else
+ {
+ Context.EmitStint(Op.Rt);
+ }
+ }
+
+ public static void Ldp(AILEmitterCtx Context)
+ {
+ AOpCodeMemPair Op = (AOpCodeMemPair)Context.CurrOp;
+
+ void EmitReadAndStore(int Rt)
+ {
+ if (Op.Extend64)
+ {
+ EmitReadSx64Call(Context, Op.Size);
+ }
+ else
+ {
+ EmitReadZxCall(Context, Op.Size);
+ }
+
+ if (Op is IAOpCodeSimd)
+ {
+ Context.EmitStvec(Rt);
+ }
+ else
+ {
+ Context.EmitStintzr(Rt);
+ }
+ }
+
+ Context.EmitLdarg(ATranslatedSub.MemoryArgIdx);
+
+ EmitLoadAddress(Context);
+
+ EmitReadAndStore(Op.Rt);
+
+ Context.EmitLdarg(ATranslatedSub.MemoryArgIdx);
+ Context.EmitLdtmp();
+ Context.EmitLdc_I8(1 << Op.Size);
+
+ Context.Emit(OpCodes.Add);
+
+ EmitReadAndStore(Op.Rt2);
+
+ EmitWBackIfNeeded(Context);
+ }
+
+ public static void Str(AILEmitterCtx Context)
+ {
+ AOpCodeMem Op = (AOpCodeMem)Context.CurrOp;
+
+ Context.EmitLdarg(ATranslatedSub.MemoryArgIdx);
+
+ EmitLoadAddress(Context);
+
+ if (Op is IAOpCodeSimd)
+ {
+ Context.EmitLdvec(Op.Rt);
+ }
+ else
+ {
+ Context.EmitLdintzr(Op.Rt);
+ }
+
+ EmitWriteCall(Context, Op.Size);
+
+ EmitWBackIfNeeded(Context);
+ }
+
+ public static void Stp(AILEmitterCtx Context)
+ {
+ AOpCodeMemPair Op = (AOpCodeMemPair)Context.CurrOp;
+
+ Context.EmitLdarg(ATranslatedSub.MemoryArgIdx);
+
+ EmitLoadAddress(Context);
+
+ if (Op is IAOpCodeSimd)
+ {
+ Context.EmitLdvec(Op.Rt);
+ }
+ else
+ {
+ Context.EmitLdintzr(Op.Rt);
+ }
+
+ EmitWriteCall(Context, Op.Size);
+
+ Context.EmitLdarg(ATranslatedSub.MemoryArgIdx);
+ Context.EmitLdtmp();
+ Context.EmitLdc_I8(1 << Op.Size);
+
+ Context.Emit(OpCodes.Add);
+
+ if (Op is IAOpCodeSimd)
+ {
+ Context.EmitLdvec(Op.Rt2);
+ }
+ else
+ {
+ Context.EmitLdintzr(Op.Rt2);
+ }
+
+ EmitWriteCall(Context, Op.Size);
+
+ EmitWBackIfNeeded(Context);
+ }
+
+ private static void EmitLoadAddress(AILEmitterCtx Context)
+ {
+ switch (Context.CurrOp)
+ {
+ case AOpCodeMemImm Op:
+ Context.EmitLdint(Op.Rn);
+
+ if (!Op.PostIdx)
+ {
+ //Pre-indexing.
+ Context.EmitLdc_I(Op.Imm);
+
+ Context.Emit(OpCodes.Add);
+ }
+ break;
+
+ case AOpCodeMemReg Op:
+ Context.EmitLdint(Op.Rn);
+ Context.EmitLdintzr(Op.Rm);
+ Context.EmitCast(Op.IntType);
+
+ if (Op.Shift)
+ {
+ Context.EmitLsl(Op.Size);
+ }
+
+ Context.Emit(OpCodes.Add);
+ break;
+ }
+
+ //Save address to Scratch var since the register value may change.
+ Context.Emit(OpCodes.Dup);
+
+ Context.EmitSttmp();
+ }
+
+ private static void EmitWBackIfNeeded(AILEmitterCtx Context)
+ {
+ //Check whenever the current OpCode has post-indexed write back, if so write it.
+ //Note: AOpCodeMemPair inherits from AOpCodeMemImm, so this works for both.
+ if (Context.CurrOp is AOpCodeMemImm Op && Op.WBack)
+ {
+ Context.EmitLdtmp();
+
+ if (Op.PostIdx)
+ {
+ Context.EmitLdc_I(Op.Imm);
+
+ Context.Emit(OpCodes.Add);
+ }
+
+ Context.EmitStint(Op.Rn);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Instruction/AInstEmitMemoryEx.cs b/Ryujinx/Cpu/Instruction/AInstEmitMemoryEx.cs
new file mode 100644
index 0000000000..ebb6e93284
--- /dev/null
+++ b/Ryujinx/Cpu/Instruction/AInstEmitMemoryEx.cs
@@ -0,0 +1,180 @@
+using ChocolArm64.Decoder;
+using ChocolArm64.Memory;
+using ChocolArm64.Translation;
+using System;
+using System.Reflection.Emit;
+using System.Threading;
+
+using static ChocolArm64.Instruction.AInstEmitMemoryHelper;
+
+namespace ChocolArm64.Instruction
+{
+ static partial class AInstEmit
+ {
+ [Flags]
+ private enum AccessType
+ {
+ None = 0,
+ Ordered = 1,
+ Exclusive = 2,
+ OrderedEx = Ordered | Exclusive
+ }
+
+ public static void Clrex(AILEmitterCtx Context)
+ {
+ EmitMemoryCall(Context, nameof(AMemory.ClearExclusive));
+ }
+
+ public static void Dmb(AILEmitterCtx Context) => EmitBarrier(Context);
+ public static void Dsb(AILEmitterCtx Context) => EmitBarrier(Context);
+
+ public static void Ldar(AILEmitterCtx Context) => EmitLdr(Context, AccessType.Ordered);
+ public static void Ldaxr(AILEmitterCtx Context) => EmitLdr(Context, AccessType.OrderedEx);
+ public static void Ldxr(AILEmitterCtx Context) => EmitLdr(Context, AccessType.Exclusive);
+ public static void Ldxp(AILEmitterCtx Context) => EmitLdp(Context, AccessType.Exclusive);
+ public static void Ldaxp(AILEmitterCtx Context) => EmitLdp(Context, AccessType.OrderedEx);
+
+ private static void EmitLdr(AILEmitterCtx Context, AccessType AccType)
+ {
+ EmitLoad(Context, AccType, false);
+ }
+
+ private static void EmitLdp(AILEmitterCtx Context, AccessType AccType)
+ {
+ EmitLoad(Context, AccType, true);
+ }
+
+ private static void EmitLoad(AILEmitterCtx Context, AccessType AccType, bool Pair)
+ {
+ AOpCodeMemEx Op = (AOpCodeMemEx)Context.CurrOp;
+
+ Context.EmitLdarg(ATranslatedSub.MemoryArgIdx);
+ Context.EmitLdint(Op.Rn);
+
+ EmitReadZxCall(Context, Op.Size);
+
+ Context.EmitStintzr(Op.Rt);
+
+ if (Pair)
+ {
+ Context.EmitLdarg(ATranslatedSub.MemoryArgIdx);
+ Context.EmitLdint(Op.Rn);
+ Context.EmitLdc_I(8 << Op.Size);
+
+ Context.Emit(OpCodes.Add);
+
+ EmitReadZxCall(Context, Op.Size);
+
+ Context.EmitStintzr(Op.Rt2);
+ }
+
+ if (AccType.HasFlag(AccessType.Exclusive))
+ {
+ EmitMemoryCall(Context, nameof(AMemory.SetExclusive), Op.Rn);
+ }
+
+ if (AccType.HasFlag(AccessType.Ordered))
+ {
+ EmitBarrier(Context);
+ }
+ }
+
+ public static void Pfrm(AILEmitterCtx Context)
+ {
+ //Memory Prefetch, execute as no-op.
+ }
+
+ public static void Stlr(AILEmitterCtx Context) => EmitStr(Context, AccessType.Ordered);
+ public static void Stlxr(AILEmitterCtx Context) => EmitStr(Context, AccessType.OrderedEx);
+ public static void Stxr(AILEmitterCtx Context) => EmitStr(Context, AccessType.Exclusive);
+ public static void Stxp(AILEmitterCtx Context) => EmitStp(Context, AccessType.Exclusive);
+ public static void Stlxp(AILEmitterCtx Context) => EmitStp(Context, AccessType.OrderedEx);
+
+ private static void EmitStr(AILEmitterCtx Context, AccessType AccType)
+ {
+ EmitStore(Context, AccType, false);
+ }
+
+ private static void EmitStp(AILEmitterCtx Context, AccessType AccType)
+ {
+ EmitStore(Context, AccType, true);
+ }
+
+ private static void EmitStore(AILEmitterCtx Context, AccessType AccType, bool Pair)
+ {
+ AOpCodeMemEx Op = (AOpCodeMemEx)Context.CurrOp;
+
+ if (AccType.HasFlag(AccessType.Ordered))
+ {
+ EmitBarrier(Context);
+ }
+
+ AILLabel LblEx = new AILLabel();
+ AILLabel LblEnd = new AILLabel();
+
+ if (AccType.HasFlag(AccessType.Exclusive))
+ {
+ EmitMemoryCall(Context, nameof(AMemory.TestExclusive), Op.Rn);
+
+ Context.Emit(OpCodes.Brtrue_S, LblEx);
+
+ Context.EmitLdc_I8(1);
+ Context.EmitStintzr(Op.Rs);
+
+ Context.Emit(OpCodes.Br_S, LblEnd);
+ }
+
+ Context.MarkLabel(LblEx);
+
+ Context.EmitLdarg(ATranslatedSub.MemoryArgIdx);
+ Context.EmitLdint(Op.Rn);
+ Context.EmitLdintzr(Op.Rt);
+
+ EmitWriteCall(Context, Op.Size);
+
+ if (Pair)
+ {
+ Context.EmitLdarg(ATranslatedSub.MemoryArgIdx);
+ Context.EmitLdint(Op.Rn);
+ Context.EmitLdc_I(8 << Op.Size);
+
+ Context.Emit(OpCodes.Add);
+
+ Context.EmitLdintzr(Op.Rt2);
+
+ EmitWriteCall(Context, Op.Size);
+ }
+
+ if (AccType.HasFlag(AccessType.Exclusive))
+ {
+ Context.EmitLdc_I8(0);
+ Context.EmitStintzr(Op.Rs);
+
+ Clrex(Context);
+ }
+
+ Context.MarkLabel(LblEnd);
+ }
+
+ private static void EmitMemoryCall(AILEmitterCtx Context, string Name, int Rn = -1)
+ {
+ Context.EmitLdarg(ATranslatedSub.MemoryArgIdx);
+ Context.EmitLdarg(ATranslatedSub.RegistersArgIdx);
+
+ if (Rn != -1)
+ {
+ Context.EmitLdint(Rn);
+ }
+
+ Context.EmitCall(typeof(AMemory), Name);
+ }
+
+ private static void EmitBarrier(AILEmitterCtx Context)
+ {
+ //Note: This barrier is most likely not necessary, and probably
+ //doesn't make any difference since we need to do a ton of stuff
+ //(software MMU emulation) to read or write anything anyway.
+ Context.EmitCall(typeof(Thread), nameof(Thread.MemoryBarrier));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Instruction/AInstEmitMemoryHelper.cs b/Ryujinx/Cpu/Instruction/AInstEmitMemoryHelper.cs
new file mode 100644
index 0000000000..e05153d998
--- /dev/null
+++ b/Ryujinx/Cpu/Instruction/AInstEmitMemoryHelper.cs
@@ -0,0 +1,97 @@
+using ChocolArm64.Memory;
+using ChocolArm64.Translation;
+using System;
+using System.Reflection.Emit;
+
+namespace ChocolArm64.Instruction
+{
+ static class AInstEmitMemoryHelper
+ {
+ private enum Extension
+ {
+ Zx,
+ Sx32,
+ Sx64
+ }
+
+ public static void EmitReadZxCall(AILEmitterCtx Context, int Size)
+ {
+ EmitReadCall(Context, Extension.Zx, Size);
+ }
+
+ public static void EmitReadSx32Call(AILEmitterCtx Context, int Size)
+ {
+ EmitReadCall(Context, Extension.Sx32, Size);
+ }
+
+ public static void EmitReadSx64Call(AILEmitterCtx Context, int Size)
+ {
+ EmitReadCall(Context, Extension.Sx64, Size);
+ }
+
+ private static void EmitReadCall(AILEmitterCtx Context, Extension Ext, int Size)
+ {
+ if (Size < 0 || Size > 4)
+ {
+ throw new ArgumentOutOfRangeException(nameof(Size));
+ }
+
+ string Name = null;
+
+ switch (Size)
+ {
+ case 0: Name = nameof(AMemory.ReadByte); break;
+ case 1: Name = nameof(AMemory.ReadUInt16); break;
+ case 2: Name = nameof(AMemory.ReadUInt32); break;
+ case 3: Name = nameof(AMemory.ReadUInt64); break;
+ case 4: Name = nameof(AMemory.ReadVector128); break;
+ }
+
+ Context.EmitCall(typeof(AMemory), Name);
+
+ if (Ext == Extension.Sx32 ||
+ Ext == Extension.Sx64)
+ {
+ switch (Size)
+ {
+ case 0: Context.Emit(OpCodes.Conv_I1); break;
+ case 1: Context.Emit(OpCodes.Conv_I2); break;
+ case 2: Context.Emit(OpCodes.Conv_I4); break;
+ }
+ }
+
+ if (Size < 3)
+ {
+ Context.Emit(Ext == Extension.Sx64
+ ? OpCodes.Conv_I8
+ : OpCodes.Conv_U8);
+ }
+ }
+
+ public static void EmitWriteCall(AILEmitterCtx Context, int Size)
+ {
+ if (Size < 0 || Size > 4)
+ {
+ throw new ArgumentOutOfRangeException(nameof(Size));
+ }
+
+ if (Size < 3)
+ {
+ Context.Emit(OpCodes.Conv_I4);
+ }
+
+ string Name = null;
+
+ switch (Size)
+ {
+ case 0: Name = nameof(AMemory.WriteByte); break;
+ case 1: Name = nameof(AMemory.WriteUInt16); break;
+ case 2: Name = nameof(AMemory.WriteUInt32); break;
+ case 3: Name = nameof(AMemory.WriteUInt64); break;
+ case 4: Name = nameof(AMemory.WriteVector128); break;
+ }
+
+ Context.EmitCall(typeof(AMemory), Name);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Instruction/AInstEmitMove.cs b/Ryujinx/Cpu/Instruction/AInstEmitMove.cs
new file mode 100644
index 0000000000..719b53d5d5
--- /dev/null
+++ b/Ryujinx/Cpu/Instruction/AInstEmitMove.cs
@@ -0,0 +1,41 @@
+using ChocolArm64.Decoder;
+using ChocolArm64.Translation;
+using System.Reflection.Emit;
+
+namespace ChocolArm64.Instruction
+{
+ static partial class AInstEmit
+ {
+ public static void Movk(AILEmitterCtx Context)
+ {
+ AOpCodeMov Op = (AOpCodeMov)Context.CurrOp;
+
+ Context.EmitLdintzr(Op.Rd);
+ Context.EmitLdc_I(~(0xffffL << Op.Pos));
+
+ Context.Emit(OpCodes.And);
+
+ Context.EmitLdc_I(Op.Imm);
+
+ Context.Emit(OpCodes.Or);
+
+ Context.EmitStintzr(Op.Rd);
+ }
+
+ public static void Movn(AILEmitterCtx Context)
+ {
+ AOpCodeMov Op = (AOpCodeMov)Context.CurrOp;
+
+ Context.EmitLdc_I(~Op.Imm);
+ Context.EmitStintzr(Op.Rd);
+ }
+
+ public static void Movz(AILEmitterCtx Context)
+ {
+ AOpCodeMov Op = (AOpCodeMov)Context.CurrOp;
+
+ Context.EmitLdc_I(Op.Imm);
+ Context.EmitStintzr(Op.Rd);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Instruction/AInstEmitMul.cs b/Ryujinx/Cpu/Instruction/AInstEmitMul.cs
new file mode 100644
index 0000000000..3713c81f6c
--- /dev/null
+++ b/Ryujinx/Cpu/Instruction/AInstEmitMul.cs
@@ -0,0 +1,80 @@
+using ChocolArm64.Decoder;
+using ChocolArm64.Translation;
+using System.Reflection.Emit;
+
+namespace ChocolArm64.Instruction
+{
+ static partial class AInstEmit
+ {
+ public static void Madd(AILEmitterCtx Context) => EmitMul(Context, OpCodes.Add);
+ public static void Msub(AILEmitterCtx Context) => EmitMul(Context, OpCodes.Sub);
+
+ private static void EmitMul(AILEmitterCtx Context, OpCode ILOp)
+ {
+ AOpCodeMul Op = (AOpCodeMul)Context.CurrOp;
+
+ Context.EmitLdintzr(Op.Ra);
+ Context.EmitLdintzr(Op.Rn);
+ Context.EmitLdintzr(Op.Rm);
+
+ Context.Emit(OpCodes.Mul);
+ Context.Emit(ILOp);
+
+ Context.EmitStintzr(Op.Rd);
+ }
+
+ public static void Smaddl(AILEmitterCtx Context) => EmitMull(Context, OpCodes.Add, true);
+ public static void Smsubl(AILEmitterCtx Context) => EmitMull(Context, OpCodes.Sub, true);
+ public static void Umaddl(AILEmitterCtx Context) => EmitMull(Context, OpCodes.Add, false);
+ public static void Umsubl(AILEmitterCtx Context) => EmitMull(Context, OpCodes.Sub, false);
+
+ private static void EmitMull(AILEmitterCtx Context, OpCode AddSubOp, bool Signed)
+ {
+ AOpCodeMul Op = (AOpCodeMul)Context.CurrOp;
+
+ OpCode CastOp = Signed
+ ? OpCodes.Conv_I8
+ : OpCodes.Conv_U8;
+
+ Context.EmitLdintzr(Op.Ra);
+ Context.EmitLdintzr(Op.Rn);
+
+ Context.Emit(OpCodes.Conv_I4);
+ Context.Emit(CastOp);
+
+ Context.EmitLdintzr(Op.Rm);
+
+ Context.Emit(OpCodes.Conv_I4);
+ Context.Emit(CastOp);
+ Context.Emit(OpCodes.Mul);
+
+ Context.Emit(AddSubOp);
+
+ Context.EmitStintzr(Op.Rd);
+ }
+
+ public static void Smulh(AILEmitterCtx Context)
+ {
+ AOpCodeMul Op = (AOpCodeMul)Context.CurrOp;
+
+ Context.EmitLdintzr(Op.Rn);
+ Context.EmitLdintzr(Op.Rm);
+
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.SMulHi128));
+
+ Context.EmitStintzr(Op.Rd);
+ }
+
+ public static void Umulh(AILEmitterCtx Context)
+ {
+ AOpCodeMul Op = (AOpCodeMul)Context.CurrOp;
+
+ Context.EmitLdintzr(Op.Rn);
+ Context.EmitLdintzr(Op.Rm);
+
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.UMulHi128));
+
+ Context.EmitStintzr(Op.Rd);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Instruction/AInstEmitScalar.cs b/Ryujinx/Cpu/Instruction/AInstEmitScalar.cs
new file mode 100644
index 0000000000..fcddb0c983
--- /dev/null
+++ b/Ryujinx/Cpu/Instruction/AInstEmitScalar.cs
@@ -0,0 +1,659 @@
+using ChocolArm64.Decoder;
+using ChocolArm64.State;
+using ChocolArm64.Translation;
+using System;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace ChocolArm64.Instruction
+{
+ static partial class AInstEmit
+ {
+ public static void Addp_S(AILEmitterCtx Context)
+ {
+ AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdc_I4(Op.Size);
+
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.Addp_S));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Dup_S(AILEmitterCtx Context)
+ {
+ AOpCodeSimdIns Op = (AOpCodeSimdIns)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdc_I4(Op.DstIndex);
+ Context.EmitLdc_I4(Op.Size);
+
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.Dup_S));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Fabs_S(AILEmitterCtx Context)
+ {
+ AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp;
+
+ Context.EmitLdvecsf(Op.Rn);
+
+ MethodInfo MthdInfo;
+
+ if (Op.Size == 0)
+ {
+ MthdInfo = typeof(MathF).GetMethod(nameof(MathF.Abs), new Type[] { typeof(float) });
+ }
+ else if (Op.Size == 1)
+ {
+ MthdInfo = typeof(Math).GetMethod(nameof(Math.Abs), new Type[] { typeof(double) });
+ }
+ else
+ {
+ throw new InvalidOperationException();
+ }
+
+ Context.EmitCall(MthdInfo);
+
+ Context.EmitStvecsf(Op.Rd);
+ }
+
+ public static void Fadd_S(AILEmitterCtx Context) => EmitScalarOp(Context, OpCodes.Add);
+
+ public static void Fccmp_S(AILEmitterCtx Context)
+ {
+ AOpCodeSimdFcond Op = (AOpCodeSimdFcond)Context.CurrOp;
+
+ AILLabel LblTrue = new AILLabel();
+ AILLabel LblEnd = new AILLabel();
+
+ Context.EmitCondBranch(LblTrue, Op.Cond);
+
+ //TODO: Share this logic with Ccmp.
+ Context.EmitLdc_I4((Op.NZCV >> 0) & 1);
+
+ Context.EmitStflg((int)APState.VBit);
+
+ Context.EmitLdc_I4((Op.NZCV >> 1) & 1);
+
+ Context.EmitStflg((int)APState.CBit);
+
+ Context.EmitLdc_I4((Op.NZCV >> 2) & 1);
+
+ Context.EmitStflg((int)APState.ZBit);
+
+ Context.EmitLdc_I4((Op.NZCV >> 3) & 1);
+
+ Context.EmitStflg((int)APState.NBit);
+
+ Context.Emit(OpCodes.Br_S, LblEnd);
+
+ Context.MarkLabel(LblTrue);
+
+ Fcmp_S(Context);
+
+ Context.MarkLabel(LblEnd);
+ }
+
+ public static void Fcmp_S(AILEmitterCtx Context)
+ {
+ AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp;
+
+ bool CmpWithZero = !(Op is AOpCodeSimdFcond) ? Op.Bit3 : false;
+
+ //todo
+ //Context.TryMarkCondWithoutCmp();
+
+ void EmitLoadOpers()
+ {
+ Context.EmitLdvecsf(Op.Rn);
+
+ if (CmpWithZero)
+ {
+ EmitLdcImmF(Context, 0, Op.Size);
+ }
+ else
+ {
+ Context.EmitLdvecsf(Op.Rm);
+ }
+ }
+
+ //Z = Rn == Rm
+ EmitLoadOpers();
+
+ Context.Emit(OpCodes.Ceq);
+ Context.Emit(OpCodes.Dup);
+
+ Context.EmitStflg((int)APState.ZBit);
+
+ //C = Rn >= Rm
+ EmitLoadOpers();
+
+ Context.Emit(OpCodes.Cgt);
+ Context.Emit(OpCodes.Or);
+
+ Context.EmitStflg((int)APState.CBit);
+
+ //N = Rn < Rm
+ EmitLoadOpers();
+
+ Context.Emit(OpCodes.Clt);
+
+ Context.EmitStflg((int)APState.NBit);
+
+ //Handle NaN case. If any number is NaN, then NZCV = 0011.
+ AILLabel LblNotNaN = new AILLabel();
+
+ if (CmpWithZero)
+ {
+ EmitNaNCheck(Context, Op.Rn);
+ }
+ else
+ {
+ EmitNaNCheck(Context, Op.Rn);
+ EmitNaNCheck(Context, Op.Rm);
+
+ Context.Emit(OpCodes.Or);
+ }
+
+ Context.Emit(OpCodes.Brfalse_S, LblNotNaN);
+
+ Context.EmitLdc_I4(1);
+ Context.EmitLdc_I4(1);
+
+ Context.EmitStflg((int)APState.CBit);
+ Context.EmitStflg((int)APState.VBit);
+
+ Context.MarkLabel(LblNotNaN);
+ }
+
+ public static void Fcsel_S(AILEmitterCtx Context)
+ {
+ AOpCodeSimdFcond Op = (AOpCodeSimdFcond)Context.CurrOp;
+
+ AILLabel LblTrue = new AILLabel();
+ AILLabel LblEnd = new AILLabel();
+
+ Context.EmitCondBranch(LblTrue, Op.Cond);
+ Context.EmitLdvecsf(Op.Rm);
+ Context.EmitStvecsf(Op.Rd);
+
+ Context.Emit(OpCodes.Br_S, LblEnd);
+
+ Context.MarkLabel(LblTrue);
+
+ Context.EmitLdvecsf(Op.Rn);
+ Context.EmitStvecsf(Op.Rd);
+
+ Context.MarkLabel(LblEnd);
+ }
+
+ public static void Fcvt_S(AILEmitterCtx Context)
+ {
+ AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp;
+
+ Context.EmitLdvecsf(Op.Rn);
+
+ EmitFloatCast(Context, Op.Opc);
+
+ Context.EmitStvecsf(Op.Rd);
+ }
+
+ public static void Fcvtms_S(AILEmitterCtx Context) => EmitMathOpCvtToInt(Context, nameof(Math.Floor));
+ public static void Fcvtps_S(AILEmitterCtx Context) => EmitMathOpCvtToInt(Context, nameof(Math.Ceiling));
+
+ public static void Fcvtzs_S(AILEmitterCtx Context) => EmitFcvtz_(Context, true);
+ public static void Fcvtzu_S(AILEmitterCtx Context) => EmitFcvtz_(Context, false);
+
+ private static void EmitFcvtz_(AILEmitterCtx Context, bool Signed)
+ {
+ AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp;
+
+ Context.EmitLdvecsf(Op.Rn);
+
+ if (Signed)
+ {
+ EmitCvtToInt(Context, Op.Size);
+ }
+ else
+ {
+ EmitCvtToUInt(Context, Op.Size);
+ }
+
+ Context.EmitStintzr(Op.Rd);
+ }
+
+ public static void Fcvtzs_Fix(AILEmitterCtx Context) => EmitFcvtz__Fix(Context, true);
+ public static void Fcvtzu_Fix(AILEmitterCtx Context) => EmitFcvtz__Fix(Context, false);
+
+ private static void EmitFcvtz__Fix(AILEmitterCtx Context, bool Signed)
+ {
+ AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp;
+
+ Context.EmitLdvecsf(Op.Rn);
+
+ EmitLdcImmF(Context, 1L << Op.FBits, Op.Size);
+
+ Context.Emit(OpCodes.Mul);
+
+ if (Signed)
+ {
+ EmitCvtToInt(Context, Op.Size);
+ }
+ else
+ {
+ EmitCvtToUInt(Context, Op.Size);
+ }
+
+ Context.EmitStintzr(Op.Rd);
+ }
+
+ public static void Fdiv_S(AILEmitterCtx Context) => EmitScalarOp(Context, OpCodes.Div);
+
+ public static void Fmax_S(AILEmitterCtx Context) => EmitMathOp3(Context, nameof(Math.Max));
+ public static void Fmin_S(AILEmitterCtx Context) => EmitMathOp3(Context, nameof(Math.Min));
+
+ public static void Fmaxnm_S(AILEmitterCtx Context) => EmitMathOp3(Context, nameof(Math.Max));
+ public static void Fminnm_S(AILEmitterCtx Context) => EmitMathOp3(Context, nameof(Math.Min));
+
+ public static void Fmov_S(AILEmitterCtx Context)
+ {
+ AOpCodeSimdFmov Op = (AOpCodeSimdFmov)Context.CurrOp;
+
+ Context.EmitLdc_I8(Op.Imm);
+ Context.EmitLdc_I4(0);
+ Context.EmitLdc_I4(Op.Size + 2);
+
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.Fmov_S));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Fmov_Ftoi(AILEmitterCtx Context)
+ {
+ AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp;
+
+ Context.EmitLdvecsi(Op.Rn);
+ Context.EmitStintzr(Op.Rd);
+ }
+
+ public static void Fmov_Itof(AILEmitterCtx Context)
+ {
+ AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp;
+
+ Context.EmitLdintzr(Op.Rn);
+ Context.EmitStvecsi(Op.Rd);
+ }
+
+ public static void Fmov_Ftoi1(AILEmitterCtx Context)
+ {
+ AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdc_I4(1);
+ Context.EmitLdc_I4(3);
+
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.ExtractVec));
+
+ Context.EmitStintzr(Op.Rd);
+ }
+
+ public static void Fmov_Itof1(AILEmitterCtx Context)
+ {
+ AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp;
+
+ Context.EmitLdintzr(Op.Rn);
+ Context.EmitLdc_I4(1);
+ Context.EmitLdc_I4(3);
+
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.Fmov_S));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Fmul_S(AILEmitterCtx Context) => EmitScalarOp(Context, OpCodes.Mul);
+
+ public static void Fneg_S(AILEmitterCtx Context) => EmitScalarOp(Context, OpCodes.Neg);
+
+ public static void Fnmul_S(AILEmitterCtx Context)
+ {
+ AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp;
+
+ Context.EmitLdvecsf(Op.Rn);
+ Context.EmitLdvecsf(Op.Rm);
+
+ Context.Emit(OpCodes.Mul);
+ Context.Emit(OpCodes.Neg);
+
+ Context.EmitStvecsf(Op.Rd);
+ }
+
+ public static void Frinta_S(AILEmitterCtx Context)
+ {
+ AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp;
+
+ Context.EmitLdvecsf(Op.Rn);
+ Context.EmitLdc_I4((int)MidpointRounding.AwayFromZero);
+
+ MethodInfo MthdInfo;
+
+ if (Op.Size == 0)
+ {
+ Type[] Types = new Type[] { typeof(float), typeof(MidpointRounding) };
+
+ MthdInfo = typeof(MathF).GetMethod(nameof(MathF.Round), Types);
+ }
+ else if (Op.Size == 1)
+ {
+ Type[] Types = new Type[] { typeof(double), typeof(MidpointRounding) };
+
+ MthdInfo = typeof(Math).GetMethod(nameof(Math.Round), Types);
+ }
+ else
+ {
+ throw new InvalidOperationException();
+ }
+
+ Context.EmitCall(MthdInfo);
+
+ Context.EmitStvecsf(Op.Rd);
+ }
+
+ public static void Frintm_S(AILEmitterCtx Context)
+ {
+ AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp;
+
+ Context.EmitLdvecsf(Op.Rn);
+
+ MethodInfo MthdInfo;
+
+ if (Op.Size == 0)
+ {
+ MthdInfo = typeof(MathF).GetMethod(nameof(MathF.Floor), new Type[] { typeof(float) });
+ }
+ else if (Op.Size == 1)
+ {
+ MthdInfo = typeof(Math).GetMethod(nameof(Math.Floor), new Type[] { typeof(double) });
+ }
+ else
+ {
+ throw new InvalidOperationException();
+ }
+
+ Context.EmitCall(MthdInfo);
+
+ Context.EmitStvecsf(Op.Rd);
+ }
+
+ public static void Fsqrt_S(AILEmitterCtx Context) => EmitMathOp2(Context, nameof(Math.Sqrt));
+
+ public static void Fsub_S(AILEmitterCtx Context) => EmitScalarOp(Context, OpCodes.Sub);
+
+ public static void Scvtf_Gp(AILEmitterCtx Context)
+ {
+ AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp;
+
+ Context.EmitLdintzr(Op.Rn);
+
+ EmitFloatCast(Context, Op.Size);
+
+ Context.EmitStvecsf(Op.Rd);
+ }
+
+ public static void Scvtf_S(AILEmitterCtx Context)
+ {
+ AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp;
+
+ Context.EmitLdvecsi(Op.Rn);
+
+ EmitFloatCast(Context, Op.Size);
+
+ Context.EmitStvecsf(Op.Rd);
+ }
+
+ public static void Shl_S(AILEmitterCtx Context)
+ {
+ AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp;
+
+ Context.EmitLdvecsi(Op.Rn);
+ Context.EmitLdc_I4(Op.Imm - (8 << Op.Size));
+
+ Context.Emit(OpCodes.Shl);
+
+ Context.EmitStvecsi(Op.Rd);
+ }
+
+ public static void Sshr_S(AILEmitterCtx Context)
+ {
+ AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp;
+
+ Context.EmitLdvecsi(Op.Rn);
+ Context.EmitLdc_I4((8 << (Op.Size + 1)) - Op.Imm);
+
+ Context.Emit(OpCodes.Shr);
+
+ Context.EmitStvecsi(Op.Rd);
+ }
+
+ public static void Sub_S(AILEmitterCtx Context)
+ {
+ AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp;
+
+ Context.EmitLdvecsi(Op.Rn);
+ Context.EmitLdvecsi(Op.Rm);
+
+ Context.Emit(OpCodes.Sub);
+
+ Context.EmitStvecsi(Op.Rd);
+ }
+
+ public static void Ucvtf_Gp(AILEmitterCtx Context)
+ {
+ AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp;
+
+ Context.EmitLdintzr(Op.Rn);
+
+ Context.Emit(OpCodes.Conv_R_Un);
+
+ EmitFloatCast(Context, Op.Size);
+
+ Context.EmitStvecsf(Op.Rd);
+ }
+
+ private static void EmitScalarOp(AILEmitterCtx Context, OpCode ILOp)
+ {
+ AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp;
+
+ Context.EmitLdvecsf(Op.Rn);
+
+ //Negate and Not are the only unary operations supported on IL.
+ //"Not" doesn't work with floats, so we don't need to compare it.
+ if (ILOp != OpCodes.Neg)
+ {
+ Context.EmitLdvecsf(Op.Rm);
+ }
+
+ Context.Emit(ILOp);
+
+ Context.EmitStvecsf(Op.Rd);
+ }
+
+ private static void EmitMathOp2(AILEmitterCtx Context, string Name)
+ {
+ AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp;
+
+ Context.EmitLdvecsf(Op.Rn);
+
+ EmitMathOpCall(Context, Name);
+
+ Context.EmitStvecsf(Op.Rd);
+ }
+
+ private static void EmitMathOp3(AILEmitterCtx Context, string Name)
+ {
+ AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp;
+
+ Context.EmitLdvecsf(Op.Rn);
+ Context.EmitLdvecsf(Op.Rm);
+
+ EmitMathOpCall(Context, Name);
+
+ Context.EmitStvecsf(Op.Rd);
+ }
+
+ public static void EmitMathOpCvtToInt(AILEmitterCtx Context, string Name)
+ {
+ AOpCodeSimdCvt Op = (AOpCodeSimdCvt)Context.CurrOp;
+
+ Context.EmitLdvecsf(Op.Rn);
+
+ EmitMathOpCall(Context, Name);
+
+ EmitCvtToInt(Context, Op.Size);
+
+ Context.EmitStintzr(Op.Rd);
+ }
+
+ private static void EmitMathOpCall(AILEmitterCtx Context, string Name)
+ {
+ IAOpCodeSimd Op = (IAOpCodeSimd)Context.CurrOp;
+
+ MethodInfo MthdInfo;
+
+ if (Op.Size == 0)
+ {
+ MthdInfo = typeof(MathF).GetMethod(Name);
+ }
+ else if (Op.Size == 1)
+ {
+ MthdInfo = typeof(Math).GetMethod(Name);
+ }
+ else
+ {
+ throw new InvalidOperationException();
+ }
+
+ Context.EmitCall(MthdInfo);
+ }
+
+ private static void EmitCvtToInt(AILEmitterCtx Context, int Size)
+ {
+ if (Size < 0 || Size > 1)
+ {
+ throw new ArgumentOutOfRangeException(nameof(Size));
+ }
+
+ Context.EmitLdc_I4(0);
+
+ if (Context.CurrOp.RegisterSize == ARegisterSize.Int32)
+ {
+ if (Size == 0)
+ {
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.SatSingleToInt32));
+ }
+ else /* if (Size == 1) */
+ {
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.SatDoubleToInt32));
+ }
+ }
+ else
+ {
+ if (Size == 0)
+ {
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.SatSingleToInt64));
+ }
+ else /* if (Size == 1) */
+ {
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.SatDoubleToInt64));
+ }
+ }
+ }
+
+ private static void EmitCvtToUInt(AILEmitterCtx Context, int Size)
+ {
+ if (Size < 0 || Size > 1)
+ {
+ throw new ArgumentOutOfRangeException(nameof(Size));
+ }
+
+ Context.EmitLdc_I4(0);
+
+ if (Context.CurrOp.RegisterSize == ARegisterSize.Int32)
+ {
+ if (Size == 0)
+ {
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.SatSingleToUInt32));
+ }
+ else /* if (Size == 1) */
+ {
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.SatDoubleToUInt32));
+ }
+ }
+ else
+ {
+ if (Size == 0)
+ {
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.SatSingleToUInt64));
+ }
+ else /* if (Size == 1) */
+ {
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.SatDoubleToUInt64));
+ }
+ }
+ }
+
+ private static void EmitFloatCast(AILEmitterCtx Context, int Size)
+ {
+ if (Size == 0)
+ {
+ Context.Emit(OpCodes.Conv_R4);
+ }
+ else if (Size == 1)
+ {
+ Context.Emit(OpCodes.Conv_R8);
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(nameof(Size));
+ }
+ }
+
+ private static void EmitLdcImmF(AILEmitterCtx Context, double ImmF, int Size)
+ {
+ if (Size == 0)
+ {
+ Context.EmitLdc_R4((float)ImmF);
+ }
+ else if (Size == 1)
+ {
+ Context.EmitLdc_R8(ImmF);
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(nameof(Size));
+ }
+ }
+
+ private static void EmitNaNCheck(AILEmitterCtx Context, int Index)
+ {
+ IAOpCodeSimd Op = (IAOpCodeSimd)Context.CurrOp;
+
+ Context.EmitLdvecsf(Index);
+
+ if (Op.Size == 0)
+ {
+ Context.EmitCall(typeof(float), nameof(float.IsNaN));
+ }
+ else if (Op.Size == 1)
+ {
+ Context.EmitCall(typeof(double), nameof(double.IsNaN));
+ }
+ else
+ {
+ throw new InvalidOperationException();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Instruction/AInstEmitSimd.cs b/Ryujinx/Cpu/Instruction/AInstEmitSimd.cs
new file mode 100644
index 0000000000..0801716ad2
--- /dev/null
+++ b/Ryujinx/Cpu/Instruction/AInstEmitSimd.cs
@@ -0,0 +1,965 @@
+using ChocolArm64.Decoder;
+using ChocolArm64.State;
+using ChocolArm64.Translation;
+using System;
+using System.Reflection;
+using System.Reflection.Emit;
+
+using static ChocolArm64.Instruction.AInstEmitMemoryHelper;
+
+namespace ChocolArm64.Instruction
+{
+ static partial class AInstEmit
+ {
+ public static void Add_V(AILEmitterCtx Context) => EmitVectorBinaryZx(Context, OpCodes.Add);
+
+ public static void Addp_V(AILEmitterCtx Context)
+ {
+ AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdvec(Op.Rm);
+ Context.EmitLdc_I4(Op.Size);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Addp64),
+ nameof(ASoftFallback.Addp128));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Addv_V(AILEmitterCtx Context) => EmitVectorAddv(Context);
+
+ public static void And_V(AILEmitterCtx Context) => EmitVectorBinaryZx(Context, OpCodes.And);
+
+ public static void Bic_V(AILEmitterCtx Context) => EmitVectorBic(Context);
+ public static void Bic_Vi(AILEmitterCtx Context)
+ {
+ AOpCodeSimdImm Op = (AOpCodeSimdImm)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rd);
+ Context.EmitLdc_I8(Op.Imm);
+ Context.EmitLdc_I4(Op.Size);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Bic_Vi64),
+ nameof(ASoftFallback.Bic_Vi128));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Bsl_V(AILEmitterCtx Context) => EmitVectorBsl(Context);
+
+ public static void Cmeq_V(AILEmitterCtx Context) => EmitVectorCmp(Context, OpCodes.Beq_S);
+ public static void Cmge_V(AILEmitterCtx Context) => EmitVectorCmp(Context, OpCodes.Bge_S);
+ public static void Cmgt_V(AILEmitterCtx Context) => EmitVectorCmp(Context, OpCodes.Bgt_S);
+ public static void Cmhi_V(AILEmitterCtx Context) => EmitVectorCmp(Context, OpCodes.Bgt_Un_S);
+ public static void Cmhs_V(AILEmitterCtx Context) => EmitVectorCmp(Context, OpCodes.Bge_Un_S);
+ public static void Cmle_V(AILEmitterCtx Context) => EmitVectorCmp(Context, OpCodes.Ble_S);
+ public static void Cmlt_V(AILEmitterCtx Context) => EmitVectorCmp(Context, OpCodes.Blt_S);
+
+ public static void Cnt_V(AILEmitterCtx Context)
+ {
+ AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Cnt64),
+ nameof(ASoftFallback.Cnt128));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Dup_Gp(AILEmitterCtx Context)
+ {
+ AOpCodeSimdIns Op = (AOpCodeSimdIns)Context.CurrOp;
+
+ Context.EmitLdintzr(Op.Rn);
+ Context.EmitLdc_I4(Op.Size);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Dup_Gp64),
+ nameof(ASoftFallback.Dup_Gp128));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Dup_V(AILEmitterCtx Context)
+ {
+ AOpCodeSimdIns Op = (AOpCodeSimdIns)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdc_I4(Op.DstIndex);
+ Context.EmitLdc_I4(Op.Size);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Dup_V64),
+ nameof(ASoftFallback.Dup_V128));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Eor_V(AILEmitterCtx Context) => EmitVectorBinaryZx(Context, OpCodes.Xor);
+
+ public static void Fadd_V(AILEmitterCtx Context)
+ {
+ AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdvec(Op.Rm);
+ Context.EmitLdc_I4(Op.SizeF);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Fadd64),
+ nameof(ASoftFallback.Fadd128));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Fcvtzs_V(AILEmitterCtx Context)
+ {
+ AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdc_I4(Op.SizeF);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Fcvtzs_V64),
+ nameof(ASoftFallback.Fcvtzs_V128));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Fcvtzu_V(AILEmitterCtx Context)
+ {
+ AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdc_I4(0);
+ Context.EmitLdc_I4(Op.SizeF);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Fcvtzu_V_64),
+ nameof(ASoftFallback.Fcvtzu_V_128));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Fcvtzu_V_Fix(AILEmitterCtx Context)
+ {
+ AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdc_I4((8 << (Op.Size + 1)) - Op.Imm);
+ Context.EmitLdc_I4(Op.Size - 2);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Fcvtzu_V_64),
+ nameof(ASoftFallback.Fcvtzu_V_128));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Fmla_V(AILEmitterCtx Context)
+ {
+ AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rd);
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdvec(Op.Rm);
+ Context.EmitLdc_I4(Op.SizeF);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Fmla64),
+ nameof(ASoftFallback.Fmla128));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Fmla_Vs(AILEmitterCtx Context)
+ {
+ AOpCodeSimdRegElem Op = (AOpCodeSimdRegElem)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rd);
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdvec(Op.Rm);
+ Context.EmitLdc_I4(Op.Index);
+ Context.EmitLdc_I4(Op.SizeF);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Fmla_Ve64),
+ nameof(ASoftFallback.Fmla_Ve128));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Fmov_V(AILEmitterCtx Context)
+ {
+ AOpCodeSimdImm Op = (AOpCodeSimdImm)Context.CurrOp;
+
+ Context.EmitLdc_I8(Op.Imm);
+ Context.EmitLdc_I4(Op.Size + 2);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Dup_Gp64),
+ nameof(ASoftFallback.Dup_Gp128));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Fmul_V(AILEmitterCtx Context)
+ {
+ AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdvec(Op.Rm);
+ Context.EmitLdc_I4(Op.SizeF);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Fmul64),
+ nameof(ASoftFallback.Fmul128));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Fmul_Vs(AILEmitterCtx Context)
+ {
+ AOpCodeSimdRegElem Op = (AOpCodeSimdRegElem)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdvec(Op.Rm);
+ Context.EmitLdc_I4(Op.Index);
+ Context.EmitLdc_I4(Op.SizeF);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Fmul_Ve64),
+ nameof(ASoftFallback.Fmul_Ve128));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Fsub_V(AILEmitterCtx Context)
+ {
+ AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdvec(Op.Rm);
+ Context.EmitLdc_I4(Op.SizeF);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Fsub64),
+ nameof(ASoftFallback.Fsub128));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Ins_Gp(AILEmitterCtx Context)
+ {
+ AOpCodeSimdIns Op = (AOpCodeSimdIns)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rd);
+ Context.EmitLdintzr(Op.Rn);
+ Context.EmitLdc_I4(Op.DstIndex);
+ Context.EmitLdc_I4(Op.Size);
+
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.Ins_Gp));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Ins_V(AILEmitterCtx Context)
+ {
+ AOpCodeSimdIns Op = (AOpCodeSimdIns)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rd);
+ Context.EmitLdintzr(Op.Rn);
+ Context.EmitLdc_I4(Op.SrcIndex);
+ Context.EmitLdc_I4(Op.DstIndex);
+ Context.EmitLdc_I4(Op.Size);
+
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.Ins_V));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Ld__V(AILEmitterCtx Context) => EmitSimdMultLdSt(Context, IsLoad: true);
+
+ public static void Mla_V(AILEmitterCtx Context) => EmitVectorMla(Context);
+
+ public static void Movi_V(AILEmitterCtx Context) => EmitMovi_V(Context, false);
+
+ public static void Mul_V(AILEmitterCtx Context) => EmitVectorBinaryZx(Context, OpCodes.Mul);
+
+ public static void Mvni_V(AILEmitterCtx Context) => EmitMovi_V(Context, true);
+
+ private static void EmitMovi_V(AILEmitterCtx Context, bool Not)
+ {
+ AOpCodeSimdImm Op = (AOpCodeSimdImm)Context.CurrOp;
+
+ Context.EmitLdc_I8(Not ? ~Op.Imm : Op.Imm);
+ Context.EmitLdc_I4(Op.Size);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Dup_Gp64),
+ nameof(ASoftFallback.Dup_Gp128));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Neg_V(AILEmitterCtx Context) => EmitVectorUnarySx(Context, OpCodes.Neg);
+
+ public static void Not_V(AILEmitterCtx Context) => EmitVectorUnaryZx(Context, OpCodes.Not);
+
+ public static void Orr_V(AILEmitterCtx Context) => EmitVectorBinaryZx(Context, OpCodes.Or);
+
+ public static void Orr_Vi(AILEmitterCtx Context)
+ {
+ AOpCodeSimdImm Op = (AOpCodeSimdImm)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rd);
+ Context.EmitLdc_I8(Op.Imm);
+ Context.EmitLdc_I4(Op.Size);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Orr_Vi64),
+ nameof(ASoftFallback.Orr_Vi128));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Saddw_V(AILEmitterCtx Context)
+ {
+ AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdvec(Op.Rm);
+ Context.EmitLdc_I4(Op.Size);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Saddw),
+ nameof(ASoftFallback.Saddw2));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Scvtf_V(AILEmitterCtx Context)
+ {
+ AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdc_I4(Op.SizeF);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Scvtf_V64),
+ nameof(ASoftFallback.Scvtf_V128));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Shl_V(AILEmitterCtx Context)
+ {
+ AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdc_I4(Op.Imm - (8 << Op.Size));
+ Context.EmitLdc_I4(Op.Size);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Shl64),
+ nameof(ASoftFallback.Shl128));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Smax_V(AILEmitterCtx Context) => EmitVectorSmax(Context);
+ public static void Smin_V(AILEmitterCtx Context) => EmitVectorSmin(Context);
+
+ public static void Sshll_V(AILEmitterCtx Context)
+ {
+ AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdc_I4(Op.Imm - (8 << Op.Size));
+ Context.EmitLdc_I4(Op.Size);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Sshll),
+ nameof(ASoftFallback.Sshll2));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Sshr_V(AILEmitterCtx Context)
+ {
+ AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdc_I4((8 << (Op.Size + 1)) - Op.Imm);
+ Context.EmitLdc_I4(Op.Size);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Sshr64),
+ nameof(ASoftFallback.Sshr128));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void St__V(AILEmitterCtx Context) => EmitSimdMultLdSt(Context, IsLoad: false);
+
+ public static void Sub_V(AILEmitterCtx Context) => EmitVectorBinaryZx(Context, OpCodes.Sub);
+
+ public static void Tbl_V(AILEmitterCtx Context)
+ {
+ AOpCodeSimdTbl Op = (AOpCodeSimdTbl)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rm);
+
+ for (int Index = 0; Index < Op.Size; Index++)
+ {
+ Context.EmitLdvec((Op.Rn + Index) & 0x1f);
+ }
+
+ switch (Op.Size)
+ {
+ case 1: ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Tbl1_V64),
+ nameof(ASoftFallback.Tbl1_V128)); break;
+
+ case 2: ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Tbl2_V64),
+ nameof(ASoftFallback.Tbl2_V128)); break;
+
+ case 3: ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Tbl3_V64),
+ nameof(ASoftFallback.Tbl3_V128)); break;
+
+ case 4: ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Tbl4_V64),
+ nameof(ASoftFallback.Tbl4_V128)); break;
+
+ default: throw new InvalidOperationException();
+ }
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Uaddlv_V(AILEmitterCtx Context)
+ {
+ AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdc_I4(Op.Size);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Uaddlv64),
+ nameof(ASoftFallback.Uaddlv128));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Uaddw_V(AILEmitterCtx Context)
+ {
+ AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdvec(Op.Rm);
+ Context.EmitLdc_I4(Op.Size);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Uaddw),
+ nameof(ASoftFallback.Uaddw2));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Ucvtf_V(AILEmitterCtx Context)
+ {
+ AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+
+ if (Op.Size == 0)
+ {
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.Ucvtf_V_F));
+ }
+ else if (Op.Size == 1)
+ {
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.Ucvtf_V_D));
+ }
+ else
+ {
+ throw new InvalidOperationException();
+ }
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Umov_S(AILEmitterCtx Context)
+ {
+ AOpCodeSimdIns Op = (AOpCodeSimdIns)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdc_I4(Op.DstIndex);
+ Context.EmitLdc_I4(Op.Size);
+
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.ExtractVec));
+
+ Context.EmitStintzr(Op.Rd);
+ }
+
+ public static void Ushl_V(AILEmitterCtx Context) => EmitVectorUshl(Context);
+
+ public static void Ushll_V(AILEmitterCtx Context)
+ {
+ AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdc_I4(Op.Imm - (8 << Op.Size));
+ Context.EmitLdc_I4(Op.Size);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Ushll),
+ nameof(ASoftFallback.Ushll2));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Ushr_V(AILEmitterCtx Context)
+ {
+ AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdc_I4((8 << (Op.Size + 1)) - Op.Imm);
+ Context.EmitLdc_I4(Op.Size);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Ushr64),
+ nameof(ASoftFallback.Ushr128));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Usra_V(AILEmitterCtx Context)
+ {
+ AOpCodeSimdShImm Op = (AOpCodeSimdShImm)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rd);
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdc_I4((8 << (Op.Size + 1)) - Op.Imm);
+ Context.EmitLdc_I4(Op.Size);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Usra64),
+ nameof(ASoftFallback.Usra128));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Uzp1_V(AILEmitterCtx Context)
+ {
+ AOpCodeSimdReg Op = (AOpCodeSimdReg)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdvec(Op.Rm);
+ Context.EmitLdc_I4(Op.Size);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Uzp1_V64),
+ nameof(ASoftFallback.Uzp1_V128));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ public static void Xtn_V(AILEmitterCtx Context)
+ {
+ AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp;
+
+ Context.EmitLdvec(Op.Rn);
+ Context.EmitLdc_I4(Op.Size);
+
+ ASoftFallback.EmitCall(Context,
+ nameof(ASoftFallback.Xtn),
+ nameof(ASoftFallback.Xtn2));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ private static void EmitSimdMultLdSt(AILEmitterCtx Context, bool IsLoad)
+ {
+ AOpCodeSimdMemMult Op = (AOpCodeSimdMemMult)Context.CurrOp;
+
+ int Offset = 0;
+
+ for (int Rep = 0; Rep < Op.Reps; Rep++)
+ for (int Elem = 0; Elem < Op.Elems; Elem++)
+ for (int SElem = 0; SElem < Op.SElems; SElem++)
+ {
+ int Rtt = (Op.Rt + Rep + SElem) & 0x1f;
+
+ if (IsLoad)
+ {
+ Context.EmitLdvec(Rtt);
+ Context.EmitLdc_I4(Elem);
+ Context.EmitLdc_I4(Op.Size);
+ Context.EmitLdarg(ATranslatedSub.MemoryArgIdx);
+ Context.EmitLdint(Op.Rn);
+ Context.EmitLdc_I8(Offset);
+
+ Context.Emit(OpCodes.Add);
+
+ EmitReadZxCall(Context, Op.Size);
+
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.InsertVec));
+
+ Context.EmitStvec(Rtt);
+
+ if (Op.RegisterSize == ARegisterSize.SIMD64 && Elem == Op.Elems - 1)
+ {
+ EmitVectorZeroUpper(Context, Rtt);
+ }
+ }
+ else
+ {
+ Context.EmitLdarg(ATranslatedSub.MemoryArgIdx);
+ Context.EmitLdint(Op.Rn);
+ Context.EmitLdc_I8(Offset);
+
+ Context.Emit(OpCodes.Add);
+
+ Context.EmitLdvec(Rtt);
+ Context.EmitLdc_I4(Elem);
+ Context.EmitLdc_I4(Op.Size);
+
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.ExtractVec));
+
+ EmitWriteCall(Context, Op.Size);
+ }
+
+ Offset += 1 << Op.Size;
+ }
+
+ if (Op.WBack)
+ {
+ Context.EmitLdint(Op.Rn);
+
+ if (Op.Rm != ARegisters.ZRIndex)
+ {
+ Context.EmitLdint(Op.Rm);
+ }
+ else
+ {
+ Context.EmitLdc_I8(Offset);
+ }
+
+ Context.Emit(OpCodes.Add);
+
+ Context.EmitStint(Op.Rn);
+ }
+ }
+
+ private static void EmitVectorAddv(AILEmitterCtx Context)
+ {
+ AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp;
+
+ int Bytes = Context.CurrOp.GetBitsCount() >> 3;
+
+ EmitVectorZeroLower(Context, Op.Rd);
+ EmitVectorZeroUpper(Context, Op.Rd);
+
+ Context.EmitLdvec(Op.Rd);
+ Context.EmitLdc_I4(0);
+ Context.EmitLdc_I4(Op.Size);
+
+ EmitVectorExtractZx(Context, Op.Rn, 0);
+
+ for (int Index = 1; Index < (Bytes >> Op.Size); Index++)
+ {
+ EmitVectorExtractZx(Context, Op.Rn, Index);
+
+ Context.Emit(OpCodes.Add);
+ }
+
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.InsertVec));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ private static void EmitVectorBic(AILEmitterCtx Context)
+ {
+ EmitVectorBinaryZx(Context, () =>
+ {
+ Context.Emit(OpCodes.Not);
+ Context.Emit(OpCodes.And);
+ });
+ }
+
+ private static void EmitVectorBsl(AILEmitterCtx Context)
+ {
+ EmitVectorTernaryZx(Context, () =>
+ {
+ Context.EmitSttmp();
+ Context.EmitLdtmp();
+
+ Context.Emit(OpCodes.Xor);
+ Context.Emit(OpCodes.And);
+
+ Context.EmitLdtmp();
+
+ Context.Emit(OpCodes.Xor);
+ });
+ }
+
+ private static void EmitVectorMla(AILEmitterCtx Context)
+ {
+ EmitVectorTernaryZx(Context, () =>
+ {
+ Context.Emit(OpCodes.Mul);
+ Context.Emit(OpCodes.Add);
+ });
+ }
+
+ private static void EmitVectorSmax(AILEmitterCtx Context)
+ {
+ Type[] Types = new Type[] { typeof(long), typeof(long) };
+
+ MethodInfo MthdInfo = typeof(Math).GetMethod(nameof(Math.Max), Types);
+
+ EmitVectorBinarySx(Context, () => Context.EmitCall(MthdInfo));
+ }
+
+ private static void EmitVectorSmin(AILEmitterCtx Context)
+ {
+ Type[] Types = new Type[] { typeof(long), typeof(long) };
+
+ MethodInfo MthdInfo = typeof(Math).GetMethod(nameof(Math.Min), Types);
+
+ EmitVectorBinarySx(Context, () => Context.EmitCall(MthdInfo));
+ }
+
+ private static void EmitVectorUshl(AILEmitterCtx Context)
+ {
+ //This instruction shifts the value on vector A by the number of bits
+ //specified on the signed, lower 8 bits of vector B. If the shift value
+ //is greater or equal to the data size of each lane, then the result is zero.
+ //Additionally, negative shifts produces right shifts by the negated shift value.
+ AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp;
+
+ int MaxShift = 8 << Op.Size;
+
+ EmitVectorBinaryZx(Context, () =>
+ {
+ AILLabel LblShl = new AILLabel();
+ AILLabel LblZero = new AILLabel();
+ AILLabel LblEnd = new AILLabel();
+
+ void EmitShift(OpCode ILOp)
+ {
+ Context.Emit(OpCodes.Dup);
+
+ Context.EmitLdc_I4(MaxShift);
+
+ Context.Emit(OpCodes.Bge_S, LblZero);
+ Context.Emit(ILOp);
+ Context.Emit(OpCodes.Br_S, LblEnd);
+ }
+
+ Context.Emit(OpCodes.Conv_I1);
+ Context.Emit(OpCodes.Dup);
+
+ Context.EmitLdc_I4(0);
+
+ Context.Emit(OpCodes.Bge_S, LblShl);
+ Context.Emit(OpCodes.Neg);
+
+ EmitShift(OpCodes.Shr_Un);
+
+ Context.MarkLabel(LblShl);
+
+ EmitShift(OpCodes.Shl);
+
+ Context.MarkLabel(LblZero);
+
+ Context.Emit(OpCodes.Pop);
+ Context.Emit(OpCodes.Pop);
+
+ Context.EmitLdc_I8(0);
+
+ Context.MarkLabel(LblEnd);
+ });
+ }
+
+ private static void EmitVectorUnarySx(AILEmitterCtx Context, OpCode ILOp)
+ {
+ EmitVectorUnarySx(Context, () => Context.Emit(ILOp));
+ }
+
+ private static void EmitVectorUnaryZx(AILEmitterCtx Context, OpCode ILOp)
+ {
+ EmitVectorUnaryZx(Context, () => Context.Emit(ILOp));
+ }
+
+ private static void EmitVectorBinaryZx(AILEmitterCtx Context, OpCode ILOp)
+ {
+ EmitVectorBinaryZx(Context, () => Context.Emit(ILOp));
+ }
+
+ private static void EmitVectorUnarySx(AILEmitterCtx Context, Action Emit)
+ {
+ EmitVectorOp(Context, Emit, 1, true);
+ }
+
+ private static void EmitVectorBinarySx(AILEmitterCtx Context, Action Emit)
+ {
+ EmitVectorOp(Context, Emit, 2, true);
+ }
+
+ private static void EmitVectorUnaryZx(AILEmitterCtx Context, Action Emit)
+ {
+ EmitVectorOp(Context, Emit, 1, false);
+ }
+
+ private static void EmitVectorBinaryZx(AILEmitterCtx Context, Action Emit)
+ {
+ EmitVectorOp(Context, Emit, 2, false);
+ }
+
+ private static void EmitVectorTernaryZx(AILEmitterCtx Context, Action Emit)
+ {
+ EmitVectorOp(Context, Emit, 3, false);
+ }
+
+ private static void EmitVectorOp(AILEmitterCtx Context, Action Emit, int Opers, bool Signed)
+ {
+ if (Opers < 1 || Opers > 3)
+ {
+ throw new ArgumentOutOfRangeException(nameof(Opers));
+ }
+
+ AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp;
+
+ int Bytes = Context.CurrOp.GetBitsCount() >> 3;
+
+ for (int Index = 0; Index < (Bytes >> Op.Size); Index++)
+ {
+ Context.EmitLdvec(Op.Rd);
+ Context.EmitLdc_I4(Index);
+ Context.EmitLdc_I4(Op.Size);
+
+ if (Opers == 3)
+ {
+ EmitVectorExtract(Context, Op.Rd, Index, Signed);
+ }
+
+ if (Opers >= 1)
+ {
+ EmitVectorExtract(Context, Op.Rn, Index, Signed);
+ }
+
+ if (Opers >= 2)
+ {
+ EmitVectorExtract(Context, ((AOpCodeSimdReg)Op).Rm, Index, Signed);
+ }
+
+ Emit();
+
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.InsertVec));
+
+ Context.EmitStvec(Op.Rd);
+ }
+
+ if (Op.RegisterSize == ARegisterSize.SIMD64)
+ {
+ EmitVectorZeroUpper(Context, Op.Rd);
+ }
+ }
+
+ private static void EmitVectorCmp(AILEmitterCtx Context, OpCode ILOp)
+ {
+ AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp;
+
+ int Bytes = Context.CurrOp.GetBitsCount() >> 3;
+
+ ulong SzMask = ulong.MaxValue >> (64 - (8 << Op.Size));
+
+ for (int Index = 0; Index < (Bytes >> Op.Size); Index++)
+ {
+ EmitVectorExtractSx(Context, Op.Rn, Index);
+
+ if (Op is AOpCodeSimdReg BinOp)
+ {
+ EmitVectorExtractSx(Context, BinOp.Rm, Index);
+ }
+ else
+ {
+ Context.EmitLdc_I8(0);
+ }
+
+ AILLabel LblTrue = new AILLabel();
+ AILLabel LblEnd = new AILLabel();
+
+ Context.Emit(ILOp, LblTrue);
+
+ EmitVectorInsert(Context, Op.Rd, Index, Op.Size, 0);
+
+ Context.Emit(OpCodes.Br_S, LblEnd);
+
+ Context.MarkLabel(LblTrue);
+
+ EmitVectorInsert(Context, Op.Rd, Index, Op.Size, (long)SzMask);
+
+ Context.MarkLabel(LblEnd);
+ }
+
+ if (Op.RegisterSize == ARegisterSize.SIMD64)
+ {
+ EmitVectorZeroUpper(Context, Op.Rd);
+ }
+ }
+
+ private static void EmitVectorExtractSx(AILEmitterCtx Context, int Reg, int Index)
+ {
+ EmitVectorExtract(Context, Reg, Index, true);
+ }
+
+ private static void EmitVectorExtractZx(AILEmitterCtx Context, int Reg, int Index)
+ {
+ EmitVectorExtract(Context, Reg, Index, false);
+ }
+
+ private static void EmitVectorExtract(AILEmitterCtx Context, int Reg, int Index, bool Signed)
+ {
+ AOpCodeSimd Op = (AOpCodeSimd)Context.CurrOp;
+
+ Context.EmitLdvec(Reg);
+ Context.EmitLdc_I4(Index);
+ Context.EmitLdc_I4(Op.Size);
+
+ ASoftFallback.EmitCall(Context, Signed
+ ? nameof(ASoftFallback.ExtractSVec)
+ : nameof(ASoftFallback.ExtractVec));
+ }
+
+ private static void EmitVectorZeroLower(AILEmitterCtx Context, int Rd)
+ {
+ EmitVectorInsert(Context, Rd, 0, 3, 0);
+ }
+
+ private static void EmitVectorZeroUpper(AILEmitterCtx Context, int Rd)
+ {
+ EmitVectorInsert(Context, Rd, 1, 3, 0);
+ }
+
+ private static void EmitVectorInsert(AILEmitterCtx Context, int Reg, int Index, int Size, long Value)
+ {
+ Context.EmitLdvec(Reg);
+ Context.EmitLdc_I4(Index);
+ Context.EmitLdc_I4(Size);
+ Context.EmitLdc_I8(Value);
+
+ ASoftFallback.EmitCall(Context, nameof(ASoftFallback.InsertVec));
+
+ Context.EmitStvec(Reg);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Instruction/AInstEmitSystem.cs b/Ryujinx/Cpu/Instruction/AInstEmitSystem.cs
new file mode 100644
index 0000000000..23a0b6b2d3
--- /dev/null
+++ b/Ryujinx/Cpu/Instruction/AInstEmitSystem.cs
@@ -0,0 +1,84 @@
+using ChocolArm64.Decoder;
+using ChocolArm64.State;
+using ChocolArm64.Translation;
+using System.Reflection.Emit;
+
+namespace ChocolArm64.Instruction
+{
+ static partial class AInstEmit
+ {
+ public static void Mrs(AILEmitterCtx Context)
+ {
+ AOpCodeSystem Op = (AOpCodeSystem)Context.CurrOp;
+
+ Context.EmitLdarg(ATranslatedSub.RegistersArgIdx);
+
+ Context.EmitLdc_I4(Op.Op0);
+ Context.EmitLdc_I4(Op.Op1);
+ Context.EmitLdc_I4(Op.CRn);
+ Context.EmitLdc_I4(Op.CRm);
+ Context.EmitLdc_I4(Op.Op2);
+
+ Context.EmitCall(typeof(ARegisters), nameof(ARegisters.GetSystemReg));
+
+ Context.EmitStintzr(Op.Rt);
+ }
+
+ public static void Msr(AILEmitterCtx Context)
+ {
+ AOpCodeSystem Op = (AOpCodeSystem)Context.CurrOp;
+
+ Context.EmitLdarg(ATranslatedSub.RegistersArgIdx);
+
+ Context.EmitLdc_I4(Op.Op0);
+ Context.EmitLdc_I4(Op.Op1);
+ Context.EmitLdc_I4(Op.CRn);
+ Context.EmitLdc_I4(Op.CRm);
+ Context.EmitLdc_I4(Op.Op2);
+ Context.EmitLdintzr(Op.Rt);
+
+ Context.EmitCall(typeof(ARegisters), nameof(ARegisters.SetSystemReg));
+ }
+
+ public static void Nop(AILEmitterCtx Context)
+ {
+ //Do nothing.
+ }
+
+ public static void Sys(AILEmitterCtx Context)
+ {
+ //This instruction is used to do some operations on the CPU like cache invalidation,
+ //address translation and the like.
+ //We treat it as no-op here since we don't have any cache being emulated anyway.
+ AOpCodeSystem Op = (AOpCodeSystem)Context.CurrOp;
+
+ int Id;
+
+ Id = Op.Op2 << 0;
+ Id |= Op.CRm << 3;
+ Id |= Op.CRn << 7;
+ Id |= Op.Op1 << 11;
+
+ switch (Id)
+ {
+ case 0b011_0111_0100_001:
+ {
+ //DC ZVA
+ for (int Offs = 0; Offs < 64; Offs += 8)
+ {
+ Context.EmitLdarg(ATranslatedSub.MemoryArgIdx);
+ Context.EmitLdint(Op.Rt);
+ Context.EmitLdc_I(Offs);
+
+ Context.Emit(OpCodes.Add);
+
+ Context.EmitLdc_I8(0);
+
+ AInstEmitMemoryHelper.EmitWriteCall(Context, 3);
+ }
+ break;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Instruction/AInstEmitter.cs b/Ryujinx/Cpu/Instruction/AInstEmitter.cs
new file mode 100644
index 0000000000..8712a7367c
--- /dev/null
+++ b/Ryujinx/Cpu/Instruction/AInstEmitter.cs
@@ -0,0 +1,6 @@
+using ChocolArm64.Translation;
+
+namespace ChocolArm64.Instruction
+{
+ delegate void AInstEmitter(AILEmitterCtx Context);
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Instruction/ASoftFallback.cs b/Ryujinx/Cpu/Instruction/ASoftFallback.cs
new file mode 100644
index 0000000000..9101231486
--- /dev/null
+++ b/Ryujinx/Cpu/Instruction/ASoftFallback.cs
@@ -0,0 +1,1207 @@
+using ChocolArm64.State;
+using ChocolArm64.Translation;
+using System;
+
+namespace ChocolArm64.Instruction
+{
+ static class ASoftFallback
+ {
+ public static void EmitCall(AILEmitterCtx Context, string Name64, string Name128)
+ {
+ bool IsSimd64 = Context.CurrOp.RegisterSize == ARegisterSize.SIMD64;
+
+ Context.EmitCall(typeof(ASoftFallback), IsSimd64 ? Name64 : Name128);
+ }
+
+ public static void EmitCall(AILEmitterCtx Context, string MthdName)
+ {
+ Context.EmitCall(typeof(ASoftFallback), MthdName);
+ }
+
+ public static uint CountLeadingZeros32(uint Value) => (uint)CountLeadingZeros(Value, 32);
+ public static ulong CountLeadingZeros64(ulong Value) => (ulong)CountLeadingZeros(Value, 64);
+
+ private static ulong CountLeadingZeros(ulong Value, int Size)
+ {
+ int HighBit = Size - 1;
+
+ for (int Bit = HighBit; Bit >= 0; Bit--)
+ {
+ if (((Value >> Bit) & 1) != 0)
+ {
+ return (ulong)(HighBit - Bit);
+ }
+ }
+
+ return (ulong)Size;
+ }
+
+ public static uint ReverseBits32(uint Value)
+ {
+ Value = ((Value & 0xaaaaaaaa) >> 1) | ((Value & 0x55555555) << 1);
+ Value = ((Value & 0xcccccccc) >> 2) | ((Value & 0x33333333) << 2);
+ Value = ((Value & 0xf0f0f0f0) >> 4) | ((Value & 0x0f0f0f0f) << 4);
+ Value = ((Value & 0xff00ff00) >> 8) | ((Value & 0x00ff00ff) << 8);
+
+ return (Value >> 16) | (Value << 16);
+ }
+
+ public static ulong ReverseBits64(ulong Value)
+ {
+ Value = ((Value & 0xaaaaaaaaaaaaaaaa) >> 1) | ((Value & 0x5555555555555555) << 1);
+ Value = ((Value & 0xcccccccccccccccc) >> 2) | ((Value & 0x3333333333333333) << 2);
+ Value = ((Value & 0xf0f0f0f0f0f0f0f0) >> 4) | ((Value & 0x0f0f0f0f0f0f0f0f) << 4);
+ Value = ((Value & 0xff00ff00ff00ff00) >> 8) | ((Value & 0x00ff00ff00ff00ff) << 8);
+ Value = ((Value & 0xffff0000ffff0000) >> 16) | ((Value & 0x0000ffff0000ffff) << 16);
+
+ return (Value >> 32) | (Value << 32);
+ }
+
+ public static uint ReverseBytes16_32(uint Value) => (uint)ReverseBytes16_64(Value);
+ public static uint ReverseBytes32_32(uint Value) => (uint)ReverseBytes32_64(Value);
+
+ public static ulong ReverseBytes16_64(ulong Value) => ReverseBytes(Value, RevSize.Rev16);
+ public static ulong ReverseBytes32_64(ulong Value) => ReverseBytes(Value, RevSize.Rev32);
+ public static ulong ReverseBytes64(ulong Value) => ReverseBytes(Value, RevSize.Rev64);
+
+ private enum RevSize
+ {
+ Rev16,
+ Rev32,
+ Rev64
+ }
+
+ private static ulong ReverseBytes(ulong Value, RevSize Size)
+ {
+ Value = ((Value & 0xff00ff00ff00ff00) >> 8) | ((Value & 0x00ff00ff00ff00ff) << 8);
+
+ if (Size == RevSize.Rev16)
+ {
+ return Value;
+ }
+
+ Value = ((Value & 0xffff0000ffff0000) >> 16) | ((Value & 0x0000ffff0000ffff) << 16);
+
+ if (Size == RevSize.Rev32)
+ {
+ return Value;
+ }
+
+ Value = ((Value & 0xffffffff00000000) >> 32) | ((Value & 0x00000000ffffffff) << 32);
+
+ if (Size == RevSize.Rev64)
+ {
+ return Value;
+ }
+
+ throw new ArgumentException(nameof(Size));
+ }
+
+ public static int SatDoubleToInt32(double Value, int FBits = 0)
+ {
+ if (FBits != 0) Value *= Math.Pow(2, FBits);
+
+ return Value > int.MaxValue ? int.MaxValue :
+ Value < int.MinValue ? int.MinValue : (int)Value;
+ }
+
+ public static long SatDoubleToInt64(double Value, int FBits = 0)
+ {
+ if (FBits != 0) Value *= Math.Pow(2, FBits);
+
+ return Value > long.MaxValue ? long.MaxValue :
+ Value < long.MinValue ? long.MinValue : (long)Value;
+ }
+
+ public static uint SatDoubleToUInt32(double Value, int FBits = 0)
+ {
+ if (FBits != 0) Value *= Math.Pow(2, FBits);
+
+ return Value > uint.MaxValue ? uint.MaxValue :
+ Value < uint.MinValue ? uint.MinValue : (uint)Value;
+ }
+
+ public static ulong SatDoubleToUInt64(double Value, int FBits = 0)
+ {
+ if (FBits != 0) Value *= Math.Pow(2, FBits);
+
+ return Value > ulong.MaxValue ? ulong.MaxValue :
+ Value < ulong.MinValue ? ulong.MinValue : (ulong)Value;
+ }
+
+ public static int SatSingleToInt32(float Value, int FBits = 0)
+ {
+ if (FBits != 0) Value *= MathF.Pow(2, FBits);
+
+ return Value > int.MaxValue ? int.MaxValue :
+ Value < int.MinValue ? int.MinValue : (int)Value;
+ }
+
+ public static long SatSingleToInt64(float Value, int FBits = 0)
+ {
+ if (FBits != 0) Value *= MathF.Pow(2, FBits);
+
+ return Value > long.MaxValue ? long.MaxValue :
+ Value < long.MinValue ? long.MinValue : (long)Value;
+ }
+
+ public static uint SatSingleToUInt32(float Value, int FBits = 0)
+ {
+ if (FBits != 0) Value *= MathF.Pow(2, FBits);
+
+ return Value > uint.MaxValue ? uint.MaxValue :
+ Value < uint.MinValue ? uint.MinValue : (uint)Value;
+ }
+
+ public static ulong SatSingleToUInt64(float Value, int FBits = 0)
+ {
+ if (FBits != 0) Value *= MathF.Pow(2, FBits);
+
+ return Value > ulong.MaxValue ? ulong.MaxValue :
+ Value < ulong.MinValue ? ulong.MinValue : (ulong)Value;
+ }
+
+ public static ulong SMulHi128(ulong LHS, ulong RHS)
+ {
+ long LLo = (uint)(LHS >> 0);
+ long LHi = (int)(LHS >> 32);
+ long RLo = (uint)(RHS >> 0);
+ long RHi = (int)(RHS >> 32);
+
+ long LHiRHi = LHi * RHi;
+ long LHiRLo = LHi * RLo;
+ long LLoRHi = LLo * RHi;
+ long LLoRLo = LLo * RLo;
+
+ long Carry = ((uint)LHiRLo + ((uint)LLoRHi + (LLoRLo >> 32))) >> 32;
+
+ long ResHi = LHiRHi + (LHiRLo >> 32) + (LLoRHi >> 32) + Carry;
+
+ return (ulong)ResHi;
+ }
+
+ public static ulong UMulHi128(ulong LHS, ulong RHS)
+ {
+ ulong LLo = (uint)(LHS >> 0);
+ ulong LHi = (uint)(LHS >> 32);
+ ulong RLo = (uint)(RHS >> 0);
+ ulong RHi = (uint)(RHS >> 32);
+
+ ulong LHiRHi = LHi * RHi;
+ ulong LHiRLo = LHi * RLo;
+ ulong LLoRHi = LLo * RHi;
+ ulong LLoRLo = LLo * RLo;
+
+ ulong Carry = ((uint)LHiRLo + ((uint)LLoRHi + (LLoRLo >> 32))) >> 32;
+
+ ulong ResHi = LHiRHi + (LHiRLo >> 32) + (LLoRHi >> 32) + Carry;
+
+ return ResHi;
+ }
+
+ public static AVec Addp_S(AVec Vector, int Size)
+ {
+ ulong Low = ExtractVec(Vector, 0, Size);
+ ulong High = ExtractVec(Vector, 1, Size);
+
+ return InsertVec(new AVec(), 0, Size, Low + High);
+ }
+
+ public static AVec Addp64(AVec LHS, AVec RHS, int Size)
+ {
+ return Addp(LHS, RHS, Size, 8);
+ }
+
+ public static AVec Addp128(AVec LHS, AVec RHS, int Size)
+ {
+ return Addp(LHS, RHS, Size, 16);
+ }
+
+ private static AVec Addp(AVec LHS, AVec RHS, int Size, int Bytes)
+ {
+ AVec Res = new AVec();
+
+ int Elems = Bytes >> Size;
+ int Half = Elems >> 1;
+
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ int Elem = (Index & (Half - 1)) << 1;
+
+ ulong L = Index < Half
+ ? ExtractVec(LHS, Elem + 0, Size)
+ : ExtractVec(RHS, Elem + 0, Size);
+
+ ulong R = Index < Half
+ ? ExtractVec(LHS, Elem + 1, Size)
+ : ExtractVec(RHS, Elem + 1, Size);
+
+ Res = InsertVec(Res, Index, Size, L + R);
+ }
+
+ return Res;
+ }
+
+ public static AVec Bic_Vi64(AVec Res, ulong Imm, int Size)
+ {
+ return Bic_Vi(Res, Imm, Size, 8);
+ }
+
+ public static AVec Bic_Vi128(AVec Res, ulong Imm, int Size)
+ {
+ return Bic_Vi(Res, Imm, Size, 16);
+ }
+
+ private static AVec Bic_Vi(AVec Res, ulong Imm, int Size, int Bytes)
+ {
+ int Elems = Bytes >> Size;
+
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ ulong Value = ExtractVec(Res, Index, Size);
+
+ Res = InsertVec(Res, Index, Size, Value & ~Imm);
+ }
+
+ return Res;
+ }
+
+ public static AVec Cnt64(AVec Vector)
+ {
+ AVec Res = new AVec();
+
+ Res.B0 = (byte)CountSetBits8(Vector.B0);
+ Res.B1 = (byte)CountSetBits8(Vector.B1);
+ Res.B2 = (byte)CountSetBits8(Vector.B2);
+ Res.B3 = (byte)CountSetBits8(Vector.B3);
+ Res.B4 = (byte)CountSetBits8(Vector.B4);
+ Res.B5 = (byte)CountSetBits8(Vector.B5);
+ Res.B6 = (byte)CountSetBits8(Vector.B6);
+ Res.B7 = (byte)CountSetBits8(Vector.B7);
+
+ return Res;
+ }
+
+ public static AVec Cnt128(AVec Vector)
+ {
+ AVec Res = new AVec();
+
+ Res.B0 = (byte)CountSetBits8(Vector.B0);
+ Res.B1 = (byte)CountSetBits8(Vector.B1);
+ Res.B2 = (byte)CountSetBits8(Vector.B2);
+ Res.B3 = (byte)CountSetBits8(Vector.B3);
+ Res.B4 = (byte)CountSetBits8(Vector.B4);
+ Res.B5 = (byte)CountSetBits8(Vector.B5);
+ Res.B6 = (byte)CountSetBits8(Vector.B6);
+ Res.B7 = (byte)CountSetBits8(Vector.B7);
+ Res.B8 = (byte)CountSetBits8(Vector.B8);
+ Res.B9 = (byte)CountSetBits8(Vector.B9);
+ Res.B10 = (byte)CountSetBits8(Vector.B10);
+ Res.B11 = (byte)CountSetBits8(Vector.B11);
+ Res.B12 = (byte)CountSetBits8(Vector.B12);
+ Res.B13 = (byte)CountSetBits8(Vector.B13);
+ Res.B14 = (byte)CountSetBits8(Vector.B14);
+ Res.B15 = (byte)CountSetBits8(Vector.B15);
+
+ return Res;
+ }
+
+ private static int CountSetBits8(byte Value)
+ {
+ return (Value >> 0) & 1 + (Value >> 1) & 1 +
+ (Value >> 2) & 1 + (Value >> 3) & 1 +
+ (Value >> 4) & 1 + (Value >> 5) & 1 +
+ (Value >> 6) & 1 + (Value >> 7);
+ }
+
+ public static AVec Dup_Gp64(ulong Value, int Size)
+ {
+ return Dup_Gp(Value, Size, 8);
+ }
+
+ public static AVec Dup_Gp128(ulong Value, int Size)
+ {
+ return Dup_Gp(Value, Size, 16);
+ }
+
+ private static AVec Dup_Gp(ulong Value, int Size, int Bytes)
+ {
+ AVec Res = new AVec();
+
+ for (int Index = 0; Index < (Bytes >> Size); Index++)
+ {
+ Res = InsertVec(Res, Index, Size, Value);
+ }
+
+ return Res;
+ }
+
+ public static AVec Dup_S(AVec Vector, int Elem, int Size)
+ {
+ return InsertVec(new AVec(), 0, Size, ExtractVec(Vector, Elem, Size));
+ }
+
+ public static AVec Dup_V64(AVec Vector, int Elem, int Size)
+ {
+ return Dup_V(Vector, Elem, Size, 8);
+ }
+
+ public static AVec Dup_V128(AVec Vector, int Elem, int Size)
+ {
+ return Dup_V(Vector, Elem, Size, 16);
+ }
+
+ private static AVec Dup_V(AVec Vector, int Elem, int Size, int Bytes)
+ {
+ AVec Res = new AVec();
+
+ ulong Value = ExtractVec(Vector, Elem, Size);
+
+ for (Elem = 0; Elem < (Bytes >> Size); Elem++)
+ {
+ Res = InsertVec(Res, Elem, Size, Value);
+ }
+
+ return Res;
+ }
+
+ public static AVec Fadd64(AVec LHS, AVec RHS, int Size)
+ {
+ return Fadd(LHS, RHS, Size, 2);
+ }
+
+ public static AVec Fadd128(AVec LHS, AVec RHS, int Size)
+ {
+ return Fadd(LHS, RHS, Size, 4);
+ }
+
+ private static AVec Fadd(AVec LHS, AVec RHS, int Size, int Bytes)
+ {
+ AVec Res = new AVec();
+
+ int Elems = Bytes >> Size;
+
+ if (Size == 0)
+ {
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ float L = LHS.ExtractSingle(Index);
+ float R = RHS.ExtractSingle(Index);
+
+ Res = AVec.InsertSingle(Res, Index, L + R);
+ }
+ }
+ else
+ {
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ double L = LHS.ExtractDouble(Index);
+ double R = RHS.ExtractDouble(Index);
+
+ Res = AVec.InsertDouble(Res, Index, L + R);
+ }
+ }
+
+ return Res;
+ }
+
+ public static AVec Fcvtzs_V64(AVec Vector, int Size)
+ {
+ return Fcvtzs_V(Vector, Size, 2);
+ }
+
+ public static AVec Fcvtzs_V128(AVec Vector, int Size)
+ {
+ return Fcvtzs_V(Vector, Size, 4);
+ }
+
+ private static AVec Fcvtzs_V(AVec Vector, int Size, int Bytes)
+ {
+ AVec Res = new AVec();
+
+ int Elems = Bytes >> Size;
+
+ if (Size == 0)
+ {
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ float Value = Vector.ExtractSingle(Index);
+
+ Res = InsertSVec(Res, Index, Size + 2, SatSingleToInt32(Value));
+ }
+ }
+ else
+ {
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ double Value = Vector.ExtractDouble(Index);
+
+ Res = InsertSVec(Res, Index, Size + 2, SatDoubleToInt64(Value));
+ }
+ }
+
+ return Res;
+ }
+
+ public static AVec Fcvtzu_V_64(AVec Vector, int FBits, int Size)
+ {
+ return Fcvtzu_V(Vector, FBits, Size, 2);
+ }
+
+ public static AVec Fcvtzu_V_128(AVec Vector, int FBits, int Size)
+ {
+ return Fcvtzu_V(Vector, FBits, Size, 4);
+ }
+
+ private static AVec Fcvtzu_V(AVec Vector, int FBits, int Size, int Bytes)
+ {
+ AVec Res = new AVec();
+
+ int Elems = Bytes >> Size;
+
+ if (Size == 0)
+ {
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ float Value = Vector.ExtractSingle(Index);
+
+ Res = InsertVec(Res, Index, Size + 2, SatSingleToUInt32(Value, FBits));
+ }
+ }
+ else
+ {
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ double Value = Vector.ExtractDouble(Index);
+
+ Res = InsertVec(Res, Index, Size + 2, SatDoubleToUInt64(Value, FBits));
+ }
+ }
+
+ return Res;
+ }
+
+ public static AVec Fmla64(AVec Res, AVec LHS, AVec RHS, int Size)
+ {
+ return Fmla(Res, LHS, RHS, Size, 2);
+ }
+
+ public static AVec Fmla128(AVec Res, AVec LHS, AVec RHS, int Size)
+ {
+ return Fmla(Res, LHS, RHS, Size, 4);
+ }
+
+ private static AVec Fmla(AVec Res, AVec LHS, AVec RHS, int Size, int Bytes)
+ {
+ int Elems = Bytes >> Size;
+
+ if (Size == 0)
+ {
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ float L = LHS.ExtractSingle(Index);
+ float R = RHS.ExtractSingle(Index);
+ float Addend = Res.ExtractSingle(Index);
+
+ Res = AVec.InsertSingle(Res, Index, Addend + L * R);
+ }
+ }
+ else
+ {
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ double L = LHS.ExtractDouble(Index);
+ double R = RHS.ExtractDouble(Index);
+ double Addend = Res.ExtractDouble(Index);
+
+ Res = AVec.InsertDouble(Res, Index, Addend + L * R);
+ }
+ }
+
+ return Res;
+ }
+
+ public static AVec Fmla_Ve64(AVec Res, AVec LHS, AVec RHS, int SIdx, int Size)
+ {
+ return Fmla_Ve(Res, LHS, RHS, SIdx, Size, 2);
+ }
+
+ public static AVec Fmla_Ve128(AVec Res, AVec LHS, AVec RHS, int SIdx, int Size)
+ {
+ return Fmla_Ve(Res, LHS, RHS, SIdx, Size, 4);
+ }
+
+ private static AVec Fmla_Ve(AVec Res, AVec LHS, AVec RHS, int SIdx, int Size, int Bytes)
+ {
+ int Elems = Bytes >> Size;
+
+ if (Size == 0)
+ {
+ float R = RHS.ExtractSingle(SIdx);
+
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ float L = LHS.ExtractSingle(Index);
+ float Addend = Res.ExtractSingle(Index);
+
+ Res = AVec.InsertSingle(Res, Index, Addend + L * R);
+ }
+ }
+ else
+ {
+ double R = RHS.ExtractDouble(SIdx);
+
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ double L = LHS.ExtractDouble(Index);
+ double Addend = Res.ExtractDouble(Index);
+
+ Res = AVec.InsertDouble(Res, Index, Addend + L * R);
+ }
+ }
+
+ return Res;
+ }
+
+ public static AVec Fmov_S(ulong Value, int Elem, int Size)
+ {
+ return InsertVec(new AVec(), Elem, Size, Value);
+ }
+
+ public static AVec Fmul64(AVec LHS, AVec RHS, int Size)
+ {
+ return Fmul(LHS, RHS, Size, 2);
+ }
+
+ public static AVec Fmul128(AVec LHS, AVec RHS, int Size)
+ {
+ return Fmul(LHS, RHS, Size, 4);
+ }
+
+ private static AVec Fmul(AVec LHS, AVec RHS, int Size, int Bytes)
+ {
+ AVec Res = new AVec();
+
+ int Elems = Bytes >> Size;
+
+ if (Size == 0)
+ {
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ float L = LHS.ExtractSingle(Index);
+ float R = RHS.ExtractSingle(Index);
+
+ Res = AVec.InsertSingle(Res, Index, L * R);
+ }
+ }
+ else
+ {
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ double L = LHS.ExtractDouble(Index);
+ double R = RHS.ExtractDouble(Index);
+
+ Res = AVec.InsertDouble(Res, Index, L * R);
+ }
+ }
+
+ return Res;
+ }
+
+ public static AVec Fmul_Ve64(AVec LHS, AVec RHS, int SIdx, int Size)
+ {
+ return Fmul_Ve(LHS, RHS, SIdx, Size, 2);
+ }
+
+ public static AVec Fmul_Ve128(AVec LHS, AVec RHS, int SIdx, int Size)
+ {
+ return Fmul_Ve(LHS, RHS, SIdx, Size, 4);
+ }
+
+ private static AVec Fmul_Ve(AVec LHS, AVec RHS, int SIdx, int Size, int Bytes)
+ {
+ AVec Res = new AVec();
+
+ int Elems = Bytes >> Size;
+
+ if (Size == 0)
+ {
+ float R = RHS.ExtractSingle(SIdx);
+
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ float L = LHS.ExtractSingle(Index);
+
+ Res = AVec.InsertSingle(Res, Index, L * R);
+ }
+ }
+ else
+ {
+ double R = RHS.ExtractDouble(SIdx);
+
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ double L = LHS.ExtractDouble(Index);
+
+ Res = AVec.InsertDouble(Res, Index, L * R);
+ }
+ }
+
+ return Res;
+ }
+
+ public static AVec Fsub64(AVec LHS, AVec RHS, int Size)
+ {
+ return Fsub(LHS, RHS, Size, 2);
+ }
+
+ public static AVec Fsub128(AVec LHS, AVec RHS, int Size)
+ {
+ return Fsub(LHS, RHS, Size, 4);
+ }
+
+ private static AVec Fsub(AVec LHS, AVec RHS, int Size, int Bytes)
+ {
+ AVec Res = new AVec();
+
+ int Elems = Bytes >> Size;
+
+ if (Size == 0)
+ {
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ float L = LHS.ExtractSingle(Index);
+ float R = RHS.ExtractSingle(Index);
+
+ Res = AVec.InsertSingle(Res, Index, L - R);
+ }
+ }
+ else
+ {
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ double L = LHS.ExtractDouble(Index);
+ double R = RHS.ExtractDouble(Index);
+
+ Res = AVec.InsertDouble(Res, Index, L - R);
+ }
+ }
+
+ return Res;
+ }
+
+ public static AVec Ins_Gp(AVec Res, ulong Value, int Elem, int Size)
+ {
+ return InsertVec(Res, Elem, Size, Value);
+ }
+
+ public static AVec Ins_V(AVec Res, AVec Value, int Src, int Dst, int Size)
+ {
+ return InsertVec(Res, Dst, Size, ExtractVec(Value, Src, Size));;
+ }
+
+ public static AVec Orr_Vi64(AVec Res, ulong Imm, int Size)
+ {
+ return Orr_Vi(Res, Imm, Size, 8);
+ }
+
+ public static AVec Orr_Vi128(AVec Res, ulong Imm, int Size)
+ {
+ return Orr_Vi(Res, Imm, Size, 16);
+ }
+
+ private static AVec Orr_Vi(AVec Res, ulong Imm, int Size, int Bytes)
+ {
+ int Elems = Bytes >> Size;
+
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ ulong Value = ExtractVec(Res, Index, Size);
+
+ Res = InsertVec(Res, Index, Size, Value | Imm);
+ }
+
+ return Res;
+ }
+
+ public static AVec Saddw(AVec LHS, AVec RHS, int Size)
+ {
+ return Saddw_(LHS, RHS, Size, false);
+ }
+
+ public static AVec Saddw2(AVec LHS, AVec RHS, int Size)
+ {
+ return Saddw_(LHS, RHS, Size, true);
+ }
+
+ private static AVec Saddw_(AVec LHS, AVec RHS, int Size, bool High)
+ {
+ AVec Res = new AVec();
+
+ int Elems = 8 >> Size;
+ int Part = High ? Elems : 0;
+
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ long L = ExtractSVec(LHS, Index, Size + 1);
+ long R = ExtractSVec(RHS, Index + Part, Size);
+
+ Res = InsertSVec(Res, Index, Size + 1, L + R);
+ }
+
+ return Res;
+ }
+
+ public static AVec Scvtf_V64(AVec Vector, int Size)
+ {
+ return Scvtf_V(Vector, Size, 2);
+ }
+
+ public static AVec Scvtf_V128(AVec Vector, int Size)
+ {
+ return Scvtf_V(Vector, Size, 4);
+ }
+
+ private static AVec Scvtf_V(AVec Vector, int Size, int Bytes)
+ {
+ AVec Res = new AVec();
+
+ int Elems = Bytes >> Size;
+
+ if (Size == 0)
+ {
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ int Value = (int)ExtractSVec(Vector, Index, Size + 2);
+
+ Res = AVec.InsertSingle(Res, Index, Value);
+ }
+ }
+ else
+ {
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ long Value = ExtractSVec(Vector, Index, Size + 2);
+
+ Res = AVec.InsertDouble(Res, Index, Value);
+ }
+ }
+
+ return Res;
+ }
+
+ public static AVec Shl64(AVec Vector, int Shift, int Size)
+ {
+ return Shl(Vector, Shift, Size, 8);
+ }
+
+ public static AVec Shl128(AVec Vector, int Shift, int Size)
+ {
+ return Shl(Vector, Shift, Size, 16);
+ }
+
+ private static AVec Shl(AVec Vector, int Shift, int Size, int Bytes)
+ {
+ AVec Res = new AVec();
+
+ int Elems = Bytes >> Size;
+
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ ulong Value = ExtractVec(Vector, Index, Size);
+
+ Res = InsertVec(Res, Index, Size, Value << Shift);
+ }
+
+ return Res;
+ }
+
+ public static AVec Sshll(AVec Vector, int Shift, int Size)
+ {
+ return Sshll_(Vector, Shift, Size, false);
+ }
+
+ public static AVec Sshll2(AVec Vector, int Shift, int Size)
+ {
+ return Sshll_(Vector, Shift, Size, true);
+ }
+
+ private static AVec Sshll_(AVec Vector, int Shift, int Size, bool High)
+ {
+ AVec Res = new AVec();
+
+ int Elems = 8 >> Size;
+ int Part = High ? Elems : 0;
+
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ long Value = ExtractSVec(Vector, Index + Part, Size);
+
+ Res = InsertSVec(Res, Index, Size + 1, Value << Shift);
+ }
+
+ return Res;
+ }
+
+ public static AVec Sshr64(AVec Vector, int Shift, int Size)
+ {
+ return Sshr(Vector, Shift, Size, 8);
+ }
+
+ public static AVec Sshr128(AVec Vector, int Shift, int Size)
+ {
+ return Sshr(Vector, Shift, Size, 16);
+ }
+
+ private static AVec Sshr(AVec Vector, int Shift, int Size, int Bytes)
+ {
+ AVec Res = new AVec();
+
+ int Elems = Bytes >> Size;
+
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ long Value = ExtractSVec(Vector, Index, Size);
+
+ Res = InsertSVec(Res, Index, Size, Value >> Shift);
+ }
+
+ return Res;
+ }
+
+ public static AVec Tbl1_V64(AVec Vector, AVec Tb0)
+ {
+ return Tbl(Vector, 8, Tb0);
+ }
+
+ public static AVec Tbl1_V128(AVec Vector, AVec Tb0)
+ {
+ return Tbl(Vector, 16, Tb0);
+ }
+
+ public static AVec Tbl2_V64(AVec Vector, AVec Tb0, AVec Tb1)
+ {
+ return Tbl(Vector, 8, Tb0, Tb1);
+ }
+
+ public static AVec Tbl2_V128(AVec Vector, AVec Tb0, AVec Tb1)
+ {
+ return Tbl(Vector, 16, Tb0, Tb1);
+ }
+
+ public static AVec Tbl3_V64(AVec Vector, AVec Tb0, AVec Tb1, AVec Tb2)
+ {
+ return Tbl(Vector, 8, Tb0, Tb1, Tb2);
+ }
+
+ public static AVec Tbl3_V128(AVec Vector, AVec Tb0, AVec Tb1, AVec Tb2)
+ {
+ return Tbl(Vector, 16, Tb0, Tb1, Tb2);
+ }
+
+ public static AVec Tbl4_V64(AVec Vector, AVec Tb0, AVec Tb1, AVec Tb2, AVec Tb3)
+ {
+ return Tbl(Vector, 8, Tb0, Tb1, Tb2, Tb3);
+ }
+
+ public static AVec Tbl4_V128(AVec Vector, AVec Tb0, AVec Tb1, AVec Tb2, AVec Tb3)
+ {
+ return Tbl(Vector, 16, Tb0, Tb1, Tb2, Tb3);
+ }
+
+ private static AVec Tbl(AVec Vector, int Bytes, params AVec[] Tb)
+ {
+ AVec Res = new AVec();
+
+ byte[] Table = new byte[Tb.Length * 16];
+
+ for (int Index = 0; Index < Tb.Length; Index++)
+ for (int Index2 = 0; Index2 < 16; Index2++)
+ {
+ Table[Index * 16 + Index2] = (byte)ExtractVec(Tb[Index], Index2, 0);
+ }
+
+ for (int Index = 0; Index < Bytes; Index++)
+ {
+ byte TblIdx = (byte)ExtractVec(Vector, Index, 0);
+
+ if (TblIdx < Table.Length)
+ {
+ Res = InsertVec(Res, Index, 0, Table[TblIdx]);
+ }
+ }
+
+ return Res;
+ }
+
+ public static AVec Uaddlv64(AVec Vector, int Size)
+ {
+ return Uaddlv(Vector, Size, 8);
+ }
+
+ public static AVec Uaddlv128(AVec Vector, int Size)
+ {
+ return Uaddlv(Vector, Size, 16);
+ }
+
+ private static AVec Uaddlv(AVec Vector, int Size, int Bytes)
+ {
+ int Elems = Bytes >> Size;
+
+ ulong Sum = 0;
+
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ Sum += ExtractVec(Vector, Index, Size);
+ }
+
+ return InsertVec(new AVec(), 0, 3, Sum);
+ }
+
+ public static AVec Uaddw(AVec LHS, AVec RHS, int Size)
+ {
+ return Uaddw_(LHS, RHS, Size, false);
+ }
+
+ public static AVec Uaddw2(AVec LHS, AVec RHS, int Size)
+ {
+ return Uaddw_(LHS, RHS, Size, true);
+ }
+
+ private static AVec Uaddw_(AVec LHS, AVec RHS, int Size, bool High)
+ {
+ AVec Res = new AVec();
+
+ int Elems = 8 >> Size;
+ int Part = High ? Elems : 0;
+
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ ulong L = ExtractVec(LHS, Index, Size + 1);
+ ulong R = ExtractVec(RHS, Index + Part, Size);
+
+ Res = InsertVec(Res, Index, Size + 1, L + R);
+ }
+
+ return Res;
+ }
+
+ public static AVec Ucvtf_V_F(AVec Vector)
+ {
+ return new AVec()
+ {
+ S0 = (uint)Vector.W0,
+ S1 = (uint)Vector.W1,
+ S2 = (uint)Vector.W2,
+ S3 = (uint)Vector.W3
+ };
+ }
+
+ public static AVec Ucvtf_V_D(AVec Vector)
+ {
+ return new AVec()
+ {
+ D0 = (ulong)Vector.X0,
+ D1 = (ulong)Vector.X1
+ };
+ }
+
+ public static AVec Ushll(AVec Vector, int Shift, int Size)
+ {
+ return Ushll_(Vector, Shift, Size, false);
+ }
+
+ public static AVec Ushll2(AVec Vector, int Shift, int Size)
+ {
+ return Ushll_(Vector, Shift, Size, true);
+ }
+
+ private static AVec Ushll_(AVec Vector, int Shift, int Size, bool High)
+ {
+ AVec Res = new AVec();
+
+ int Elems = 8 >> Size;
+ int Part = High ? Elems : 0;
+
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ ulong Value = ExtractVec(Vector, Index + Part, Size);
+
+ Res = InsertVec(Res, Index, Size + 1, Value << Shift);
+ }
+
+ return Res;
+ }
+
+ public static AVec Ushr64(AVec Vector, int Shift, int Size)
+ {
+ return Ushr(Vector, Shift, Size, 8);
+ }
+
+ public static AVec Ushr128(AVec Vector, int Shift, int Size)
+ {
+ return Ushr(Vector, Shift, Size, 16);
+ }
+
+ private static AVec Ushr(AVec Vector, int Shift, int Size, int Bytes)
+ {
+ AVec Res = new AVec();
+
+ int Elems = Bytes >> Size;
+
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ ulong Value = ExtractVec(Vector, Index, Size);
+
+ Res = InsertVec(Res, Index, Size, Value >> Shift);
+ }
+
+ return Res;
+ }
+
+ public static AVec Usra64(AVec Res, AVec Vector, int Shift, int Size)
+ {
+ return Usra(Res, Vector, Shift, Size, 8);
+ }
+
+ public static AVec Usra128(AVec Res, AVec Vector, int Shift, int Size)
+ {
+ return Usra(Res, Vector, Shift, Size, 16);
+ }
+
+ private static AVec Usra(AVec Res, AVec Vector, int Shift, int Size, int Bytes)
+ {
+ int Elems = Bytes >> Size;
+
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ ulong Value = ExtractVec(Vector, Index, Size);
+ ulong Addend = ExtractVec(Res, Index, Size);
+
+ Res = InsertVec(Res, Index, Size, Addend + (Value >> Shift));
+ }
+
+ return Res;
+ }
+
+ public static AVec Uzp1_V64(AVec LHS, AVec RHS, int Size)
+ {
+ return Uzp(LHS, RHS, Size, 0, 8);
+ }
+
+ public static AVec Uzp1_V128(AVec LHS, AVec RHS, int Size)
+ {
+ return Uzp(LHS, RHS, Size, 0, 16);
+ }
+
+ public static AVec Uzp2_V64(AVec LHS, AVec RHS, int Size)
+ {
+ return Uzp(LHS, RHS, Size, 1, 8);
+ }
+
+ public static AVec Uzp2_V128(AVec LHS, AVec RHS, int Size)
+ {
+ return Uzp(LHS, RHS, Size, 1, 16);
+ }
+
+ private static AVec Uzp(AVec LHS, AVec RHS, int Size, int Part, int Bytes)
+ {
+ AVec Res = new AVec();
+
+ int Elems = Bytes >> Size;
+ int Half = Elems >> 1;
+
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ int Elem = (Index & (Half - 1)) << 1;
+
+ ulong Value = Index < Half
+ ? ExtractVec(LHS, Elem + Part, Size)
+ : ExtractVec(RHS, Elem + Part, Size);
+
+ Res = InsertVec(Res, Index, Size, Value);
+ }
+
+ return Res;
+ }
+
+ public static AVec Xtn(AVec Vector, int Size)
+ {
+ return Xtn_(Vector, Size, false);
+ }
+
+ public static AVec Xtn2(AVec Vector, int Size)
+ {
+ return Xtn_(Vector, Size, true);
+ }
+
+ private static AVec Xtn_(AVec Vector, int Size, bool High)
+ {
+ AVec Res = new AVec();
+
+ int Elems = 8 >> Size;
+ int Part = High ? Elems : 0;
+
+ for (int Index = 0; Index < Elems; Index++)
+ {
+ ulong Value = ExtractVec(Vector, Index, Size + 1);
+
+ Res = InsertVec(Res, Index + Part, Size, Value);
+ }
+
+ return Res;
+ }
+
+ public static ulong ExtractVec(AVec Vector, int Index, int Size)
+ {
+ switch (Size)
+ {
+ case 0: return Vector.ExtractByte(Index);
+ case 1: return Vector.ExtractUInt16(Index);
+ case 2: return Vector.ExtractUInt32(Index);
+ case 3: return Vector.ExtractUInt64(Index);
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(Size));
+ }
+
+ public static long ExtractSVec(AVec Vector, int Index, int Size)
+ {
+ switch (Size)
+ {
+ case 0: return (sbyte)Vector.ExtractByte(Index);
+ case 1: return (short)Vector.ExtractUInt16(Index);
+ case 2: return (int)Vector.ExtractUInt32(Index);
+ case 3: return (long)Vector.ExtractUInt64(Index);
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(Size));
+ }
+
+ public static AVec InsertVec(AVec Vector, int Index, int Size, ulong Value)
+ {
+ switch (Size)
+ {
+ case 0: return AVec.InsertByte(Vector, Index, (byte)Value);
+ case 1: return AVec.InsertUInt16(Vector, Index, (ushort)Value);
+ case 2: return AVec.InsertUInt32(Vector, Index, (uint)Value);
+ case 3: return AVec.InsertUInt64(Vector, Index, (ulong)Value);
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(Size));
+ }
+
+ public static AVec InsertSVec(AVec Vector, int Index, int Size, long Value)
+ {
+ switch (Size)
+ {
+ case 0: return AVec.InsertByte(Vector, Index, (byte)Value);
+ case 1: return AVec.InsertUInt16(Vector, Index, (ushort)Value);
+ case 2: return AVec.InsertUInt32(Vector, Index, (uint)Value);
+ case 3: return AVec.InsertUInt64(Vector, Index, (ulong)Value);
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(Size));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Memory/AMemory.cs b/Ryujinx/Cpu/Memory/AMemory.cs
new file mode 100644
index 0000000000..4e9bd53fcc
--- /dev/null
+++ b/Ryujinx/Cpu/Memory/AMemory.cs
@@ -0,0 +1,237 @@
+using ChocolArm64.State;
+using System;
+using System.Collections.Generic;
+
+namespace ChocolArm64.Memory
+{
+ public unsafe class AMemory
+ {
+ public AMemoryMgr Manager { get; private set; }
+
+ private struct ExMonitor
+ {
+ public long Position { get; private set; }
+
+ private bool ExState;
+
+ public ExMonitor(long Position, bool ExState)
+ {
+ this.Position = Position;
+ this.ExState = ExState;
+ }
+
+ public bool HasExclusiveAccess(long Position)
+ {
+ return this.Position == Position && ExState;
+ }
+
+ public void Reset()
+ {
+ ExState = false;
+ }
+ }
+
+ private Dictionary Monitors;
+
+ private HashSet ExAddrs;
+
+ private byte* RamPtr;
+
+ public AMemory(IntPtr Ram, AMemoryAlloc Allocator)
+ {
+ Manager = new AMemoryMgr(Allocator);
+
+ Monitors = new Dictionary();
+
+ ExAddrs = new HashSet();
+
+ RamPtr = (byte*)Ram;
+ }
+
+ public void RemoveMonitor(int ThreadId)
+ {
+ lock (Monitors)
+ {
+ Monitors.Remove(ThreadId);
+ }
+ }
+
+ public void SetExclusive(ARegisters Registers, long Position)
+ {
+ lock (Monitors)
+ {
+ bool ExState = !ExAddrs.Contains(Position);
+
+ if (ExState)
+ {
+ ExAddrs.Add(Position);
+ }
+
+ ExMonitor Monitor = new ExMonitor(Position, ExState);
+
+ if (!Monitors.TryAdd(Registers.ThreadId, Monitor))
+ {
+ Monitors[Registers.ThreadId] = Monitor;
+ }
+ }
+ }
+
+ public bool TestExclusive(ARegisters Registers, long Position)
+ {
+ lock (Monitors)
+ {
+ if (!Monitors.TryGetValue(Registers.ThreadId, out ExMonitor Monitor))
+ {
+ return false;
+ }
+
+ return Monitor.HasExclusiveAccess(Position);
+ }
+ }
+
+ public void ClearExclusive(ARegisters Registers)
+ {
+ lock (Monitors)
+ {
+ if (Monitors.TryGetValue(Registers.ThreadId, out ExMonitor Monitor))
+ {
+ Monitor.Reset();
+ ExAddrs.Remove(Monitor.Position);
+ }
+ }
+ }
+
+ public sbyte ReadSByte(long Position) => (sbyte)ReadByte (Position);
+ public short ReadInt16(long Position) => (short)ReadUInt16(Position);
+ public int ReadInt32(long Position) => (int)ReadUInt32(Position);
+ public long ReadInt64(long Position) => (long)ReadUInt64(Position);
+
+ public byte ReadByte(long Position)
+ {
+ return *((byte*)(RamPtr + Manager.GetPhys(Position, AMemoryPerm.Read)));
+ }
+
+ public ushort ReadUInt16(long Position)
+ {
+ long PhysPos = Manager.GetPhys(Position, AMemoryPerm.Read);
+
+ if (BitConverter.IsLittleEndian && !IsPageCrossed(Position, 2))
+ {
+ return *((ushort*)(RamPtr + PhysPos));
+ }
+ else
+ {
+ return (ushort)(
+ ReadByte(Position + 0) << 0 |
+ ReadByte(Position + 1) << 8);
+ }
+ }
+
+ public uint ReadUInt32(long Position)
+ {
+ long PhysPos = Manager.GetPhys(Position, AMemoryPerm.Read);
+
+ if (BitConverter.IsLittleEndian && !IsPageCrossed(Position, 4))
+ {
+ return *((uint*)(RamPtr + PhysPos));
+ }
+ else
+ {
+ return (uint)(
+ ReadUInt16(Position + 0) << 0 |
+ ReadUInt16(Position + 2) << 16);
+ }
+ }
+
+ public ulong ReadUInt64(long Position)
+ {
+ long PhysPos = Manager.GetPhys(Position, AMemoryPerm.Read);
+
+ if (BitConverter.IsLittleEndian && !IsPageCrossed(Position, 8))
+ {
+ return *((ulong*)(RamPtr + PhysPos));
+ }
+ else
+ {
+ return
+ (ulong)ReadUInt32(Position + 0) << 0 |
+ (ulong)ReadUInt32(Position + 4) << 32;
+ }
+ }
+
+ public AVec ReadVector128(long Position)
+ {
+ return new AVec()
+ {
+ X0 = ReadUInt64(Position + 0),
+ X1 = ReadUInt64(Position + 8)
+ };
+ }
+
+ public void WriteSByte(long Position, sbyte Value) => WriteByte (Position, (byte)Value);
+ public void WriteInt16(long Position, short Value) => WriteUInt16(Position, (ushort)Value);
+ public void WriteInt32(long Position, int Value) => WriteUInt32(Position, (uint)Value);
+ public void WriteInt64(long Position, long Value) => WriteUInt64(Position, (ulong)Value);
+
+ public void WriteByte(long Position, byte Value)
+ {
+ *((byte*)(RamPtr + Manager.GetPhys(Position, AMemoryPerm.Write))) = Value;
+ }
+
+ public void WriteUInt16(long Position, ushort Value)
+ {
+ long PhysPos = Manager.GetPhys(Position, AMemoryPerm.Write);
+
+ if (BitConverter.IsLittleEndian && !IsPageCrossed(Position, 2))
+ {
+ *((ushort*)(RamPtr + PhysPos)) = Value;
+ }
+ else
+ {
+ WriteByte(Position + 0, (byte)(Value >> 0));
+ WriteByte(Position + 1, (byte)(Value >> 8));
+ }
+ }
+
+ public void WriteUInt32(long Position, uint Value)
+ {
+ long PhysPos = Manager.GetPhys(Position, AMemoryPerm.Write);
+
+ if (BitConverter.IsLittleEndian && !IsPageCrossed(Position, 4))
+ {
+ *((uint*)(RamPtr + PhysPos)) = Value;
+ }
+ else
+ {
+ WriteUInt16(Position + 0, (ushort)(Value >> 0));
+ WriteUInt16(Position + 2, (ushort)(Value >> 16));
+ }
+ }
+
+ public void WriteUInt64(long Position, ulong Value)
+ {
+ long PhysPos = Manager.GetPhys(Position, AMemoryPerm.Write);
+
+ if (BitConverter.IsLittleEndian && !IsPageCrossed(Position, 8))
+ {
+ *((ulong*)(RamPtr + PhysPos)) = Value;
+ }
+ else
+ {
+ WriteUInt32(Position + 0, (uint)(Value >> 0));
+ WriteUInt32(Position + 4, (uint)(Value >> 32));
+ }
+ }
+
+ public void WriteVector128(long Position, AVec Value)
+ {
+ WriteUInt64(Position + 0, Value.X0);
+ WriteUInt64(Position + 8, Value.X1);
+ }
+
+ private bool IsPageCrossed(long Position, int Size)
+ {
+ return (Position & AMemoryMgr.PageMask) + Size > AMemoryMgr.PageSize;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Memory/AMemoryAlloc.cs b/Ryujinx/Cpu/Memory/AMemoryAlloc.cs
new file mode 100644
index 0000000000..b11e779314
--- /dev/null
+++ b/Ryujinx/Cpu/Memory/AMemoryAlloc.cs
@@ -0,0 +1,35 @@
+using ChocolArm64.Exceptions;
+
+namespace ChocolArm64.Memory
+{
+ public class AMemoryAlloc
+ {
+ private long PhysPos;
+
+ public long Alloc(long Size)
+ {
+ long Position = PhysPos;
+
+ Size = AMemoryHelper.PageRoundUp(Size);
+
+ PhysPos += Size;
+
+ if (PhysPos > AMemoryMgr.RamSize || PhysPos < 0)
+ {
+ throw new VmmOutOfMemoryException(Size);
+ }
+
+ return Position;
+ }
+
+ public void Free(long Position)
+ {
+ //TODO
+ }
+
+ public long GetFreeMem()
+ {
+ return AMemoryMgr.RamSize - PhysPos;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Memory/AMemoryHelper.cs b/Ryujinx/Cpu/Memory/AMemoryHelper.cs
new file mode 100644
index 0000000000..219aeebf9e
--- /dev/null
+++ b/Ryujinx/Cpu/Memory/AMemoryHelper.cs
@@ -0,0 +1,73 @@
+using System.IO;
+using System.Text;
+
+namespace ChocolArm64.Memory
+{
+ public static class AMemoryHelper
+ {
+ public static void FillWithZeros(AMemory Memory, long Position, int Size)
+ {
+ int Size8 = Size & ~(8 - 1);
+
+ for (int Offs = 0; Offs < Size8; Offs += 8)
+ {
+ Memory.WriteInt64(Position + Offs, 0);
+ }
+
+ for (int Offs = Size8; Offs < (Size - Size8); Offs++)
+ {
+ Memory.WriteByte(Position + Offs, 0);
+ }
+ }
+
+ public static byte[] ReadBytes(AMemory Memory, long Position, int Size)
+ {
+ byte[] Data = new byte[Size];
+
+ for (int Offs = 0; Offs < Size; Offs++)
+ {
+ Data[Offs] = (byte)Memory.ReadByte(Position + Offs);
+ }
+
+ return Data;
+ }
+
+ public static void WriteBytes(AMemory Memory, long Position, byte[] Data)
+ {
+ for (int Offs = 0; Offs < Data.Length; Offs++)
+ {
+ Memory.WriteByte(Position + Offs, Data[Offs]);
+ }
+ }
+
+ public static string ReadAsciiString(AMemory Memory, long Position, int MaxSize = -1)
+ {
+ using (MemoryStream MS = new MemoryStream())
+ {
+ for (int Offs = 0; Offs < MaxSize || MaxSize == -1; Offs++)
+ {
+ byte Value = (byte)Memory.ReadByte(Position + Offs);
+
+ if (Value == 0)
+ {
+ break;
+ }
+
+ MS.WriteByte(Value);
+ }
+
+ return Encoding.ASCII.GetString(MS.ToArray());
+ }
+ }
+
+ public static long PageRoundUp(long Value)
+ {
+ return (Value + AMemoryMgr.PageMask) & ~AMemoryMgr.PageMask;
+ }
+
+ public static long PageRoundDown(long Value)
+ {
+ return Value & ~AMemoryMgr.PageMask;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Memory/AMemoryMapInfo.cs b/Ryujinx/Cpu/Memory/AMemoryMapInfo.cs
new file mode 100644
index 0000000000..8ba6c25e60
--- /dev/null
+++ b/Ryujinx/Cpu/Memory/AMemoryMapInfo.cs
@@ -0,0 +1,19 @@
+namespace ChocolArm64.Memory
+{
+ public struct AMemoryMapInfo
+ {
+ public long Position { get; private set; }
+ public long Size { get; private set; }
+ public int Type { get; private set; }
+
+ public AMemoryPerm Perm { get; private set; }
+
+ public AMemoryMapInfo(long Position, long Size, int Type, AMemoryPerm Perm)
+ {
+ this.Position = Position;
+ this.Size = Size;
+ this.Type = Type;
+ this.Perm = Perm;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Memory/AMemoryMgr.cs b/Ryujinx/Cpu/Memory/AMemoryMgr.cs
new file mode 100644
index 0000000000..4d995469fe
--- /dev/null
+++ b/Ryujinx/Cpu/Memory/AMemoryMgr.cs
@@ -0,0 +1,336 @@
+using ChocolArm64.Exceptions;
+using System;
+using System.Runtime.CompilerServices;
+
+namespace ChocolArm64.Memory
+{
+ public class AMemoryMgr
+ {
+ public const long AddrSize = 1L << 36;
+ public const long RamSize = 2L * 1024 * 1024 * 1024;
+
+ private const int PTLvl0Bits = 11;
+ private const int PTLvl1Bits = 13;
+ 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 + PTLvl0Bits;
+ private const int PTLvl1Bit = PTPageBits;
+
+ private AMemoryAlloc Allocator;
+
+ private enum PTMap
+ {
+ Unmapped,
+ Physical,
+ Mirror
+ }
+
+ private struct PTEntry
+ {
+ public long Position;
+ public int Type;
+
+ public PTMap Map;
+ public AMemoryPerm Perm;
+
+ public PTEntry(long Position, int Type, PTMap Map, AMemoryPerm Perm)
+ {
+ this.Position = Position;
+ this.Type = Type;
+ this.Map = Map;
+ this.Perm = Perm;
+ }
+ }
+
+ private PTEntry[][] PageTable;
+
+ private bool IsHeapInitialized;
+
+ public long HeapAddr { get; private set; }
+ public int HeapSize { get; private set; }
+
+ public AMemoryMgr(AMemoryAlloc Allocator)
+ {
+ this.Allocator = Allocator;
+
+ PageTable = new PTEntry[PTLvl0Size][];
+ }
+
+ public long GetTotalMemorySize()
+ {
+ return Allocator.GetFreeMem() + GetUsedMemorySize();
+ }
+
+ public long GetUsedMemorySize()
+ {
+ long Size = 0;
+
+ for (int L0 = 0; L0 < PageTable.Length; L0++)
+ {
+ if (PageTable[L0] == null)
+ {
+ continue;
+ }
+
+ for (int L1 = 0; L1 < PageTable[L0].Length; L1++)
+ {
+ Size += PageTable[L0][L1].Map != PTMap.Unmapped ? PageSize : 0;
+ }
+ }
+
+ return Size;
+ }
+
+ public bool SetHeapAddr(long Position)
+ {
+ if (!IsHeapInitialized)
+ {
+ HeapAddr = Position;
+
+ IsHeapInitialized = true;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public void SetHeapSize(int Size, int Type)
+ {
+ //TODO: Return error when theres no enough space to allocate heap.
+ Size = (int)AMemoryHelper.PageRoundUp(Size);
+
+ long Position = HeapAddr;
+
+ if ((ulong)Size < (ulong)HeapSize)
+ {
+ //Try to free now free area if size is smaller than old size.
+ Position += Size;
+
+ while ((ulong)Size < (ulong)HeapSize)
+ {
+ Allocator.Free(GetPhys(Position, AMemoryPerm.None));
+
+ Position += PageSize;
+ }
+ }
+ else
+ {
+ //Allocate extra needed size.
+ Position += HeapSize;
+ Size -= HeapSize;
+
+ MapPhys(Position, Size, Type, AMemoryPerm.RW);
+ }
+
+ HeapSize = Size;
+ }
+
+ public bool MapPhys(long Src, long Dst, long Size, int Type, AMemoryPerm Perm)
+ {
+ Src = AMemoryHelper.PageRoundDown(Src);
+ Dst = AMemoryHelper.PageRoundDown(Dst);
+
+ Size = AMemoryHelper.PageRoundUp(Size);
+
+ if (Dst < 0 || Dst + Size >= RamSize)
+ {
+ return false;
+ }
+
+ long PagesCount = Size / PageSize;
+
+ while (PagesCount-- > 0)
+ {
+ SetPTEntry(Src, new PTEntry(Dst, Type, PTMap.Physical, Perm));
+
+ Src += PageSize;
+ Dst += PageSize;
+ }
+
+ return true;
+ }
+
+ public void MapPhys(long Position, long Size, int Type, AMemoryPerm Perm)
+ {
+ while (Size > 0)
+ {
+ if (!HasPTEntry(Position))
+ {
+ long PhysPos = Allocator.Alloc(PageSize);
+
+ SetPTEntry(Position, new PTEntry(PhysPos, Type, PTMap.Physical, Perm));
+ }
+
+ long CPgSize = PageSize - (Position & PageMask);
+
+ Position += CPgSize;
+ Size -= CPgSize;
+ }
+ }
+
+ public void MapMirror(long Src, long Dst, long Size, int Type)
+ {
+ Src = AMemoryHelper.PageRoundDown(Src);
+ Dst = AMemoryHelper.PageRoundDown(Dst);
+
+ Size = AMemoryHelper.PageRoundUp(Size);
+
+ long PagesCount = Size / PageSize;
+
+ while (PagesCount-- > 0)
+ {
+ PTEntry Entry = GetPTEntry(Src);
+
+ Entry.Type = Type;
+ Entry.Map = PTMap.Mirror;
+ Entry.Position = Dst;
+
+ SetPTEntry(Src, Entry);
+
+ Src += PageSize;
+ Dst += PageSize;
+ }
+ }
+
+ public void Reprotect(long Position, long Size, AMemoryPerm Perm)
+ {
+ Position = AMemoryHelper.PageRoundDown(Position);
+
+ Size = AMemoryHelper.PageRoundUp(Size);
+
+ long PagesCount = Size / PageSize;
+
+ while (PagesCount-- > 0)
+ {
+ PTEntry Entry = GetPTEntry(Position);
+
+ Entry.Perm = Perm;
+
+ SetPTEntry(Position, Entry);
+
+ Position += PageSize;
+ }
+ }
+
+ public AMemoryMapInfo GetMapInfo(long Position)
+ {
+ Position = AMemoryHelper.PageRoundDown(Position);
+
+ PTEntry BaseEntry = GetPTEntry(Position);
+
+ bool IsSameSegment(long Pos)
+ {
+ PTEntry Entry = GetPTEntry(Pos);
+
+ return Entry.Type == BaseEntry.Type &&
+ Entry.Map == BaseEntry.Map &&
+ Entry.Perm == BaseEntry.Perm;
+ }
+
+ long Start = Position;
+ long End = Position + PageSize;
+
+ while (Start > 0 && IsSameSegment(Start - PageSize))
+ {
+ Start -= PageSize;
+ }
+
+ while (End < AddrSize && IsSameSegment(End))
+ {
+ End += PageSize;
+ }
+
+ long Size = End - Start;
+
+ return new AMemoryMapInfo(Start, Size, BaseEntry.Type, BaseEntry.Perm);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public long GetPhys(long Position, AMemoryPerm Perm)
+ {
+ if (!HasPTEntry(Position))
+ {
+ if (Position < 0x08000000)
+ {
+ Console.WriteLine($"HACK: Ignoring bad access at {Position:x16}");
+
+ return 0;
+ }
+
+ throw new VmmPageFaultException(Position);
+ }
+
+ PTEntry Entry = GetPTEntry(Position);
+
+ long AbsPos = Entry.Position + (Position & PageMask);
+
+ if (Entry.Map == PTMap.Mirror)
+ {
+ return GetPhys(AbsPos, Perm);
+ }
+
+ if (Entry.Map == PTMap.Unmapped)
+ {
+ throw new VmmPageFaultException(Position);
+ }
+
+ return AbsPos;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private bool HasPTEntry(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].Map != PTMap.Unmapped;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private PTEntry GetPTEntry(long Position)
+ {
+ long L0 = (Position >> PTLvl0Bit) & PTLvl0Mask;
+ long L1 = (Position >> PTLvl1Bit) & PTLvl1Mask;
+
+ if (PageTable[L0] == null)
+ {
+ return default(PTEntry);
+ }
+
+ return PageTable[L0][L1];
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void SetPTEntry(long Position, PTEntry Entry)
+ {
+ long L0 = (Position >> PTLvl0Bit) & PTLvl0Mask;
+ long L1 = (Position >> PTLvl1Bit) & PTLvl1Mask;
+
+ if (PageTable[L0] == null)
+ {
+ PageTable[L0] = new PTEntry[PTLvl1Size];
+ }
+
+ PageTable[L0][L1] = Entry;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Memory/AMemoryPerm.cs b/Ryujinx/Cpu/Memory/AMemoryPerm.cs
new file mode 100644
index 0000000000..b425eb94b2
--- /dev/null
+++ b/Ryujinx/Cpu/Memory/AMemoryPerm.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace ChocolArm64.Memory
+{
+ [Flags]
+ public enum AMemoryPerm
+ {
+ None = 0,
+ Read = 1 << 0,
+ Write = 1 << 1,
+ Execute = 1 << 2,
+ RW = Read | Write,
+ RX = Read | Execute
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/State/ACoreType.cs b/Ryujinx/Cpu/State/ACoreType.cs
new file mode 100644
index 0000000000..3fed78cf6e
--- /dev/null
+++ b/Ryujinx/Cpu/State/ACoreType.cs
@@ -0,0 +1,8 @@
+namespace ChocolArm64.State
+{
+ public enum ACoreType
+ {
+ CortexA53,
+ CortexA57
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/State/APState.cs b/Ryujinx/Cpu/State/APState.cs
new file mode 100644
index 0000000000..f55431a661
--- /dev/null
+++ b/Ryujinx/Cpu/State/APState.cs
@@ -0,0 +1,23 @@
+using System;
+
+namespace ChocolArm64.State
+{
+ [Flags]
+ public enum APState
+ {
+ VBit = 28,
+ CBit = 29,
+ ZBit = 30,
+ NBit = 31,
+
+ V = 1 << VBit,
+ C = 1 << CBit,
+ Z = 1 << ZBit,
+ N = 1 << NBit,
+
+ NZ = N | Z,
+ CV = C | V,
+
+ NZCV = NZ | CV
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/State/ARegister.cs b/Ryujinx/Cpu/State/ARegister.cs
new file mode 100644
index 0000000000..5146bc313d
--- /dev/null
+++ b/Ryujinx/Cpu/State/ARegister.cs
@@ -0,0 +1,142 @@
+using System;
+using System.Reflection;
+
+namespace ChocolArm64.State
+{
+ struct ARegister
+ {
+ public int Index;
+
+ public ARegisterType Type;
+
+ public ARegister(int Index, ARegisterType Type)
+ {
+ this.Index = Index;
+ this.Type = Type;
+ }
+
+ public override int GetHashCode()
+ {
+ return (ushort)Index | ((ushort)Type << 16);
+ }
+
+ public override bool Equals(object Obj)
+ {
+ return Obj is ARegister Reg &&
+ Reg.Index == Index &&
+ Reg.Type == Type;
+ }
+
+ public FieldInfo GetField()
+ {
+ switch (Type)
+ {
+ case ARegisterType.Flag: return GetFieldFlag();
+ case ARegisterType.Int: return GetFieldInt();
+ case ARegisterType.Vector: return GetFieldVector();
+ }
+
+ throw new InvalidOperationException();
+ }
+
+ private FieldInfo GetFieldFlag()
+ {
+ switch ((APState)Index)
+ {
+ case APState.VBit: return GetField(nameof(ARegisters.Overflow));
+ case APState.CBit: return GetField(nameof(ARegisters.Carry));
+ case APState.ZBit: return GetField(nameof(ARegisters.Zero));
+ case APState.NBit: return GetField(nameof(ARegisters.Negative));
+ }
+
+ throw new InvalidOperationException();
+ }
+
+ private FieldInfo GetFieldInt()
+ {
+ switch (Index)
+ {
+ case 0: return GetField(nameof(ARegisters.X0));
+ case 1: return GetField(nameof(ARegisters.X1));
+ case 2: return GetField(nameof(ARegisters.X2));
+ case 3: return GetField(nameof(ARegisters.X3));
+ case 4: return GetField(nameof(ARegisters.X4));
+ case 5: return GetField(nameof(ARegisters.X5));
+ case 6: return GetField(nameof(ARegisters.X6));
+ case 7: return GetField(nameof(ARegisters.X7));
+ case 8: return GetField(nameof(ARegisters.X8));
+ case 9: return GetField(nameof(ARegisters.X9));
+ case 10: return GetField(nameof(ARegisters.X10));
+ case 11: return GetField(nameof(ARegisters.X11));
+ case 12: return GetField(nameof(ARegisters.X12));
+ case 13: return GetField(nameof(ARegisters.X13));
+ case 14: return GetField(nameof(ARegisters.X14));
+ case 15: return GetField(nameof(ARegisters.X15));
+ case 16: return GetField(nameof(ARegisters.X16));
+ case 17: return GetField(nameof(ARegisters.X17));
+ case 18: return GetField(nameof(ARegisters.X18));
+ case 19: return GetField(nameof(ARegisters.X19));
+ case 20: return GetField(nameof(ARegisters.X20));
+ case 21: return GetField(nameof(ARegisters.X21));
+ case 22: return GetField(nameof(ARegisters.X22));
+ case 23: return GetField(nameof(ARegisters.X23));
+ case 24: return GetField(nameof(ARegisters.X24));
+ case 25: return GetField(nameof(ARegisters.X25));
+ case 26: return GetField(nameof(ARegisters.X26));
+ case 27: return GetField(nameof(ARegisters.X27));
+ case 28: return GetField(nameof(ARegisters.X28));
+ case 29: return GetField(nameof(ARegisters.X29));
+ case 30: return GetField(nameof(ARegisters.X30));
+ case 31: return GetField(nameof(ARegisters.X31));
+ }
+
+ throw new InvalidOperationException();
+ }
+
+ private FieldInfo GetFieldVector()
+ {
+ switch (Index)
+ {
+ case 0: return GetField(nameof(ARegisters.V0));
+ case 1: return GetField(nameof(ARegisters.V1));
+ case 2: return GetField(nameof(ARegisters.V2));
+ case 3: return GetField(nameof(ARegisters.V3));
+ case 4: return GetField(nameof(ARegisters.V4));
+ case 5: return GetField(nameof(ARegisters.V5));
+ case 6: return GetField(nameof(ARegisters.V6));
+ case 7: return GetField(nameof(ARegisters.V7));
+ case 8: return GetField(nameof(ARegisters.V8));
+ case 9: return GetField(nameof(ARegisters.V9));
+ case 10: return GetField(nameof(ARegisters.V10));
+ case 11: return GetField(nameof(ARegisters.V11));
+ case 12: return GetField(nameof(ARegisters.V12));
+ case 13: return GetField(nameof(ARegisters.V13));
+ case 14: return GetField(nameof(ARegisters.V14));
+ case 15: return GetField(nameof(ARegisters.V15));
+ case 16: return GetField(nameof(ARegisters.V16));
+ case 17: return GetField(nameof(ARegisters.V17));
+ case 18: return GetField(nameof(ARegisters.V18));
+ case 19: return GetField(nameof(ARegisters.V19));
+ case 20: return GetField(nameof(ARegisters.V20));
+ case 21: return GetField(nameof(ARegisters.V21));
+ case 22: return GetField(nameof(ARegisters.V22));
+ case 23: return GetField(nameof(ARegisters.V23));
+ case 24: return GetField(nameof(ARegisters.V24));
+ case 25: return GetField(nameof(ARegisters.V25));
+ case 26: return GetField(nameof(ARegisters.V26));
+ case 27: return GetField(nameof(ARegisters.V27));
+ case 28: return GetField(nameof(ARegisters.V28));
+ case 29: return GetField(nameof(ARegisters.V29));
+ case 30: return GetField(nameof(ARegisters.V30));
+ case 31: return GetField(nameof(ARegisters.V31));
+ }
+
+ throw new InvalidOperationException();
+ }
+
+ private FieldInfo GetField(string Name)
+ {
+ return typeof(ARegisters).GetField(Name);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/State/ARegisterSize.cs b/Ryujinx/Cpu/State/ARegisterSize.cs
new file mode 100644
index 0000000000..144f36b929
--- /dev/null
+++ b/Ryujinx/Cpu/State/ARegisterSize.cs
@@ -0,0 +1,10 @@
+namespace ChocolArm64.State
+{
+ enum ARegisterSize
+ {
+ Int32,
+ Int64,
+ SIMD64,
+ SIMD128
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/State/ARegisterType.cs b/Ryujinx/Cpu/State/ARegisterType.cs
new file mode 100644
index 0000000000..f9776bb7dd
--- /dev/null
+++ b/Ryujinx/Cpu/State/ARegisterType.cs
@@ -0,0 +1,9 @@
+namespace ChocolArm64.State
+{
+ enum ARegisterType
+ {
+ Flag,
+ Int,
+ Vector
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/State/ARegisters.cs b/Ryujinx/Cpu/State/ARegisters.cs
new file mode 100644
index 0000000000..3a424a7f93
--- /dev/null
+++ b/Ryujinx/Cpu/State/ARegisters.cs
@@ -0,0 +1,126 @@
+using System;
+
+namespace ChocolArm64.State
+{
+ public class ARegisters
+ {
+ internal const int LRIndex = 30;
+ internal const int ZRIndex = 31;
+
+ public ulong X0, X1, X2, X3, X4, X5, X6, X7,
+ X8, X9, X10, X11, X12, X13, X14, X15,
+ X16, X17, X18, X19, X20, X21, X22, X23,
+ X24, X25, X26, X27, X28, X29, X30, X31;
+
+ public AVec V0, V1, V2, V3, V4, V5, V6, V7,
+ V8, V9, V10, V11, V12, V13, V14, V15,
+ V16, V17, V18, V19, V20, V21, V22, V23,
+ V24, V25, V26, V27, V28, V29, V30, V31;
+
+ public bool Overflow;
+ public bool Carry;
+ public bool Zero;
+ public bool Negative;
+
+ public int ProcessId;
+ public int ThreadId;
+ public long TlsAddrEl0;
+ public long TlsAddr;
+
+ private int FPCR;
+ private int FPSR;
+
+ public ACoreType CoreType;
+
+ private const ulong A53DczidEl0 = 4;
+ private const ulong A53CtrEl0 = 0x84448004;
+ private const ulong A57CtrEl0 = 0x8444c004;
+
+ private const ulong TicksPerS = 19_200_000;
+ private const ulong TicksPerMS = TicksPerS / 1_000;
+
+ public event EventHandler SvcCall;
+ public event EventHandler Undefined;
+
+ public ulong GetSystemReg(int Op0, int Op1, int CRn, int CRm, int Op2)
+ {
+ switch (PackRegId(Op0, Op1, CRn, CRm, Op2))
+ {
+ case 0b11_011_0000_0000_001: return GetCtrEl0();
+ case 0b11_011_0000_0000_111: return GetDczidEl0();
+ case 0b11_011_0100_0100_000: return (ulong)PackFPCR();
+ case 0b11_011_0100_0100_001: return (ulong)PackFPSR();
+ case 0b11_011_1101_0000_010: return (ulong)TlsAddrEl0;
+ case 0b11_011_1101_0000_011: return (ulong)TlsAddr;
+ case 0b11_011_1110_0000_001: return (ulong)Environment.TickCount * TicksPerMS;
+
+ default: throw new ArgumentException();
+ }
+ }
+
+ public void SetSystemReg(int Op0, int Op1, int CRn, int CRm, int Op2, ulong Value)
+ {
+ switch (PackRegId(Op0, Op1, CRn, CRm, Op2))
+ {
+ case 0b11_011_0100_0100_000: UnpackFPCR((int)Value); break;
+ case 0b11_011_0100_0100_001: UnpackFPSR((int)Value); break;
+ case 0b11_011_1101_0000_010: TlsAddrEl0 = (long)Value; break;
+
+ default: throw new ArgumentException();
+ }
+ }
+
+ private int PackRegId(int Op0, int Op1, int CRn, int CRm, int Op2)
+ {
+ int Id;
+
+ Id = Op2 << 0;
+ Id |= CRm << 3;
+ Id |= CRn << 7;
+ Id |= Op1 << 11;
+ Id |= Op0 << 14;
+
+ return Id;
+ }
+
+ public ulong GetCtrEl0()
+ {
+ return CoreType == ACoreType.CortexA53 ? A53CtrEl0 : A57CtrEl0;
+ }
+
+ public ulong GetDczidEl0()
+ {
+ return A53DczidEl0;
+ }
+
+ public int PackFPCR()
+ {
+ return FPCR; //TODO
+ }
+
+ public int PackFPSR()
+ {
+ return FPSR; //TODO
+ }
+
+ public void UnpackFPCR(int Value)
+ {
+ FPCR = Value;
+ }
+
+ public void UnpackFPSR(int Value)
+ {
+ FPSR = Value;
+ }
+
+ public void OnSvcCall(int Imm)
+ {
+ SvcCall?.Invoke(this, new SvcEventArgs(Imm));
+ }
+
+ public void OnUndefined()
+ {
+ Undefined?.Invoke(this, EventArgs.Empty);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/State/AVec.cs b/Ryujinx/Cpu/State/AVec.cs
new file mode 100644
index 0000000000..f7eb2e221b
--- /dev/null
+++ b/Ryujinx/Cpu/State/AVec.cs
@@ -0,0 +1,243 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace ChocolArm64.State
+{
+ [StructLayout(LayoutKind.Explicit, Size = 16)]
+ public struct AVec
+ {
+ [FieldOffset(0x0)] public byte B0;
+ [FieldOffset(0x1)] public byte B1;
+ [FieldOffset(0x2)] public byte B2;
+ [FieldOffset(0x3)] public byte B3;
+ [FieldOffset(0x4)] public byte B4;
+ [FieldOffset(0x5)] public byte B5;
+ [FieldOffset(0x6)] public byte B6;
+ [FieldOffset(0x7)] public byte B7;
+ [FieldOffset(0x8)] public byte B8;
+ [FieldOffset(0x9)] public byte B9;
+ [FieldOffset(0xa)] public byte B10;
+ [FieldOffset(0xb)] public byte B11;
+ [FieldOffset(0xc)] public byte B12;
+ [FieldOffset(0xd)] public byte B13;
+ [FieldOffset(0xe)] public byte B14;
+ [FieldOffset(0xf)] public byte B15;
+
+ [FieldOffset(0x0)] public ushort H0;
+ [FieldOffset(0x2)] public ushort H1;
+ [FieldOffset(0x4)] public ushort H2;
+ [FieldOffset(0x6)] public ushort H3;
+ [FieldOffset(0x8)] public ushort H4;
+ [FieldOffset(0xa)] public ushort H5;
+ [FieldOffset(0xc)] public ushort H6;
+ [FieldOffset(0xe)] public ushort H7;
+
+ [FieldOffset(0x0)] public uint W0;
+ [FieldOffset(0x4)] public uint W1;
+ [FieldOffset(0x8)] public uint W2;
+ [FieldOffset(0xc)] public uint W3;
+
+ [FieldOffset(0x0)] public float S0;
+ [FieldOffset(0x4)] public float S1;
+ [FieldOffset(0x8)] public float S2;
+ [FieldOffset(0xc)] public float S3;
+
+ [FieldOffset(0x0)] public ulong X0;
+ [FieldOffset(0x8)] public ulong X1;
+
+ [FieldOffset(0x0)] public double D0;
+ [FieldOffset(0x8)] public double D1;
+
+ public byte ExtractByte(int Index)
+ {
+ switch (Index)
+ {
+ case 0: return B0;
+ case 1: return B1;
+ case 2: return B2;
+ case 3: return B3;
+ case 4: return B4;
+ case 5: return B5;
+ case 6: return B6;
+ case 7: return B7;
+ case 8: return B8;
+ case 9: return B9;
+ case 10: return B10;
+ case 11: return B11;
+ case 12: return B12;
+ case 13: return B13;
+ case 14: return B14;
+ case 15: return B15;
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(Index));
+ }
+
+ public ushort ExtractUInt16(int Index)
+ {
+ switch (Index)
+ {
+ case 0: return H0;
+ case 1: return H1;
+ case 2: return H2;
+ case 3: return H3;
+ case 4: return H4;
+ case 5: return H5;
+ case 6: return H6;
+ case 7: return H7;
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(Index));
+ }
+
+ public uint ExtractUInt32(int Index)
+ {
+ switch (Index)
+ {
+ case 0: return W0;
+ case 1: return W1;
+ case 2: return W2;
+ case 3: return W3;
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(Index));
+ }
+
+ public float ExtractSingle(int Index)
+ {
+ switch (Index)
+ {
+ case 0: return S0;
+ case 1: return S1;
+ case 2: return S2;
+ case 3: return S3;
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(Index));
+ }
+
+ public ulong ExtractUInt64(int Index)
+ {
+ switch (Index)
+ {
+ case 0: return X0;
+ case 1: return X1;
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(Index));
+ }
+
+ public double ExtractDouble(int Index)
+ {
+ switch (Index)
+ {
+ case 0: return D0;
+ case 1: return D1;
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(Index));
+ }
+
+ public static AVec InsertByte(AVec Vec, int Index, byte Value)
+ {
+ switch (Index)
+ {
+ case 0: Vec.B0 = Value; break;
+ case 1: Vec.B1 = Value; break;
+ case 2: Vec.B2 = Value; break;
+ case 3: Vec.B3 = Value; break;
+ case 4: Vec.B4 = Value; break;
+ case 5: Vec.B5 = Value; break;
+ case 6: Vec.B6 = Value; break;
+ case 7: Vec.B7 = Value; break;
+ case 8: Vec.B8 = Value; break;
+ case 9: Vec.B9 = Value; break;
+ case 10: Vec.B10 = Value; break;
+ case 11: Vec.B11 = Value; break;
+ case 12: Vec.B12 = Value; break;
+ case 13: Vec.B13 = Value; break;
+ case 14: Vec.B14 = Value; break;
+ case 15: Vec.B15 = Value; break;
+
+ default: throw new ArgumentOutOfRangeException(nameof(Index));
+ }
+
+ return Vec;
+ }
+
+ public static AVec InsertUInt16(AVec Vec, int Index, ushort Value)
+ {
+ switch (Index)
+ {
+ case 0: Vec.H0 = Value; break;
+ case 1: Vec.H1 = Value; break;
+ case 2: Vec.H2 = Value; break;
+ case 3: Vec.H3 = Value; break;
+ case 4: Vec.H4 = Value; break;
+ case 5: Vec.H5 = Value; break;
+ case 6: Vec.H6 = Value; break;
+ case 7: Vec.H7 = Value; break;
+
+ default: throw new ArgumentOutOfRangeException(nameof(Index));
+ }
+
+ return Vec;
+ }
+
+ public static AVec InsertUInt32(AVec Vec, int Index, uint Value)
+ {
+ switch (Index)
+ {
+ case 0: Vec.W0 = Value; break;
+ case 1: Vec.W1 = Value; break;
+ case 2: Vec.W2 = Value; break;
+ case 3: Vec.W3 = Value; break;
+
+ default: throw new ArgumentOutOfRangeException(nameof(Index));
+ }
+
+ return Vec;
+ }
+
+ public static AVec InsertSingle(AVec Vec, int Index, float Value)
+ {
+ switch (Index)
+ {
+ case 0: Vec.S0 = Value; break;
+ case 1: Vec.S1 = Value; break;
+ case 2: Vec.S2 = Value; break;
+ case 3: Vec.S3 = Value; break;
+
+ default: throw new ArgumentOutOfRangeException(nameof(Index));
+ }
+
+ return Vec;
+ }
+
+ public static AVec InsertUInt64(AVec Vec, int Index, ulong Value)
+ {
+ switch (Index)
+ {
+ case 0: Vec.X0 = Value; break;
+ case 1: Vec.X1 = Value; break;
+
+ default: throw new ArgumentOutOfRangeException(nameof(Index));
+ }
+
+ return Vec;
+ }
+
+ public static AVec InsertDouble(AVec Vec, int Index, double Value)
+ {
+ switch (Index)
+ {
+ case 0: Vec.D0 = Value; break;
+ case 1: Vec.D1 = Value; break;
+
+ default: throw new ArgumentOutOfRangeException(nameof(Index));
+ }
+
+ return Vec;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/State/SvcEventArgs.cs b/Ryujinx/Cpu/State/SvcEventArgs.cs
new file mode 100644
index 0000000000..3a43241a64
--- /dev/null
+++ b/Ryujinx/Cpu/State/SvcEventArgs.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace ChocolArm64.State
+{
+ public class SvcEventArgs : EventArgs
+ {
+ public int Id { get; private set; }
+
+ public SvcEventArgs(int Id)
+ {
+ this.Id = Id;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Translation/AILBlock.cs b/Ryujinx/Cpu/Translation/AILBlock.cs
new file mode 100644
index 0000000000..2746e4288d
--- /dev/null
+++ b/Ryujinx/Cpu/Translation/AILBlock.cs
@@ -0,0 +1,65 @@
+using System.Collections.Generic;
+
+namespace ChocolArm64.Translation
+{
+ class AILBlock : IAILEmit
+ {
+ public long IntInputs { get; private set; }
+ public long IntOutputs { get; private set; }
+
+ public long VecInputs { get; private set; }
+ public long VecOutputs { get; private set; }
+
+ public bool HasStateStore { get; private set; }
+
+ public List ILEmitters { get; private set; }
+
+ public AILBlock Next { get; set; }
+ public AILBlock Branch { get; set; }
+
+ public AILBlock()
+ {
+ ILEmitters = new List();
+ }
+
+ public void Add(IAILEmit ILEmitter)
+ {
+ if (ILEmitter is AILOpCodeLoad Ld && AILEmitter.IsRegIndex(Ld.Index))
+ {
+ switch (Ld.IoType & AIoType.Mask)
+ {
+ case AIoType.Flag: IntInputs |= ((1L << Ld.Index) << 32) & ~IntOutputs; break;
+ case AIoType.Int: IntInputs |= (1L << Ld.Index) & ~IntOutputs; break;
+ case AIoType.Vector: VecInputs |= (1L << Ld.Index) & ~VecOutputs; break;
+ }
+ }
+ else if (ILEmitter is AILOpCodeStore St)
+ {
+ if (AILEmitter.IsRegIndex(St.Index))
+ {
+ switch (St.IoType & AIoType.Mask)
+ {
+ case AIoType.Flag: IntOutputs |= (1L << St.Index) << 32; break;
+ case AIoType.Int: IntOutputs |= 1L << St.Index; break;
+ case AIoType.Vector: VecOutputs |= 1L << St.Index; break;
+ }
+ }
+
+ if (St.IoType == AIoType.Fields)
+ {
+ HasStateStore = true;
+ }
+ }
+
+ ILEmitters.Add(ILEmitter);
+ }
+
+ public void Emit(AILEmitter Context)
+ {
+ foreach (IAILEmit ILEmitter in ILEmitters)
+ {
+ ILEmitter.Emit(Context);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Translation/AILConv.cs b/Ryujinx/Cpu/Translation/AILConv.cs
new file mode 100644
index 0000000000..8969dc4e94
--- /dev/null
+++ b/Ryujinx/Cpu/Translation/AILConv.cs
@@ -0,0 +1,113 @@
+using ChocolArm64.State;
+using System;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace ChocolArm64.Translation
+{
+ static class AILConv
+ {
+ public static void EmitConv(AILEmitter Context, Type SrcType, Type TgtType)
+ {
+ if (SrcType == TgtType)
+ {
+ //If both types are equal we don't need to cast anything.
+ return;
+ }
+
+ if (SrcType.IsPrimitive)
+ {
+ if (TgtType == typeof(byte))
+ {
+ Context.Generator.Emit(OpCodes.Conv_U1);
+ }
+ else if (TgtType == typeof(ushort))
+ {
+ Context.Generator.Emit(OpCodes.Conv_U2);
+ }
+ else if (TgtType == typeof(uint))
+ {
+ Context.Generator.Emit(OpCodes.Conv_U4);
+ }
+ else if (TgtType == typeof(ulong))
+ {
+ Context.Generator.Emit(OpCodes.Conv_U8);
+ }
+ else if (TgtType == typeof(float))
+ {
+ Context.Generator.Emit(OpCodes.Conv_R4);
+ }
+ else if (TgtType == typeof(double))
+ {
+ Context.Generator.Emit(OpCodes.Conv_R8);
+ }
+ else if (TgtType == typeof(AVec))
+ {
+ EmitMakeVec(Context, SrcType);
+ }
+ else
+ {
+ throw new ArgumentException(nameof(TgtType));
+ }
+ }
+ else if (SrcType == typeof(AVec))
+ {
+ if (TgtType == typeof(float))
+ {
+ EmitScalarLdfld(Context, nameof(AVec.S0));
+ }
+ else if (TgtType == typeof(double))
+ {
+ EmitScalarLdfld(Context, nameof(AVec.D0));
+ }
+ else if (TgtType == typeof(byte))
+ {
+ EmitScalarLdfld(Context, nameof(AVec.B0));
+ }
+ else if (TgtType == typeof(ushort))
+ {
+ EmitScalarLdfld(Context, nameof(AVec.H0));
+ }
+ else if (TgtType == typeof(uint))
+ {
+ EmitScalarLdfld(Context, nameof(AVec.W0));
+ }
+ else if (TgtType == typeof(ulong))
+ {
+ EmitScalarLdfld(Context, nameof(AVec.X0));
+ }
+ else
+ {
+ throw new ArgumentException(nameof(TgtType));
+ }
+ }
+ else
+ {
+ throw new ArgumentException(nameof(SrcType));
+ }
+ }
+
+ private static void EmitScalarLdfld(AILEmitter Context,string FldName)
+ {
+ Context.Generator.Emit(OpCodes.Ldfld, typeof(AVec).GetField(FldName));
+ }
+
+ private static void EmitMakeVec(AILEmitter Context, Type SrcType)
+ {
+ string MthdName = nameof(MakeScalar);
+
+ Type[] MthdTypes = new Type[] { SrcType };
+
+ MethodInfo MthdInfo = typeof(AILConv).GetMethod(MthdName, MthdTypes);
+
+ Context.Generator.Emit(OpCodes.Call, MthdInfo);
+ }
+
+ public static AVec MakeScalar(byte Value) => new AVec { B0 = Value };
+ public static AVec MakeScalar(ushort Value) => new AVec { H0 = Value };
+ public static AVec MakeScalar(uint Value) => new AVec { W0 = Value };
+ public static AVec MakeScalar(float Value) => new AVec { S0 = Value };
+ public static AVec MakeScalar(ulong Value) => new AVec { X0 = Value };
+ public static AVec MakeScalar(double Value) => new AVec { D0 = Value };
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Translation/AILEmitter.cs b/Ryujinx/Cpu/Translation/AILEmitter.cs
new file mode 100644
index 0000000000..0619149c3d
--- /dev/null
+++ b/Ryujinx/Cpu/Translation/AILEmitter.cs
@@ -0,0 +1,190 @@
+using ChocolArm64.Decoder;
+using ChocolArm64.State;
+using System;
+using System.Collections.Generic;
+using System.Reflection.Emit;
+
+namespace ChocolArm64.Translation
+{
+ class AILEmitter
+ {
+ public ALocalAlloc LocalAlloc { get; private set; }
+
+ public ILGenerator Generator { get; private set; }
+
+ private Dictionary Locals;
+
+ private AILBlock[] ILBlocks;
+
+ private AILBlock Root;
+
+ private ATranslatedSub Subroutine;
+
+ private string SubName;
+
+ private int LocalsCount;
+
+ public AILEmitter(ABlock[] Graph, ABlock Root, string SubName)
+ {
+ this.SubName = SubName;
+
+ Locals = new Dictionary();
+
+ ILBlocks = new AILBlock[Graph.Length];
+
+ AILBlock GetBlock(int Index)
+ {
+ if (Index < 0 || Index >= ILBlocks.Length)
+ {
+ return null;
+ }
+
+ if (ILBlocks[Index] == null)
+ {
+ ILBlocks[Index] = new AILBlock();
+ }
+
+ return ILBlocks[Index];
+ }
+
+ for (int Index = 0; Index < ILBlocks.Length; Index++)
+ {
+ AILBlock Block = GetBlock(Index);
+
+ Block.Next = GetBlock(Array.IndexOf(Graph, Graph[Index].Next));
+ Block.Branch = GetBlock(Array.IndexOf(Graph, Graph[Index].Branch));
+ }
+
+ this.Root = ILBlocks[Array.IndexOf(Graph, Root)];
+ }
+
+ public ATranslatedSub GetSubroutine()
+ {
+ LocalAlloc = new ALocalAlloc(ILBlocks, Root);
+
+ InitSubroutine();
+ InitLocals();
+
+ foreach (AILBlock ILBlock in ILBlocks)
+ {
+ ILBlock.Emit(this);
+ }
+
+ return Subroutine;
+ }
+
+ public AILBlock GetILBlock(int Index) => ILBlocks[Index];
+
+ private void InitLocals()
+ {
+ int ParamsStart = ATranslatedSub.FixedArgTypes.Length;
+
+ Locals = new Dictionary();
+
+ for (int Index = 0; Index < Subroutine.Params.Count; Index++)
+ {
+ ARegister Reg = Subroutine.Params[Index];
+
+ Generator.EmitLdarg(Index + ParamsStart);
+
+ AILConv.EmitConv(this, GetFieldType(Reg.Type), GetLocalType(Reg));
+
+ Generator.EmitStloc(GetLocalIndex(Reg));
+ }
+ }
+
+ private void InitSubroutine()
+ {
+ List Params = new List();
+
+ void SetParams(long Inputs, ARegisterType BaseType)
+ {
+ for (int Bit = 0; Bit < 64; Bit++)
+ {
+ long Mask = 1L << Bit;
+
+ if ((Inputs & Mask) != 0)
+ {
+ Params.Add(GetRegFromBit(Bit, BaseType));
+ }
+ }
+ }
+
+ SetParams(LocalAlloc.GetIntInputs(Root), ARegisterType.Int);
+ SetParams(LocalAlloc.GetVecInputs(Root), ARegisterType.Vector);
+
+ DynamicMethod Mthd = new DynamicMethod(SubName, typeof(long), GetParamTypes(Params));
+
+ Generator = Mthd.GetILGenerator();
+
+ Subroutine = new ATranslatedSub(Mthd, Params);
+ }
+
+ private Type[] GetParamTypes(IList Params)
+ {
+ Type[] FixedArgs = ATranslatedSub.FixedArgTypes;
+
+ Type[] Output = new Type[Params.Count + FixedArgs.Length];
+
+ FixedArgs.CopyTo(Output, 0);
+
+ int TypeIdx = FixedArgs.Length;
+
+ for (int Index = 0; Index < Params.Count; Index++)
+ {
+ Output[TypeIdx++] = GetFieldType(Params[Index].Type);
+ }
+
+ return Output;
+ }
+
+ public int GetLocalIndex(ARegister Reg)
+ {
+ if (!Locals.TryGetValue(Reg, out int Index))
+ {
+ Generator.DeclareLocal(GetLocalType(Reg));
+
+ Index = LocalsCount++;
+
+ Locals.Add(Reg, Index);
+ }
+
+ return Index;
+ }
+
+ public Type GetLocalType(ARegister Reg) => GetFieldType(Reg.Type);
+
+ public Type GetFieldType(ARegisterType RegType)
+ {
+ switch (RegType)
+ {
+ case ARegisterType.Flag: return typeof(bool);
+ case ARegisterType.Int: return typeof(ulong);
+ case ARegisterType.Vector: return typeof(AVec);
+ }
+
+ throw new ArgumentException(nameof(RegType));
+ }
+
+ public static ARegister GetRegFromBit(int Bit, ARegisterType BaseType)
+ {
+ if (Bit < 32)
+ {
+ return new ARegister(Bit, BaseType);
+ }
+ else if (BaseType == ARegisterType.Int)
+ {
+ return new ARegister(Bit & 0x1f, ARegisterType.Flag);
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(nameof(Bit));
+ }
+ }
+
+ public static bool IsRegIndex(int Index)
+ {
+ return Index >= 0 && Index < 32;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Translation/AILEmitterCtx.cs b/Ryujinx/Cpu/Translation/AILEmitterCtx.cs
new file mode 100644
index 0000000000..88841db77c
--- /dev/null
+++ b/Ryujinx/Cpu/Translation/AILEmitterCtx.cs
@@ -0,0 +1,548 @@
+using ChocolArm64.Decoder;
+using ChocolArm64.Instruction;
+using ChocolArm64.State;
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace ChocolArm64.Translation
+{
+ class AILEmitterCtx
+ {
+ private ATranslator Translator;
+
+ private Dictionary Labels;
+
+ private AILEmitter Emitter;
+
+ private AILBlock ILBlock;
+
+ private AOpCode LastCmpOp;
+ private AOpCode LastFlagOp;
+
+ private int BlkIndex;
+ private int OpcIndex;
+
+ private ABlock[] Graph;
+ private ABlock Root;
+ public ABlock CurrBlock => Graph[BlkIndex];
+ public AOpCode CurrOp => Graph[BlkIndex].OpCodes[OpcIndex];
+
+ //This is the index of the temporary register, used to store temporary
+ //values needed by some functions, since IL doesn't have a swap instruction.
+ //You can use any value here as long it doesn't conflict with the indices
+ //for the other registers. Any value >= 64 or < 0 will do.
+ private const int Tmp1Index = -1;
+ private const int Tmp2Index = -2;
+ private const int Tmp3Index = -3;
+ private const int Tmp4Index = -4;
+
+ public AILEmitterCtx(ATranslator Translator, ABlock[] Graph, ABlock Root)
+ {
+ this.Translator = Translator;
+ this.Graph = Graph;
+ this.Root = Root;
+
+ string SubName = $"Sub{Root.Position:X16}";
+
+ Labels = new Dictionary();
+
+ Emitter = new AILEmitter(Graph, Root, SubName);
+
+ ILBlock = Emitter.GetILBlock(0);
+
+ OpcIndex = -1;
+
+ if (!AdvanceOpCode())
+ {
+ throw new ArgumentException(nameof(Graph));
+ }
+ }
+
+ public ATranslatedSub GetSubroutine() => Emitter.GetSubroutine();
+
+ public bool AdvanceOpCode()
+ {
+ while (++OpcIndex >= (CurrBlock?.OpCodes.Count ?? 0))
+ {
+ if (BlkIndex + 1 >= Graph.Length)
+ {
+ return false;
+ }
+
+ BlkIndex++;
+ OpcIndex = -1;
+
+ ILBlock = Emitter.GetILBlock(BlkIndex);
+ }
+
+ return true;
+ }
+
+ public void EmitOpCode()
+ {
+ if (OpcIndex == 0)
+ {
+ MarkLabel(GetLabel(CurrBlock.Position));
+ }
+
+ CurrOp.Emitter(this);
+ }
+
+ public bool TryOptEmitSubroutineCall()
+ {
+ if (!Translator.TryGetCachedSub(CurrOp, out ATranslatedSub Sub))
+ {
+ return false;
+ }
+
+ for (int Index = 0; Index < ATranslatedSub.FixedArgTypes.Length; Index++)
+ {
+ EmitLdarg(Index);
+ }
+
+ foreach (ARegister Reg in Sub.Params)
+ {
+ switch (Reg.Type)
+ {
+ case ARegisterType.Flag: Ldloc(Reg.Index, AIoType.Flag); break;
+ case ARegisterType.Int: Ldloc(Reg.Index, AIoType.Int); break;
+ case ARegisterType.Vector: Ldloc(Reg.Index, AIoType.Vector); break;
+ }
+ }
+
+ EmitCall(Sub.Method);
+
+ return true;
+ }
+
+ public void TryOptMarkCondWithoutCmp()
+ {
+ LastCmpOp = CurrOp;
+
+ AInstEmitAluHelper.EmitDataLoadOpers(this);
+
+ Stloc(Tmp4Index, AIoType.Int);
+ Stloc(Tmp3Index, AIoType.Int);
+ }
+
+ private Dictionary BranchOps = new Dictionary()
+ {
+ { ACond.Eq, OpCodes.Beq },
+ { ACond.Ne, OpCodes.Bne_Un },
+ { ACond.Ge_Un, OpCodes.Bge_Un },
+ { ACond.Lt_Un, OpCodes.Blt_Un },
+ { ACond.Gt_Un, OpCodes.Bgt_Un },
+ { ACond.Le_Un, OpCodes.Ble_Un },
+ { ACond.Ge, OpCodes.Bge },
+ { ACond.Lt, OpCodes.Blt },
+ { ACond.Gt, OpCodes.Bgt },
+ { ACond.Le, OpCodes.Ble }
+ };
+
+ public void EmitCondBranch(AILLabel Target, ACond Cond)
+ {
+ OpCode ILOp;
+
+ int IntCond = (int)Cond;
+
+ if (LastFlagOp == LastCmpOp && BranchOps.ContainsKey(Cond))
+ {
+ Ldloc(Tmp3Index, AIoType.Int, GetIntType(LastCmpOp));
+ Ldloc(Tmp4Index, AIoType.Int, GetIntType(LastCmpOp));
+
+ if (LastCmpOp.Emitter == AInstEmit.Adds)
+ {
+ Emit(OpCodes.Neg);
+ }
+
+ ILOp = BranchOps[Cond];
+ }
+ else if (IntCond < 14)
+ {
+ int CondTrue = IntCond >> 1;
+
+ switch (CondTrue)
+ {
+ case 0: EmitLdflg((int)APState.ZBit); break;
+ case 1: EmitLdflg((int)APState.CBit); break;
+ case 2: EmitLdflg((int)APState.NBit); break;
+ case 3: EmitLdflg((int)APState.VBit); break;
+
+ case 4:
+ EmitLdflg((int)APState.CBit);
+ EmitLdflg((int)APState.ZBit);
+
+ Emit(OpCodes.Not);
+ Emit(OpCodes.And);
+ break;
+
+ case 5:
+ case 6:
+ EmitLdflg((int)APState.NBit);
+ EmitLdflg((int)APState.VBit);
+
+ Emit(OpCodes.Ceq);
+
+ if (CondTrue == 6)
+ {
+ EmitLdflg((int)APState.ZBit);
+
+ Emit(OpCodes.Not);
+ Emit(OpCodes.And);
+ }
+ break;
+ }
+
+ ILOp = (IntCond & 1) != 0
+ ? OpCodes.Brfalse
+ : OpCodes.Brtrue;
+ }
+ else
+ {
+ ILOp = OpCodes.Br;
+ }
+
+ Emit(ILOp, Target);
+ }
+
+ public void EmitCast(AIntType IntType)
+ {
+ switch (IntType)
+ {
+ case AIntType.UInt8: Emit(OpCodes.Conv_U1); break;
+ case AIntType.UInt16: Emit(OpCodes.Conv_U2); break;
+ case AIntType.UInt32: Emit(OpCodes.Conv_U4); break;
+ case AIntType.UInt64: Emit(OpCodes.Conv_U8); break;
+ case AIntType.Int8: Emit(OpCodes.Conv_I1); break;
+ case AIntType.Int16: Emit(OpCodes.Conv_I2); break;
+ case AIntType.Int32: Emit(OpCodes.Conv_I4); break;
+ case AIntType.Int64: Emit(OpCodes.Conv_I8); break;
+ }
+
+ if (IntType == AIntType.UInt64 ||
+ IntType == AIntType.Int64)
+ {
+ return;
+ }
+
+ if (CurrOp.RegisterSize != ARegisterSize.Int32)
+ {
+ Emit(IntType >= AIntType.Int8
+ ? OpCodes.Conv_I8
+ : OpCodes.Conv_U8);
+ }
+ }
+
+ public void EmitLsl(int Amount) => EmitILShift(Amount, OpCodes.Shl);
+ public void EmitLsr(int Amount) => EmitILShift(Amount, OpCodes.Shr_Un);
+ public void EmitAsr(int Amount) => EmitILShift(Amount, OpCodes.Shr);
+
+ private void EmitILShift(int Amount, OpCode ILOp)
+ {
+ if (Amount > 0)
+ {
+ EmitLdc_I4(Amount);
+
+ Emit(ILOp);
+ }
+ }
+
+ public void EmitRor(int Amount)
+ {
+ if (Amount > 0)
+ {
+ Stloc(Tmp2Index, AIoType.Int);
+ Ldloc(Tmp2Index, AIoType.Int);
+
+ EmitLdc_I4(Amount);
+
+ Emit(OpCodes.Shr_Un);
+
+ Ldloc(Tmp2Index, AIoType.Int);
+
+ EmitLdc_I4(CurrOp.GetBitsCount() - Amount);
+
+ Emit(OpCodes.Shl);
+ Emit(OpCodes.Or);
+ }
+ }
+
+ public AILLabel GetLabel(long Position)
+ {
+ if (!Labels.TryGetValue(Position, out AILLabel Output))
+ {
+ Output = new AILLabel();
+
+ Labels.Add(Position, Output);
+ }
+
+ return Output;
+ }
+
+ public void MarkLabel(AILLabel Label)
+ {
+ ILBlock.Add(Label);
+ }
+
+ public void Emit(OpCode ILOp)
+ {
+ ILBlock.Add(new AILOpCode(ILOp));
+ }
+
+ public void Emit(OpCode ILOp, AILLabel Label)
+ {
+ ILBlock.Add(new AILOpCodeBranch(ILOp, Label));
+ }
+
+ public void Emit(string Text)
+ {
+ ILBlock.Add(new AILOpCodeLog(Text));
+ }
+
+ public void EmitLdarg(int Index)
+ {
+ ILBlock.Add(new AILOpCodeLoad(Index, AIoType.Arg));
+ }
+
+ public void EmitLdintzr(int Index)
+ {
+ if (Index != ARegisters.ZRIndex)
+ {
+ EmitLdint(Index);
+ }
+ else
+ {
+ EmitLdc_I(0);
+ }
+ }
+
+ public void EmitStintzr(int Index)
+ {
+ if (Index != ARegisters.ZRIndex)
+ {
+ EmitStint(Index);
+ }
+ else
+ {
+ Emit(OpCodes.Pop);
+ }
+ }
+
+ public void EmitLoadState(ABlock RetBlk)
+ {
+ ILBlock.Add(new AILOpCodeLoad(Array.IndexOf(Graph, RetBlk), AIoType.Fields));
+ }
+
+ public void EmitStoreState()
+ {
+ ILBlock.Add(new AILOpCodeStore(Array.IndexOf(Graph, CurrBlock), AIoType.Fields));
+ }
+
+ public void EmitLdtmp() => EmitLdint(Tmp1Index);
+ public void EmitSttmp() => EmitStint(Tmp1Index);
+
+ public void EmitLdint(int Index) => Ldloc(Index, AIoType.Int);
+ public void EmitStint(int Index) => Stloc(Index, AIoType.Int);
+
+ public void EmitLdvec(int Index) => Ldloc(Index, AIoType.Vector);
+ public void EmitStvec(int Index) => Stloc(Index, AIoType.Vector);
+
+ public void EmitLdvecsi(int Index) => Ldloc(Index, AIoType.VectorI);
+ public void EmitStvecsi(int Index) => Stloc(Index, AIoType.VectorI);
+
+ public void EmitLdvecsf(int Index) => Ldloc(Index, AIoType.VectorF);
+ public void EmitStvecsf(int Index) => Stloc(Index, AIoType.VectorF);
+
+ public void EmitLdflg(int Index) => Ldloc(Index, AIoType.Flag);
+ public void EmitStflg(int Index)
+ {
+ LastFlagOp = CurrOp;
+
+ Stloc(Index, AIoType.Flag);
+ }
+
+ private void Ldloc(int Index, AIoType IoType)
+ {
+ ILBlock.Add(new AILOpCodeLoad(Index, IoType, GetOperType(IoType)));
+ }
+
+ private void Ldloc(int Index, AIoType IoType, Type Type)
+ {
+ ILBlock.Add(new AILOpCodeLoad(Index, IoType, Type));
+ }
+
+ private void Stloc(int Index, AIoType IoType)
+ {
+ ILBlock.Add(new AILOpCodeStore(Index, IoType, GetOutOperType(IoType)));
+ }
+
+ private Type GetOutOperType(AIoType IoType)
+ {
+ //This instruction is used to convert between floating point
+ //types, so the input and output types are different.
+ if (CurrOp.Emitter == AInstEmit.Fcvt_S)
+ {
+ return GetFloatType(((AOpCodeSimd)CurrOp).Opc);
+ }
+ else
+ {
+ return GetOperType(IoType);
+ }
+ }
+
+ private Type GetOperType(AIoType IoType)
+ {
+ switch (IoType & AIoType.Mask)
+ {
+ case AIoType.Flag: return typeof(bool);
+ case AIoType.Int: return GetIntType(CurrOp);
+ case AIoType.Vector: return GetVecType(CurrOp, IoType);
+ }
+
+ throw new ArgumentException(nameof(IoType));
+ }
+
+ private Type GetIntType(AOpCode OpCode)
+ {
+ //Always default to 64-bits.
+ return OpCode.RegisterSize == ARegisterSize.Int32
+ ? typeof(uint)
+ : typeof(ulong);
+ }
+
+ private Type GetVecType(AOpCode OpCode, AIoType IoType)
+ {
+ if (!(OpCode is IAOpCodeSimd Op))
+ {
+ return typeof(AVec);
+ }
+
+ int Size = Op.Size;
+
+ if (Op.Emitter == AInstEmit.Fmov_Ftoi ||
+ Op.Emitter == AInstEmit.Fmov_Itof)
+ {
+ Size |= 2;
+ }
+
+ if (Op is AOpCodeMem || Op is IAOpCodeLit)
+ {
+ return Size < 4 ? typeof(ulong) : typeof(AVec);
+ }
+ else if (IoType == AIoType.VectorI)
+ {
+ return GetIntType(Size);
+ }
+ else if (IoType == AIoType.VectorF)
+ {
+ return GetFloatType(Size);
+ }
+
+ return typeof(AVec);
+ }
+
+ private static Type GetIntType(int Size)
+ {
+ switch (Size)
+ {
+ case 0: return typeof(byte);
+ case 1: return typeof(ushort);
+ case 2: return typeof(uint);
+ case 3: return typeof(ulong);
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(Size));
+ }
+
+ private static Type GetFloatType(int Size)
+ {
+ switch (Size)
+ {
+ case 0: return typeof(float);
+ case 1: return typeof(double);
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(Size));
+ }
+
+ public void EmitCall(Type MthdType, string MthdName)
+ {
+ if (MthdType == null)
+ {
+ throw new ArgumentNullException(nameof(MthdType));
+ }
+
+ if (MthdName == null)
+ {
+ throw new ArgumentNullException(nameof(MthdName));
+ }
+
+ EmitCall(MthdType.GetMethod(MthdName));
+ }
+
+ public void EmitCall(MethodInfo MthdInfo)
+ {
+ if (MthdInfo == null)
+ {
+ throw new ArgumentNullException(nameof(MthdInfo));
+ }
+
+ ILBlock.Add(new AILOpCodeCall(MthdInfo));
+ }
+
+ public void EmitLdc_I(long Value)
+ {
+ if (CurrOp.RegisterSize == ARegisterSize.Int32)
+ {
+ EmitLdc_I4((int)Value);
+ }
+ else
+ {
+ EmitLdc_I8(Value);
+ }
+ }
+
+ public void EmitLdc_I4(int Value)
+ {
+ ILBlock.Add(new AILOpCodeConst(Value));
+ }
+
+ public void EmitLdc_I8(long Value)
+ {
+ ILBlock.Add(new AILOpCodeConst(Value));
+ }
+
+ public void EmitLdc_R4(float Value)
+ {
+ ILBlock.Add(new AILOpCodeConst(Value));
+ }
+
+ public void EmitLdc_R8(double Value)
+ {
+ ILBlock.Add(new AILOpCodeConst(Value));
+ }
+
+ public void EmitZNFlagCheck()
+ {
+ EmitZNCheck(OpCodes.Ceq, (int)APState.ZBit);
+ EmitZNCheck(OpCodes.Clt, (int)APState.NBit);
+ }
+
+ private void EmitZNCheck(OpCode ILCmpOp, int Flag)
+ {
+ Emit(OpCodes.Dup);
+ Emit(OpCodes.Ldc_I4_0);
+
+ if (CurrOp.RegisterSize != ARegisterSize.Int32)
+ {
+ Emit(OpCodes.Conv_I8);
+ }
+
+ Emit(ILCmpOp);
+
+ EmitStflg(Flag);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Translation/AILLabel.cs b/Ryujinx/Cpu/Translation/AILLabel.cs
new file mode 100644
index 0000000000..0ee39ad7e2
--- /dev/null
+++ b/Ryujinx/Cpu/Translation/AILLabel.cs
@@ -0,0 +1,28 @@
+using System.Reflection.Emit;
+
+namespace ChocolArm64.Translation
+{
+ class AILLabel : IAILEmit
+ {
+ private bool HasLabel;
+
+ private Label Lbl;
+
+ public void Emit(AILEmitter Context)
+ {
+ Context.Generator.MarkLabel(GetLabel(Context));
+ }
+
+ public Label GetLabel(AILEmitter Context)
+ {
+ if (!HasLabel)
+ {
+ Lbl = Context.Generator.DefineLabel();
+
+ HasLabel = true;
+ }
+
+ return Lbl;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Translation/AILOpCode.cs b/Ryujinx/Cpu/Translation/AILOpCode.cs
new file mode 100644
index 0000000000..a4bc93a065
--- /dev/null
+++ b/Ryujinx/Cpu/Translation/AILOpCode.cs
@@ -0,0 +1,19 @@
+using System.Reflection.Emit;
+
+namespace ChocolArm64.Translation
+{
+ struct AILOpCode : IAILEmit
+ {
+ private OpCode ILOp;
+
+ public AILOpCode(OpCode ILOp)
+ {
+ this.ILOp = ILOp;
+ }
+
+ public void Emit(AILEmitter Context)
+ {
+ Context.Generator.Emit(ILOp);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Translation/AILOpCodeBranch.cs b/Ryujinx/Cpu/Translation/AILOpCodeBranch.cs
new file mode 100644
index 0000000000..e4caad1ffa
--- /dev/null
+++ b/Ryujinx/Cpu/Translation/AILOpCodeBranch.cs
@@ -0,0 +1,21 @@
+using System.Reflection.Emit;
+
+namespace ChocolArm64.Translation
+{
+ struct AILOpCodeBranch : IAILEmit
+ {
+ private OpCode ILOp;
+ private AILLabel Label;
+
+ public AILOpCodeBranch(OpCode ILOp, AILLabel Label)
+ {
+ this.ILOp = ILOp;
+ this.Label = Label;
+ }
+
+ public void Emit(AILEmitter Context)
+ {
+ Context.Generator.Emit(ILOp, Label.GetLabel(Context));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Translation/AILOpCodeCall.cs b/Ryujinx/Cpu/Translation/AILOpCodeCall.cs
new file mode 100644
index 0000000000..8cd944eb01
--- /dev/null
+++ b/Ryujinx/Cpu/Translation/AILOpCodeCall.cs
@@ -0,0 +1,20 @@
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace ChocolArm64.Translation
+{
+ struct AILOpCodeCall : IAILEmit
+ {
+ private MethodInfo MthdInfo;
+
+ public AILOpCodeCall(MethodInfo MthdInfo)
+ {
+ this.MthdInfo = MthdInfo;
+ }
+
+ public void Emit(AILEmitter Context)
+ {
+ Context.Generator.Emit(OpCodes.Call, MthdInfo);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Translation/AILOpCodeConst.cs b/Ryujinx/Cpu/Translation/AILOpCodeConst.cs
new file mode 100644
index 0000000000..80150ec5a1
--- /dev/null
+++ b/Ryujinx/Cpu/Translation/AILOpCodeConst.cs
@@ -0,0 +1,81 @@
+using System.Reflection.Emit;
+using System.Runtime.InteropServices;
+
+namespace ChocolArm64.Translation
+{
+ class AILOpCodeConst : IAILEmit
+ {
+ [StructLayout(LayoutKind.Explicit, Size = 8)]
+ private struct ImmVal
+ {
+ [FieldOffset(0)] public int I4;
+ [FieldOffset(0)] public long I8;
+ [FieldOffset(0)] public float R4;
+ [FieldOffset(0)] public double R8;
+ }
+
+ private ImmVal Value;
+
+ private enum ConstType
+ {
+ Int32,
+ Int64,
+ Single,
+ Double
+ }
+
+ private ConstType Type;
+
+ private AILOpCodeConst(ConstType Type)
+ {
+ this.Type = Type;
+ }
+
+ public AILOpCodeConst(int Value) : this(ConstType.Int32)
+ {
+ this.Value = new ImmVal { I4 = Value };
+ }
+
+ public AILOpCodeConst(long Value) : this(ConstType.Int64)
+ {
+ this.Value = new ImmVal { I8 = Value };
+ }
+
+ public AILOpCodeConst(float Value) : this(ConstType.Single)
+ {
+ this.Value = new ImmVal { R4 = Value };
+ }
+
+ public AILOpCodeConst(double Value) : this(ConstType.Double)
+ {
+ this.Value = new ImmVal { R8 = Value };
+ }
+
+ public void Emit(AILEmitter Context)
+ {
+ switch (Type)
+ {
+ case ConstType.Int32: Context.Generator.EmitLdc_I4(Value.I4); break;
+
+ case ConstType.Int64:
+ {
+ if (Value.I8 >= int.MinValue &&
+ Value.I8 <= int.MaxValue)
+ {
+ Context.Generator.EmitLdc_I4(Value.I4);
+
+ Context.Generator.Emit(OpCodes.Conv_I8);
+ }
+ else
+ {
+ Context.Generator.Emit(OpCodes.Ldc_I8, Value.I8);
+ }
+ break;
+ }
+
+ case ConstType.Single: Context.Generator.Emit(OpCodes.Ldc_R4, Value.R4); break;
+ case ConstType.Double: Context.Generator.Emit(OpCodes.Ldc_R8, Value.R8); break;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Translation/AILOpCodeLoad.cs b/Ryujinx/Cpu/Translation/AILOpCodeLoad.cs
new file mode 100644
index 0000000000..2169cc7779
--- /dev/null
+++ b/Ryujinx/Cpu/Translation/AILOpCodeLoad.cs
@@ -0,0 +1,82 @@
+using ChocolArm64.State;
+using System;
+using System.Reflection.Emit;
+
+namespace ChocolArm64.Translation
+{
+ struct AILOpCodeLoad : IAILEmit
+ {
+ public int Index { get; private set; }
+
+ public AIoType IoType { get; private set; }
+
+ public Type OperType { get; private set; }
+
+ public AILOpCodeLoad(int Index, AIoType IoType) : this(Index, IoType, null) { }
+
+ public AILOpCodeLoad(int Index, AIoType IoType, Type OperType)
+ {
+ this.IoType = IoType;
+ this.Index = Index;
+ this.OperType = OperType;
+ }
+
+ public void Emit(AILEmitter Context)
+ {
+ switch (IoType & AIoType.Mask)
+ {
+ case AIoType.Arg: EmitLdarg(Context, Index); break;
+ case AIoType.Fields: EmitLdfld(Context, Index); break;
+ case AIoType.Flag: EmitLdloc(Context, Index, ARegisterType.Flag); break;
+ case AIoType.Int: EmitLdloc(Context, Index, ARegisterType.Int); break;
+ case AIoType.Vector: EmitLdloc(Context, Index, ARegisterType.Vector); break;
+ }
+ }
+
+ private void EmitLdarg(AILEmitter Context, int Index)
+ {
+ Context.Generator.EmitLdarg(Index);
+ }
+
+ private void EmitLdfld(AILEmitter Context, int Index)
+ {
+ long IntInputs = Context.LocalAlloc.GetIntInputs(Context.GetILBlock(Index));
+ long VecInputs = Context.LocalAlloc.GetVecInputs(Context.GetILBlock(Index));
+
+ LoadLocals(Context, IntInputs, ARegisterType.Int);
+ LoadLocals(Context, VecInputs, ARegisterType.Vector);
+ }
+
+ private void LoadLocals(AILEmitter Context, long Inputs, ARegisterType BaseType)
+ {
+ for (int Bit = 0; Bit < 64; Bit++)
+ {
+ long Mask = 1L << Bit;
+
+ if ((Inputs & Mask) != 0)
+ {
+ ARegister Reg = AILEmitter.GetRegFromBit(Bit, BaseType);
+
+ Context.Generator.EmitLdarg(ATranslatedSub.RegistersArgIdx);
+ Context.Generator.Emit(OpCodes.Ldfld, Reg.GetField());
+
+ AILConv.EmitConv(
+ Context,
+ Context.GetFieldType(Reg.Type),
+ Context.GetLocalType(Reg));
+
+ Context.Generator.EmitStloc(Context.GetLocalIndex(Reg));
+ }
+ }
+ }
+
+ private void EmitLdloc(AILEmitter Context, int Index, ARegisterType Type)
+ {
+ ARegister Reg = new ARegister(Index, Type);
+
+ Context.Generator.EmitLdloc(Context.GetLocalIndex(Reg));
+
+ AILConv.EmitConv(Context, Context.GetLocalType(Reg), OperType);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Translation/AILOpCodeLog.cs b/Ryujinx/Cpu/Translation/AILOpCodeLog.cs
new file mode 100644
index 0000000000..1338ca1f3a
--- /dev/null
+++ b/Ryujinx/Cpu/Translation/AILOpCodeLog.cs
@@ -0,0 +1,17 @@
+namespace ChocolArm64.Translation
+{
+ struct AILOpCodeLog : IAILEmit
+ {
+ private string Text;
+
+ public AILOpCodeLog(string Text)
+ {
+ this.Text = Text;
+ }
+
+ public void Emit(AILEmitter Context)
+ {
+ Context.Generator.EmitWriteLine(Text);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Translation/AILOpCodeStore.cs b/Ryujinx/Cpu/Translation/AILOpCodeStore.cs
new file mode 100644
index 0000000000..012e24aecf
--- /dev/null
+++ b/Ryujinx/Cpu/Translation/AILOpCodeStore.cs
@@ -0,0 +1,82 @@
+using ChocolArm64.State;
+using System;
+using System.Reflection.Emit;
+
+namespace ChocolArm64.Translation
+{
+ struct AILOpCodeStore : IAILEmit
+ {
+ public AIoType IoType { get; private set; }
+
+ public Type OperType { get; private set; }
+
+ public int Index { get; private set; }
+
+ public AILOpCodeStore(int Index, AIoType IoType) : this(Index, IoType, null) { }
+
+ public AILOpCodeStore(int Index, AIoType IoType, Type OperType)
+ {
+ this.IoType = IoType;
+ this.Index = Index;
+ this.OperType = OperType;
+ }
+
+ public void Emit(AILEmitter Context)
+ {
+ switch (IoType & AIoType.Mask)
+ {
+ case AIoType.Arg: EmitStarg(Context, Index); break;
+ case AIoType.Fields: EmitStfld(Context, Index); break;
+ case AIoType.Flag: EmitStloc(Context, Index, ARegisterType.Flag); break;
+ case AIoType.Int: EmitStloc(Context, Index, ARegisterType.Int); break;
+ case AIoType.Vector: EmitStloc(Context, Index, ARegisterType.Vector); break;
+ }
+ }
+
+ private void EmitStarg(AILEmitter Context, int Index)
+ {
+ Context.Generator.EmitStarg(Index);
+ }
+
+ private void EmitStfld(AILEmitter Context, int Index)
+ {
+ long IntOutputs = Context.LocalAlloc.GetIntOutputs(Context.GetILBlock(Index));
+ long VecOutputs = Context.LocalAlloc.GetVecOutputs(Context.GetILBlock(Index));
+
+ StoreLocals(Context, IntOutputs, ARegisterType.Int);
+ StoreLocals(Context, VecOutputs, ARegisterType.Vector);
+ }
+
+ private void StoreLocals(AILEmitter Context, long Outputs, ARegisterType BaseType)
+ {
+ for (int Bit = 0; Bit < 64; Bit++)
+ {
+ long Mask = 1L << Bit;
+
+ if ((Outputs & Mask) != 0)
+ {
+ ARegister Reg = AILEmitter.GetRegFromBit(Bit, BaseType);
+
+ Context.Generator.EmitLdarg(ATranslatedSub.RegistersArgIdx);
+ Context.Generator.EmitLdloc(Context.GetLocalIndex(Reg));
+
+ AILConv.EmitConv(
+ Context,
+ Context.GetLocalType(Reg),
+ Context.GetFieldType(Reg.Type));
+
+ Context.Generator.Emit(OpCodes.Stfld, Reg.GetField());
+ }
+ }
+ }
+
+ private void EmitStloc(AILEmitter Context, int Index, ARegisterType Type)
+ {
+ ARegister Reg = new ARegister(Index, Type);
+
+ AILConv.EmitConv(Context, OperType, Context.GetLocalType(Reg));
+
+ Context.Generator.EmitStloc(Context.GetLocalIndex(Reg));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Translation/AIoType.cs b/Ryujinx/Cpu/Translation/AIoType.cs
new file mode 100644
index 0000000000..34aa224e5f
--- /dev/null
+++ b/Ryujinx/Cpu/Translation/AIoType.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace ChocolArm64.Translation
+{
+ [Flags]
+ enum AIoType
+ {
+ Arg,
+ Fields,
+ Flag,
+ Int,
+ Float,
+ Vector,
+ Mask = 0xff,
+ VectorI = Vector | 1 << 8,
+ VectorF = Vector | 1 << 9
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Translation/ALocalAlloc.cs b/Ryujinx/Cpu/Translation/ALocalAlloc.cs
new file mode 100644
index 0000000000..0661ddc8dd
--- /dev/null
+++ b/Ryujinx/Cpu/Translation/ALocalAlloc.cs
@@ -0,0 +1,231 @@
+using System.Collections.Generic;
+
+namespace ChocolArm64.Translation
+{
+ class ALocalAlloc
+ {
+ private class PathIo
+ {
+ private Dictionary AllInputs;
+ private Dictionary CmnOutputs;
+
+ private long AllOutputs;
+
+ public PathIo()
+ {
+ AllInputs = new Dictionary();
+ CmnOutputs = new Dictionary();
+ }
+
+ public PathIo(AILBlock Root, long Inputs, long Outputs) : this()
+ {
+ Set(Root, Inputs, Outputs);
+ }
+
+ public void Set(AILBlock Root, long Inputs, long Outputs)
+ {
+ if (!AllInputs.TryAdd(Root, Inputs))
+ {
+ AllInputs[Root] |= Inputs;
+ }
+
+ if (!CmnOutputs.TryAdd(Root, Outputs))
+ {
+ CmnOutputs[Root] &= Outputs;
+ }
+
+ AllOutputs |= Outputs;
+ }
+
+ public long GetInputs(AILBlock Root)
+ {
+ if (AllInputs.TryGetValue(Root, out long Inputs))
+ {
+ return Inputs | (AllOutputs & ~CmnOutputs[Root]);
+ }
+
+ return 0;
+ }
+
+ public long GetOutputs()
+ {
+ return AllOutputs;
+ }
+ }
+
+ private Dictionary IntPaths;
+ private Dictionary VecPaths;
+
+ private struct BlockIo
+ {
+ public AILBlock Block;
+ public AILBlock Entry;
+
+ public long IntInputs;
+ public long VecInputs;
+ public long IntOutputs;
+ public long VecOutputs;
+ }
+
+ private const int MaxOptGraphLength = 120;
+
+ public ALocalAlloc(AILBlock[] Graph, AILBlock Root)
+ {
+ IntPaths = new Dictionary();
+ VecPaths = new Dictionary();
+
+ if (Graph.Length < MaxOptGraphLength)
+ {
+ InitializeOptimal(Graph, Root);
+ }
+ else
+ {
+ InitializeFast(Graph);
+ }
+ }
+
+ private void InitializeOptimal(AILBlock[] Graph, AILBlock Root)
+ {
+ //This will go through all possible paths on the graph,
+ //and store all inputs/outputs for each block. A register
+ //that was previously written to already is not considered an input.
+ //When a block can be reached by more than one path, then the
+ //output from all paths needs to be set for this block, and
+ //only outputs present in all of the parent blocks can be considered
+ //when doing input elimination. Each block chain have a root, that's where
+ //the code starts executing. They are present on the subroutine start point,
+ //and on call return points too (address written to X30 by BL).
+ HashSet Visited = new HashSet();
+
+ Queue Unvisited = new Queue();
+
+ void Enqueue(BlockIo Block)
+ {
+ if (!Visited.Contains(Block))
+ {
+ Unvisited.Enqueue(Block);
+
+ Visited.Add(Block);
+ }
+ }
+
+ Enqueue(new BlockIo()
+ {
+ Block = Root,
+ Entry = Root
+ });
+
+ while (Unvisited.Count > 0)
+ {
+ BlockIo Current = Unvisited.Dequeue();
+
+ Current.IntInputs |= Current.Block.IntInputs & ~Current.IntOutputs;
+ Current.VecInputs |= Current.Block.VecInputs & ~Current.VecOutputs;
+ Current.IntOutputs |= Current.Block.IntOutputs;
+ Current.VecOutputs |= Current.Block.VecOutputs;
+
+ //Check if this is a exit block
+ //(a block that returns or calls another sub).
+ if ((Current.Block.Next == null &&
+ Current.Block.Branch == null) || Current.Block.HasStateStore)
+ {
+ if (!IntPaths.TryGetValue(Current.Block, out PathIo IntPath))
+ {
+ IntPaths.Add(Current.Block, IntPath = new PathIo());
+ }
+
+ if (!VecPaths.TryGetValue(Current.Block, out PathIo VecPath))
+ {
+ VecPaths.Add(Current.Block, VecPath = new PathIo());
+ }
+
+ IntPath.Set(Current.Entry, Current.IntInputs, Current.IntOutputs);
+ VecPath.Set(Current.Entry, Current.VecInputs, Current.VecOutputs);
+ }
+
+ void EnqueueFromCurrent(AILBlock Block, bool RetTarget)
+ {
+ BlockIo BlkIO = new BlockIo() { Block = Block };
+
+ if (RetTarget)
+ {
+ BlkIO.Entry = Block;
+ BlkIO.IntInputs = 0;
+ BlkIO.VecInputs = 0;
+ BlkIO.IntOutputs = 0;
+ BlkIO.VecOutputs = 0;
+ }
+ else
+ {
+ BlkIO.Entry = Current.Entry;
+ BlkIO.IntInputs = Current.IntInputs;
+ BlkIO.VecInputs = Current.VecInputs;
+ BlkIO.IntOutputs = Current.IntOutputs;
+ BlkIO.VecOutputs = Current.VecOutputs;
+ }
+
+ Enqueue(BlkIO);
+ }
+
+ if (Current.Block.Next != null)
+ {
+ EnqueueFromCurrent(Current.Block.Next, Current.Block.HasStateStore);
+ }
+
+ if (Current.Block.Branch != null)
+ {
+ EnqueueFromCurrent(Current.Block.Branch, false);
+ }
+ }
+ }
+
+ private void InitializeFast(AILBlock[] Graph)
+ {
+ //This is WAY faster than InitializeOptimal, but results in
+ //uneeded loads and stores, so the resulting code will be slower.
+ long IntInputs = 0;
+ long IntOutputs = 0;
+ long VecInputs = 0;
+ long VecOutputs = 0;
+
+ foreach (AILBlock Block in Graph)
+ {
+ IntInputs |= Block.IntInputs;
+ IntOutputs |= Block.IntOutputs;
+ VecInputs |= Block.VecInputs;
+ VecOutputs |= Block.VecOutputs;
+ }
+
+ //It's possible that not all code paths writes to those output registers,
+ //in those cases if we attempt to write an output registers that was
+ //not written, we will be just writing zero and messing up the old register value.
+ //So we just need to ensure that all outputs are loaded.
+ IntInputs |= IntOutputs;
+ VecInputs |= VecOutputs;
+
+ foreach (AILBlock Block in Graph)
+ {
+ IntPaths.Add(Block, new PathIo(Block, IntInputs, IntOutputs));
+ VecPaths.Add(Block, new PathIo(Block, VecInputs, VecOutputs));
+ }
+ }
+
+ public long GetIntInputs(AILBlock Root) => GetInputsImpl(Root, IntPaths.Values);
+ public long GetVecInputs(AILBlock Root) => GetInputsImpl(Root, VecPaths.Values);
+
+ private long GetInputsImpl(AILBlock Root, IEnumerable Values)
+ {
+ long Inputs = 0;
+
+ foreach (PathIo Path in Values)
+ {
+ Inputs |= Path.GetInputs(Root);
+ }
+
+ return Inputs;
+ }
+
+ public long GetIntOutputs(AILBlock Block) => IntPaths[Block].GetOutputs();
+ public long GetVecOutputs(AILBlock Block) => VecPaths[Block].GetOutputs();
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Translation/IAILEmit.cs b/Ryujinx/Cpu/Translation/IAILEmit.cs
new file mode 100644
index 0000000000..6e4e9a7855
--- /dev/null
+++ b/Ryujinx/Cpu/Translation/IAILEmit.cs
@@ -0,0 +1,7 @@
+namespace ChocolArm64.Translation
+{
+ interface IAILEmit
+ {
+ void Emit(AILEmitter Context);
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Cpu/Translation/ILGeneratorEx.cs b/Ryujinx/Cpu/Translation/ILGeneratorEx.cs
new file mode 100644
index 0000000000..abb35ec3f1
--- /dev/null
+++ b/Ryujinx/Cpu/Translation/ILGeneratorEx.cs
@@ -0,0 +1,129 @@
+using System;
+
+namespace ChocolArm64
+{
+ using System.Reflection.Emit;
+
+ static class ILGeneratorEx
+ {
+ public static void EmitLdc_I4(this ILGenerator Generator,int Value)
+ {
+ switch (Value)
+ {
+ case 0: Generator.Emit(OpCodes.Ldc_I4_0); break;
+ case 1: Generator.Emit(OpCodes.Ldc_I4_1); break;
+ case 2: Generator.Emit(OpCodes.Ldc_I4_2); break;
+ case 3: Generator.Emit(OpCodes.Ldc_I4_3); break;
+ case 4: Generator.Emit(OpCodes.Ldc_I4_4); break;
+ case 5: Generator.Emit(OpCodes.Ldc_I4_5); break;
+ case 6: Generator.Emit(OpCodes.Ldc_I4_6); break;
+ case 7: Generator.Emit(OpCodes.Ldc_I4_7); break;
+ case 8: Generator.Emit(OpCodes.Ldc_I4_8); break;
+ case -1: Generator.Emit(OpCodes.Ldc_I4_M1); break;
+ default: Generator.Emit(OpCodes.Ldc_I4, Value); break;
+ }
+ }
+
+ public static void EmitLdarg(this ILGenerator Generator, int Index)
+ {
+ switch (Index)
+ {
+ case 0: Generator.Emit(OpCodes.Ldarg_0); break;
+ case 1: Generator.Emit(OpCodes.Ldarg_1); break;
+ case 2: Generator.Emit(OpCodes.Ldarg_2); break;
+ case 3: Generator.Emit(OpCodes.Ldarg_3); break;
+
+ default:
+ if ((uint)Index <= byte.MaxValue)
+ {
+ Generator.Emit(OpCodes.Ldarg_S, (byte)Index);
+ }
+ else if ((uint)Index < ushort.MaxValue)
+ {
+ Generator.Emit(OpCodes.Ldarg, (short)Index);
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(nameof(Index));
+ }
+ break;
+ }
+ }
+
+ public static void EmitStarg(this ILGenerator Generator, int Index)
+ {
+ if ((uint)Index <= byte.MaxValue)
+ {
+ Generator.Emit(OpCodes.Starg_S, (byte)Index);
+ }
+ else if ((uint)Index < ushort.MaxValue)
+ {
+ Generator.Emit(OpCodes.Starg, (short)Index);
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(nameof(Index));
+ }
+ }
+
+ public static void EmitLdloc(this ILGenerator Generator, int Index)
+ {
+ switch (Index)
+ {
+ case 0: Generator.Emit(OpCodes.Ldloc_0); break;
+ case 1: Generator.Emit(OpCodes.Ldloc_1); break;
+ case 2: Generator.Emit(OpCodes.Ldloc_2); break;
+ case 3: Generator.Emit(OpCodes.Ldloc_3); break;
+
+ default:
+ if ((uint)Index <= byte.MaxValue)
+ {
+ Generator.Emit(OpCodes.Ldloc_S, (byte)Index);
+ }
+ else if ((uint)Index < ushort.MaxValue)
+ {
+ Generator.Emit(OpCodes.Ldloc, (short)Index);
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(nameof(Index));
+ }
+ break;
+ }
+ }
+
+ public static void EmitStloc(this ILGenerator Generator, int Index)
+ {
+ switch (Index)
+ {
+ case 0: Generator.Emit(OpCodes.Stloc_0); break;
+ case 1: Generator.Emit(OpCodes.Stloc_1); break;
+ case 2: Generator.Emit(OpCodes.Stloc_2); break;
+ case 3: Generator.Emit(OpCodes.Stloc_3); break;
+
+ default:
+ if ((uint)Index <= byte.MaxValue)
+ {
+ Generator.Emit(OpCodes.Stloc_S, (byte)Index);
+ }
+ else if ((uint)Index < ushort.MaxValue)
+ {
+ Generator.Emit(OpCodes.Stloc, (short)Index);
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(nameof(Index));
+ }
+ break;
+ }
+ }
+
+ public static void EmitLdargSeq(this ILGenerator Generator, int Count)
+ {
+ for (int Index = 0; Index < Count; Index++)
+ {
+ Generator.EmitLdarg(Index);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Gal/GalPrimitiveType.cs b/Ryujinx/Gal/GalPrimitiveType.cs
new file mode 100644
index 0000000000..7b6d99a0bd
--- /dev/null
+++ b/Ryujinx/Gal/GalPrimitiveType.cs
@@ -0,0 +1,21 @@
+namespace Gal
+{
+ public enum GalPrimitiveType
+ {
+ Points = 0x0,
+ Lines = 0x1,
+ LineLoop = 0x2,
+ LineStrip = 0x3,
+ Triangles = 0x4,
+ TriangleStrip = 0x5,
+ TriangleFan = 0x6,
+ Quads = 0x7,
+ QuadStrip = 0x8,
+ Polygon = 0x9,
+ LinesAdjacency = 0xa,
+ LineStripAdjacency = 0xb,
+ TrianglesAdjacency = 0xc,
+ TriangleStripAdjacency = 0xd,
+ Patches = 0xe
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Gal/GalVertexAttrib.cs b/Ryujinx/Gal/GalVertexAttrib.cs
new file mode 100644
index 0000000000..bbc3263375
--- /dev/null
+++ b/Ryujinx/Gal/GalVertexAttrib.cs
@@ -0,0 +1,33 @@
+namespace Gal
+{
+ public struct GalVertexAttrib
+ {
+ public int Index { get; private set; }
+ public int Buffer { get; private set; }
+ public bool IsConst { get; private set; }
+ public int Offset { get; private set; }
+
+ public GalVertexAttribSize Size { get; private set; }
+ public GalVertexAttribType Type { get; private set; }
+
+ public bool IsBgra { get; private set; }
+
+ public GalVertexAttrib(
+ int Index,
+ int Buffer,
+ bool IsConst,
+ int Offset,
+ GalVertexAttribSize Size,
+ GalVertexAttribType Type,
+ bool IsBgra)
+ {
+ this.Index = Index;
+ this.Buffer = Buffer;
+ this.IsConst = IsConst;
+ this.Offset = Offset;
+ this.Size = Size;
+ this.Type = Type;
+ this.IsBgra = IsBgra;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Gal/GalVertexAttribSize.cs b/Ryujinx/Gal/GalVertexAttribSize.cs
new file mode 100644
index 0000000000..11f0470c28
--- /dev/null
+++ b/Ryujinx/Gal/GalVertexAttribSize.cs
@@ -0,0 +1,20 @@
+namespace Gal
+{
+ public enum GalVertexAttribSize
+ {
+ _32_32_32_32 = 0x1,
+ _32_32_32 = 0x2,
+ _16_16_16_16 = 0x3,
+ _32_32 = 0x4,
+ _16_16_16 = 0x5,
+ _8_8_8_8 = 0xa,
+ _16_16 = 0xf,
+ _32 = 0x12,
+ _8_8_8 = 0x13,
+ _8_8 = 0x18,
+ _16 = 0x1b,
+ _8 = 0x1d,
+ _10_10_10_2 = 0x30,
+ _11_11_10 = 0x31
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Gal/GalVertexAttribType.cs b/Ryujinx/Gal/GalVertexAttribType.cs
new file mode 100644
index 0000000000..c0ed59fb27
--- /dev/null
+++ b/Ryujinx/Gal/GalVertexAttribType.cs
@@ -0,0 +1,13 @@
+namespace Gal
+{
+ public enum GalVertexAttribType
+ {
+ Snorm = 1,
+ Unorm = 2,
+ Sint = 3,
+ Uint = 4,
+ Uscaled = 5,
+ Sscaled = 6,
+ Float = 7
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Gal/IGalRenderer.cs b/Ryujinx/Gal/IGalRenderer.cs
new file mode 100644
index 0000000000..306d0d5111
--- /dev/null
+++ b/Ryujinx/Gal/IGalRenderer.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Gal
+{
+ public interface IGalRenderer
+ {
+ long FrameBufferPtr { get; set; }
+
+ void QueueAction(Action ActionMthd);
+ void RunActions();
+
+ void Render();
+ void SendVertexBuffer(int Index, byte[] Buffer, int Stride, GalVertexAttrib[] Attribs);
+ void SendR8G8B8A8Texture(int Index, byte[] Buffer, int Width, int Height);
+ void BindTexture(int Index);
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Gal/OpenGL/OpenGLRenderer.cs b/Ryujinx/Gal/OpenGL/OpenGLRenderer.cs
new file mode 100644
index 0000000000..72ad6f706f
--- /dev/null
+++ b/Ryujinx/Gal/OpenGL/OpenGLRenderer.cs
@@ -0,0 +1,282 @@
+using OpenTK.Graphics.OpenGL;
+using System;
+using System.Collections.Generic;
+
+namespace Gal.OpenGL
+{
+ public class OpenGLRenderer : IGalRenderer
+ {
+ private struct VertexBuffer
+ {
+ public int VaoHandle;
+ public int VboHandle;
+
+ public int PrimCount;
+ }
+
+ private struct Texture
+ {
+ public int Handle;
+ }
+
+ private List VertexBuffers;
+
+ private Texture[] Textures;
+
+ private Queue ActionsQueue;
+
+ public long FrameBufferPtr { get; set; }
+
+ public OpenGLRenderer()
+ {
+ VertexBuffers = new List();
+
+ Textures = new Texture[8];
+
+ ActionsQueue = new Queue();
+ }
+
+ public void QueueAction(Action ActionMthd)
+ {
+ ActionsQueue.Enqueue(ActionMthd);
+ }
+
+ public void RunActions()
+ {
+ while (ActionsQueue.Count > 0)
+ {
+ ActionsQueue.Dequeue()();
+ }
+ }
+
+ public void Render()
+ {
+ for (int Index = 0; Index < VertexBuffers.Count; Index++)
+ {
+ VertexBuffer Vb = VertexBuffers[Index];
+
+ if (Vb.VaoHandle != 0 &&
+ Vb.PrimCount != 0)
+ {
+ GL.BindVertexArray(Vb.VaoHandle);
+ GL.DrawArrays(PrimitiveType.TriangleStrip, 0, Vb.PrimCount);
+ }
+ }
+
+ }
+
+ public void SendVertexBuffer(int Index, byte[] Buffer, int Stride, GalVertexAttrib[] Attribs)
+ {
+ if (Index < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(Index));
+ }
+
+ if (Buffer.Length == 0 || Stride == 0)
+ {
+ return;
+ }
+
+ EnsureVbInitialized(Index);
+
+ VertexBuffer Vb = VertexBuffers[Index];
+
+ Vb.PrimCount = Buffer.Length / Stride;
+
+ VertexBuffers[Index] = Vb;
+
+ IntPtr Length = new IntPtr(Buffer.Length);
+
+ GL.BindBuffer(BufferTarget.ArrayBuffer, Vb.VboHandle);
+ GL.BufferData(BufferTarget.ArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw);
+ GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
+
+ GL.BindVertexArray(Vb.VaoHandle);
+
+ for (int Attr = 0; Attr < 16; Attr++)
+ {
+ GL.DisableVertexAttribArray(Attr);
+ }
+
+ foreach (GalVertexAttrib Attrib in Attribs)
+ {
+ if (Attrib.Index >= 3) break;
+
+ GL.EnableVertexAttribArray(Attrib.Index);
+
+ GL.BindBuffer(BufferTarget.ArrayBuffer, Vb.VboHandle);
+
+ int Size = 0;
+
+ switch (Attrib.Size)
+ {
+ case GalVertexAttribSize._8:
+ case GalVertexAttribSize._16:
+ case GalVertexAttribSize._32:
+ Size = 1;
+ break;
+ case GalVertexAttribSize._8_8:
+ case GalVertexAttribSize._16_16:
+ case GalVertexAttribSize._32_32:
+ Size = 2;
+ break;
+ case GalVertexAttribSize._8_8_8:
+ case GalVertexAttribSize._11_11_10:
+ case GalVertexAttribSize._16_16_16:
+ case GalVertexAttribSize._32_32_32:
+ Size = 3;
+ break;
+ case GalVertexAttribSize._8_8_8_8:
+ case GalVertexAttribSize._10_10_10_2:
+ case GalVertexAttribSize._16_16_16_16:
+ case GalVertexAttribSize._32_32_32_32:
+ Size = 4;
+ break;
+ }
+
+ bool Signed =
+ Attrib.Type == GalVertexAttribType.Snorm ||
+ Attrib.Type == GalVertexAttribType.Sint ||
+ Attrib.Type == GalVertexAttribType.Sscaled;
+
+ bool Normalize =
+ Attrib.Type == GalVertexAttribType.Snorm ||
+ Attrib.Type == GalVertexAttribType.Unorm;
+
+ VertexAttribPointerType Type = 0;
+
+ switch (Attrib.Type)
+ {
+ case GalVertexAttribType.Snorm:
+ case GalVertexAttribType.Unorm:
+ case GalVertexAttribType.Sint:
+ case GalVertexAttribType.Uint:
+ case GalVertexAttribType.Uscaled:
+ case GalVertexAttribType.Sscaled:
+ {
+ switch (Attrib.Size)
+ {
+ case GalVertexAttribSize._8:
+ case GalVertexAttribSize._8_8:
+ case GalVertexAttribSize._8_8_8:
+ case GalVertexAttribSize._8_8_8_8:
+ {
+ Type = Signed
+ ? VertexAttribPointerType.Byte
+ : VertexAttribPointerType.UnsignedByte;
+
+ break;
+ }
+
+ case GalVertexAttribSize._16:
+ case GalVertexAttribSize._16_16:
+ case GalVertexAttribSize._16_16_16:
+ case GalVertexAttribSize._16_16_16_16:
+ {
+ Type = Signed
+ ? VertexAttribPointerType.Short
+ : VertexAttribPointerType.UnsignedShort;
+
+ break;
+ }
+
+ case GalVertexAttribSize._10_10_10_2:
+ case GalVertexAttribSize._11_11_10:
+ case GalVertexAttribSize._32:
+ case GalVertexAttribSize._32_32:
+ case GalVertexAttribSize._32_32_32:
+ case GalVertexAttribSize._32_32_32_32:
+ {
+ Type = Signed
+ ? VertexAttribPointerType.Int
+ : VertexAttribPointerType.UnsignedInt;
+
+ break;
+ }
+ }
+
+ break;
+ }
+
+ case GalVertexAttribType.Float:
+ {
+ Type = VertexAttribPointerType.Float;
+
+ break;
+ }
+ }
+
+ GL.VertexAttribPointer(
+ Attrib.Index,
+ Size,
+ Type,
+ Normalize,
+ Stride,
+ Attrib.Offset);
+ }
+
+ GL.BindVertexArray(0);
+ }
+
+ public void SendR8G8B8A8Texture(int Index, byte[] Buffer, int Width, int Height)
+ {
+ EnsureTexInitialized(Index);
+
+ GL.BindTexture(TextureTarget.Texture2D, Textures[Index].Handle);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
+ GL.TexImage2D(TextureTarget.Texture2D,
+ 0,
+ PixelInternalFormat.Rgba,
+ Width,
+ Height,
+ 0,
+ PixelFormat.Rgba,
+ PixelType.UnsignedByte,
+ Buffer);
+ }
+
+ public void BindTexture(int Index)
+ {
+ GL.ActiveTexture(TextureUnit.Texture0 + Index);
+
+ GL.BindTexture(TextureTarget.Texture2D, Textures[Index].Handle);
+ }
+
+ private void EnsureVbInitialized(int VbIndex)
+ {
+ while (VbIndex >= VertexBuffers.Count)
+ {
+ VertexBuffers.Add(new VertexBuffer());
+ }
+
+ VertexBuffer Vb = VertexBuffers[VbIndex];
+
+ if (Vb.VaoHandle == 0)
+ {
+ Vb.VaoHandle = GL.GenVertexArray();
+ }
+
+ if (Vb.VboHandle == 0)
+ {
+ Vb.VboHandle = GL.GenBuffer();
+ }
+
+ VertexBuffers[VbIndex] = Vb;
+ }
+
+ private void EnsureTexInitialized(int TexIndex)
+ {
+ Texture Tex = Textures[TexIndex];
+
+ if (Tex.Handle == 0)
+ {
+ Tex.Handle = GL.GenTexture();
+ }
+
+ Textures[TexIndex] = Tex;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Gpu/BCn.cs b/Ryujinx/Gpu/BCn.cs
new file mode 100644
index 0000000000..bf782d167f
--- /dev/null
+++ b/Ryujinx/Gpu/BCn.cs
@@ -0,0 +1,468 @@
+using System;
+using System.Drawing;
+
+namespace Ryujinx.Gpu
+{
+ static class BCn
+ {
+ public static byte[] DecodeBC1(NsGpuTexture Tex, int Offset)
+ {
+ int W = (Tex.Width + 3) / 4;
+ int H = (Tex.Height + 3) / 4;
+
+ byte[] Output = new byte[W * H * 64];
+
+ SwizzleAddr Swizzle = new SwizzleAddr(W, H, 8);
+
+ for (int Y = 0; Y < H; Y++)
+ {
+ for (int X = 0; X < W; X++)
+ {
+ int IOffs = Offset + Swizzle.GetSwizzledAddress64(X, Y) * 8;
+
+ byte[] Tile = BCnDecodeTile(Tex.Data, IOffs, true);
+
+ int TOffset = 0;
+
+ for (int TY = 0; TY < 4; TY++)
+ {
+ for (int TX = 0; TX < 4; TX++)
+ {
+ int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4;
+
+ Output[OOffset + 0] = Tile[TOffset + 0];
+ Output[OOffset + 1] = Tile[TOffset + 1];
+ Output[OOffset + 2] = Tile[TOffset + 2];
+ Output[OOffset + 3] = Tile[TOffset + 3];
+
+ TOffset += 4;
+ }
+ }
+ }
+ }
+
+ return Output;
+ }
+
+ public static byte[] DecodeBC2(NsGpuTexture Tex, int Offset)
+ {
+ int W = (Tex.Width + 3) / 4;
+ int H = (Tex.Height + 3) / 4;
+
+ byte[] Output = new byte[W * H * 64];
+
+ SwizzleAddr Swizzle = new SwizzleAddr(W, H, 4);
+
+ for (int Y = 0; Y < H; Y++)
+ {
+ for (int X = 0; X < W; X++)
+ {
+ int IOffs = Offset + Swizzle.GetSwizzledAddress128(X, Y) * 16;
+
+ byte[] Tile = BCnDecodeTile(Tex.Data, IOffs + 8, false);
+
+ int AlphaLow = Get32(Tex.Data, IOffs + 0);
+ int AlphaHigh = Get32(Tex.Data, IOffs + 4);
+
+ ulong AlphaCh = (uint)AlphaLow | (ulong)AlphaHigh << 32;
+
+ int TOffset = 0;
+
+ for (int TY = 0; TY < 4; TY++)
+ {
+ for (int TX = 0; TX < 4; TX++)
+ {
+ ulong Alpha = (AlphaCh >> (TY * 16 + TX * 4)) & 0xf;
+
+ int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4;
+
+ Output[OOffset + 0] = Tile[TOffset + 0];
+ Output[OOffset + 1] = Tile[TOffset + 1];
+ Output[OOffset + 2] = Tile[TOffset + 2];
+ Output[OOffset + 3] = (byte)(Alpha | (Alpha << 4));
+
+ TOffset += 4;
+ }
+ }
+ }
+ }
+
+ return Output;
+ }
+
+ public static byte[] DecodeBC3(NsGpuTexture Tex, int Offset)
+ {
+ int W = (Tex.Width + 3) / 4;
+ int H = (Tex.Height + 3) / 4;
+
+ byte[] Output = new byte[W * H * 64];
+
+ SwizzleAddr Swizzle = new SwizzleAddr(W, H, 4);
+
+ for (int Y = 0; Y < H; Y++)
+ {
+ for (int X = 0; X < W; X++)
+ {
+ int IOffs = Offset + Swizzle.GetSwizzledAddress128(X, Y) * 16;
+
+ byte[] Tile = BCnDecodeTile(Tex.Data, IOffs + 8, false);
+
+ byte[] Alpha = new byte[8];
+
+ Alpha[0] = Tex.Data[IOffs + 0];
+ Alpha[1] = Tex.Data[IOffs + 1];
+
+ CalculateBC3Alpha(Alpha);
+
+ int AlphaLow = Get32(Tex.Data, IOffs + 2);
+ int AlphaHigh = Get16(Tex.Data, IOffs + 6);
+
+ ulong AlphaCh = (uint)AlphaLow | (ulong)AlphaHigh << 32;
+
+ int TOffset = 0;
+
+ for (int TY = 0; TY < 4; TY++)
+ {
+ for (int TX = 0; TX < 4; TX++)
+ {
+ int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4;
+
+ byte AlphaPx = Alpha[(AlphaCh >> (TY * 12 + TX * 3)) & 7];
+
+ Output[OOffset + 0] = Tile[TOffset + 0];
+ Output[OOffset + 1] = Tile[TOffset + 1];
+ Output[OOffset + 2] = Tile[TOffset + 2];
+ Output[OOffset + 3] = AlphaPx;
+
+ TOffset += 4;
+ }
+ }
+ }
+ }
+
+ return Output;
+ }
+
+ public static byte[] DecodeBC4(NsGpuTexture Tex, int Offset)
+ {
+ int W = (Tex.Width + 3) / 4;
+ int H = (Tex.Height + 3) / 4;
+
+ byte[] Output = new byte[W * H * 64];
+
+ SwizzleAddr Swizzle = new SwizzleAddr(W, H, 8);
+
+ for (int Y = 0; Y < H; Y++)
+ {
+ for (int X = 0; X < W; X++)
+ {
+ int IOffs = Swizzle.GetSwizzledAddress64(X, Y) * 8;
+
+ byte[] Red = new byte[8];
+
+ Red[0] = Tex.Data[IOffs + 0];
+ Red[1] = Tex.Data[IOffs + 1];
+
+ CalculateBC3Alpha(Red);
+
+ int RedLow = Get32(Tex.Data, IOffs + 2);
+ int RedHigh = Get16(Tex.Data, IOffs + 6);
+
+ ulong RedCh = (uint)RedLow | (ulong)RedHigh << 32;
+
+ int TOffset = 0;
+
+ for (int TY = 0; TY < 4; TY++)
+ {
+ for (int TX = 0; TX < 4; TX++)
+ {
+ int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4;
+
+ byte RedPx = Red[(RedCh >> (TY * 12 + TX * 3)) & 7];
+
+ Output[OOffset + 0] = RedPx;
+ Output[OOffset + 1] = RedPx;
+ Output[OOffset + 2] = RedPx;
+ Output[OOffset + 3] = 0xff;
+
+ TOffset += 4;
+ }
+ }
+ }
+ }
+
+ return Output;
+ }
+
+ public static byte[] DecodeBC5(NsGpuTexture Tex, int Offset, bool SNorm)
+ {
+ int W = (Tex.Width + 3) / 4;
+ int H = (Tex.Height + 3) / 4;
+
+ byte[] Output = new byte[W * H * 64];
+
+ SwizzleAddr Swizzle = new SwizzleAddr(W, H, 4);
+
+ for (int Y = 0; Y < H; Y++)
+ {
+ for (int X = 0; X < W; X++)
+ {
+ int IOffs = Swizzle.GetSwizzledAddress128(X, Y) * 16;
+
+ byte[] Red = new byte[8];
+ byte[] Green = new byte[8];
+
+ Red[0] = Tex.Data[IOffs + 0];
+ Red[1] = Tex.Data[IOffs + 1];
+
+ Green[0] = Tex.Data[IOffs + 8];
+ Green[1] = Tex.Data[IOffs + 9];
+
+ if (SNorm)
+ {
+ CalculateBC3AlphaS(Red);
+ CalculateBC3AlphaS(Green);
+ }
+ else
+ {
+ CalculateBC3Alpha(Red);
+ CalculateBC3Alpha(Green);
+ }
+
+ int RedLow = Get32(Tex.Data, IOffs + 2);
+ int RedHigh = Get16(Tex.Data, IOffs + 6);
+
+ int GreenLow = Get32(Tex.Data, IOffs + 10);
+ int GreenHigh = Get16(Tex.Data, IOffs + 14);
+
+ ulong RedCh = (uint)RedLow | (ulong)RedHigh << 32;
+ ulong GreenCh = (uint)GreenLow | (ulong)GreenHigh << 32;
+
+ int TOffset = 0;
+
+ if (SNorm)
+ {
+ for (int TY = 0; TY < 4; TY++)
+ {
+ for (int TX = 0; TX < 4; TX++)
+ {
+ int Shift = TY * 12 + TX * 3;
+
+ int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4;
+
+ byte RedPx = Red [(RedCh >> Shift) & 7];
+ byte GreenPx = Green[(GreenCh >> Shift) & 7];
+
+ RedPx += 0x80;
+ GreenPx += 0x80;
+
+ float NX = (RedPx / 255f) * 2 - 1;
+ float NY = (GreenPx / 255f) * 2 - 1;
+
+ float NZ = (float)Math.Sqrt(1 - (NX * NX + NY * NY));
+
+ Output[OOffset + 0] = Clamp((NZ + 1) * 0.5f);
+ Output[OOffset + 1] = Clamp((NY + 1) * 0.5f);
+ Output[OOffset + 2] = Clamp((NX + 1) * 0.5f);
+ Output[OOffset + 3] = 0xff;
+
+ TOffset += 4;
+ }
+ }
+ }
+ else
+ {
+ for (int TY = 0; TY < 4; TY++)
+ {
+ for (int TX = 0; TX < 4; TX++)
+ {
+ int Shift = TY * 12 + TX * 3;
+
+ int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4;
+
+ byte RedPx = Red [(RedCh >> Shift) & 7];
+ byte GreenPx = Green[(GreenCh >> Shift) & 7];
+
+ Output[OOffset + 0] = RedPx;
+ Output[OOffset + 1] = RedPx;
+ Output[OOffset + 2] = RedPx;
+ Output[OOffset + 3] = GreenPx;
+
+ TOffset += 4;
+ }
+ }
+ }
+ }
+ }
+
+ return Output;
+ }
+
+ private static byte Clamp(float Value)
+ {
+ if (Value > 1)
+ {
+ return 0xff;
+ }
+ else if (Value < 0)
+ {
+ return 0;
+ }
+ else
+ {
+ return (byte)(Value * 0xff);
+ }
+ }
+
+ private static void CalculateBC3Alpha(byte[] Alpha)
+ {
+ for (int i = 2; i < 8; i++)
+ {
+ if (Alpha[0] > Alpha[1])
+ {
+ Alpha[i] = (byte)(((8 - i) * Alpha[0] + (i - 1) * Alpha[1]) / 7);
+ }
+ else if (i < 6)
+ {
+ Alpha[i] = (byte)(((6 - i) * Alpha[0] + (i - 1) * Alpha[1]) / 7);
+ }
+ else if (i == 6)
+ {
+ Alpha[i] = 0;
+ }
+ else /* i == 7 */
+ {
+ Alpha[i] = 0xff;
+ }
+ }
+ }
+
+ private static void CalculateBC3AlphaS(byte[] Alpha)
+ {
+ for (int i = 2; i < 8; i++)
+ {
+ if ((sbyte)Alpha[0] > (sbyte)Alpha[1])
+ {
+ Alpha[i] = (byte)(((8 - i) * (sbyte)Alpha[0] + (i - 1) * (sbyte)Alpha[1]) / 7);
+ }
+ else if (i < 6)
+ {
+ Alpha[i] = (byte)(((6 - i) * (sbyte)Alpha[0] + (i - 1) * (sbyte)Alpha[1]) / 7);
+ }
+ else if (i == 6)
+ {
+ Alpha[i] = 0x80;
+ }
+ else /* i == 7 */
+ {
+ Alpha[i] = 0x7f;
+ }
+ }
+ }
+
+ private static byte[] BCnDecodeTile(
+ byte[] Input,
+ int Offset,
+ bool IsBC1)
+ {
+ Color[] CLUT = new Color[4];
+
+ int c0 = Get16(Input, Offset + 0);
+ int c1 = Get16(Input, Offset + 2);
+
+ CLUT[0] = DecodeRGB565(c0);
+ CLUT[1] = DecodeRGB565(c1);
+ CLUT[2] = CalculateCLUT2(CLUT[0], CLUT[1], c0, c1, IsBC1);
+ CLUT[3] = CalculateCLUT3(CLUT[0], CLUT[1], c0, c1, IsBC1);
+
+ int Indices = Get32(Input, Offset + 4);
+
+ int IdxShift = 0;
+
+ byte[] Output = new byte[4 * 4 * 4];
+
+ int OOffset = 0;
+
+ for (int TY = 0; TY < 4; TY++)
+ {
+ for (int TX = 0; TX < 4; TX++)
+ {
+ int Idx = (Indices >> IdxShift) & 3;
+
+ IdxShift += 2;
+
+ Color Pixel = CLUT[Idx];
+
+ Output[OOffset + 0] = Pixel.R;
+ Output[OOffset + 1] = Pixel.G;
+ Output[OOffset + 2] = Pixel.B;
+ Output[OOffset + 3] = Pixel.A;
+
+ OOffset += 4;
+ }
+ }
+
+ return Output;
+ }
+
+ private static Color CalculateCLUT2(Color C0, Color C1, int c0, int c1, bool IsBC1)
+ {
+ if (c0 > c1 || !IsBC1)
+ {
+ return Color.FromArgb(
+ (2 * C0.R + C1.R) / 3,
+ (2 * C0.G + C1.G) / 3,
+ (2 * C0.B + C1.B) / 3);
+ }
+ else
+ {
+ return Color.FromArgb(
+ (C0.R + C1.R) / 2,
+ (C0.G + C1.G) / 2,
+ (C0.B + C1.B) / 2);
+ }
+ }
+
+ private static Color CalculateCLUT3(Color C0, Color C1, int c0, int c1, bool IsBC1)
+ {
+ if (c0 > c1 || !IsBC1)
+ {
+ return
+ Color.FromArgb(
+ (2 * C1.R + C0.R) / 3,
+ (2 * C1.G + C0.G) / 3,
+ (2 * C1.B + C0.B) / 3);
+ }
+
+ return Color.Transparent;
+ }
+
+ private static Color DecodeRGB565(int Value)
+ {
+ int B = ((Value >> 0) & 0x1f) << 3;
+ int G = ((Value >> 5) & 0x3f) << 2;
+ int R = ((Value >> 11) & 0x1f) << 3;
+
+ return Color.FromArgb(
+ R | (R >> 5),
+ G | (G >> 6),
+ B | (B >> 5));
+ }
+
+ private static int Get16(byte[] Data, int Address)
+ {
+ return
+ Data[Address + 0] << 0 |
+ Data[Address + 1] << 8;
+ }
+
+ private static int Get32(byte[] Data, int Address)
+ {
+ return
+ Data[Address + 0] << 0 |
+ Data[Address + 1] << 8 |
+ Data[Address + 2] << 16 |
+ Data[Address + 3] << 24;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Gpu/NsGpu.cs b/Ryujinx/Gpu/NsGpu.cs
new file mode 100644
index 0000000000..6aa7332cdf
--- /dev/null
+++ b/Ryujinx/Gpu/NsGpu.cs
@@ -0,0 +1,22 @@
+using Gal;
+
+namespace Ryujinx.Gpu
+{
+ class NsGpu
+ {
+ public IGalRenderer Renderer { get; private set; }
+
+ public NsGpuMemoryMgr MemoryMgr { get; private set; }
+
+ public NsGpuPGraph PGraph { get; private set; }
+
+ public NsGpu(IGalRenderer Renderer)
+ {
+ this.Renderer = Renderer;
+
+ MemoryMgr = new NsGpuMemoryMgr();
+
+ PGraph = new NsGpuPGraph(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Gpu/NsGpuEngine.cs b/Ryujinx/Gpu/NsGpuEngine.cs
new file mode 100644
index 0000000000..bf1045696a
--- /dev/null
+++ b/Ryujinx/Gpu/NsGpuEngine.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.Gpu
+{
+ enum NsGpuEngine
+ {
+ None = 0,
+ _2d = 0x902d,
+ _3d = 0xb197,
+ Compute = 0xb1c0,
+ Kepler = 0xa140,
+ Dma = 0xb0b5,
+ GpFifo = 0xb06f
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Gpu/NsGpuMemoryMgr.cs b/Ryujinx/Gpu/NsGpuMemoryMgr.cs
new file mode 100644
index 0000000000..e555f2af3e
--- /dev/null
+++ b/Ryujinx/Gpu/NsGpuMemoryMgr.cs
@@ -0,0 +1,204 @@
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Gpu
+{
+ 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 + PTLvl0Bits;
+ 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 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;
+
+ 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);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ 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;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ 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];
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ 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/Gpu/NsGpuPBEntry.cs b/Ryujinx/Gpu/NsGpuPBEntry.cs
new file mode 100644
index 0000000000..226a7f61df
--- /dev/null
+++ b/Ryujinx/Gpu/NsGpuPBEntry.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.IO;
+
+namespace Ryujinx.Gpu
+{
+ struct NsGpuPBEntry
+ {
+ public NsGpuRegister Register { get; private set; }
+
+ public int SubChannel { get; private set; }
+
+ private int[] m_Arguments;
+
+ public ReadOnlyCollection Arguments => Array.AsReadOnly(m_Arguments);
+
+ public NsGpuPBEntry(NsGpuRegister Register, int SubChannel, params int[] Arguments)
+ {
+ this.Register = Register;
+ this.SubChannel = SubChannel;
+ this.m_Arguments = Arguments;
+ }
+
+ public static NsGpuPBEntry[] DecodePushBuffer(byte[] Data)
+ {
+ using (MemoryStream MS = new MemoryStream(Data))
+ {
+ BinaryReader Reader = new BinaryReader(MS);
+
+ List GpFifos = new List();
+
+ bool CanRead() => MS.Position + 4 <= MS.Length;
+
+ while (CanRead())
+ {
+ int Packed = Reader.ReadInt32();
+
+ int Reg = (Packed << 2) & 0x7ffc;
+ int SubC = (Packed >> 13) & 7;
+ int Args = (Packed >> 16) & 0x1fff;
+ int Mode = (Packed >> 29) & 7;
+
+ if (Mode == 4)
+ {
+ //Inline Mode.
+ GpFifos.Add(new NsGpuPBEntry((NsGpuRegister)Reg, SubC, Args));
+ }
+ else
+ {
+ //Word mode.
+ if (Mode == 1)
+ {
+ //Sequential Mode.
+ for (int Index = 0; Index < Args && CanRead(); Index++, Reg += 4)
+ {
+ GpFifos.Add(new NsGpuPBEntry((NsGpuRegister)Reg, SubC, Reader.ReadInt32()));
+ }
+ }
+ else
+ {
+ //Non-Sequential Mode.
+ int[] Arguments = new int[Args];
+
+ for (int Index = 0; Index < Args && CanRead(); Index++)
+ {
+ Arguments[Index] = Reader.ReadInt32();
+ }
+
+ GpFifos.Add(new NsGpuPBEntry((NsGpuRegister)Reg, SubC, Arguments));
+ }
+ }
+ }
+
+ return GpFifos.ToArray();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Gpu/NsGpuPGraph.cs b/Ryujinx/Gpu/NsGpuPGraph.cs
new file mode 100644
index 0000000000..e40b6283ee
--- /dev/null
+++ b/Ryujinx/Gpu/NsGpuPGraph.cs
@@ -0,0 +1,276 @@
+using ChocolArm64.Memory;
+using Gal;
+using System.Collections.Generic;
+
+namespace Ryujinx.Gpu
+{
+ class NsGpuPGraph
+ {
+ private NsGpu Gpu;
+
+ private int[] Registers;
+
+ public NsGpuEngine[] SubChannels;
+
+ private Dictionary CurrentVertexBuffers;
+
+ public NsGpuPGraph(NsGpu Gpu)
+ {
+ this.Gpu = Gpu;
+
+ Registers = new int[0x1000];
+
+ SubChannels = new NsGpuEngine[8];
+
+ CurrentVertexBuffers = new Dictionary();
+ }
+
+ public void ProcessPushBuffer(NsGpuPBEntry[] PushBuffer, AMemory Memory)
+ {
+ bool HasQuery = false;
+
+ foreach (NsGpuPBEntry Entry in PushBuffer)
+ {
+ if (Entry.Arguments.Count == 1)
+ {
+ SetRegister(Entry.Register, Entry.Arguments[0]);
+ }
+
+ switch (Entry.Register)
+ {
+ case NsGpuRegister.BindChannel:
+ if (Entry.Arguments.Count > 0)
+ {
+ SubChannels[Entry.SubChannel] = (NsGpuEngine)Entry.Arguments[0];
+ }
+ break;
+
+ case NsGpuRegister._3dVertexArray0Fetch:
+ SendVertexBuffers(Memory);
+ break;
+
+ case NsGpuRegister._3dCbData0:
+ if (GetRegister(NsGpuRegister._3dCbPos) == 0x20)
+ {
+ SendTexture(Memory);
+ }
+ break;
+
+ case NsGpuRegister._3dQueryAddressHigh:
+ case NsGpuRegister._3dQueryAddressLow:
+ case NsGpuRegister._3dQuerySequence:
+ case NsGpuRegister._3dQueryGet:
+ HasQuery = true;
+ break;
+ }
+ }
+
+ if (HasQuery)
+ {
+ long Position =
+ (long)GetRegister(NsGpuRegister._3dQueryAddressHigh) << 32 |
+ (long)GetRegister(NsGpuRegister._3dQueryAddressLow) << 0;
+
+ int Seq = GetRegister(NsGpuRegister._3dQuerySequence);
+ int Get = GetRegister(NsGpuRegister._3dQueryGet);
+
+ int Mode = Get & 3;
+
+ if (Mode == 0)
+ {
+ //Write
+ Position = Gpu.MemoryMgr.GetCpuAddr(Position);
+
+ if (Position != -1)
+ {
+ Gpu.Renderer.QueueAction(delegate()
+ {
+ Memory.WriteInt32(Position, Seq);
+ });
+ }
+ }
+ }
+ }
+
+ private void SendVertexBuffers(AMemory Memory)
+ {
+ long Position =
+ (long)GetRegister(NsGpuRegister._3dVertexArray0StartHigh) << 32 |
+ (long)GetRegister(NsGpuRegister._3dVertexArray0StartLow) << 0;
+
+ long Limit =
+ (long)GetRegister(NsGpuRegister._3dVertexArray0LimitHigh) << 32 |
+ (long)GetRegister(NsGpuRegister._3dVertexArray0LimitLow) << 0;
+
+ int VbIndex = CurrentVertexBuffers.Count;
+
+ if (!CurrentVertexBuffers.TryAdd(Position, VbIndex))
+ {
+ VbIndex = CurrentVertexBuffers[Position];
+ }
+
+ if (Limit != 0)
+ {
+ long Size = (Limit - Position) + 1;
+
+ Position = Gpu.MemoryMgr.GetCpuAddr(Position);
+
+ if (Position != -1)
+ {
+ byte[] Buffer = AMemoryHelper.ReadBytes(Memory, Position, (int)Size);
+
+ int Stride = GetRegister(NsGpuRegister._3dVertexArray0Fetch) & 0xfff;
+
+ List Attribs = new List();
+
+ for (int Attr = 0; Attr < 16; Attr++)
+ {
+ int Packed = GetRegister(NsGpuRegister._3dVertexAttrib0Format + Attr * 4);
+
+ GalVertexAttrib Attrib = new GalVertexAttrib(Attr,
+ (Packed >> 0) & 0x1f,
+ ((Packed >> 6) & 0x1) != 0,
+ (Packed >> 7) & 0x3fff,
+ (GalVertexAttribSize)((Packed >> 21) & 0x3f),
+ (GalVertexAttribType)((Packed >> 27) & 0x7),
+ ((Packed >> 31) & 0x1) != 0);
+
+ if (Attrib.Offset < Stride)
+ {
+ Attribs.Add(Attrib);
+ }
+ }
+
+ Gpu.Renderer.QueueAction(delegate()
+ {
+ Gpu.Renderer.SendVertexBuffer(VbIndex, Buffer, Stride, Attribs.ToArray());
+ });
+ }
+ }
+ }
+
+ private void SendTexture(AMemory Memory)
+ {
+ long TicPos = (long)GetRegister(NsGpuRegister._3dTicAddressHigh) << 32 |
+ (long)GetRegister(NsGpuRegister._3dTicAddressLow) << 0;
+
+ int CbData = GetRegister(NsGpuRegister._3dCbData0);
+
+ int TicIndex = (CbData >> 0) & 0xfffff;
+ int TscIndex = (CbData >> 20) & 0xfff; //I guess?
+
+ TicPos = Gpu.MemoryMgr.GetCpuAddr(TicPos + TicIndex * 0x20);
+
+ if (TicPos != -1)
+ {
+ int Word0 = Memory.ReadInt32(TicPos + 0x0);
+ int Word1 = Memory.ReadInt32(TicPos + 0x4);
+ int Word2 = Memory.ReadInt32(TicPos + 0x8);
+ int Word3 = Memory.ReadInt32(TicPos + 0xc);
+ int Word4 = Memory.ReadInt32(TicPos + 0x10);
+ int Word5 = Memory.ReadInt32(TicPos + 0x14);
+ int Word6 = Memory.ReadInt32(TicPos + 0x18);
+ int Word7 = Memory.ReadInt32(TicPos + 0x1c);
+
+ long TexAddress = Word1;
+
+ TexAddress |= (long)(Word2 & 0xff) << 32;
+
+ TexAddress = Gpu.MemoryMgr.GetCpuAddr(TexAddress);
+
+ if (TexAddress != -1)
+ {
+ NsGpuTextureFormat Format = (NsGpuTextureFormat)(Word0 & 0x7f);
+
+ int Width = (Word4 & 0xffff) + 1;
+ int Height = (Word5 & 0xffff) + 1;
+
+ byte[] Buffer = GetDecodedTexture(Memory, Format, TexAddress, Width, Height);
+
+ if (Buffer != null)
+ {
+ Gpu.Renderer.QueueAction(delegate()
+ {
+ Gpu.Renderer.SendR8G8B8A8Texture(0, Buffer, Width, Height);
+ });
+ }
+ }
+ }
+ }
+
+ private static byte[] GetDecodedTexture(
+ AMemory Memory,
+ NsGpuTextureFormat Format,
+ long Position,
+ int Width,
+ int Height)
+ {
+ byte[] Data = null;
+
+ switch (Format)
+ {
+ case NsGpuTextureFormat.BC1:
+ {
+ int Size = (Width * Height) >> 1;
+
+ Data = AMemoryHelper.ReadBytes(Memory, Position, Size);
+
+ Data = BCn.DecodeBC1(new NsGpuTexture()
+ {
+ Width = Width,
+ Height = Height,
+ Data = Data
+ }, 0);
+
+ break;
+ }
+
+ case NsGpuTextureFormat.BC2:
+ {
+ int Size = Width * Height;
+
+ Data = AMemoryHelper.ReadBytes(Memory, Position, Size);
+
+ Data = BCn.DecodeBC2(new NsGpuTexture()
+ {
+ Width = Width,
+ Height = Height,
+ Data = Data
+ }, 0);
+
+ break;
+ }
+
+ case NsGpuTextureFormat.BC3:
+ {
+ int Size = Width * Height;
+
+ Data = AMemoryHelper.ReadBytes(Memory, Position, Size);
+
+ Data = BCn.DecodeBC3(new NsGpuTexture()
+ {
+ Width = Width,
+ Height = Height,
+ Data = Data
+ }, 0);
+
+ break;
+ }
+
+ //default: throw new NotImplementedException(Format.ToString());
+ }
+
+ return Data;
+ }
+
+ public int GetRegister(NsGpuRegister Register)
+ {
+ return Registers[((int)Register >> 2) & 0xfff];
+ }
+
+ public void SetRegister(NsGpuRegister Register, int Value)
+ {
+ Registers[((int)Register >> 2) & 0xfff] = Value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Gpu/NsGpuRegister.cs b/Ryujinx/Gpu/NsGpuRegister.cs
new file mode 100644
index 0000000000..740ca9feb5
--- /dev/null
+++ b/Ryujinx/Gpu/NsGpuRegister.cs
@@ -0,0 +1,93 @@
+namespace Ryujinx.Gpu
+{
+ enum NsGpuRegister
+ {
+ BindChannel = 0,
+
+ _2dClipEnable = 0x0290,
+ _2dOperation = 0x02ac,
+
+ _3dGlobalBase = 0x02c8,
+ _3dRt0AddressHigh = 0x0800,
+ _3dRt0AddressLow = 0x0804,
+ _3dRt0Horiz = 0x0808,
+ _3dRt0Vert = 0x080c,
+ _3dRt0Format = 0x0810,
+ _3dRt0BlockDimensions = 0x0814,
+ _3dRt0ArrayMode = 0x0818,
+ _3dRt0LayerStride = 0x081c,
+ _3dRt0BaseLayer = 0x0820,
+ _3dViewportScaleX = 0x0a00,
+ _3dViewportScaleY = 0x0a04,
+ _3dViewportScaleZ = 0x0a08,
+ _3dViewportTranslateX = 0x0a0c,
+ _3dViewportTranslateY = 0x0a10,
+ _3dViewportTranslateZ = 0x0a14,
+ _3dViewportHoriz = 0x0c00,
+ _3dViewportVert = 0x0c04,
+ _3dDepthRangeNear = 0x0c08,
+ _3dDepthRangeFar = 0x0c0c,
+ _3dClearColorR = 0x0d80,
+ _3dClearColorG = 0x0d84,
+ _3dClearColorB = 0x0d88,
+ _3dClearColorA = 0x0d8c,
+ _3dScreenScissorHoriz = 0x0ff4,
+ _3dScreenScissorVert = 0x0ff8,
+ _3dVertexAttrib0Format = 0x1160,
+ _3dVertexAttrib1Format = 0x1164,
+ _3dVertexAttrib2Format = 0x1168,
+ _3dVertexAttrib3Format = 0x116c,
+ _3dVertexAttrib4Format = 0x1170,
+ _3dVertexAttrib5Format = 0x1174,
+ _3dVertexAttrib6Format = 0x1178,
+ _3dVertexAttrib7Format = 0x117c,
+ _3dVertexAttrib8Format = 0x1180,
+ _3dVertexAttrib9Format = 0x1184,
+ _3dVertexAttrib10Format = 0x1188,
+ _3dVertexAttrib11Format = 0x118c,
+ _3dVertexAttrib12Format = 0x1190,
+ _3dVertexAttrib13Format = 0x1194,
+ _3dVertexAttrib14Format = 0x1198,
+ _3dVertexAttrib15Format = 0x119c,
+ _3dScreenYControl = 0x13ac,
+ _3dTscAddressHigh = 0x155c,
+ _3dTscAddressLow = 0x1560,
+ _3dTscLimit = 0x1564,
+ _3dTicAddressHigh = 0x1574,
+ _3dTicAddressLow = 0x1578,
+ _3dTicLimit = 0x157c,
+ _3dMultiSampleMode = 0x15d0,
+ _3dVertexEndGl = 0x1614,
+ _3dVertexBeginGl = 0x1618,
+ _3dQueryAddressHigh = 0x1b00,
+ _3dQueryAddressLow = 0x1b04,
+ _3dQuerySequence = 0x1b08,
+ _3dQueryGet = 0x1b0c,
+ _3dVertexArray0Fetch = 0x1c00,
+ _3dVertexArray0StartHigh = 0x1c04,
+ _3dVertexArray0StartLow = 0x1c08,
+ _3dVertexArray1Fetch = 0x1c10, //todo: the rest
+ _3dVertexArray0LimitHigh = 0x1f00,
+ _3dVertexArray0LimitLow = 0x1f04,
+ _3dCbSize = 0x2380,
+ _3dCbAddressHigh = 0x2384,
+ _3dCbAddressLow = 0x2388,
+ _3dCbPos = 0x238c,
+ _3dCbData0 = 0x2390,
+ _3dCbData1 = 0x2394,
+ _3dCbData2 = 0x2398,
+ _3dCbData3 = 0x239c,
+ _3dCbData4 = 0x23a0,
+ _3dCbData5 = 0x23a4,
+ _3dCbData6 = 0x23a8,
+ _3dCbData7 = 0x23ac,
+ _3dCbData8 = 0x23b0,
+ _3dCbData9 = 0x23b4,
+ _3dCbData10 = 0x23b8,
+ _3dCbData11 = 0x23bc,
+ _3dCbData12 = 0x23c0,
+ _3dCbData13 = 0x23c4,
+ _3dCbData14 = 0x23c8,
+ _3dCbData15 = 0x23cc,
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Gpu/NsGpuTexture.cs b/Ryujinx/Gpu/NsGpuTexture.cs
new file mode 100644
index 0000000000..26500c04a2
--- /dev/null
+++ b/Ryujinx/Gpu/NsGpuTexture.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Gpu
+{
+ struct NsGpuTexture
+ {
+ public int Width;
+ public int Height;
+
+ public byte[] Data;
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Gpu/NsGpuTextureFormat.cs b/Ryujinx/Gpu/NsGpuTextureFormat.cs
new file mode 100644
index 0000000000..9bb122812d
--- /dev/null
+++ b/Ryujinx/Gpu/NsGpuTextureFormat.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Gpu
+{
+ enum NsGpuTextureFormat
+ {
+ BC1 = 0x24,
+ BC2 = 0x25,
+ BC3 = 0x26
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Gpu/SwizzleAddr.cs b/Ryujinx/Gpu/SwizzleAddr.cs
new file mode 100644
index 0000000000..5ad35a5389
--- /dev/null
+++ b/Ryujinx/Gpu/SwizzleAddr.cs
@@ -0,0 +1,144 @@
+using System;
+
+namespace Ryujinx.Gpu
+{
+ class SwizzleAddr
+ {
+ private int Width;
+
+ private int XB;
+ private int YB;
+
+ public SwizzleAddr(int Width, int Height, int Pad)
+ {
+ int W = Pow2RoundUp(Width);
+ int H = Pow2RoundUp(Height);
+
+ XB = CountZeros(W);
+ YB = CountZeros(H);
+
+ int HH = H >> 1;
+
+ if (!IsPow2(Height) && Height <= HH + HH / 3 && YB > 3)
+ {
+ YB--;
+ }
+
+ this.Width = RoundSize(Width, Pad);
+ }
+
+ private static int Pow2RoundUp(int Value)
+ {
+ Value--;
+
+ Value |= (Value >> 1);
+ Value |= (Value >> 2);
+ Value |= (Value >> 4);
+ Value |= (Value >> 8);
+ Value |= (Value >> 16);
+
+ return ++Value;
+ }
+
+ private static bool IsPow2(int Value)
+ {
+ return Value != 0 && (Value & (Value - 1)) == 0;
+ }
+
+ private static int CountZeros(int Value)
+ {
+ int Count = 0;
+
+ for (int i = 0; i < 32; i++)
+ {
+ if ((Value & (1 << i)) != 0)
+ {
+ break;
+ }
+
+ Count++;
+ }
+
+ return Count;
+ }
+
+ private static int RoundSize(int Size, int Pad)
+ {
+ int Mask = Pad - 1;
+
+ if ((Size & Mask) != 0)
+ {
+ Size &= ~Mask;
+ Size += Pad;
+ }
+
+ return Size;
+ }
+
+ public int GetSwizzledAddress8(int X, int Y)
+ {
+ return GetSwizzledAddress(X, Y, 4);
+ }
+
+ public int GetSwizzledAddress16(int X, int Y)
+ {
+ return GetSwizzledAddress(X, Y, 3);
+ }
+
+ public int GetSwizzledAddress32(int X, int Y)
+ {
+ return GetSwizzledAddress(X, Y, 2);
+ }
+
+ public int GetSwizzledAddress64(int X, int Y)
+ {
+ return GetSwizzledAddress(X, Y, 1);
+ }
+
+ public int GetSwizzledAddress128(int X, int Y)
+ {
+ return GetSwizzledAddress(X, Y, 0);
+ }
+
+ private int GetSwizzledAddress(int X, int Y, int XBase)
+ {
+ /*
+ * Examples of patterns:
+ * x x y x y y x y 0 0 0 0 64 x 64 dxt5
+ * x x x x x y y y y x y y x y 0 0 0 0 512 x 512 dxt5
+ * y x x x x x x y y y y x y y x y 0 0 0 0 1024 x 1024 dxt5
+ * y y x x x x x x y y y y x y y x y x 0 0 0 2048 x 2048 dxt1
+ * y y y x x x x x x y y y y x y y x y x x 0 0 1024 x 1024 rgba8888
+ *
+ * Read from right to left, LSB first.
+ */
+ int XCnt = XBase;
+ int YCnt = 1;
+ int XUsed = 0;
+ int YUsed = 0;
+ int Address = 0;
+
+ while (XUsed < XBase + 2 && XUsed + XCnt < XB)
+ {
+ int XMask = (1 << XCnt) - 1;
+ int YMask = (1 << YCnt) - 1;
+
+ Address |= (X & XMask) << XUsed + YUsed;
+ Address |= (Y & YMask) << XUsed + YUsed + XCnt;
+
+ X >>= XCnt;
+ Y >>= YCnt;
+
+ XUsed += XCnt;
+ YUsed += YCnt;
+
+ XCnt = Math.Min(XB - XUsed, 1);
+ YCnt = Math.Min(YB - YUsed, YCnt << 1);
+ }
+
+ Address |= (X + Y * (Width >> XUsed)) << (XUsed + YUsed);
+
+ return Address;
+ }
+ }
+}
diff --git a/Ryujinx/Loaders/Compression/Lz4.cs b/Ryujinx/Loaders/Compression/Lz4.cs
new file mode 100644
index 0000000000..aace200cc3
--- /dev/null
+++ b/Ryujinx/Loaders/Compression/Lz4.cs
@@ -0,0 +1,78 @@
+using System;
+
+namespace Ryujinx.Loaders.Compression
+{
+ static class Lz4
+ {
+ public static byte[] Decompress(byte[] Cmp, int DecLength)
+ {
+ byte[] Dec = new byte[DecLength];
+
+ int CmpPos = 0;
+ int DecPos = 0;
+
+ int GetLength(int Length)
+ {
+ byte Sum;
+
+ if (Length == 0xf)
+ {
+ do
+ {
+ Length += (Sum = Cmp[CmpPos++]);
+ }
+ while (Sum == 0xff);
+ }
+
+ return Length;
+ }
+
+ do
+ {
+ byte Token = Cmp[CmpPos++];
+
+ int EncCount = (Token >> 0) & 0xf;
+ int LitCount = (Token >> 4) & 0xf;
+
+ //Copy literal chunck
+ LitCount = GetLength(LitCount);
+
+ Buffer.BlockCopy(Cmp, CmpPos, Dec, DecPos, LitCount);
+
+ CmpPos += LitCount;
+ DecPos += LitCount;
+
+ if (CmpPos >= Cmp.Length)
+ {
+ break;
+ }
+
+ //Copy compressed chunck
+ int Back = Cmp[CmpPos++] << 0 |
+ Cmp[CmpPos++] << 8;
+
+ EncCount = GetLength(EncCount) + 4;
+
+ int EncPos = DecPos - Back;
+
+ if (EncCount <= Back)
+ {
+ Buffer.BlockCopy(Dec, EncPos, Dec, DecPos, EncCount);
+
+ DecPos += EncCount;
+ }
+ else
+ {
+ while (EncCount-- > 0)
+ {
+ Dec[DecPos++] = Dec[EncPos++];
+ }
+ }
+ }
+ while (CmpPos < Cmp.Length &&
+ DecPos < Dec.Length);
+
+ return Dec;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Loaders/ElfDyn.cs b/Ryujinx/Loaders/ElfDyn.cs
new file mode 100644
index 0000000000..595d6cfb5a
--- /dev/null
+++ b/Ryujinx/Loaders/ElfDyn.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.Loaders
+{
+ struct ElfDyn
+ {
+ public ElfDynTag Tag { get; private set; }
+
+ public long Value { get; private set; }
+
+ public ElfDyn(ElfDynTag Tag, long Value)
+ {
+ this.Tag = Tag;
+ this.Value = Value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Loaders/ElfDynTag.cs b/Ryujinx/Loaders/ElfDynTag.cs
new file mode 100644
index 0000000000..fb6cab3fb9
--- /dev/null
+++ b/Ryujinx/Loaders/ElfDynTag.cs
@@ -0,0 +1,72 @@
+namespace Ryujinx.Loaders
+{
+ enum ElfDynTag
+ {
+ DT_NULL = 0,
+ DT_NEEDED = 1,
+ DT_PLTRELSZ = 2,
+ DT_PLTGOT = 3,
+ DT_HASH = 4,
+ DT_STRTAB = 5,
+ DT_SYMTAB = 6,
+ DT_RELA = 7,
+ DT_RELASZ = 8,
+ DT_RELAENT = 9,
+ DT_STRSZ = 10,
+ DT_SYMENT = 11,
+ DT_INIT = 12,
+ DT_FINI = 13,
+ DT_SONAME = 14,
+ DT_RPATH = 15,
+ DT_SYMBOLIC = 16,
+ DT_REL = 17,
+ DT_RELSZ = 18,
+ DT_RELENT = 19,
+ DT_PLTREL = 20,
+ DT_DEBUG = 21,
+ DT_TEXTREL = 22,
+ DT_JMPREL = 23,
+ DT_BIND_NOW = 24,
+ DT_INIT_ARRAY = 25,
+ DT_FINI_ARRAY = 26,
+ DT_INIT_ARRAYSZ = 27,
+ DT_FINI_ARRAYSZ = 28,
+ DT_RUNPATH = 29,
+ DT_FLAGS = 30,
+ DT_ENCODING = 32,
+ DT_PREINIT_ARRAY = 32,
+ DT_PREINIT_ARRAYSZ = 33,
+ DT_GNU_PRELINKED = 0x6ffffdf5,
+ DT_GNU_CONFLICTSZ = 0x6ffffdf6,
+ DT_GNU_LIBLISTSZ = 0x6ffffdf7,
+ DT_CHECKSUM = 0x6ffffdf8,
+ DT_PLTPADSZ = 0x6ffffdf9,
+ DT_MOVEENT = 0x6ffffdfa,
+ DT_MOVESZ = 0x6ffffdfb,
+ DT_FEATURE_1 = 0x6ffffdfc,
+ DT_POSFLAG_1 = 0x6ffffdfd,
+ DT_SYMINSZ = 0x6ffffdfe,
+ DT_SYMINENT = 0x6ffffdff,
+ DT_GNU_HASH = 0x6ffffef5,
+ DT_TLSDESC_PLT = 0x6ffffef6,
+ DT_TLSDESC_GOT = 0x6ffffef7,
+ DT_GNU_CONFLICT = 0x6ffffef8,
+ DT_GNU_LIBLIST = 0x6ffffef9,
+ DT_CONFIG = 0x6ffffefa,
+ DT_DEPAUDIT = 0x6ffffefb,
+ DT_AUDIT = 0x6ffffefc,
+ DT_PLTPAD = 0x6ffffefd,
+ DT_MOVETAB = 0x6ffffefe,
+ DT_SYMINFO = 0x6ffffeff,
+ DT_VERSYM = 0x6ffffff0,
+ DT_RELACOUNT = 0x6ffffff9,
+ DT_RELCOUNT = 0x6ffffffa,
+ DT_FLAGS_1 = 0x6ffffffb,
+ DT_VERDEF = 0x6ffffffc,
+ DT_VERDEFNUM = 0x6ffffffd,
+ DT_VERNEED = 0x6ffffffe,
+ DT_VERNEEDNUM = 0x6fffffff,
+ DT_AUXILIARY = 0x7ffffffd,
+ DT_FILTER = 0x7fffffff
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Loaders/ElfRel.cs b/Ryujinx/Loaders/ElfRel.cs
new file mode 100644
index 0000000000..8b691d99c3
--- /dev/null
+++ b/Ryujinx/Loaders/ElfRel.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.Loaders
+{
+ struct ElfRel
+ {
+ public long Offset { get; private set; }
+ public long Addend { get; private set; }
+
+ public ElfSym Symbol { get; private set; }
+ public ElfRelType Type { get; private set; }
+
+ public ElfRel(long Offset, long Addend, ElfSym Symbol, ElfRelType Type)
+ {
+ this.Offset = Offset;
+ this.Addend = Addend;
+ this.Symbol = Symbol;
+ this.Type = Type;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Loaders/ElfRelType.cs b/Ryujinx/Loaders/ElfRelType.cs
new file mode 100644
index 0000000000..cc638b19dd
--- /dev/null
+++ b/Ryujinx/Loaders/ElfRelType.cs
@@ -0,0 +1,128 @@
+namespace Ryujinx.Loaders
+{
+ enum ElfRelType
+ {
+ R_AARCH64_NONE = 0,
+ R_AARCH64_ABS64 = 257,
+ R_AARCH64_ABS32 = 258,
+ R_AARCH64_ABS16 = 259,
+ R_AARCH64_PREL64 = 260,
+ R_AARCH64_PREL32 = 261,
+ R_AARCH64_PREL16 = 262,
+ R_AARCH64_MOVW_UABS_G0 = 263,
+ R_AARCH64_MOVW_UABS_G0_NC = 264,
+ R_AARCH64_MOVW_UABS_G1 = 265,
+ R_AARCH64_MOVW_UABS_G1_NC = 266,
+ R_AARCH64_MOVW_UABS_G2 = 267,
+ R_AARCH64_MOVW_UABS_G2_NC = 268,
+ R_AARCH64_MOVW_UABS_G3 = 269,
+ R_AARCH64_MOVW_SABS_G0 = 270,
+ R_AARCH64_MOVW_SABS_G1 = 271,
+ R_AARCH64_MOVW_SABS_G2 = 272,
+ R_AARCH64_LD_PREL_LO19 = 273,
+ R_AARCH64_ADR_PREL_LO21 = 274,
+ R_AARCH64_ADR_PREL_PG_HI21 = 275,
+ R_AARCH64_ADR_PREL_PG_HI21_NC = 276,
+ R_AARCH64_ADD_ABS_LO12_NC = 277,
+ R_AARCH64_LDST8_ABS_LO12_NC = 278,
+ R_AARCH64_TSTBR14 = 279,
+ R_AARCH64_CONDBR19 = 280,
+ R_AARCH64_JUMP26 = 282,
+ R_AARCH64_CALL26 = 283,
+ R_AARCH64_LDST16_ABS_LO12_NC = 284,
+ R_AARCH64_LDST32_ABS_LO12_NC = 285,
+ R_AARCH64_LDST64_ABS_LO12_NC = 286,
+ R_AARCH64_MOVW_PREL_G0 = 287,
+ R_AARCH64_MOVW_PREL_G0_NC = 288,
+ R_AARCH64_MOVW_PREL_G1 = 289,
+ R_AARCH64_MOVW_PREL_G1_NC = 290,
+ R_AARCH64_MOVW_PREL_G2 = 291,
+ R_AARCH64_MOVW_PREL_G2_NC = 292,
+ R_AARCH64_MOVW_PREL_G3 = 293,
+ R_AARCH64_LDST128_ABS_LO12_NC = 299,
+ R_AARCH64_MOVW_GOTOFF_G0 = 300,
+ R_AARCH64_MOVW_GOTOFF_G0_NC = 301,
+ R_AARCH64_MOVW_GOTOFF_G1 = 302,
+ R_AARCH64_MOVW_GOTOFF_G1_NC = 303,
+ R_AARCH64_MOVW_GOTOFF_G2 = 304,
+ R_AARCH64_MOVW_GOTOFF_G2_NC = 305,
+ R_AARCH64_MOVW_GOTOFF_G3 = 306,
+ R_AARCH64_GOTREL64 = 307,
+ R_AARCH64_GOTREL32 = 308,
+ R_AARCH64_GOT_LD_PREL19 = 309,
+ R_AARCH64_LD64_GOTOFF_LO15 = 310,
+ R_AARCH64_ADR_GOT_PAGE = 311,
+ R_AARCH64_LD64_GOT_LO12_NC = 312,
+ R_AARCH64_LD64_GOTPAGE_LO15 = 313,
+ R_AARCH64_TLSGD_ADR_PREL21 = 512,
+ R_AARCH64_TLSGD_ADR_PAGE21 = 513,
+ R_AARCH64_TLSGD_ADD_LO12_NC = 514,
+ R_AARCH64_TLSGD_MOVW_G1 = 515,
+ R_AARCH64_TLSGD_MOVW_G0_NC = 516,
+ R_AARCH64_TLSLD_ADR_PREL21 = 517,
+ R_AARCH64_TLSLD_ADR_PAGE21 = 518,
+ R_AARCH64_TLSLD_ADD_LO12_NC = 519,
+ R_AARCH64_TLSLD_MOVW_G1 = 520,
+ R_AARCH64_TLSLD_MOVW_G0_NC = 521,
+ R_AARCH64_TLSLD_LD_PREL19 = 522,
+ R_AARCH64_TLSLD_MOVW_DTPREL_G2 = 523,
+ R_AARCH64_TLSLD_MOVW_DTPREL_G1 = 524,
+ R_AARCH64_TLSLD_MOVW_DTPREL_G1_NC = 525,
+ R_AARCH64_TLSLD_MOVW_DTPREL_G0 = 526,
+ R_AARCH64_TLSLD_MOVW_DTPREL_G0_NC = 527,
+ R_AARCH64_TLSLD_ADD_DTPREL_HI12 = 528,
+ R_AARCH64_TLSLD_ADD_DTPREL_LO12 = 529,
+ R_AARCH64_TLSLD_ADD_DTPREL_LO12_NC = 530,
+ R_AARCH64_TLSLD_LDST8_DTPREL_LO12 = 531,
+ R_AARCH64_TLSLD_LDST8_DTPREL_LO12_NC = 532,
+ R_AARCH64_TLSLD_LDST16_DTPREL_LO12 = 533,
+ R_AARCH64_TLSLD_LDST16_DTPREL_LO12_NC = 534,
+ R_AARCH64_TLSLD_LDST32_DTPREL_LO12 = 535,
+ R_AARCH64_TLSLD_LDST32_DTPREL_LO12_NC = 536,
+ R_AARCH64_TLSLD_LDST64_DTPREL_LO12 = 537,
+ R_AARCH64_TLSLD_LDST64_DTPREL_LO12_NC = 538,
+ R_AARCH64_TLSIE_MOVW_GOTTPREL_G1 = 539,
+ R_AARCH64_TLSIE_MOVW_GOTTPREL_G0_NC = 540,
+ R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21 = 541,
+ R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC = 542,
+ R_AARCH64_TLSIE_LD_GOTTPREL_PREL19 = 543,
+ R_AARCH64_TLSLE_MOVW_TPREL_G2 = 544,
+ R_AARCH64_TLSLE_MOVW_TPREL_G1 = 545,
+ R_AARCH64_TLSLE_MOVW_TPREL_G1_NC = 546,
+ R_AARCH64_TLSLE_MOVW_TPREL_G0 = 547,
+ R_AARCH64_TLSLE_MOVW_TPREL_G0_NC = 548,
+ R_AARCH64_TLSLE_ADD_TPREL_HI12 = 549,
+ R_AARCH64_TLSLE_ADD_TPREL_LO12 = 550,
+ R_AARCH64_TLSLE_ADD_TPREL_LO12_NC = 551,
+ R_AARCH64_TLSLE_LDST8_TPREL_LO12 = 552,
+ R_AARCH64_TLSLE_LDST8_TPREL_LO12_NC = 553,
+ R_AARCH64_TLSLE_LDST16_TPREL_LO12 = 554,
+ R_AARCH64_TLSLE_LDST16_TPREL_LO12_NC = 555,
+ R_AARCH64_TLSLE_LDST32_TPREL_LO12 = 556,
+ R_AARCH64_TLSLE_LDST32_TPREL_LO12_NC = 557,
+ R_AARCH64_TLSLE_LDST64_TPREL_LO12 = 558,
+ R_AARCH64_TLSLE_LDST64_TPREL_LO12_NC = 559,
+ R_AARCH64_TLSDESC_LD_PREL19 = 560,
+ R_AARCH64_TLSDESC_ADR_PREL21 = 561,
+ R_AARCH64_TLSDESC_ADR_PAGE21 = 562,
+ R_AARCH64_TLSDESC_LD64_LO12 = 563,
+ R_AARCH64_TLSDESC_ADD_LO12 = 564,
+ R_AARCH64_TLSDESC_OFF_G1 = 565,
+ R_AARCH64_TLSDESC_OFF_G0_NC = 566,
+ R_AARCH64_TLSDESC_LDR = 567,
+ R_AARCH64_TLSDESC_ADD = 568,
+ R_AARCH64_TLSDESC_CALL = 569,
+ R_AARCH64_TLSLE_LDST128_TPREL_LO12 = 570,
+ R_AARCH64_TLSLE_LDST128_TPREL_LO12_NC = 571,
+ R_AARCH64_TLSLD_LDST128_DTPREL_LO12 = 572,
+ R_AARCH64_TLSLD_LDST128_DTPREL_LO12_NC = 573,
+ R_AARCH64_COPY = 1024,
+ R_AARCH64_GLOB_DAT = 1025,
+ R_AARCH64_JUMP_SLOT = 1026,
+ R_AARCH64_RELATIVE = 1027,
+ R_AARCH64_TLS_DTPMOD64 = 1028,
+ R_AARCH64_TLS_DTPREL64 = 1029,
+ R_AARCH64_TLS_TPREL64 = 1030,
+ R_AARCH64_TLSDESC = 1031
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Loaders/ElfSym.cs b/Ryujinx/Loaders/ElfSym.cs
new file mode 100644
index 0000000000..c4ed810ce6
--- /dev/null
+++ b/Ryujinx/Loaders/ElfSym.cs
@@ -0,0 +1,43 @@
+namespace Ryujinx.Loaders
+{
+ struct ElfSym
+ {
+ public string Name { get; private set; }
+
+ public ElfSymType Type { get; private set; }
+ public ElfSymBinding Binding { get; private set; }
+ public ElfSymVisibility Visibility { get; private set; }
+
+ public bool IsFuncOrObject =>
+ Type == ElfSymType.STT_FUNC ||
+ Type == ElfSymType.STT_OBJECT;
+
+ public bool IsGlobalOrWeak =>
+ Binding == ElfSymBinding.STB_GLOBAL ||
+ Binding == ElfSymBinding.STB_WEAK;
+
+ public int SHIdx { get; private set; }
+ public long ValueAbs { get; private set; }
+ public long Value { get; private set; }
+ public long Size { get; private set; }
+
+ public ElfSym(
+ string Name,
+ int Info,
+ int Other,
+ int SHIdx,
+ long ImageBase,
+ long Value,
+ long Size)
+ {
+ this.Name = Name;
+ this.Type = (ElfSymType)(Info & 0xf);
+ this.Binding = (ElfSymBinding)(Info >> 4);
+ this.Visibility = (ElfSymVisibility)Other;
+ this.SHIdx = SHIdx;
+ this.ValueAbs = Value + ImageBase;
+ this.Value = Value;
+ this.Size = Size;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Loaders/ElfSymBinding.cs b/Ryujinx/Loaders/ElfSymBinding.cs
new file mode 100644
index 0000000000..8bbc6d4e0f
--- /dev/null
+++ b/Ryujinx/Loaders/ElfSymBinding.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Loaders
+{
+ enum ElfSymBinding
+ {
+ STB_LOCAL = 0,
+ STB_GLOBAL = 1,
+ STB_WEAK = 2
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Loaders/ElfSymType.cs b/Ryujinx/Loaders/ElfSymType.cs
new file mode 100644
index 0000000000..e504411e17
--- /dev/null
+++ b/Ryujinx/Loaders/ElfSymType.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.Loaders
+{
+ enum ElfSymType
+ {
+ STT_NOTYPE = 0,
+ STT_OBJECT = 1,
+ STT_FUNC = 2,
+ STT_SECTION = 3,
+ STT_FILE = 4,
+ STT_COMMON = 5,
+ STT_TLS = 6
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Loaders/ElfSymVisibility.cs b/Ryujinx/Loaders/ElfSymVisibility.cs
new file mode 100644
index 0000000000..a308ef7952
--- /dev/null
+++ b/Ryujinx/Loaders/ElfSymVisibility.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Loaders
+{
+ enum ElfSymVisibility
+ {
+ STV_DEFAULT = 0,
+ STV_INTERNAL = 1,
+ STV_HIDDEN = 2,
+ STV_PROTECTED = 3
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Loaders/Executable.cs b/Ryujinx/Loaders/Executable.cs
new file mode 100644
index 0000000000..31caf2946f
--- /dev/null
+++ b/Ryujinx/Loaders/Executable.cs
@@ -0,0 +1,144 @@
+using ChocolArm64.Memory;
+using Ryujinx.Loaders.Executables;
+using Ryujinx.OsHle;
+using System.Collections.Generic;
+
+namespace Ryujinx.Loaders
+{
+ class Executable
+ {
+ private IElf NsoData;
+ private AMemory Memory;
+
+ private ElfDyn[] Dynamic;
+
+ public long ImageBase { get; private set; }
+ public long ImageEnd { get; private set; }
+
+ public Executable(IElf NsoData, AMemory Memory, long ImageBase)
+ {
+ this.NsoData = NsoData;
+ this.Memory = Memory;
+ this.ImageBase = ImageBase;
+ this.ImageEnd = ImageBase;
+
+ WriteData(ImageBase + NsoData.TextOffset, NsoData.Text, MemoryType.CodeStatic, AMemoryPerm.RX);
+ WriteData(ImageBase + NsoData.ROOffset, NsoData.RO, MemoryType.Normal, AMemoryPerm.Read);
+ WriteData(ImageBase + NsoData.DataOffset, NsoData.Data, MemoryType.Normal, AMemoryPerm.RW);
+
+ if (NsoData.Text.Count == 0)
+ {
+ return;
+ }
+
+ long Mod0Offset = ImageBase + NsoData.Mod0Offset;
+
+ int Mod0Magic = Memory.ReadInt32(Mod0Offset + 0x0);
+ long DynamicOffset = Memory.ReadInt32(Mod0Offset + 0x4) + Mod0Offset;
+ long BssStartOffset = Memory.ReadInt32(Mod0Offset + 0x8) + Mod0Offset;
+ long BssEndOffset = Memory.ReadInt32(Mod0Offset + 0xc) + Mod0Offset;
+ long EhHdrStartOffset = Memory.ReadInt32(Mod0Offset + 0x10) + Mod0Offset;
+ long EhHdrEndOffset = Memory.ReadInt32(Mod0Offset + 0x14) + Mod0Offset;
+ long ModObjOffset = Memory.ReadInt32(Mod0Offset + 0x18) + Mod0Offset;
+
+ long BssSize = BssEndOffset - BssStartOffset;
+
+ Memory.Manager.MapPhys(BssStartOffset, BssSize, (int)MemoryType.Normal, AMemoryPerm.RW);
+
+ ImageEnd = BssEndOffset;
+
+ List Dynamic = new List();
+
+ while (true)
+ {
+ long TagVal = Memory.ReadInt64(DynamicOffset + 0);
+ long Value = Memory.ReadInt64(DynamicOffset + 8);
+
+ DynamicOffset += 0x10;
+
+ ElfDynTag Tag = (ElfDynTag)TagVal;
+
+ if (Tag == ElfDynTag.DT_NULL)
+ {
+ break;
+ }
+
+ Dynamic.Add(new ElfDyn(Tag, Value));
+ }
+
+ this.Dynamic = Dynamic.ToArray();
+ }
+
+ private void WriteData(
+ long Position,
+ IList Data,
+ MemoryType Type,
+ AMemoryPerm Perm)
+ {
+ Memory.Manager.MapPhys(Position, Data.Count, (int)Type, Perm);
+
+ for (int Index = 0; Index < Data.Count; Index++)
+ {
+ Memory.WriteByte(Position + Index, Data[Index]);
+ }
+ }
+
+ private ElfRel GetRelocation(long Position)
+ {
+ long Offset = Memory.ReadInt64(Position + 0);
+ long Info = Memory.ReadInt64(Position + 8);
+ long Addend = Memory.ReadInt64(Position + 16);
+
+ int RelType = (int)(Info >> 0);
+ int SymIdx = (int)(Info >> 32);
+
+ ElfSym Symbol = GetSymbol(SymIdx);
+
+ return new ElfRel(Offset, Addend, Symbol, (ElfRelType)RelType);
+ }
+
+ private ElfSym GetSymbol(int Index)
+ {
+ long StrTblAddr = ImageBase + GetFirstValue(ElfDynTag.DT_STRTAB);
+ long SymTblAddr = ImageBase + GetFirstValue(ElfDynTag.DT_SYMTAB);
+
+ long SymEntSize = GetFirstValue(ElfDynTag.DT_SYMENT);
+
+ long Position = SymTblAddr + Index * SymEntSize;
+
+ return GetSymbol(Position, StrTblAddr);
+ }
+
+ private ElfSym GetSymbol(long Position, long StrTblAddr)
+ {
+ int NameIndex = Memory.ReadInt32(Position + 0);
+ int Info = Memory.ReadByte(Position + 4);
+ int Other = Memory.ReadByte(Position + 5);
+ int SHIdx = Memory.ReadInt16(Position + 6);
+ long Value = Memory.ReadInt64(Position + 8);
+ long Size = Memory.ReadInt64(Position + 16);
+
+ string Name = string.Empty;
+
+ for (int Chr; (Chr = Memory.ReadByte(StrTblAddr + NameIndex++)) != 0;)
+ {
+ Name += (char)Chr;
+ }
+
+ return new ElfSym(Name, Info, Other, SHIdx, ImageBase, Value, Size);
+ }
+
+ private long GetFirstValue(ElfDynTag Tag)
+ {
+ foreach (ElfDyn Entry in Dynamic)
+ {
+ if (Entry.Tag == Tag)
+ {
+ return Entry.Value;
+ }
+ }
+
+ return 0;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Loaders/Executables/IElf.cs b/Ryujinx/Loaders/Executables/IElf.cs
new file mode 100644
index 0000000000..bc8eb1bcfc
--- /dev/null
+++ b/Ryujinx/Loaders/Executables/IElf.cs
@@ -0,0 +1,17 @@
+using System.Collections.ObjectModel;
+
+namespace Ryujinx.Loaders.Executables
+{
+ interface IElf
+ {
+ ReadOnlyCollection Text { get; }
+ ReadOnlyCollection RO { get; }
+ ReadOnlyCollection Data { get; }
+
+ int Mod0Offset { get; }
+ int TextOffset { get; }
+ int ROOffset { get; }
+ int DataOffset { get; }
+ int BssSize { get; }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Loaders/Executables/Nro.cs b/Ryujinx/Loaders/Executables/Nro.cs
new file mode 100644
index 0000000000..5067ba12e4
--- /dev/null
+++ b/Ryujinx/Loaders/Executables/Nro.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.ObjectModel;
+using System.IO;
+
+namespace Ryujinx.Loaders.Executables
+{
+ class Nro : IElf
+ {
+ private byte[] m_Text;
+ private byte[] m_RO;
+ private byte[] m_Data;
+
+ public ReadOnlyCollection Text => Array.AsReadOnly(m_Text);
+ public ReadOnlyCollection RO => Array.AsReadOnly(m_RO);
+ public ReadOnlyCollection Data => Array.AsReadOnly(m_Data);
+
+ public int Mod0Offset { get; private set; }
+ public int TextOffset { get; private set; }
+ public int ROOffset { get; private set; }
+ public int DataOffset { get; private set; }
+ public int BssSize { get; private set; }
+
+ public Nro(Stream Input)
+ {
+ BinaryReader Reader = new BinaryReader(Input);
+
+ Input.Seek(4, SeekOrigin.Begin);
+
+ int Mod0Offset = Reader.ReadInt32();
+ int Padding8 = Reader.ReadInt32();
+ int Paddingc = Reader.ReadInt32();
+ int NroMagic = Reader.ReadInt32();
+ int Unknown14 = Reader.ReadInt32();
+ int FileSize = Reader.ReadInt32();
+ int Unknown1c = Reader.ReadInt32();
+ int TextOffset = Reader.ReadInt32();
+ int TextSize = Reader.ReadInt32();
+ int ROOffset = Reader.ReadInt32();
+ int ROSize = Reader.ReadInt32();
+ int DataOffset = Reader.ReadInt32();
+ int DataSize = Reader.ReadInt32();
+ int BssSize = Reader.ReadInt32();
+
+ this.Mod0Offset = Mod0Offset;
+ this.TextOffset = TextOffset;
+ this.ROOffset = ROOffset;
+ this.DataOffset = DataOffset;
+ this.BssSize = BssSize;
+
+ byte[] Read(long Position, int Size)
+ {
+ Input.Seek(Position, SeekOrigin.Begin);
+
+ return Reader.ReadBytes(Size);
+ }
+
+ m_Text = Read(TextOffset, TextSize);
+ m_RO = Read(ROOffset, ROSize);
+ m_Data = Read(DataOffset, DataSize);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/Loaders/Executables/Nso.cs b/Ryujinx/Loaders/Executables/Nso.cs
new file mode 100644
index 0000000000..ae9a9af627
--- /dev/null
+++ b/Ryujinx/Loaders/Executables/Nso.cs
@@ -0,0 +1,122 @@
+using Ryujinx.Loaders.Compression;
+using System;
+using System.Collections.ObjectModel;
+using System.IO;
+
+namespace Ryujinx.Loaders.Executables
+{
+ class Nso : IElf
+ {
+ private byte[] m_Text;
+ private byte[] m_RO;
+ private byte[] m_Data;
+
+ public ReadOnlyCollection Text => Array.AsReadOnly(m_Text);
+ public ReadOnlyCollection RO => Array.AsReadOnly(m_RO);
+ public ReadOnlyCollection Data => Array.AsReadOnly(m_Data);
+
+ public int Mod0Offset { get; private set; }
+ public int TextOffset { get; private set; }
+ public int ROOffset { get; private set; }
+ public int DataOffset { get; private set; }
+ public int BssSize { get; private set; }
+
+ [Flags]
+ private enum NsoFlags
+ {
+ IsTextCompressed = 1 << 0,
+ IsROCompressed = 1 << 1,
+ IsDataCompressed = 1 << 2,
+ HasTextHash = 1 << 3,
+ HasROHash = 1 << 4,
+ HasDataHash = 1 << 5
+ }
+
+ public Nso(Stream Input)
+ {
+ BinaryReader Reader = new BinaryReader(Input);
+
+ Input.Seek(0, SeekOrigin.Begin);
+
+ int NsoMagic = Reader.ReadInt32();
+ int Version = Reader.ReadInt32();
+ int Reserved = Reader.ReadInt32();
+ int FlagsMsk = Reader.ReadInt32();
+ int TextOffset = Reader.ReadInt32();
+ int TextMemOffset = Reader.ReadInt32();
+ int TextDecSize = Reader.ReadInt32();
+ int ModNameOffset = Reader.ReadInt32();
+ int ROOffset = Reader.ReadInt32();
+ int ROMemOffset = Reader.ReadInt32();
+ int RODecSize = Reader.ReadInt32();
+ int ModNameSize = Reader.ReadInt32();
+ int DataOffset = Reader.ReadInt32();
+ int DataMemOffset = Reader.ReadInt32();
+ int DataDecSize = Reader.ReadInt32();
+ int BssSize = Reader.ReadInt32();
+
+ byte[] BuildId = Reader.ReadBytes(0x20);
+
+ int TextSize = Reader.ReadInt32();
+ int ROSize = Reader.ReadInt32();
+ int DataSize = Reader.ReadInt32();
+
+ Input.Seek(0x24, SeekOrigin.Current);
+
+ int DynStrOffset = Reader.ReadInt32();
+ int DynStrSize = Reader.ReadInt32();
+ int DynSymOffset = Reader.ReadInt32();
+ int DynSymSize = Reader.ReadInt32();
+
+ byte[] TextHash = Reader.ReadBytes(0x20);
+ byte[] ROHash = Reader.ReadBytes(0x20);
+ byte[] DataHash = Reader.ReadBytes(0x20);
+
+ NsoFlags Flags = (NsoFlags)FlagsMsk;
+
+ this.TextOffset = TextMemOffset;
+ this.ROOffset = ROMemOffset;
+ this.DataOffset = DataMemOffset;
+ this.BssSize = BssSize;
+
+ //Text segment
+ Input.Seek(TextOffset, SeekOrigin.Begin);
+
+ m_Text = Reader.ReadBytes(TextSize);
+
+ if (Flags.HasFlag(NsoFlags.IsTextCompressed) || true)
+ {
+ m_Text = Lz4.Decompress(m_Text, TextDecSize);
+ }
+
+ //Read-only data segment
+ Input.Seek(ROOffset, SeekOrigin.Begin);
+
+ m_RO = Reader.ReadBytes(ROSize);
+
+ if (Flags.HasFlag(NsoFlags.IsROCompressed) || true)
+ {
+ m_RO = Lz4.Decompress(m_RO, RODecSize);
+ }
+
+ //Data segment
+ Input.Seek(DataOffset, SeekOrigin.Begin);
+
+ m_Data = Reader.ReadBytes(DataSize);
+
+ if (Flags.HasFlag(NsoFlags.IsDataCompressed) || true)
+ {
+ m_Data = Lz4.Decompress(m_Data, DataDecSize);
+ }
+
+ using (MemoryStream Text = new MemoryStream(m_Text))
+ {
+ BinaryReader TextReader = new BinaryReader(Text);
+
+ Text.Seek(4, SeekOrigin.Begin);
+
+ Mod0Offset = TextReader.ReadInt32();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/OsHle/CondVar.cs b/Ryujinx/OsHle/CondVar.cs
new file mode 100644
index 0000000000..02fb8ba37f
--- /dev/null
+++ b/Ryujinx/OsHle/CondVar.cs
@@ -0,0 +1,86 @@
+using ChocolArm64.Memory;
+using System.Collections.Concurrent;
+using System.Threading;
+
+namespace Ryujinx.OsHle
+{
+ class CondVar
+ {
+ private AMemory Memory;
+
+ private long CondVarAddress;
+ private long Timeout;
+
+ private class WaitingThread
+ {
+ public int Handle;
+
+ public ManualResetEvent Event;
+
+ public WaitingThread(int Handle, ManualResetEvent Event)
+ {
+ this.Handle = Handle;
+ this.Event = Event;
+ }
+ }
+
+ private ConcurrentQueue WaitingThreads;
+
+ public CondVar(AMemory Memory, long CondVarAddress, long Timeout)
+ {
+ this.Memory = Memory;
+ this.CondVarAddress = CondVarAddress;
+ this.Timeout = Timeout;
+
+ WaitingThreads = new ConcurrentQueue();
+ }
+
+ public void WaitForSignal(int ThreadHandle)
+ {
+ int Count = Memory.ReadInt32(CondVarAddress);
+
+ if (Count <= 0)
+ {
+ return;
+ }
+
+ Memory.WriteInt32(CondVarAddress, Count - 1);
+
+ ManualResetEvent Event = new ManualResetEvent(false);
+
+ WaitingThreads.Enqueue(new WaitingThread(ThreadHandle, Event));
+
+ if (Timeout != -1)
+ {
+ Event.WaitOne((int)(Timeout / 1000000));
+ }
+ else
+ {
+ Event.WaitOne();
+ }
+ }
+
+ public void SetSignal(int Count)
+ {
+ if (Count == -1)
+ {
+ while (WaitingThreads.TryDequeue(out WaitingThread Thread))
+ {
+ Thread.Event.Set();
+ }
+
+ Memory.WriteInt32(CondVarAddress, WaitingThreads.Count);
+ }
+ else
+ {
+ //TODO: Threads with the highest priority needs to be signaled first.
+ if (WaitingThreads.TryDequeue(out WaitingThread Thread))
+ {
+ Thread.Event.Set();
+ }
+
+ Memory.WriteInt32(CondVarAddress, Count);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/OsHle/Display.cs b/Ryujinx/OsHle/Display.cs
new file mode 100644
index 0000000000..f62430fa89
--- /dev/null
+++ b/Ryujinx/OsHle/Display.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.OsHle
+{
+ class Display
+ {
+ public string Name { get; private set; }
+
+ public Display(string Name)
+ {
+ this.Name = Name;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/OsHle/FileDesc.cs b/Ryujinx/OsHle/FileDesc.cs
new file mode 100644
index 0000000000..2a21f5007c
--- /dev/null
+++ b/Ryujinx/OsHle/FileDesc.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.OsHle
+{
+ class FileDesc
+ {
+ public string Name { get; private set; }
+
+ public FileDesc(string Name)
+ {
+ this.Name = Name;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/OsHle/Handles/HDomain.cs b/Ryujinx/OsHle/Handles/HDomain.cs
new file mode 100644
index 0000000000..fd252f5d7a
--- /dev/null
+++ b/Ryujinx/OsHle/Handles/HDomain.cs
@@ -0,0 +1,58 @@
+using Ryujinx.OsHle.Utilities;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.OsHle.Handles
+{
+ class HDomain : HSession
+ {
+ private Dictionary Objects;
+
+ private IdPool ObjIds;
+
+ public HDomain(HSession Session) : base(Session)
+ {
+ Objects = new Dictionary();
+
+ ObjIds = new IdPool();
+ }
+
+ public int GenertateObjectId(object Obj)
+ {
+ int Id = ObjIds.GenerateId();
+
+ if (Id == -1)
+ {
+ throw new InvalidOperationException();
+ }
+
+ Objects.Add(Id, Obj);
+
+ return Id;
+ }
+
+ public void DeleteObject(int Id)
+ {
+ if (Objects.TryGetValue(Id, out object Obj))
+ {
+ if (Obj is IDisposable DisposableObj)
+ {
+ DisposableObj.Dispose();
+ }
+
+ ObjIds.DeleteId(Id);
+ Objects.Remove(Id);
+ }
+ }
+
+ public object GetObject(int Id)
+ {
+ if (Objects.TryGetValue(Id, out object Obj))
+ {
+ return Obj;
+ }
+
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/OsHle/Handles/HEvent.cs b/Ryujinx/OsHle/Handles/HEvent.cs
new file mode 100644
index 0000000000..d9d0ff4c2d
--- /dev/null
+++ b/Ryujinx/OsHle/Handles/HEvent.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.OsHle.Handles
+{
+ class HEvent
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/OsHle/Handles/HNvMap.cs b/Ryujinx/OsHle/Handles/HNvMap.cs
new file mode 100644
index 0000000000..3e15eda385
--- /dev/null
+++ b/Ryujinx/OsHle/Handles/HNvMap.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.OsHle.Handles
+{
+ class HNvMap
+ {
+ public int Id { get; private set; }
+ public int Size { get; private set; }
+
+ public int Align { get; set; }
+ public int Kind { get; set; }
+ public long Address { get; set; }
+
+ public HNvMap(int Id, int Size)
+ {
+ this.Id = Id;
+ this.Size = Size;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/OsHle/Handles/HSession.cs b/Ryujinx/OsHle/Handles/HSession.cs
new file mode 100644
index 0000000000..6b90165916
--- /dev/null
+++ b/Ryujinx/OsHle/Handles/HSession.cs
@@ -0,0 +1,27 @@
+namespace Ryujinx.OsHle.Handles
+{
+ class HSession
+ {
+ public string ServiceName { get; private set; }
+
+ public bool IsInitialized { get; private set; }
+
+ public int State { get; set; }
+
+ public HSession(string ServiceName)
+ {
+ this.ServiceName = ServiceName;
+ }
+
+ public HSession(HSession Session)
+ {
+ ServiceName = Session.ServiceName;
+ IsInitialized = Session.IsInitialized;
+ }
+
+ public void Initialize()
+ {
+ IsInitialized = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/OsHle/Handles/HSessionObj.cs b/Ryujinx/OsHle/Handles/HSessionObj.cs
new file mode 100644
index 0000000000..c1e5e41a4c
--- /dev/null
+++ b/Ryujinx/OsHle/Handles/HSessionObj.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.OsHle.Handles
+{
+ class HSessionObj : HSession
+ {
+ public object Obj { get; private set; }
+
+ public HSessionObj(HSession Session, object Obj) : base(Session)
+ {
+ this.Obj = Obj;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/OsHle/Handles/HSharedMem.cs b/Ryujinx/OsHle/Handles/HSharedMem.cs
new file mode 100644
index 0000000000..acc1e7ebd7
--- /dev/null
+++ b/Ryujinx/OsHle/Handles/HSharedMem.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.OsHle.Handles
+{
+ class HSharedMem
+ {
+ public long PhysPos { get; private set; }
+
+ public HSharedMem(long PhysPos)
+ {
+ this.PhysPos = PhysPos;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/OsHle/Handles/HThread.cs b/Ryujinx/OsHle/Handles/HThread.cs
new file mode 100644
index 0000000000..9fb0b57bee
--- /dev/null
+++ b/Ryujinx/OsHle/Handles/HThread.cs
@@ -0,0 +1,14 @@
+using ChocolArm64;
+
+namespace Ryujinx.OsHle.Handles
+{
+ class HThread
+ {
+ public AThread Thread { get; private set; }
+
+ public HThread(AThread Thread)
+ {
+ this.Thread = Thread;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/OsHle/Handles/HTransferMem.cs b/Ryujinx/OsHle/Handles/HTransferMem.cs
new file mode 100644
index 0000000000..962d1b6640
--- /dev/null
+++ b/Ryujinx/OsHle/Handles/HTransferMem.cs
@@ -0,0 +1,23 @@
+using ChocolArm64.Memory;
+
+namespace Ryujinx.OsHle.Handles
+{
+ class HTransferMem
+ {
+ public AMemory Memory { get; private set; }
+ public AMemoryPerm Perm { get; private set; }
+
+ public long Position { get; private set; }
+ public long Size { get; private set; }
+ public long PhysPos { get; private set; }
+
+ public HTransferMem(AMemory Memory, AMemoryPerm Perm, long Position, long Size, long PhysPos)
+ {
+ this.Memory = Memory;
+ this.Perm = Perm;
+ this.Position = Position;
+ this.Size = Size;
+ this.PhysPos = PhysPos;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx/OsHle/Horizon.cs b/Ryujinx/OsHle/Horizon.cs
new file mode 100644
index 0000000000..bae33f8eb3
--- /dev/null
+++ b/Ryujinx/OsHle/Horizon.cs
@@ -0,0 +1,163 @@
+using ChocolArm64.Memory;
+using Ryujinx.Loaders.Executables;
+using Ryujinx.OsHle.Handles;
+using Ryujinx.OsHle.Utilities;
+using System.Collections.Concurrent;
+using System.IO;
+
+namespace Ryujinx.OsHle
+{
+ class Horizon
+ {
+ internal const int HidSize = 0x40000;
+ internal const int FontSize = 0x50;
+
+ internal int HidHandle { get; private set; }
+ internal int FontHandle { get; private set; }
+
+ public long HidOffset { get; private set; }
+ public long FontOffset { get; private set; }
+
+ internal IdPool IdGen { get; private set; }
+ internal IdPool NvMapIds { get; private set; }
+
+ internal IdPoolWithObj Handles { get; private set; }
+ internal IdPoolWithObj Fds { get; private set; }
+ internal IdPoolWithObj Displays { get; private set; }
+
+ public ConcurrentDictionary Mutexes { get; private set; }
+ public ConcurrentDictionary CondVars { get; private set; }
+
+ private ConcurrentDictionary Processes;
+
+ private AMemoryAlloc Allocator;
+
+ private Switch Ns;
+
+ public Horizon(Switch Ns)
+ {
+ this.Ns = Ns;
+
+ IdGen = new IdPool();
+ NvMapIds = new IdPool();
+
+ Handles = new IdPoolWithObj();
+ Fds = new IdPoolWithObj();
+ Displays = new IdPoolWithObj();
+
+ Mutexes = new ConcurrentDictionary();
+ CondVars = new ConcurrentDictionary();
+
+ Processes = new ConcurrentDictionary();
+
+ Allocator = new AMemoryAlloc();
+
+ HidOffset = Allocator.Alloc(HidSize);
+ FontOffset = Allocator.Alloc(FontSize);
+
+ HidHandle = Handles.GenerateId(new HSharedMem(HidOffset));
+ FontHandle = Handles.GenerateId(new HSharedMem(FontOffset));
+ }
+
+ public void LoadCart(string ExeFsDir, string RomFsFile = null)
+ {
+ if (RomFsFile != null)
+ {
+ Ns.VFs.LoadRomFs(RomFsFile);
+ }
+
+ int ProcessId = IdGen.GenerateId();
+
+ Process MainProcess = new Process(Ns, Allocator, ProcessId);
+
+ void LoadNso(string FileName)
+ {
+ foreach (string File in Directory.GetFiles(ExeFsDir, FileName))
+ {
+ if (Path.GetExtension(File) != string.Empty)
+ {
+ continue;
+ }
+
+ using (FileStream Input = new FileStream(File, FileMode.Open))
+ {
+ Nso Program = new Nso(Input);
+
+ MainProcess.LoadProgram(Program);
+ }
+ }
+ }
+
+ LoadNso("rtld");
+
+ MainProcess.SetEmptyArgs();
+
+ LoadNso("main");
+ LoadNso("subsdk*");
+ LoadNso("sdk");
+
+ MainProcess.InitializeHeap();
+ MainProcess.Run();
+
+ Processes.TryAdd(ProcessId, MainProcess);
+ }
+
+ public void LoadProgram(string FileName)
+ {
+ int ProcessId = IdGen.GenerateId();
+
+ Process MainProcess = new Process(Ns, Allocator, ProcessId);
+
+ using (FileStream Input = new FileStream(FileName, FileMode.Open))
+ {
+ if (Path.GetExtension(FileName).ToLower() == ".nro")
+ {
+ MainProcess.LoadProgram(new Nro(Input));
+ }
+ else
+ {
+ MainProcess.LoadProgram(new Nso(Input));
+ }
+ }
+
+ MainProcess.SetEmptyArgs();
+ MainProcess.InitializeHeap();
+ MainProcess.Run();
+
+ Processes.TryAdd(ProcessId, MainProcess);
+ }
+
+ public void StopAllProcesses()
+ {
+ foreach (Process Process in Processes.Values)
+ {
+ Process.StopAllThreads();
+ }
+ }
+
+ internal bool TryGetProcess(int ProcessId, out Process Process)
+ {
+ if (!Processes.TryGetValue(ProcessId, out Process))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ internal void CloseHandle(int Handle)
+ {
+ object HndData = Handles.GetData