commit b7e1d9930db6d80fcb1f7c5c6b0aa627e42e6595 Author: gdkchan Date: Sun Feb 4 20:08:20 2018 -0300 aloha diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..1ff0c4230 --- /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 000000000..82d9719b5 --- /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 000000000..d757db8fc --- /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 000000000..00d2e135a --- /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 000000000..44dc67c1f --- /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 000000000..2c25afc0f --- /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 000000000..c75364f71 --- /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 000000000..357dd45d1 --- /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 000000000..d15d2e123 --- /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 000000000..2fafcdd53 --- /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 000000000..1a821f479 --- /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 000000000..ba7f3df6f --- /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 000000000..32974c1ab --- /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 000000000..f2da8bd29 --- /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 000000000..a5601a3ab --- /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 000000000..1d3400396 --- /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 000000000..a2179f49e --- /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 000000000..242fdada1 --- /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 000000000..4e5a80700 --- /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 000000000..49f756e72 --- /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 000000000..981af800e --- /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 000000000..6b0adbc25 --- /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 000000000..8439df6f2 --- /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 000000000..7dd72a684 --- /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 000000000..2a56d4af4 --- /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 000000000..1b6a98e7c --- /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 000000000..e0ce57e38 --- /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 000000000..e4ae845fe --- /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 000000000..6b8b966db --- /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 000000000..a71fc338a --- /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 000000000..6498d8ec6 --- /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 000000000..ab7aa7545 --- /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 000000000..803eefc24 --- /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 000000000..c364ae68b --- /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 000000000..cdef3e745 --- /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 000000000..6d4a03861 --- /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 000000000..1950b2867 --- /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 000000000..3a28cfd73 --- /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 000000000..14edc5148 --- /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 000000000..b94294353 --- /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 000000000..ec866c84e --- /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 000000000..989271282 --- /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 000000000..3d1431fb1 --- /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 000000000..ca2b0cdb3 --- /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 000000000..796199845 --- /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 000000000..41f4d3b14 --- /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 000000000..e38e74247 --- /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 000000000..1047beffc --- /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 000000000..3a08ce63a --- /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 000000000..0b60bbe83 --- /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 000000000..1ef19a5d6 --- /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 000000000..cf6915f56 --- /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 000000000..9731c7e74 --- /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 000000000..db99e3d44 --- /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 000000000..aabf48461 --- /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 000000000..d3a8b76aa --- /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 000000000..828fe7884 --- /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 000000000..a677e579b --- /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 000000000..c8ae5bac7 --- /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 000000000..95b291001 --- /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 000000000..34ceea208 --- /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 000000000..44bf9cb2f --- /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 000000000..22af4c82d --- /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 000000000..04b5c5f7d --- /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 000000000..5ca9de403 --- /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 000000000..b49d5325a --- /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 000000000..1655abaac --- /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 000000000..0f5092d07 --- /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 000000000..19032ad94 --- /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 000000000..dd9d7a646 --- /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 000000000..c11384dae --- /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 000000000..d55c2c1ca --- /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 000000000..cab597d65 --- /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 000000000..c09b18632 --- /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 000000000..03355ebad --- /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 000000000..4eff013d1 --- /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 000000000..7153a6a0d --- /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 000000000..330809806 --- /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 000000000..6e5665fbc --- /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 000000000..6fa19c9fc --- /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 000000000..ca0c82a37 --- /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 000000000..ebb6e9328 --- /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 000000000..e05153d99 --- /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 000000000..719b53d5d --- /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 000000000..3713c81f6 --- /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 000000000..fcddb0c98 --- /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 000000000..0801716ad --- /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 000000000..23a0b6b2d --- /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 000000000..8712a7367 --- /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 000000000..910123148 --- /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 000000000..4e9bd53fc --- /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 000000000..b11e77931 --- /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 000000000..219aeebf9 --- /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 000000000..8ba6c25e6 --- /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 000000000..4d995469f --- /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 000000000..b425eb94b --- /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 000000000..3fed78cf6 --- /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 000000000..f55431a66 --- /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 000000000..5146bc313 --- /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 000000000..144f36b92 --- /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 000000000..f9776bb7d --- /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 000000000..3a424a7f9 --- /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 000000000..f7eb2e221 --- /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 000000000..3a43241a6 --- /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 000000000..2746e4288 --- /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 000000000..8969dc4e9 --- /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 000000000..0619149c3 --- /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 000000000..88841db77 --- /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 000000000..0ee39ad7e --- /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 000000000..a4bc93a06 --- /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 000000000..e4caad1ff --- /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 000000000..8cd944eb0 --- /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 000000000..80150ec5a --- /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 000000000..2169cc777 --- /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 000000000..1338ca1f3 --- /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 000000000..012e24aec --- /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 000000000..34aa224e5 --- /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 000000000..0661ddc8d --- /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 000000000..6e4e9a785 --- /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 000000000..abb35ec3f --- /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 000000000..7b6d99a0b --- /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 000000000..bbc326337 --- /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 000000000..11f0470c2 --- /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 000000000..c0ed59fb2 --- /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 000000000..306d0d511 --- /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 000000000..72ad6f706 --- /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 000000000..bf782d167 --- /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 000000000..6aa7332cd --- /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 000000000..bf1045696 --- /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 000000000..e555f2af3 --- /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 000000000..226a7f61d --- /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 000000000..e40b6283e --- /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 000000000..740ca9feb --- /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 000000000..26500c04a --- /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 000000000..9bb122812 --- /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 000000000..5ad35a538 --- /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 000000000..aace200cc --- /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 000000000..595d6cfb5 --- /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 000000000..fb6cab3fb --- /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 000000000..8b691d99c --- /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 000000000..cc638b19d --- /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 000000000..c4ed810ce --- /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 000000000..8bbc6d4e0 --- /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 000000000..e504411e1 --- /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 000000000..a308ef795 --- /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 000000000..31caf2946 --- /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 000000000..bc8eb1bcf --- /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 000000000..5067ba12e --- /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 000000000..ae9a9af62 --- /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 000000000..02fb8ba37 --- /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 000000000..f62430fa8 --- /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 000000000..2a21f5007 --- /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 000000000..fd252f5d7 --- /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 000000000..d9d0ff4c2 --- /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 000000000..3e15eda38 --- /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 000000000..6b9016591 --- /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 000000000..c1e5e41a4 --- /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 000000000..acc1e7ebd --- /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 000000000..9fb0b57be --- /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 000000000..962d1b664 --- /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 000000000..bae33f8eb --- /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(Handle); + + if (HndData is HTransferMem TransferMem) + { + TransferMem.Memory.Manager.Reprotect( + TransferMem.Position, + TransferMem.Size, + TransferMem.Perm); + } + + Handles.Delete(Handle); + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Ipc/IpcBuffDesc.cs b/Ryujinx/OsHle/Ipc/IpcBuffDesc.cs new file mode 100644 index 000000000..41b1efe03 --- /dev/null +++ b/Ryujinx/OsHle/Ipc/IpcBuffDesc.cs @@ -0,0 +1,27 @@ +using System.IO; + +namespace Ryujinx.OsHle.Ipc +{ + struct IpcBuffDesc + { + public long Position { get; private set; } + public long Size { get; private set; } + public int Flags { get; private set; } + + public IpcBuffDesc(BinaryReader Reader) + { + long Word0 = Reader.ReadUInt32(); + long Word1 = Reader.ReadUInt32(); + long Word2 = Reader.ReadUInt32(); + + Position = Word1; + Position |= (Word2 << 4) & 0x0f00000000; + Position |= (Word2 << 34) & 0x7000000000; + + Size = Word0; + Size |= (Word2 << 8) & 0xf00000000; + + Flags = (int)Word2 & 3; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Ipc/IpcDomCmd.cs b/Ryujinx/OsHle/Ipc/IpcDomCmd.cs new file mode 100644 index 000000000..035671859 --- /dev/null +++ b/Ryujinx/OsHle/Ipc/IpcDomCmd.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.OsHle.Ipc +{ + enum IpcDomCmd + { + SendMsg = 1, + DeleteObj = 2 + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Ipc/IpcHandleDesc.cs b/Ryujinx/OsHle/Ipc/IpcHandleDesc.cs new file mode 100644 index 000000000..fa5d7e1de --- /dev/null +++ b/Ryujinx/OsHle/Ipc/IpcHandleDesc.cs @@ -0,0 +1,90 @@ +using System; +using System.IO; + +namespace Ryujinx.OsHle.Ipc +{ + class IpcHandleDesc + { + public bool HasPId { get; private set; } + + public long PId { get; private set; } + + public int[] ToCopy { get; private set; } + public int[] ToMove { get; private set; } + + public IpcHandleDesc(BinaryReader Reader) + { + int Word = Reader.ReadInt32(); + + HasPId = (Word & 1) != 0; + + ToCopy = new int[(Word >> 1) & 0xf]; + ToMove = new int[(Word >> 5) & 0xf]; + + PId = HasPId ? Reader.ReadInt64() : 0; + + for (int Index = 0; Index < ToCopy.Length; Index++) + { + ToCopy[Index] = Reader.ReadInt32(); + } + + for (int Index = 0; Index < ToMove.Length; Index++) + { + ToMove[Index] = Reader.ReadInt32(); + } + } + + public IpcHandleDesc(int[] Copy, int[] Move) + { + ToCopy = Copy ?? throw new ArgumentNullException(nameof(Copy)); + ToMove = Move ?? throw new ArgumentNullException(nameof(Move)); + } + + public IpcHandleDesc(int[] Copy, int[] Move, long PId) : this(Copy, Move) + { + this.PId = PId; + + HasPId = true; + } + + public static IpcHandleDesc MakeCopy(int Handle) => new IpcHandleDesc( + new int[] { Handle }, + new int[0]); + + public static IpcHandleDesc MakeMove(int Handle) => new IpcHandleDesc( + new int[0], + new int[] { Handle }); + + public byte[] GetBytes() + { + using (MemoryStream MS = new MemoryStream()) + { + BinaryWriter Writer = new BinaryWriter(MS); + + int Word = HasPId ? 1 : 0; + + Word |= (ToCopy.Length & 0xf) << 1; + Word |= (ToMove.Length & 0xf) << 5; + + Writer.Write(Word); + + if (HasPId) + { + Writer.Write((long)PId); + } + + foreach (int Handle in ToCopy) + { + Writer.Write(Handle); + } + + foreach (int Handle in ToMove) + { + Writer.Write(Handle); + } + + return MS.ToArray(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Ipc/IpcHandler.cs b/Ryujinx/OsHle/Ipc/IpcHandler.cs new file mode 100644 index 000000000..444b1022b --- /dev/null +++ b/Ryujinx/OsHle/Ipc/IpcHandler.cs @@ -0,0 +1,358 @@ +using ChocolArm64.Memory; +using Ryujinx.OsHle.Handles; +using Ryujinx.OsHle.Objects; +using Ryujinx.OsHle.Services; +using System; +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.OsHle.Ipc +{ + static class IpcHandler + { + private delegate long ServiceProcessRequest(ServiceCtx Context); + + private static Dictionary<(string, int), ServiceProcessRequest> ServiceCmds = + new Dictionary<(string, int), ServiceProcessRequest>() + { + { ( "acc:u0", 3), Service.AccU0ListOpenUsers }, + { ( "acc:u0", 5), Service.AccU0GetProfile }, + { ( "acc:u0", 100), Service.AccU0InitializeApplicationInfo }, + { ( "acc:u0", 101), Service.AccU0GetBaasAccountManagerForApplication }, + { ( "apm", 0), Service.ApmOpenSession }, + { ( "appletOE", 0), Service.AppletOpenApplicationProxy }, + { ( "audout:u", 0), Service.AudOutListAudioOuts }, + { ( "audout:u", 1), Service.AudOutOpenAudioOut }, + { ( "audren:u", 0), Service.AudRenOpenAudioRenderer }, + { ( "audren:u", 1), Service.AudRenGetAudioRendererWorkBufferSize }, + { ( "friend:a", 0), Service.FriendCreateFriendService }, + { ( "fsp-srv", 1), Service.FspSrvInitialize }, + { ( "fsp-srv", 51), Service.FspSrvMountSaveData }, + { ( "fsp-srv", 200), Service.FspSrvOpenDataStorageByCurrentProcess }, + { ( "fsp-srv", 203), Service.FspSrvOpenRomStorage }, + { ( "fsp-srv", 1005), Service.FspSrvGetGlobalAccessLogMode }, + { ( "hid", 0), Service.HidCreateAppletResource }, + { ( "hid", 11), Service.HidActivateTouchScreen }, + { ( "hid", 100), Service.HidSetSupportedNpadStyleSet }, + { ( "hid", 102), Service.HidSetSupportedNpadIdType }, + { ( "hid", 103), Service.HidActivateNpad }, + { ( "hid", 120), Service.HidSetNpadJoyHoldType }, + { ( "lm", 0), Service.LmInitialize }, + { ( "nvdrv", 0), Service.NvDrvOpen }, + { ( "nvdrv", 1), Service.NvDrvIoctl }, + { ( "nvdrv", 2), Service.NvDrvClose }, + { ( "nvdrv", 3), Service.NvDrvInitialize }, + { ( "nvdrv", 4), Service.NvDrvQueryEvent }, + { ( "nvdrv:a", 0), Service.NvDrvOpen }, + { ( "nvdrv:a", 1), Service.NvDrvIoctl }, + { ( "nvdrv:a", 2), Service.NvDrvClose }, + { ( "nvdrv:a", 3), Service.NvDrvInitialize }, + { ( "nvdrv:a", 4), Service.NvDrvQueryEvent }, + { ( "pctl:a", 0), Service.PctlCreateService }, + { ( "pl:u", 1), Service.PlGetLoadState }, + { ( "pl:u", 2), Service.PlGetFontSize }, + { ( "pl:u", 3), Service.PlGetSharedMemoryAddressOffset }, + { ( "pl:u", 4), Service.PlGetSharedMemoryNativeHandle }, + { ( "set", 1), Service.SetGetAvailableLanguageCodes }, + { ( "sm:", 0), Service.SmInitialize }, + { ( "sm:", 1), Service.SmGetService }, + { ( "time:u", 0), Service.TimeGetStandardUserSystemClock }, + { ( "time:u", 1), Service.TimeGetStandardNetworkSystemClock }, + { ( "time:u", 2), Service.TimeGetStandardSteadyClock }, + { ( "time:u", 3), Service.TimeGetTimeZoneService }, + { ( "time:s", 0), Service.TimeGetStandardUserSystemClock }, + { ( "time:s", 1), Service.TimeGetStandardNetworkSystemClock }, + { ( "time:s", 2), Service.TimeGetStandardSteadyClock }, + { ( "time:s", 3), Service.TimeGetTimeZoneService }, + { ( "vi:m", 2), Service.ViGetDisplayService }, + }; + + private static Dictionary<(Type, int), ServiceProcessRequest> ObjectCmds = + new Dictionary<(Type, int), ServiceProcessRequest>() + { + //IManagerForApplication + { (typeof(AccIManagerForApplication), 0), AccIManagerForApplication.CheckAvailability }, + { (typeof(AccIManagerForApplication), 1), AccIManagerForApplication.GetAccountId }, + + //IProfile + { (typeof(AccIProfile), 1), AccIProfile.GetBase }, + + //IApplicationFunctions + { (typeof(AmIApplicationFunctions), 1), AmIApplicationFunctions.PopLaunchParameter }, + { (typeof(AmIApplicationFunctions), 20), AmIApplicationFunctions.EnsureSaveData }, + { (typeof(AmIApplicationFunctions), 21), AmIApplicationFunctions.GetDesiredLanguage }, + + //IApplicationProxy + { (typeof(AmIApplicationProxy), 0), AmIApplicationProxy.GetCommonStateGetter }, + { (typeof(AmIApplicationProxy), 1), AmIApplicationProxy.GetSelfController }, + { (typeof(AmIApplicationProxy), 2), AmIApplicationProxy.GetWindowController }, + { (typeof(AmIApplicationProxy), 3), AmIApplicationProxy.GetAudioController }, + { (typeof(AmIApplicationProxy), 4), AmIApplicationProxy.GetDisplayController }, + { (typeof(AmIApplicationProxy), 11), AmIApplicationProxy.GetLibraryAppletCreator }, + { (typeof(AmIApplicationProxy), 20), AmIApplicationProxy.GetApplicationFunctions }, + { (typeof(AmIApplicationProxy), 1000), AmIApplicationProxy.GetDebugFunctions }, + + //ICommonStateGetter + { (typeof(AmICommonStateGetter), 0), AmICommonStateGetter.GetEventHandle }, + { (typeof(AmICommonStateGetter), 1), AmICommonStateGetter.ReceiveMessage }, + { (typeof(AmICommonStateGetter), 5), AmICommonStateGetter.GetOperationMode }, + { (typeof(AmICommonStateGetter), 6), AmICommonStateGetter.GetPerformanceMode }, + { (typeof(AmICommonStateGetter), 9), AmICommonStateGetter.GetCurrentFocusState }, + + //ISelfController + { (typeof(AmISelfController), 11), AmISelfController.SetOperationModeChangedNotification }, + { (typeof(AmISelfController), 12), AmISelfController.SetPerformanceModeChangedNotification }, + { (typeof(AmISelfController), 13), AmISelfController.SetFocusHandlingMode }, + + //IStorage + { (typeof(AmIStorage), 0), AmIStorage.Open }, + + //IStorageAccessor + { (typeof(AmIStorageAccessor), 0), AmIStorageAccessor.GetSize }, + { (typeof(AmIStorageAccessor), 11), AmIStorageAccessor.Read }, + + //IWindowController + { (typeof(AmIWindowController), 1), AmIWindowController.GetAppletResourceUserId }, + { (typeof(AmIWindowController), 10), AmIWindowController.AcquireForegroundRights }, + + //ISession + { (typeof(ApmISession), 0), ApmISession.SetPerformanceConfiguration }, + + //IAudioRenderer + { (typeof(AudIAudioRenderer), 4), AudIAudioRenderer.RequestUpdateAudioRenderer }, + { (typeof(AudIAudioRenderer), 5), AudIAudioRenderer.StartAudioRenderer }, + { (typeof(AudIAudioRenderer), 7), AudIAudioRenderer.QuerySystemEvent }, + + //IFile + { (typeof(FspSrvIFile), 0), FspSrvIFile.Read }, + { (typeof(FspSrvIFile), 1), FspSrvIFile.Write }, + + //IFileSystem + { (typeof(FspSrvIFileSystem), 7), FspSrvIFileSystem.GetEntryType }, + { (typeof(FspSrvIFileSystem), 8), FspSrvIFileSystem.OpenFile }, + { (typeof(FspSrvIFileSystem), 10), FspSrvIFileSystem.Commit }, + + //IStorage + { (typeof(FspSrvIStorage), 0), FspSrvIStorage.Read }, + + //IAppletResource + { (typeof(HidIAppletResource), 0), HidIAppletResource.GetSharedMemoryHandle }, + + //ISystemClock + { (typeof(TimeISystemClock), 0), TimeISystemClock.GetCurrentTime }, + + //IApplicationDisplayService + { (typeof(ViIApplicationDisplayService), 100), ViIApplicationDisplayService.GetRelayService }, + { (typeof(ViIApplicationDisplayService), 101), ViIApplicationDisplayService.GetSystemDisplayService }, + { (typeof(ViIApplicationDisplayService), 102), ViIApplicationDisplayService.GetManagerDisplayService }, + { (typeof(ViIApplicationDisplayService), 1010), ViIApplicationDisplayService.OpenDisplay }, + { (typeof(ViIApplicationDisplayService), 2020), ViIApplicationDisplayService.OpenLayer }, + { (typeof(ViIApplicationDisplayService), 2030), ViIApplicationDisplayService.CreateStrayLayer }, + { (typeof(ViIApplicationDisplayService), 2101), ViIApplicationDisplayService.SetLayerScalingMode }, + { (typeof(ViIApplicationDisplayService), 5202), ViIApplicationDisplayService.GetDisplayVSyncEvent }, + + //IHOSBinderDriver + { (typeof(ViIHOSBinderDriver), 0), ViIHOSBinderDriver.TransactParcel }, + { (typeof(ViIHOSBinderDriver), 1), ViIHOSBinderDriver.AdjustRefcount }, + { (typeof(ViIHOSBinderDriver), 2), ViIHOSBinderDriver.GetNativeHandle }, + + //IManagerDisplayService + { (typeof(ViIManagerDisplayService), 2010), ViIManagerDisplayService.CreateManagedLayer }, + { (typeof(ViIManagerDisplayService), 6000), ViIManagerDisplayService.AddToLayerStack }, + + //ISystemDisplayService + { (typeof(ViISystemDisplayService), 2205), ViISystemDisplayService.SetLayerZ }, + }; + + private const long SfciMagic = 'S' << 0 | 'F' << 8 | 'C' << 16 | 'I' << 24; + private const long SfcoMagic = 'S' << 0 | 'F' << 8 | 'C' << 16 | 'O' << 24; + + public static void ProcessRequest( + Switch Ns, + AMemory Memory, + HSession Session, + IpcMessage Request, + long CmdPtr, + int HndId) + { + IpcMessage Response = new IpcMessage(Request.IsDomain); + + using (MemoryStream Raw = new MemoryStream(Request.RawData)) + { + BinaryReader ReqReader = new BinaryReader(Raw); + + if (Request.Type == IpcMessageType.Request) + { + string ServiceName = Session.ServiceName; + + ServiceProcessRequest ProcReq = null; + + bool IgnoreNullPR = false; + + if (Session is HDomain Dom) + { + if (Request.DomCmd == IpcDomCmd.SendMsg) + { + long Magic = ReqReader.ReadInt64(); + int CmdId = (int)ReqReader.ReadInt64(); + + object Obj = Dom.GetObject(Request.DomObjId); + + if (Obj is HDomain) + { + ServiceCmds.TryGetValue((ServiceName, CmdId), out ProcReq); + } + else if (Obj != null) + { + ObjectCmds.TryGetValue((Obj.GetType(), CmdId), out ProcReq); + } + } + else if (Request.DomCmd == IpcDomCmd.DeleteObj) + { + Dom.DeleteObject(Request.DomObjId); + + Response = FillResponse(Response, 0); + + IgnoreNullPR = true; + } + } + else + { + long Magic = ReqReader.ReadInt64(); + int CmdId = (int)ReqReader.ReadInt64(); + + if (Session is HSessionObj) + { + object Obj = ((HSessionObj)Session).Obj; + + ObjectCmds.TryGetValue((Obj.GetType(), CmdId), out ProcReq); + } + else + { + ServiceCmds.TryGetValue((ServiceName, CmdId), out ProcReq); + } + } + + if (ProcReq != null) + { + using (MemoryStream ResMS = new MemoryStream()) + { + BinaryWriter ResWriter = new BinaryWriter(ResMS); + + ServiceCtx Context = new ServiceCtx( + Ns, + Memory, + Session, + Request, + Response, + ReqReader, + ResWriter); + + long Result = ProcReq(Context); + + Response = FillResponse(Response, Result, ResMS.ToArray()); + } + } + else if (!IgnoreNullPR) + { + throw new NotImplementedException(ServiceName); + } + } + else if (Request.Type == IpcMessageType.Control) + { + long Magic = ReqReader.ReadInt64(); + long CmdId = ReqReader.ReadInt64(); + + switch (CmdId) + { + case 0: Request = IpcConvertSessionToDomain(Ns, Session, Response, HndId); break; + case 3: Request = IpcQueryBufferPointerSize(Response); break; + case 4: Request = IpcDuplicateSessionEx(Ns, Session, Response, ReqReader); break; + + default: throw new NotImplementedException(CmdId.ToString()); + } + } + else if (Request.Type == IpcMessageType.Unknown2) + { + //TODO + } + else + { + throw new NotImplementedException(Request.Type.ToString()); + } + + AMemoryHelper.WriteBytes(Memory, CmdPtr, Response.GetBytes(CmdPtr)); + } + } + + private static IpcMessage IpcConvertSessionToDomain( + Switch Ns, + HSession Session, + IpcMessage Response, + int HndId) + { + HDomain Dom = new HDomain(Session); + + Ns.Os.Handles.ReplaceData(HndId, Dom); + + return FillResponse(Response, 0, Dom.GenertateObjectId(Dom)); + } + + private static IpcMessage IpcDuplicateSessionEx( + Switch Ns, + HSession Session, + IpcMessage Response, + BinaryReader ReqReader) + { + int Unknown = ReqReader.ReadInt32(); + + int Handle = Ns.Os.Handles.GenerateId(Session); + + Response.HandleDesc = IpcHandleDesc.MakeMove(Handle); + + return FillResponse(Response, 0); + } + + private static IpcMessage IpcQueryBufferPointerSize(IpcMessage Response) + { + return FillResponse(Response, 0, 0x500); + } + + private static IpcMessage FillResponse(IpcMessage Response, long Result, params int[] Values) + { + using (MemoryStream MS = new MemoryStream()) + { + BinaryWriter Writer = new BinaryWriter(MS); + + foreach (int Value in Values) + { + Writer.Write(Value); + } + + return FillResponse(Response, Result, MS.ToArray()); + } + } + + private static IpcMessage FillResponse(IpcMessage Response, long Result, byte[] Data = null) + { + Response.Type = IpcMessageType.Response; + + using (MemoryStream MS = new MemoryStream()) + { + BinaryWriter Writer = new BinaryWriter(MS); + + Writer.Write(SfcoMagic); + Writer.Write(Result); + + if (Data != null) + { + Writer.Write(Data); + } + + Response.RawData = MS.ToArray(); + } + + return Response; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Ipc/IpcMessage.cs b/Ryujinx/OsHle/Ipc/IpcMessage.cs new file mode 100644 index 000000000..407fd65f9 --- /dev/null +++ b/Ryujinx/OsHle/Ipc/IpcMessage.cs @@ -0,0 +1,231 @@ +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.OsHle.Ipc +{ + class IpcMessage + { + public IpcMessageType Type { get; set; } + + public IpcHandleDesc HandleDesc { get; set; } + + public List PtrBuff { get; private set; } + public List SendBuff { get; private set; } + public List ReceiveBuff { get; private set; } + public List ExchangeBuff { get; private set; } + public List RecvListBuff { get; private set; } + + public List ResponseObjIds { get; private set; } + + public bool IsDomain { get; private set; } + public IpcDomCmd DomCmd { get; private set; } + public int DomObjId { get; private set; } + + public byte[] RawData { get; set; } + + public IpcMessage() + { + PtrBuff = new List(); + SendBuff = new List(); + ReceiveBuff = new List(); + ExchangeBuff = new List(); + RecvListBuff = new List(); + + ResponseObjIds = new List(); + } + + public IpcMessage(bool Domain) : this() + { + IsDomain = Domain; + } + + public IpcMessage(byte[] Data, long CmdPtr, bool Domain) : this() + { + using (MemoryStream MS = new MemoryStream(Data)) + { + BinaryReader Reader = new BinaryReader(MS); + + Initialize(Reader, CmdPtr, Domain); + } + } + + private void Initialize(BinaryReader Reader, long CmdPtr, bool Domain) + { + IsDomain = Domain; + + int Word0 = Reader.ReadInt32(); + int Word1 = Reader.ReadInt32(); + + Type = (IpcMessageType)(Word0 & 0xffff); + + int PtrBuffCount = (Word0 >> 16) & 0xf; + int SendBuffCount = (Word0 >> 20) & 0xf; + int RecvBuffCount = (Word0 >> 24) & 0xf; + int XchgBuffCount = (Word0 >> 28) & 0xf; + + int RawDataSize = (Word1 >> 0) & 0x3ff; + int RecvListFlags = (Word1 >> 10) & 0xf; + bool HndDescEnable = ((Word1 >> 31) & 0x1) != 0; + + if (HndDescEnable) + { + HandleDesc = new IpcHandleDesc(Reader); + } + + for (int Index = 0; Index < PtrBuffCount; Index++) + { + PtrBuff.Add(new IpcPtrBuffDesc(Reader)); + } + + void ReadBuff(List Buff, int Count) + { + for (int Index = 0; Index < Count; Index++) + { + Buff.Add(new IpcBuffDesc(Reader)); + } + } + + ReadBuff(SendBuff, SendBuffCount); + ReadBuff(ReceiveBuff, RecvBuffCount); + ReadBuff(ExchangeBuff, XchgBuffCount); + + RawDataSize *= 4; + + long RecvListPos = Reader.BaseStream.Position + RawDataSize; + + long Pad0 = GetPadSize16(Reader.BaseStream.Position + CmdPtr); + + Reader.BaseStream.Seek(Pad0, SeekOrigin.Current); + + int RecvListCount = RecvListFlags - 2; + + if (RecvListCount == 0) + { + RecvListCount = 1; + } + else if (RecvListCount < 0) + { + RecvListCount = 0; + } + + if (Domain) + { + int DomWord0 = Reader.ReadInt32(); + + DomCmd = (IpcDomCmd)(DomWord0 & 0xff); + + RawDataSize = (DomWord0 >> 16) & 0xffff; + + DomObjId = Reader.ReadInt32(); + + Reader.ReadInt64(); //Padding + } + + RawData = Reader.ReadBytes(RawDataSize); + + Reader.BaseStream.Seek(RecvListPos, SeekOrigin.Begin); + + for (int Index = 0; Index < RecvListCount; Index++) + { + RecvListBuff.Add(new IpcRecvListBuffDesc(Reader)); + } + } + + public byte[] GetBytes(long CmdPtr) + { + //todo + using (MemoryStream MS = new MemoryStream()) + { + BinaryWriter Writer = new BinaryWriter(MS); + + int Word0; + int Word1; + + Word0 = (int)Type; + Word0 |= (PtrBuff.Count & 0xf) << 16; + Word0 |= (SendBuff.Count & 0xf) << 20; + Word0 |= (ReceiveBuff.Count & 0xf) << 24; + Word0 |= (ExchangeBuff.Count & 0xf) << 28; + + byte[] HandleData = new byte[0]; + + if (HandleDesc != null) + { + HandleData = HandleDesc.GetBytes(); + } + + int DataLength = RawData?.Length ?? 0; + + int Pad0 = (int)GetPadSize16(CmdPtr + 8 + HandleData.Length); + + //Apparently, padding after Raw Data is 16 bytes, however when there is + //padding before Raw Data too, we need to subtract the size of this padding. + //This is the weirdest padding I've seen so far... + int Pad1 = 0x10 - Pad0; + + DataLength = (DataLength + Pad0 + Pad1 + (IsDomain ? 0x10 : 0)) / 4; + + DataLength += ResponseObjIds.Count; + + Word1 = DataLength & 0x3ff; + + if (HandleDesc != null) + { + Word1 |= 1 << 31; + } + + Writer.Write(Word0); + Writer.Write(Word1); + Writer.Write(HandleData); + + MS.Seek(Pad0, SeekOrigin.Current); + + if (IsDomain) + { + Writer.Write(ResponseObjIds.Count); + Writer.Write(0); + Writer.Write(0L); + } + + if (RawData != null) + { + Writer.Write(RawData); + } + + foreach (int Id in ResponseObjIds) + { + Writer.Write(Id); + } + + Writer.Write(new byte[Pad1]); + + return MS.ToArray(); + } + } + + private long GetPadSize16(long Position) + { + if ((Position & 0xf) != 0) + { + return 0x10 - (Position & 0xf); + } + + return 0; + } + + public long GetSendBuffPtr() + { + if (SendBuff.Count > 0 && SendBuff[0].Position != 0) + { + return SendBuff[0].Position; + } + + if (PtrBuff.Count > 0 && PtrBuff[0].Position != 0) + { + return PtrBuff[0].Position; + } + + return -1; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Ipc/IpcMessageType.cs b/Ryujinx/OsHle/Ipc/IpcMessageType.cs new file mode 100644 index 000000000..b0e283de7 --- /dev/null +++ b/Ryujinx/OsHle/Ipc/IpcMessageType.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.OsHle.Ipc +{ + enum IpcMessageType + { + Response = 0, + Unknown2 = 2, + Request = 4, + Control = 5 + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Ipc/IpcPtrBuffDesc.cs b/Ryujinx/OsHle/Ipc/IpcPtrBuffDesc.cs new file mode 100644 index 000000000..414d71f45 --- /dev/null +++ b/Ryujinx/OsHle/Ipc/IpcPtrBuffDesc.cs @@ -0,0 +1,26 @@ +using System.IO; + +namespace Ryujinx.OsHle.Ipc +{ + struct IpcPtrBuffDesc + { + public long Position { get; private set; } + public int Index { get; private set; } + public short Size { get; private set; } + + public IpcPtrBuffDesc(BinaryReader Reader) + { + long Word0 = Reader.ReadUInt32(); + long Word1 = Reader.ReadUInt32(); + + Position = Word1; + Position |= (Word0 << 20) & 0x0f00000000; + Position |= (Word0 << 30) & 0x7000000000; + + Index = ((int)Word0 >> 0) & 0x03f; + Index |= ((int)Word0 >> 3) & 0x1c0; + + Size = (short)(Word0 >> 16); + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Ipc/IpcRecvListBuffDesc.cs b/Ryujinx/OsHle/Ipc/IpcRecvListBuffDesc.cs new file mode 100644 index 000000000..8180b8dd3 --- /dev/null +++ b/Ryujinx/OsHle/Ipc/IpcRecvListBuffDesc.cs @@ -0,0 +1,19 @@ +using System.IO; + +namespace Ryujinx.OsHle.Ipc +{ + struct IpcRecvListBuffDesc + { + public long Position { get; private set; } + public short Size { get; private set; } + + public IpcRecvListBuffDesc(BinaryReader Reader) + { + long Value = Reader.ReadInt64(); + + Position = Value & 0xffffffffffff; + + Size = (short)(Value >> 48); + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/MemoryInfo.cs b/Ryujinx/OsHle/MemoryInfo.cs new file mode 100644 index 000000000..adf863b27 --- /dev/null +++ b/Ryujinx/OsHle/MemoryInfo.cs @@ -0,0 +1,28 @@ +using ChocolArm64.Memory; + +namespace Ryujinx.OsHle +{ + struct MemoryInfo + { + public long BaseAddress; + public long Size; + public int MemType; + public int MemAttr; + public int MemPerm; + public int IpcRefCount; + public int DeviceRefCount; + public int Padding; //SBZ + + public MemoryInfo(AMemoryMapInfo MapInfo) + { + BaseAddress = MapInfo.Position; + Size = MapInfo.Size; + MemType = MapInfo.Type; + MemAttr = 0; + MemPerm = (int)MapInfo.Perm; + IpcRefCount = 0; + DeviceRefCount = 0; + Padding = 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/MemoryType.cs b/Ryujinx/OsHle/MemoryType.cs new file mode 100644 index 000000000..b1ac330ae --- /dev/null +++ b/Ryujinx/OsHle/MemoryType.cs @@ -0,0 +1,25 @@ +namespace Ryujinx.OsHle +{ + enum MemoryType + { + Unmapped = 0, + Io = 1, + Normal = 2, + CodeStatic = 3, + CodeMutable = 4, + Heap = 5, + SharedMemory = 6, + ModCodeStatic = 8, + ModCodeMutable = 9, + IpcBuffer0 = 10, + MappedMemory = 11, + ThreadLocal = 12, + TransferMemoryIsolated = 13, + TransferMemory = 14, + ProcessMemory = 15, + Reserved = 16, + IpcBuffer1 = 17, + IpcBuffer3 = 18, + KernelStack = 19 + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Mutex.cs b/Ryujinx/OsHle/Mutex.cs new file mode 100644 index 000000000..99d12b28e --- /dev/null +++ b/Ryujinx/OsHle/Mutex.cs @@ -0,0 +1,89 @@ +using ChocolArm64; +using ChocolArm64.Memory; +using System.Threading; + +namespace Ryujinx.OsHle +{ + class Mutex + { + private const int MutexHasListenersMask = 0x40000000; + + private AMemory Memory; + + private long MutexAddress; + + private int CurrRequestingThreadHandle; + + private int HighestPriority; + + private ManualResetEvent ThreadEvent; + + private object EnterWaitLock; + + public Mutex(AMemory Memory, long MutexAddress) + { + this.Memory = Memory; + this.MutexAddress = MutexAddress; + + ThreadEvent = new ManualResetEvent(false); + + EnterWaitLock = new object(); + } + + public void WaitForLock(AThread RequestingThread, int RequestingThreadHandle) + { + lock (EnterWaitLock) + { + int CurrentThreadHandle = Memory.ReadInt32(MutexAddress) & ~MutexHasListenersMask; + + if (CurrentThreadHandle == RequestingThreadHandle || + CurrentThreadHandle == 0) + { + return; + } + + if (CurrRequestingThreadHandle == 0 || RequestingThread.Priority < HighestPriority) + { + CurrRequestingThreadHandle = RequestingThreadHandle; + + HighestPriority = RequestingThread.Priority; + } + } + + ThreadEvent.Reset(); + ThreadEvent.WaitOne(); + } + + public void GiveUpLock(int ThreadHandle) + { + lock (EnterWaitLock) + { + int CurrentThread = Memory.ReadInt32(MutexAddress) & ~MutexHasListenersMask; + + if (CurrentThread == ThreadHandle) + { + Unlock(); + } + } + } + + public void Unlock() + { + lock (EnterWaitLock) + { + if (CurrRequestingThreadHandle != 0) + { + Memory.WriteInt32(MutexAddress, CurrRequestingThreadHandle); + } + else + { + Memory.WriteInt32(MutexAddress, 0); + } + + CurrRequestingThreadHandle = 0; + + ThreadEvent.Set(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AccIManagerForApplication.cs b/Ryujinx/OsHle/Objects/AccIManagerForApplication.cs new file mode 100644 index 000000000..8e2a002ba --- /dev/null +++ b/Ryujinx/OsHle/Objects/AccIManagerForApplication.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.OsHle.Objects +{ + class AccIManagerForApplication + { + public static long CheckAvailability(ServiceCtx Context) + { + return 0; + } + + public static long GetAccountId(ServiceCtx Context) + { + Context.ResponseData.Write(0xcafeL); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AccIProfile.cs b/Ryujinx/OsHle/Objects/AccIProfile.cs new file mode 100644 index 000000000..2dbe189d7 --- /dev/null +++ b/Ryujinx/OsHle/Objects/AccIProfile.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.OsHle.Objects +{ + class AccIProfile + { + public static long GetBase(ServiceCtx Context) + { + Context.ResponseData.Write(0L); + Context.ResponseData.Write(0L); + Context.ResponseData.Write(0L); + Context.ResponseData.Write(0L); + Context.ResponseData.Write(0L); + Context.ResponseData.Write(0L); + Context.ResponseData.Write(0L); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AmIApplicationFunctions.cs b/Ryujinx/OsHle/Objects/AmIApplicationFunctions.cs new file mode 100644 index 000000000..e174f9433 --- /dev/null +++ b/Ryujinx/OsHle/Objects/AmIApplicationFunctions.cs @@ -0,0 +1,56 @@ +using System.IO; + +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Objects +{ + class AmIApplicationFunctions + { + private const uint LaunchParamsMagic = 0xc79497ca; + + public static long PopLaunchParameter(ServiceCtx Context) + { + //Only the first 0x18 bytes of the Data seems to be actually used. + MakeObject(Context, new AmIStorage(MakeLaunchParams())); + + return 0; + } + + public static long EnsureSaveData(ServiceCtx Context) + { + long UIdLow = Context.RequestData.ReadInt64(); + long UIdHigh = Context.RequestData.ReadInt64(); + + Context.ResponseData.Write(0L); + + return 0; + } + + public static long GetDesiredLanguage(ServiceCtx Context) + { + //This is an enumerator where each number is a differnet language. + //0 is Japanese and 1 is English, need to figure out the other codes. + Context.ResponseData.Write(1L); + + return 0; + } + + private static byte[] MakeLaunchParams() + { + //Size needs to be at least 0x88 bytes otherwise application errors. + using (MemoryStream MS = new MemoryStream()) + { + BinaryWriter Writer = new BinaryWriter(MS); + + MS.SetLength(0x88); + + Writer.Write(LaunchParamsMagic); + Writer.Write(1); //IsAccountSelected? Only lower 8 bits actually used. + Writer.Write(1L); //User Id Low (note: User Id needs to be != 0) + Writer.Write(0L); //User Id High + + return MS.ToArray(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AmIApplicationProxy.cs b/Ryujinx/OsHle/Objects/AmIApplicationProxy.cs new file mode 100644 index 000000000..2b0bc346d --- /dev/null +++ b/Ryujinx/OsHle/Objects/AmIApplicationProxy.cs @@ -0,0 +1,63 @@ +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Objects +{ + class AmIApplicationProxy + { + public static long GetCommonStateGetter(ServiceCtx Context) + { + MakeObject(Context, new AmICommonStateGetter()); + + return 0; + } + + public static long GetSelfController(ServiceCtx Context) + { + MakeObject(Context, new AmISelfController()); + + return 0; + } + + public static long GetWindowController(ServiceCtx Context) + { + MakeObject(Context, new AmIWindowController()); + + return 0; + } + + public static long GetAudioController(ServiceCtx Context) + { + MakeObject(Context, new AmIAudioController()); + + return 0; + } + + public static long GetDisplayController(ServiceCtx Context) + { + MakeObject(Context, new AmIDisplayController()); + + return 0; + } + + public static long GetLibraryAppletCreator(ServiceCtx Context) + { + MakeObject(Context, new AmILibraryAppletCreator()); + + return 0; + } + + public static long GetApplicationFunctions(ServiceCtx Context) + { + MakeObject(Context, new AmIApplicationFunctions()); + + return 0; + } + + public static long GetDebugFunctions(ServiceCtx Context) + { + MakeObject(Context, new AmIDebugFunctions()); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AmIAudioController.cs b/Ryujinx/OsHle/Objects/AmIAudioController.cs new file mode 100644 index 000000000..59d94bda4 --- /dev/null +++ b/Ryujinx/OsHle/Objects/AmIAudioController.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.OsHle.Objects +{ + class AmIAudioController + { + + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AmICommonStateGetter.cs b/Ryujinx/OsHle/Objects/AmICommonStateGetter.cs new file mode 100644 index 000000000..32f065c81 --- /dev/null +++ b/Ryujinx/OsHle/Objects/AmICommonStateGetter.cs @@ -0,0 +1,55 @@ +namespace Ryujinx.OsHle.Objects +{ + class AmICommonStateGetter + { + private enum FocusState + { + InFocus = 1, + OutOfFocus = 2 + } + + private enum OperationMode + { + Handheld = 0, + Docked = 1 + } + + public static long GetEventHandle(ServiceCtx Context) + { + Context.ResponseData.Write(0L); + + return 0; + } + + public static long ReceiveMessage(ServiceCtx Context) + { + //Program expects 0xF at 0x17ae70 on puyo sdk, + //otherwise runs on a infinite loop until it reads said value. + //What it means is still unknown. + Context.ResponseData.Write(0xfL); + + return 0; //0x680; + } + + public static long GetOperationMode(ServiceCtx Context) + { + Context.ResponseData.Write((byte)OperationMode.Handheld); + + return 0; + } + + public static long GetPerformanceMode(ServiceCtx Context) + { + Context.ResponseData.Write((byte)0); + + return 0; + } + + public static long GetCurrentFocusState(ServiceCtx Context) + { + Context.ResponseData.Write((byte)FocusState.InFocus); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AmIDebugFunctions.cs b/Ryujinx/OsHle/Objects/AmIDebugFunctions.cs new file mode 100644 index 000000000..9794c43f4 --- /dev/null +++ b/Ryujinx/OsHle/Objects/AmIDebugFunctions.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.OsHle.Objects +{ + class AmIDebugFunctions + { + + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AmIDisplayController.cs b/Ryujinx/OsHle/Objects/AmIDisplayController.cs new file mode 100644 index 000000000..be36ca0ca --- /dev/null +++ b/Ryujinx/OsHle/Objects/AmIDisplayController.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.OsHle.Objects +{ + class AmIDisplayController + { + + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AmILibraryAppletCreator.cs b/Ryujinx/OsHle/Objects/AmILibraryAppletCreator.cs new file mode 100644 index 000000000..585df9e99 --- /dev/null +++ b/Ryujinx/OsHle/Objects/AmILibraryAppletCreator.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.OsHle.Objects +{ + class AmILibraryAppletCreator + { + + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AmIParentalControlService.cs b/Ryujinx/OsHle/Objects/AmIParentalControlService.cs new file mode 100644 index 000000000..12c3c2a96 --- /dev/null +++ b/Ryujinx/OsHle/Objects/AmIParentalControlService.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.OsHle.Objects +{ + class AmIParentalControlService + { + + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AmISelfController.cs b/Ryujinx/OsHle/Objects/AmISelfController.cs new file mode 100644 index 000000000..33cde0958 --- /dev/null +++ b/Ryujinx/OsHle/Objects/AmISelfController.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.OsHle.Objects +{ + class AmISelfController + { + public static long SetOperationModeChangedNotification(ServiceCtx Context) + { + bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; + + return 0; + } + + public static long SetPerformanceModeChangedNotification(ServiceCtx Context) + { + bool Enable = Context.RequestData.ReadByte() != 0 ? true : false; + + return 0; + } + + public static long SetFocusHandlingMode(ServiceCtx Context) + { + bool Flag1 = Context.RequestData.ReadByte() != 0 ? true : false; + bool Flag2 = Context.RequestData.ReadByte() != 0 ? true : false; + bool Flag3 = Context.RequestData.ReadByte() != 0 ? true : false; + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AmIStorage.cs b/Ryujinx/OsHle/Objects/AmIStorage.cs new file mode 100644 index 000000000..7e608ac8c --- /dev/null +++ b/Ryujinx/OsHle/Objects/AmIStorage.cs @@ -0,0 +1,23 @@ +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Objects +{ + class AmIStorage + { + public byte[] Data { get; private set; } + + public AmIStorage(byte[] Data) + { + this.Data = Data; + } + + public static long Open(ServiceCtx Context) + { + AmIStorage Storage = Context.GetObject(); + + MakeObject(Context, new AmIStorageAccessor(Storage)); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AmIStorageAccessor.cs b/Ryujinx/OsHle/Objects/AmIStorageAccessor.cs new file mode 100644 index 000000000..62007ef81 --- /dev/null +++ b/Ryujinx/OsHle/Objects/AmIStorageAccessor.cs @@ -0,0 +1,56 @@ +using ChocolArm64.Memory; +using System; + +namespace Ryujinx.OsHle.Objects +{ + class AmIStorageAccessor + { + public AmIStorage Storage { get; private set; } + + public AmIStorageAccessor(AmIStorage Storage) + { + this.Storage = Storage; + } + + public static long GetSize(ServiceCtx Context) + { + AmIStorageAccessor Accessor = Context.GetObject(); + + Context.ResponseData.Write((long)Accessor.Storage.Data.Length); + + return 0; + } + + public static long Read(ServiceCtx Context) + { + AmIStorageAccessor Accessor = Context.GetObject(); + + AmIStorage Storage = Accessor.Storage; + + long ReadPosition = Context.RequestData.ReadInt64(); + + if (Context.Request.RecvListBuff.Count > 0) + { + long Position = Context.Request.RecvListBuff[0].Position; + short Size = Context.Request.RecvListBuff[0].Size; + + byte[] Data; + + if (Storage.Data.Length > Size) + { + Data = new byte[Size]; + + Buffer.BlockCopy(Storage.Data, 0, Data, 0, Size); + } + else + { + Data = Storage.Data; + } + + AMemoryHelper.WriteBytes(Context.Memory, Position, Data); + } + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AmIWindowController.cs b/Ryujinx/OsHle/Objects/AmIWindowController.cs new file mode 100644 index 000000000..ea967ae84 --- /dev/null +++ b/Ryujinx/OsHle/Objects/AmIWindowController.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.OsHle.Objects +{ + class AmIWindowController + { + public static long GetAppletResourceUserId(ServiceCtx Context) + { + Context.ResponseData.Write(0L); + + return 0; + } + + public static long AcquireForegroundRights(ServiceCtx Context) + { + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/ApmISession.cs b/Ryujinx/OsHle/Objects/ApmISession.cs new file mode 100644 index 000000000..2b2c03515 --- /dev/null +++ b/Ryujinx/OsHle/Objects/ApmISession.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.OsHle.Objects +{ + class ApmISession + { + public static long SetPerformanceConfiguration(ServiceCtx Context) + { + int PerfMode = Context.RequestData.ReadInt32(); + int PerfConfig = Context.RequestData.ReadInt32(); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/AudIAudioRenderer.cs b/Ryujinx/OsHle/Objects/AudIAudioRenderer.cs new file mode 100644 index 000000000..b451d07ba --- /dev/null +++ b/Ryujinx/OsHle/Objects/AudIAudioRenderer.cs @@ -0,0 +1,36 @@ +namespace Ryujinx.OsHle.Objects +{ + class AudIAudioRenderer + { + public static long RequestUpdateAudioRenderer(ServiceCtx Context) + { + long Position = Context.Request.ReceiveBuff[0].Position; + + //0x40 bytes header + Context.Memory.WriteInt32(Position + 0x4, 0xb0); //Behavior Out State Size? (note: this is the last section) + Context.Memory.WriteInt32(Position + 0x8, 0x18e0); //Memory Pool Out State Size? + Context.Memory.WriteInt32(Position + 0xc, 0x600); //Voice Out State Size? + Context.Memory.WriteInt32(Position + 0x14, 0xe0); //Effect Out State Size? + Context.Memory.WriteInt32(Position + 0x1c, 0x20); //Sink Out State Size? + Context.Memory.WriteInt32(Position + 0x20, 0x10); //Performance Out State Size? + Context.Memory.WriteInt32(Position + 0x3c, 0x20e0); //Total Size (including 0x40 bytes header) + + for (int Offset = 0x40; Offset < 0x40 + 0x18e0; Offset += 0x10) + { + Context.Memory.WriteInt32(Position + Offset, 5); + } + + return 0; + } + + public static long StartAudioRenderer(ServiceCtx Context) + { + return 0; + } + + public static long QuerySystemEvent(ServiceCtx Context) + { + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/FriendIFriendService.cs b/Ryujinx/OsHle/Objects/FriendIFriendService.cs new file mode 100644 index 000000000..9a39380a5 --- /dev/null +++ b/Ryujinx/OsHle/Objects/FriendIFriendService.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.OsHle.Objects +{ + class FriendIFriendService + { + + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/FspSrvIFile.cs b/Ryujinx/OsHle/Objects/FspSrvIFile.cs new file mode 100644 index 000000000..4b9f8c37b --- /dev/null +++ b/Ryujinx/OsHle/Objects/FspSrvIFile.cs @@ -0,0 +1,72 @@ +using ChocolArm64.Memory; +using System; + +using System.IO; + +namespace Ryujinx.OsHle.Objects +{ + class FspSrvIFile : IDisposable + { + public Stream BaseStream { get; private set; } + + public FspSrvIFile(Stream BaseStream) + { + this.BaseStream = BaseStream; + } + + public static long Read(ServiceCtx Context) + { + FspSrvIFile File = Context.GetObject(); + + long Position = Context.Request.ReceiveBuff[0].Position; + + long Zero = Context.RequestData.ReadInt64(); + long Offset = Context.RequestData.ReadInt64(); + long Size = Context.RequestData.ReadInt64(); + + byte[] Data = new byte[Size]; + + int ReadSize = File.BaseStream.Read(Data, 0, (int)Size); + + AMemoryHelper.WriteBytes(Context.Memory, Position, Data); + + //TODO: Use ReadSize, we need to return the size that was REALLY read from the file. + //This is a workaround because we are doing something wrong and the game expects to read + //data from a file that doesn't yet exists -- and breaks if it can't read anything. + Context.ResponseData.Write((long)Size); + + return 0; + } + + public static long Write(ServiceCtx Context) + { + FspSrvIFile File = Context.GetObject(); + + long Position = Context.Request.SendBuff[0].Position; + + long Zero = Context.RequestData.ReadInt64(); + long Offset = Context.RequestData.ReadInt64(); + long Size = Context.RequestData.ReadInt64(); + + byte[] Data = AMemoryHelper.ReadBytes(Context.Memory, Position, (int)Size); + + File.BaseStream.Seek(Offset, SeekOrigin.Begin); + File.BaseStream.Write(Data, 0, (int)Size); + + return 0; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && BaseStream != null) + { + BaseStream.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/FspSrvIFileSystem.cs b/Ryujinx/OsHle/Objects/FspSrvIFileSystem.cs new file mode 100644 index 000000000..fed1c55ac --- /dev/null +++ b/Ryujinx/OsHle/Objects/FspSrvIFileSystem.cs @@ -0,0 +1,70 @@ +using ChocolArm64.Memory; +using System.IO; + +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Objects +{ + class FspSrvIFileSystem + { + public string FilePath { get; private set; } + + public FspSrvIFileSystem(string Path) + { + this.FilePath = Path; + } + + public static long GetEntryType(ServiceCtx Context) + { + FspSrvIFileSystem FileSystem = Context.GetObject(); + + long Position = Context.Request.PtrBuff[0].Position; + + string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position); + + string FileName = Context.Ns.VFs.GetFullPath(FileSystem.FilePath, Name); + + if (FileName == null) + { + //TODO: Correct error code. + return -1; + } + + bool IsFile = File.Exists(FileName); + + Context.ResponseData.Write(IsFile ? 1 : 0); + + return 0; + } + + public static long OpenFile(ServiceCtx Context) + { + FspSrvIFileSystem FileSystem = Context.GetObject(); + + long Position = Context.Request.PtrBuff[0].Position; + + int FilterFlags = Context.RequestData.ReadInt32(); + + string Name = AMemoryHelper.ReadAsciiString(Context.Memory, Position); + + string FileName = Context.Ns.VFs.GetFullPath(FileSystem.FilePath, Name); + + if (FileName == null) + { + //TODO: Correct error code. + return -1; + } + + FileStream Stream = new FileStream(FileName, FileMode.OpenOrCreate); + + MakeObject(Context, new FspSrvIFile(Stream)); + + return 0; + } + + public static long Commit(ServiceCtx Context) + { + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/FspSrvIStorage.cs b/Ryujinx/OsHle/Objects/FspSrvIStorage.cs new file mode 100644 index 000000000..76ad8917a --- /dev/null +++ b/Ryujinx/OsHle/Objects/FspSrvIStorage.cs @@ -0,0 +1,44 @@ +using ChocolArm64.Memory; +using Ryujinx.OsHle.Ipc; +using System.IO; + +namespace Ryujinx.OsHle.Objects +{ + class FspSrvIStorage + { + public Stream BaseStream { get; private set; } + + public FspSrvIStorage(Stream BaseStream) + { + this.BaseStream = BaseStream; + } + + public static long Read(ServiceCtx Context) + { + FspSrvIStorage Storage = Context.GetObject(); + + long Offset = Context.RequestData.ReadInt64(); + long Size = Context.RequestData.ReadInt64(); + + if (Context.Request.ReceiveBuff.Count > 0) + { + IpcBuffDesc BuffDesc = Context.Request.ReceiveBuff[0]; + + //Use smaller length to avoid overflows. + if (Size > BuffDesc.Size) + { + Size = BuffDesc.Size; + } + + byte[] Data = new byte[Size]; + + Storage.BaseStream.Seek(Offset, SeekOrigin.Begin); + Storage.BaseStream.Read(Data, 0, Data.Length); + + AMemoryHelper.WriteBytes(Context.Memory, BuffDesc.Position, Data); + } + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/HidIAppletResource.cs b/Ryujinx/OsHle/Objects/HidIAppletResource.cs new file mode 100644 index 000000000..73b948df7 --- /dev/null +++ b/Ryujinx/OsHle/Objects/HidIAppletResource.cs @@ -0,0 +1,22 @@ +using Ryujinx.OsHle.Handles; +using Ryujinx.OsHle.Ipc; + +namespace Ryujinx.OsHle.Objects +{ + class HidIAppletResource + { + public HSharedMem Handle; + + public HidIAppletResource(HSharedMem Handle) + { + this.Handle = Handle; + } + + public static long GetSharedMemoryHandle(ServiceCtx Context) + { + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Context.Ns.Os.HidHandle); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/ObjHelper.cs b/Ryujinx/OsHle/Objects/ObjHelper.cs new file mode 100644 index 000000000..e37f5320e --- /dev/null +++ b/Ryujinx/OsHle/Objects/ObjHelper.cs @@ -0,0 +1,24 @@ +using Ryujinx.OsHle.Handles; +using Ryujinx.OsHle.Ipc; + +namespace Ryujinx.OsHle.Objects +{ + static class ObjHelper + { + public static void MakeObject(ServiceCtx Context, object Obj) + { + if (Context.Session is HDomain Dom) + { + Context.Response.ResponseObjIds.Add(Dom.GenertateObjectId(Obj)); + } + else + { + HSessionObj HndData = new HSessionObj(Context.Session, Obj); + + int VHandle = Context.Ns.Os.Handles.GenerateId(HndData); + + Context.Response.HandleDesc = IpcHandleDesc.MakeMove(VHandle); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/Parcel.cs b/Ryujinx/OsHle/Objects/Parcel.cs new file mode 100644 index 000000000..0d322bab8 --- /dev/null +++ b/Ryujinx/OsHle/Objects/Parcel.cs @@ -0,0 +1,58 @@ +using System; +using System.IO; + +namespace Ryujinx.OsHle.Objects.Android +{ + static class Parcel + { + public static byte[] GetParcelData(byte[] Parcel) + { + if (Parcel == null) + { + throw new ArgumentNullException(nameof(Parcel)); + } + + using (MemoryStream MS = new MemoryStream(Parcel)) + { + BinaryReader Reader = new BinaryReader(MS); + + int DataSize = Reader.ReadInt32(); + int DataOffset = Reader.ReadInt32(); + int ObjsSize = Reader.ReadInt32(); + int ObjsOffset = Reader.ReadInt32(); + + MS.Seek(DataOffset - 0x10, SeekOrigin.Current); + + return Reader.ReadBytes(DataSize); + } + } + + public static byte[] MakeParcel(byte[] Data, byte[] Objs) + { + if (Data == null) + { + throw new ArgumentNullException(nameof(Data)); + } + + if (Objs == null) + { + throw new ArgumentNullException(nameof(Objs)); + } + + using (MemoryStream MS = new MemoryStream()) + { + BinaryWriter Writer = new BinaryWriter(MS); + + Writer.Write(Data.Length); + Writer.Write(0x10); + Writer.Write(Objs.Length); + Writer.Write(Data.Length + 0x10); + + Writer.Write(Data); + Writer.Write(Objs); + + return MS.ToArray(); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/TimeISteadyClock.cs b/Ryujinx/OsHle/Objects/TimeISteadyClock.cs new file mode 100644 index 000000000..ead8c41a1 --- /dev/null +++ b/Ryujinx/OsHle/Objects/TimeISteadyClock.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.OsHle.Objects +{ + class TimeISteadyClock + { + + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/TimeISystemClock.cs b/Ryujinx/OsHle/Objects/TimeISystemClock.cs new file mode 100644 index 000000000..d9a3a073e --- /dev/null +++ b/Ryujinx/OsHle/Objects/TimeISystemClock.cs @@ -0,0 +1,16 @@ +using System; + +namespace Ryujinx.OsHle.Objects +{ + class TimeISystemClock + { + private static DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public static long GetCurrentTime(ServiceCtx Context) + { + Context.ResponseData.Write((long)(DateTime.Now - Epoch).TotalSeconds); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/TimeITimeZoneService.cs b/Ryujinx/OsHle/Objects/TimeITimeZoneService.cs new file mode 100644 index 000000000..af5490a61 --- /dev/null +++ b/Ryujinx/OsHle/Objects/TimeITimeZoneService.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.OsHle.Objects +{ + class TimeITimeZoneService + { + + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/ViIApplicationDisplayService.cs b/Ryujinx/OsHle/Objects/ViIApplicationDisplayService.cs new file mode 100644 index 000000000..81616ae11 --- /dev/null +++ b/Ryujinx/OsHle/Objects/ViIApplicationDisplayService.cs @@ -0,0 +1,148 @@ +using ChocolArm64.Memory; +using Ryujinx.OsHle.Handles; +using Ryujinx.OsHle.Ipc; +using System.IO; + +using static Ryujinx.OsHle.Objects.Android.Parcel; +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Objects +{ + class ViIApplicationDisplayService + { + public static long GetRelayService(ServiceCtx Context) + { + MakeObject(Context, new ViIHOSBinderDriver()); + + return 0; + } + + public static long GetSystemDisplayService(ServiceCtx Context) + { + MakeObject(Context, new ViISystemDisplayService()); + + return 0; + } + + public static long GetManagerDisplayService(ServiceCtx Context) + { + MakeObject(Context, new ViIManagerDisplayService()); + + return 0; + } + + public static long OpenDisplay(ServiceCtx Context) + { + string Name = GetDisplayName(Context); + + long DisplayId = Context.Ns.Os.Displays.GenerateId(new Display(Name)); + + Context.ResponseData.Write(DisplayId); + + return 0; + } + + public static long OpenLayer(ServiceCtx Context) + { + long LayerId = Context.RequestData.ReadInt64(); + long UserId = Context.RequestData.ReadInt64(); + + long ParcelPtr = Context.Request.ReceiveBuff[0].Position; + + byte[] Parcel = MakeIGraphicsBufferProducer(ParcelPtr); + + AMemoryHelper.WriteBytes(Context.Memory, ParcelPtr, Parcel); + + Context.ResponseData.Write((long)Parcel.Length); + + return 0; + } + + public static long CreateStrayLayer(ServiceCtx Context) + { + long LayerFlags = Context.RequestData.ReadInt64(); + long DisplayId = Context.RequestData.ReadInt64(); + + long ParcelPtr = Context.Request.ReceiveBuff[0].Position; + + Display Disp = Context.Ns.Os.Displays.GetData((int)DisplayId); + + byte[] Parcel = MakeIGraphicsBufferProducer(ParcelPtr); + + AMemoryHelper.WriteBytes(Context.Memory, ParcelPtr, Parcel); + + Context.ResponseData.Write(0L); + Context.ResponseData.Write((long)Parcel.Length); + + return 0; + } + + public static long SetLayerScalingMode(ServiceCtx Context) + { + int ScalingMode = Context.RequestData.ReadInt32(); + long Unknown = Context.RequestData.ReadInt64(); + + return 0; + } + + public static long GetDisplayVSyncEvent(ServiceCtx Context) + { + string Name = GetDisplayName(Context); + + int Handle = Context.Ns.Os.Handles.GenerateId(new HEvent()); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Handle); + + return 0; + } + + private static byte[] MakeIGraphicsBufferProducer(long BasePtr) + { + long Id = 0x20; + long CookiePtr = 0L; + + using (MemoryStream MS = new MemoryStream()) + { + BinaryWriter Writer = new BinaryWriter(MS); + + //flat_binder_object (size is 0x28) + Writer.Write(2); //Type (BINDER_TYPE_WEAK_BINDER) + Writer.Write(0); //Flags + Writer.Write((int)(Id >> 0)); + Writer.Write((int)(Id >> 32)); + Writer.Write((int)(CookiePtr >> 0)); + Writer.Write((int)(CookiePtr >> 32)); + Writer.Write((byte)'d'); + Writer.Write((byte)'i'); + Writer.Write((byte)'s'); + Writer.Write((byte)'p'); + Writer.Write((byte)'d'); + Writer.Write((byte)'r'); + Writer.Write((byte)'v'); + Writer.Write((byte)'\0'); + Writer.Write(0L); //Pad + + return MakeParcel(MS.ToArray(), new byte[] { 0, 0, 0, 0 }); + } + } + + private static string GetDisplayName(ServiceCtx Context) + { + string Name = string.Empty; + + for (int Index = 0; Index < 8 && + Context.RequestData.BaseStream.Position < + Context.RequestData.BaseStream.Length; Index++) + { + byte Chr = Context.RequestData.ReadByte(); + + if (Chr >= 0x20 && Chr < 0x7f) + { + Name += (char)Chr; + } + } + + return Name; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/ViIHOSBinderDriver.cs b/Ryujinx/OsHle/Objects/ViIHOSBinderDriver.cs new file mode 100644 index 000000000..72a472ae5 --- /dev/null +++ b/Ryujinx/OsHle/Objects/ViIHOSBinderDriver.cs @@ -0,0 +1,211 @@ +using ChocolArm64.Memory; +using Ryujinx.OsHle.Handles; +using Ryujinx.OsHle.Ipc; +using Ryujinx.OsHle.Utilities; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +using static Ryujinx.OsHle.Objects.Android.Parcel; + +namespace Ryujinx.OsHle.Objects +{ + class ViIHOSBinderDriver + { + private delegate long ServiceProcessRequest(ServiceCtx Context, byte[] ParcelData); + + private static Dictionary<(string, int), ServiceProcessRequest> InterfaceMthd = + new Dictionary<(string, int), ServiceProcessRequest>() + { + { ("android.gui.IGraphicBufferProducer", 0x1), GraphicBufferProducerRequestBuffer }, + { ("android.gui.IGraphicBufferProducer", 0x3), GraphicBufferProducerDequeueBuffer }, + { ("android.gui.IGraphicBufferProducer", 0x7), GraphicBufferProducerQueueBuffer }, + //{ ("android.gui.IGraphicBufferProducer", 0x8), GraphicBufferProducerCancelBuffer }, + { ("android.gui.IGraphicBufferProducer", 0x9), GraphicBufferProducerQuery }, + { ("android.gui.IGraphicBufferProducer", 0xa), GraphicBufferProducerConnect }, + { ("android.gui.IGraphicBufferProducer", 0xe), GraphicBufferPreallocateBuffer }, + }; + + private class BufferObj + { + + } + + public IdPoolWithObj BufferSlots { get; private set; } + + public byte[] Gbfr; + + public ViIHOSBinderDriver() + { + BufferSlots = new IdPoolWithObj(); + } + + public static long TransactParcel(ServiceCtx Context) + { + int Id = Context.RequestData.ReadInt32(); + int Code = Context.RequestData.ReadInt32(); + + long DataPos = Context.Request.SendBuff[0].Position; + long DataSize = Context.Request.SendBuff[0].Size; + + byte[] Data = AMemoryHelper.ReadBytes(Context.Memory, DataPos, (int)DataSize); + + Data = GetParcelData(Data); + + using (MemoryStream MS = new MemoryStream(Data)) + { + BinaryReader Reader = new BinaryReader(MS); + + MS.Seek(4, SeekOrigin.Current); + + int StrSize = Reader.ReadInt32(); + + string InterfaceName = Encoding.Unicode.GetString(Data, 8, StrSize * 2); + + if (InterfaceMthd.TryGetValue((InterfaceName, Code), out ServiceProcessRequest ProcReq)) + { + return ProcReq(Context, Data); + } + else + { + throw new NotImplementedException($"{InterfaceName} {Code}"); + } + } + } + + private static long GraphicBufferProducerRequestBuffer(ServiceCtx Context, byte[] ParcelData) + { + ViIHOSBinderDriver BinderDriver = Context.GetObject(); + + int GbfrSize = BinderDriver.Gbfr?.Length ?? 0; + + byte[] Data = new byte[GbfrSize + 4]; + + if (BinderDriver.Gbfr != null) + { + Buffer.BlockCopy(BinderDriver.Gbfr, 0, Data, 0, GbfrSize); + } + + return MakeReplyParcel(Context, Data); + } + + private static long GraphicBufferProducerDequeueBuffer(ServiceCtx Context, byte[] ParcelData) + { + ViIHOSBinderDriver BinderDriver = Context.GetObject(); + + //Note: It seems that the maximum number of slots is 64, because if we return + //a Slot number > 63, it seems to cause a buffer overrun and it reads garbage. + //Note 2: The size of each object associated with the slot is 0x30. + int Slot = BinderDriver.BufferSlots.GenerateId(new BufferObj()); + + return MakeReplyParcel(Context, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + } + + private static long GraphicBufferProducerQueueBuffer(ServiceCtx Context, byte[] ParcelData) + { + return MakeReplyParcel(Context, 1280, 720, 0, 0, 0); + } + + private static long GraphicBufferProducerCancelBuffer(ServiceCtx Context, byte[] ParcelData) + { + ViIHOSBinderDriver BinderDriver = Context.GetObject(); + + using (MemoryStream MS = new MemoryStream(ParcelData)) + { + BinaryReader Reader = new BinaryReader(MS); + + MS.Seek(0x50, SeekOrigin.Begin); + + int Slot = Reader.ReadInt32(); + + BinderDriver.BufferSlots.Delete(Slot); + + return MakeReplyParcel(Context, 0); + } + } + + private static long GraphicBufferProducerQuery(ServiceCtx Context, byte[] ParcelData) + { + return MakeReplyParcel(Context, 0, 0); + } + + private static long GraphicBufferProducerConnect(ServiceCtx Context, byte[] ParcelData) + { + return MakeReplyParcel(Context, 1280, 720, 0, 0, 0); + } + + private static long GraphicBufferPreallocateBuffer(ServiceCtx Context, byte[] ParcelData) + { + ViIHOSBinderDriver BinderDriver = Context.GetObject(); + + int GbfrSize = ParcelData.Length - 0x54; + + BinderDriver.Gbfr = new byte[GbfrSize]; + + Buffer.BlockCopy(ParcelData, 0x54, BinderDriver.Gbfr, 0, GbfrSize); + + using (MemoryStream MS = new MemoryStream(ParcelData)) + { + BinaryReader Reader = new BinaryReader(MS); + + MS.Seek(0xd4, SeekOrigin.Begin); + + int Handle = Reader.ReadInt32(); + + HNvMap NvMap = Context.Ns.Os.Handles.GetData(Handle); + + Context.Ns.Gpu.Renderer.FrameBufferPtr = + Context.Memory.Manager.GetPhys(NvMap.Address, AMemoryPerm.Read); + } + + return MakeReplyParcel(Context, 0); + } + + private static long MakeReplyParcel(ServiceCtx Context, params int[] Ints) + { + using (MemoryStream MS = new MemoryStream()) + { + BinaryWriter Writer = new BinaryWriter(MS); + + foreach (int Int in Ints) + { + Writer.Write(Int); + } + + return MakeReplyParcel(Context, MS.ToArray()); + } + } + + private static long MakeReplyParcel(ServiceCtx Context, byte[] Data) + { + long ReplyPos = Context.Request.ReceiveBuff[0].Position; + long ReplySize = Context.Request.ReceiveBuff[0].Position; + + byte[] Reply = MakeParcel(Data, new byte[0]); + + AMemoryHelper.WriteBytes(Context.Memory, ReplyPos, Reply); + + return 0; + } + + public static long AdjustRefcount(ServiceCtx Context) + { + int Id = Context.RequestData.ReadInt32(); + int AddVal = Context.RequestData.ReadInt32(); + int Type = Context.RequestData.ReadInt32(); + + return 0; + } + + public static long GetNativeHandle(ServiceCtx Context) + { + int Id = Context.RequestData.ReadInt32(); + uint Unk = Context.RequestData.ReadUInt32(); + + Context.Response.HandleDesc = IpcHandleDesc.MakeMove(0xbadcafe); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/ViIManagerDisplayService.cs b/Ryujinx/OsHle/Objects/ViIManagerDisplayService.cs new file mode 100644 index 000000000..0fdca3bac --- /dev/null +++ b/Ryujinx/OsHle/Objects/ViIManagerDisplayService.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.OsHle.Objects +{ + class ViIManagerDisplayService + { + public static long CreateManagedLayer(ServiceCtx Context) + { + Context.ResponseData.Write(0L); //LayerId + + return 0; + } + + public static long AddToLayerStack(ServiceCtx Context) + { + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Objects/ViISystemDisplayService.cs b/Ryujinx/OsHle/Objects/ViISystemDisplayService.cs new file mode 100644 index 000000000..8d3a51f4e --- /dev/null +++ b/Ryujinx/OsHle/Objects/ViISystemDisplayService.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.OsHle.Objects +{ + class ViISystemDisplayService + { + public static long SetLayerZ(ServiceCtx Context) + { + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Process.cs b/Ryujinx/OsHle/Process.cs new file mode 100644 index 000000000..1cfae9293 --- /dev/null +++ b/Ryujinx/OsHle/Process.cs @@ -0,0 +1,179 @@ +using ChocolArm64; +using ChocolArm64.Memory; +using Ryujinx.Loaders; +using Ryujinx.Loaders.Executables; +using Ryujinx.OsHle.Handles; +using Ryujinx.OsHle.Svc; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace Ryujinx.OsHle +{ + class Process + { + private const int MaxStackSize = 8 * 1024 * 1024; + + private const int TlsSize = 0x200; + private const int TotalTlsSlots = 32; + private const int TlsTotalSize = TotalTlsSlots * TlsSize; + private const long TlsPageAddr = (AMemoryMgr.AddrSize - TlsTotalSize) & ~AMemoryMgr.PageMask; + + private Switch Ns; + + public int ProcessId { get; private set; } + + public AMemory Memory { get; private set; } + + private SvcHandler SvcHandler; + + private AThread MainThread; + + private ConcurrentDictionary TlsSlots; + + private List Executables; + + private long ImageBase; + + public Process(Switch Ns, AMemoryAlloc Allocator, int ProcessId) + { + this.Ns = Ns; + this.ProcessId = ProcessId; + + Memory = new AMemory(Ns.Ram, Allocator); + SvcHandler = new SvcHandler(Ns, Memory); + TlsSlots = new ConcurrentDictionary(); + Executables = new List(); + + ImageBase = 0x8000000; + + Memory.Manager.MapPhys( + TlsPageAddr, + TlsTotalSize, + (int)MemoryType.ThreadLocal, + AMemoryPerm.RW); + } + + public void LoadProgram(IElf Program) + { + Executable Executable = new Executable(Program, Memory, ImageBase); + + Executables.Add(Executable); + + ImageBase = AMemoryHelper.PageRoundUp(Executable.ImageEnd); + } + + public void SetEmptyArgs() + { + ImageBase += AMemoryMgr.PageSize; + } + + public void InitializeHeap() + { + Memory.Manager.SetHeapAddr((ImageBase + 0x3fffffff) & ~0x3fffffff); + } + + public bool Run() + { + if (Executables.Count == 0) + { + return false; + } + + long StackBot = TlsPageAddr - MaxStackSize; + + Memory.Manager.MapPhys(StackBot, MaxStackSize, (int)MemoryType.Normal, AMemoryPerm.RW); + + int Handle = MakeThread(Executables[0].ImageBase, TlsPageAddr, 0, 48, 0); + + if (Handle == -1) + { + return false; + } + + MainThread = Ns.Os.Handles.GetData(Handle).Thread; + + MainThread.Execute(); + + return true; + } + + public void StopAllThreads() + { + if (MainThread != null) + { + while (MainThread.IsAlive) + { + MainThread.StopExecution(); + } + } + + foreach (AThread Thread in TlsSlots.Values) + { + while (Thread.IsAlive) + { + Thread.StopExecution(); + } + } + } + + public int MakeThread( + long EntryPoint, + long StackTop, + long ArgsPtr, + int Priority, + int ProcessorId) + { + AThread Thread = new AThread(Memory, EntryPoint, Priority); + + int TlsSlot = GetFreeTlsSlot(Thread); + + int Handle = Ns.Os.Handles.GenerateId(new HThread(Thread)); + + if (TlsSlot == -1 || Handle == -1) + { + return -1; + } + + Thread.Registers.SvcCall += SvcHandler.SvcCall; + Thread.Registers.ProcessId = ProcessId; + Thread.Registers.ThreadId = Ns.Os.IdGen.GenerateId(); + Thread.Registers.TlsAddr = TlsPageAddr + TlsSlot * TlsSize; + Thread.Registers.X0 = (ulong)ArgsPtr; + Thread.Registers.X1 = (ulong)Handle; + Thread.Registers.X31 = (ulong)StackTop; + + Thread.WorkFinished += ThreadFinished; + + return Handle; + } + + private int GetFreeTlsSlot(AThread Thread) + { + for (int Index = 1; Index < TotalTlsSlots; Index++) + { + if (TlsSlots.TryAdd(Index, Thread)) + { + return Index; + } + } + + return -1; + } + + private void ThreadFinished(object sender, EventArgs e) + { + if (sender is AThread Thread) + { + TlsSlots.TryRemove(GetTlsSlot(Thread.Registers.TlsAddr), out _); + + Ns.Os.IdGen.DeleteId(Thread.ThreadId); + } + } + + private int GetTlsSlot(long Position) + { + return (int)((Position - TlsPageAddr) / TlsSize); + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/ServiceCtx.cs b/Ryujinx/OsHle/ServiceCtx.cs new file mode 100644 index 000000000..88ddcdb77 --- /dev/null +++ b/Ryujinx/OsHle/ServiceCtx.cs @@ -0,0 +1,52 @@ +using ChocolArm64.Memory; +using Ryujinx.OsHle.Handles; +using Ryujinx.OsHle.Ipc; +using System.IO; + +namespace Ryujinx.OsHle +{ + class ServiceCtx + { + public Switch Ns { get; private set; } + public AMemory Memory { get; private set; } + public HSession Session { get; private set; } + public IpcMessage Request { get; private set; } + public IpcMessage Response { get; private set; } + public BinaryReader RequestData { get; private set; } + public BinaryWriter ResponseData { get; private set; } + + public ServiceCtx( + Switch Ns, + AMemory Memory, + HSession Session, + IpcMessage Request, + IpcMessage Response, + BinaryReader RequestData, + BinaryWriter ResponseData) + { + this.Ns = Ns; + this.Memory = Memory; + this.Session = Session; + this.Request = Request; + this.Response = Response; + this.RequestData = RequestData; + this.ResponseData = ResponseData; + } + + public T GetObject() + { + object Obj = null; + + if (Session is HSessionObj SessionObj) + { + Obj = SessionObj.Obj; + } + if (Session is HDomain Dom) + { + Obj = Dom.GetObject(Request.DomObjId); + } + + return Obj is T ? (T)Obj : default(T); + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceAcc.cs b/Ryujinx/OsHle/Services/ServiceAcc.cs new file mode 100644 index 000000000..14a3e83ea --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceAcc.cs @@ -0,0 +1,33 @@ +using Ryujinx.OsHle.Objects; + +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + public static long AccU0ListOpenUsers(ServiceCtx Context) + { + return 0; + } + + public static long AccU0GetProfile(ServiceCtx Context) + { + MakeObject(Context, new AccIProfile()); + + return 0; + } + + public static long AccU0InitializeApplicationInfo(ServiceCtx Context) + { + return 0; + } + + public static long AccU0GetBaasAccountManagerForApplication(ServiceCtx Context) + { + MakeObject(Context, new AccIManagerForApplication()); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceApm.cs b/Ryujinx/OsHle/Services/ServiceApm.cs new file mode 100644 index 000000000..f0df462b7 --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceApm.cs @@ -0,0 +1,16 @@ +using Ryujinx.OsHle.Objects; + +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + public static long ApmOpenSession(ServiceCtx Context) + { + MakeObject(Context, new ApmISession()); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceAppletOE.cs b/Ryujinx/OsHle/Services/ServiceAppletOE.cs new file mode 100644 index 000000000..2f98a2014 --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceAppletOE.cs @@ -0,0 +1,16 @@ +using Ryujinx.OsHle.Objects; + +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + public static long AppletOpenApplicationProxy(ServiceCtx Context) + { + MakeObject(Context, new AmIApplicationProxy()); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceAud.cs b/Ryujinx/OsHle/Services/ServiceAud.cs new file mode 100644 index 000000000..96e7e5481 --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceAud.cs @@ -0,0 +1,60 @@ +using ChocolArm64.Memory; +using Ryujinx.OsHle.Objects; +using System.Text; + +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + public static long AudOutListAudioOuts(ServiceCtx Context) + { + long Position = Context.Request.ReceiveBuff[0].Position; + + AMemoryHelper.WriteBytes(Context.Memory, Position, Encoding.ASCII.GetBytes("iface")); + + Context.ResponseData.Write(1); + + return 0; + } + + public static long AudOutOpenAudioOut(ServiceCtx Context) + { + Context.ResponseData.Write(48000); + Context.ResponseData.Write(2); + Context.ResponseData.Write(2); + Context.ResponseData.Write(0); + + return 0; + } + + public static long AudRenOpenAudioRenderer(ServiceCtx Context) + { + MakeObject(Context, new AudIAudioRenderer()); + + return 0; + } + + public static long AudRenGetAudioRendererWorkBufferSize(ServiceCtx Context) + { + int SampleRate = Context.RequestData.ReadInt32(); + int Unknown4 = Context.RequestData.ReadInt32(); + int Unknown8 = Context.RequestData.ReadInt32(); + int UnknownC = Context.RequestData.ReadInt32(); + int Unknown10 = Context.RequestData.ReadInt32(); + int Unknown14 = Context.RequestData.ReadInt32(); + int Unknown18 = Context.RequestData.ReadInt32(); + int Unknown1c = Context.RequestData.ReadInt32(); + int Unknown20 = Context.RequestData.ReadInt32(); + int Unknown24 = Context.RequestData.ReadInt32(); + int Unknown28 = Context.RequestData.ReadInt32(); + int Unknown2c = Context.RequestData.ReadInt32(); + int Rev1Magic = Context.RequestData.ReadInt32(); + + Context.ResponseData.Write(0x400L); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceFriend.cs b/Ryujinx/OsHle/Services/ServiceFriend.cs new file mode 100644 index 000000000..980d42196 --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceFriend.cs @@ -0,0 +1,16 @@ +using Ryujinx.OsHle.Objects; + +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + public static long FriendCreateFriendService(ServiceCtx Context) + { + MakeObject(Context, new FriendIFriendService()); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceFspSrv.cs b/Ryujinx/OsHle/Services/ServiceFspSrv.cs new file mode 100644 index 000000000..e2ceb0aed --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceFspSrv.cs @@ -0,0 +1,42 @@ +using Ryujinx.OsHle.Objects; + +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + public static long FspSrvInitialize(ServiceCtx Context) + { + return 0; + } + + public static long FspSrvMountSaveData(ServiceCtx Context) + { + MakeObject(Context, new FspSrvIFileSystem(Context.Ns.VFs.GetGameSavesPath())); + + return 0; + } + + public static long FspSrvOpenDataStorageByCurrentProcess(ServiceCtx Context) + { + MakeObject(Context, new FspSrvIStorage(Context.Ns.VFs.RomFs)); + + return 0; + } + + public static long FspSrvOpenRomStorage(ServiceCtx Context) + { + MakeObject(Context, new FspSrvIStorage(Context.Ns.VFs.RomFs)); + + return 0; + } + + public static long FspSrvGetGlobalAccessLogMode(ServiceCtx Context) + { + Context.ResponseData.Write(0); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceHid.cs b/Ryujinx/OsHle/Services/ServiceHid.cs new file mode 100644 index 000000000..30093f49f --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceHid.cs @@ -0,0 +1,56 @@ +using Ryujinx.OsHle.Handles; +using Ryujinx.OsHle.Objects; + +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + public static long HidCreateAppletResource(ServiceCtx Context) + { + HSharedMem HidHndData = Context.Ns.Os.Handles.GetData(Context.Ns.Os.HidHandle); + + MakeObject(Context, new HidIAppletResource(HidHndData)); + + return 0; + } + + public static long HidActivateTouchScreen(ServiceCtx Context) + { + long Unknown = Context.RequestData.ReadInt64(); + + return 0; + } + + public static long HidSetSupportedNpadStyleSet(ServiceCtx Context) + { + long Unknown0 = Context.RequestData.ReadInt64(); + long Unknown8 = Context.RequestData.ReadInt64(); + + return 0; + } + + public static long HidSetSupportedNpadIdType(ServiceCtx Context) + { + long Unknown = Context.RequestData.ReadInt64(); + + return 0; + } + + public static long HidActivateNpad(ServiceCtx Context) + { + long Unknown = Context.RequestData.ReadInt64(); + + return 0; + } + + public static long HidSetNpadJoyHoldType(ServiceCtx Context) + { + long Unknown0 = Context.RequestData.ReadInt64(); + long Unknown8 = Context.RequestData.ReadInt64(); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceLm.cs b/Ryujinx/OsHle/Services/ServiceLm.cs new file mode 100644 index 000000000..dc6acad95 --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceLm.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + public static long LmInitialize(ServiceCtx Context) + { + Context.Session.Initialize(); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceNvDrv.cs b/Ryujinx/OsHle/Services/ServiceNvDrv.cs new file mode 100644 index 000000000..f7c0d3022 --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceNvDrv.cs @@ -0,0 +1,601 @@ +using ChocolArm64.Memory; +using Ryujinx.Gpu; +using Ryujinx.OsHle.Handles; +using Ryujinx.OsHle.Ipc; +using Ryujinx.OsHle.Utilities; +using System; +using System.Collections.Generic; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + private delegate long ServiceProcessRequest(ServiceCtx Context); + + private static Dictionary<(string, int), ServiceProcessRequest> IoctlCmds = + new Dictionary<(string, int), ServiceProcessRequest>() + { + { ("/dev/nvhost-as-gpu", 0x4101), NvGpuAsIoctlBindChannel }, + { ("/dev/nvhost-as-gpu", 0x4102), NvGpuAsIoctlAllocSpace }, + { ("/dev/nvhost-as-gpu", 0x4106), NvGpuAsIoctlMapBufferEx }, + { ("/dev/nvhost-as-gpu", 0x4108), NvGpuAsIoctlGetVaRegions }, + { ("/dev/nvhost-as-gpu", 0x4109), NvGpuAsIoctlInitializeEx }, + { ("/dev/nvhost-ctrl", 0x001b), NvHostIoctlCtrlGetConfig }, + { ("/dev/nvhost-ctrl", 0x001d), NvHostIoctlCtrlEventWait }, + { ("/dev/nvhost-ctrl-gpu", 0x4701), NvGpuIoctlZcullGetCtxSize }, + { ("/dev/nvhost-ctrl-gpu", 0x4702), NvGpuIoctlZcullGetInfo }, + { ("/dev/nvhost-ctrl-gpu", 0x4705), NvGpuIoctlGetCharacteristics }, + { ("/dev/nvhost-ctrl-gpu", 0x4706), NvGpuIoctlGetTpcMasks }, + { ("/dev/nvhost-ctrl-gpu", 0x4714), NvGpuIoctlZbcGetActiveSlotMask }, + { ("/dev/nvhost-gpu", 0x4714), NvMapIoctlChannelSetUserData }, + { ("/dev/nvhost-gpu", 0x4801), NvMapIoctlChannelSetNvMap }, + { ("/dev/nvhost-gpu", 0x4808), NvMapIoctlChannelSubmitGpFifo }, + { ("/dev/nvhost-gpu", 0x4809), NvMapIoctlChannelAllocObjCtx }, + { ("/dev/nvhost-gpu", 0x480b), NvMapIoctlChannelZcullBind }, + { ("/dev/nvhost-gpu", 0x480c), NvMapIoctlChannelSetErrorNotifier }, + { ("/dev/nvhost-gpu", 0x480d), NvMapIoctlChannelSetPriority }, + { ("/dev/nvhost-gpu", 0x481a), NvMapIoctlChannelAllocGpFifoEx2 }, + { ("/dev/nvmap", 0x0101), NvMapIocCreate }, + { ("/dev/nvmap", 0x0103), NvMapIocFromId }, + { ("/dev/nvmap", 0x0104), NvMapIocAlloc }, + { ("/dev/nvmap", 0x0109), NvMapIocParam }, + { ("/dev/nvmap", 0x010e), NvMapIocGetId }, + }; + + public static long NvDrvOpen(ServiceCtx Context) + { + long NamePtr = Context.Request.SendBuff[0].Position; + + string Name = AMemoryHelper.ReadAsciiString(Context.Memory, NamePtr); + + int Fd = Context.Ns.Os.Fds.GenerateId(new FileDesc(Name)); + + Context.ResponseData.Write(Fd); + Context.ResponseData.Write(0); + + return 0; + } + + public static long NvDrvIoctl(ServiceCtx Context) + { + int Fd = Context.RequestData.ReadInt32(); + int Cmd = Context.RequestData.ReadInt32() & 0xffff; + + FileDesc FdData = Context.Ns.Os.Fds.GetData(Fd); + + long Position = Context.Request.PtrBuff[0].Position; + + Context.ResponseData.Write(0); + + if (IoctlCmds.TryGetValue((FdData.Name, Cmd), out ServiceProcessRequest ProcReq)) + { + return ProcReq(Context); + } + else + { + throw new NotImplementedException($"{FdData.Name} {Cmd:x4}"); + } + } + + public static long NvDrvClose(ServiceCtx Context) + { + int Fd = Context.RequestData.ReadInt32(); + + Context.Ns.Os.Fds.Delete(Fd); + + Context.ResponseData.Write(0); + + return 0; + } + + public static long NvDrvInitialize(ServiceCtx Context) + { + long TransferMemSize = Context.RequestData.ReadInt64(); + int TransferMemHandle = Context.Request.HandleDesc.ToCopy[0]; + + Context.ResponseData.Write(0); + + return 0; + } + + public static long NvDrvQueryEvent(ServiceCtx Context) + { + int Fd = Context.RequestData.ReadInt32(); + int EventId = Context.RequestData.ReadInt32(); + + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(0xcafe); + + Context.ResponseData.Write(0); + + return 0; + } + + private static long NvGpuAsIoctlBindChannel(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + int Fd = Context.Memory.ReadInt32(Position); + + return 0; + } + + private static long NvGpuAsIoctlAllocSpace(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemReader Reader = new MemReader(Context.Memory, Position); + + int Pages = Reader.ReadInt32(); + int PageSize = Reader.ReadInt32(); + int Flags = Reader.ReadInt32(); + int Padding = Reader.ReadInt32(); + long Align = Reader.ReadInt64(); + + if ((Flags & 1) != 0) + { + Align = Context.Ns.Gpu.MemoryMgr.Reserve(Align, (long)Pages * PageSize, 1); + } + else + { + Align = Context.Ns.Gpu.MemoryMgr.Reserve((long)Pages * PageSize, Align); + } + + Context.Memory.WriteInt64(Position + 0x10, Align); + + return 0; + } + + private static long NvGpuAsIoctlMapBufferEx(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemReader Reader = new MemReader(Context.Memory, Position); + + int Flags = Reader.ReadInt32(); + int Kind = Reader.ReadInt32(); + int Handle = Reader.ReadInt32(); + int PageSize = Reader.ReadInt32(); + long BuffAddr = Reader.ReadInt64(); + long MapSize = Reader.ReadInt64(); + long Offset = Reader.ReadInt64(); + + HNvMap NvMap = Context.Ns.Os.Handles.GetData(Handle); + + if (NvMap != null) + { + if ((Flags & 1) != 0) + { + Offset = Context.Ns.Gpu.MemoryMgr.Map(NvMap.Address, Offset, NvMap.Size); + } + else + { + Offset = Context.Ns.Gpu.MemoryMgr.Map(NvMap.Address, NvMap.Size); + } + } + + Context.Memory.WriteInt64(Position + 0x20, Offset); + + return 0; + } + + private static long NvGpuAsIoctlGetVaRegions(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemReader Reader = new MemReader(Context.Memory, Position); + MemWriter Writer = new MemWriter(Context.Memory, Position); + + long Unused = Reader.ReadInt64(); + int BuffSize = Reader.ReadInt32(); + int Padding = Reader.ReadInt32(); + + BuffSize = 0x30; + + Writer.WriteInt64(Unused); + Writer.WriteInt32(BuffSize); + Writer.WriteInt32(Padding); + + Writer.WriteInt64(0); + Writer.WriteInt32(0); + Writer.WriteInt32(0); + Writer.WriteInt64(0); + + Writer.WriteInt64(0); + Writer.WriteInt32(0); + Writer.WriteInt32(0); + Writer.WriteInt64(0); + + return 0; + } + + private static long NvGpuAsIoctlInitializeEx(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemReader Reader = new MemReader(Context.Memory, Position); + + int BigPageSize = Reader.ReadInt32(); + int AsFd = Reader.ReadInt32(); + int Flags = Reader.ReadInt32(); + int Reserved = Reader.ReadInt32(); + long Unknown10 = Reader.ReadInt64(); + long Unknown18 = Reader.ReadInt64(); + long Unknown20 = Reader.ReadInt64(); + + return 0; + } + + private static long NvHostIoctlCtrlGetConfig(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemReader Reader = new MemReader(Context.Memory, Position); + MemWriter Writer = new MemWriter(Context.Memory, Position + 0x82); + + for (int Index = 0; Index < 0x101; Index++) + { + Writer.WriteByte(0); + } + + return 0; + } + + private static long NvHostIoctlCtrlEventWait(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemReader Reader = new MemReader(Context.Memory, Position); + + int SyncPtId = Reader.ReadInt32(); + int Threshold = Reader.ReadInt32(); + int Timeout = Reader.ReadInt32(); + int Value = Reader.ReadInt32(); + + Context.Memory.WriteInt32(Position + 0xc, 0xcafe); + + return 0; + } + + private static long NvGpuIoctlZcullGetCtxSize(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + Context.Memory.WriteInt32(Position, 1); + + return 0; + } + + private static long NvGpuIoctlZcullGetInfo(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemWriter Writer = new MemWriter(Context.Memory, Position); + + Writer.WriteInt32(0); + Writer.WriteInt32(0); + Writer.WriteInt32(0); + Writer.WriteInt32(0); + Writer.WriteInt32(0); + Writer.WriteInt32(0); + Writer.WriteInt32(0); + Writer.WriteInt32(0); + Writer.WriteInt32(0); + Writer.WriteInt32(0); + + return 0; + } + + private static long NvGpuIoctlGetCharacteristics(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemReader Reader = new MemReader(Context.Memory, Position); + MemWriter Writer = new MemWriter(Context.Memory, Position); + + //Note: We should just ignore the BuffAddr, because official code + //does __memcpy_device from Position + 0x10 to BuffAddr. + long BuffSize = Reader.ReadInt64(); + long BuffAddr = Reader.ReadInt64(); + + BuffSize = 0xa0; + + Writer.WriteInt64(BuffSize); + Writer.WriteInt64(BuffAddr); + Writer.WriteInt32(0x120); //NVGPU_GPU_ARCH_GM200 + Writer.WriteInt32(0xb); //NVGPU_GPU_IMPL_GM20B + Writer.WriteInt32(0xa1); + Writer.WriteInt32(1); + Writer.WriteInt64(0x40000); + Writer.WriteInt64(0); + Writer.WriteInt32(2); + Writer.WriteInt32(0x20); //NVGPU_GPU_BUS_TYPE_AXI + Writer.WriteInt32(0x20000); + Writer.WriteInt32(0x20000); + Writer.WriteInt32(0x1b); + Writer.WriteInt32(0x30000); + Writer.WriteInt32(1); + Writer.WriteInt32(0x503); + Writer.WriteInt32(0x503); + Writer.WriteInt32(0x80); + Writer.WriteInt32(0x28); + Writer.WriteInt32(0); + Writer.WriteInt64(0x55); + Writer.WriteInt32(0x902d); //FERMI_TWOD_A + Writer.WriteInt32(0xb197); //MAXWELL_B + Writer.WriteInt32(0xb1c0); //MAXWELL_COMPUTE_B + Writer.WriteInt32(0xb06f); //MAXWELL_CHANNEL_GPFIFO_A + Writer.WriteInt32(0xa140); //KEPLER_INLINE_TO_MEMORY_B + Writer.WriteInt32(0xb0b5); //MAXWELL_DMA_COPY_A + Writer.WriteInt32(1); + Writer.WriteInt32(0); + Writer.WriteInt32(2); + Writer.WriteInt32(1); + Writer.WriteInt32(0); + Writer.WriteInt32(1); + Writer.WriteInt32(0x21d70); + Writer.WriteInt32(0); + Writer.WriteByte((byte)'g'); + Writer.WriteByte((byte)'m'); + Writer.WriteByte((byte)'2'); + Writer.WriteByte((byte)'0'); + Writer.WriteByte((byte)'b'); + Writer.WriteByte((byte)'\0'); + Writer.WriteByte((byte)'\0'); + Writer.WriteByte((byte)'\0'); + Writer.WriteInt64(0); + + return 0; + } + + private static long NvGpuIoctlGetTpcMasks(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemReader Reader = new MemReader(Context.Memory, Position); + + int MaskBuffSize = Reader.ReadInt32(); + int Reserved = Reader.ReadInt32(); + long MaskBuffAddr = Reader.ReadInt64(); + long Unknown = Reader.ReadInt64(); + + return 0; + } + + private static long NvGpuIoctlZbcGetActiveSlotMask(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + Context.Memory.WriteInt32(Position + 0, 7); + Context.Memory.WriteInt32(Position + 4, 1); + + return 0; + } + + private static long NvMapIoctlChannelSetUserData(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + return 0; + } + + private static long NvMapIoctlChannelSetNvMap(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + int Fd = Context.Memory.ReadInt32(Position); + + return 0; + } + + private static long NvMapIoctlChannelSubmitGpFifo(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemReader Reader = new MemReader(Context.Memory, Position); + MemWriter Writer = new MemWriter(Context.Memory, Position + 0x10); + + long GpFifo = Reader.ReadInt64(); + int Count = Reader.ReadInt32(); + int Flags = Reader.ReadInt32(); + int FenceId = Reader.ReadInt32(); + int FenceVal = Reader.ReadInt32(); + + for (int Index = 0; Index < Count; Index++) + { + long GpFifoHdr = Reader.ReadInt64(); + + long GpuAddr = GpFifoHdr & 0xffffffffff; + + int Size = (int)(GpFifoHdr >> 40) & 0x7ffffc; + + long CpuAddr = Context.Ns.Gpu.MemoryMgr.GetCpuAddr(GpuAddr); + + if (CpuAddr != -1) + { + byte[] Data = AMemoryHelper.ReadBytes(Context.Memory, CpuAddr, Size); + + NsGpuPBEntry[] PushBuffer = NsGpuPBEntry.DecodePushBuffer(Data); + + Context.Ns.Gpu.PGraph.ProcessPushBuffer(PushBuffer, Context.Memory); + } + } + + Writer.WriteInt32(0); + Writer.WriteInt32(0); + + return 0; + } + + private static long NvMapIoctlChannelAllocObjCtx(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + int ClassNum = Context.Memory.ReadInt32(Position + 0); + int Flags = Context.Memory.ReadInt32(Position + 4); + + Context.Memory.WriteInt32(Position + 8, 0); + + return 0; + } + + private static long NvMapIoctlChannelZcullBind(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemReader Reader = new MemReader(Context.Memory, Position); + + long GpuVa = Reader.ReadInt64(); + int Mode = Reader.ReadInt32(); + int Padding = Reader.ReadInt32(); + + return 0; + } + + private static long NvMapIoctlChannelSetErrorNotifier(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemReader Reader = new MemReader(Context.Memory, Position); + + long Offset = Reader.ReadInt64(); + long Size = Reader.ReadInt64(); + int Mem = Reader.ReadInt32(); + int Padding = Reader.ReadInt32(); + + return 0; + } + + private static long NvMapIoctlChannelSetPriority(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + int Priority = Context.Memory.ReadInt32(Position); + + return 0; + } + + private static long NvMapIoctlChannelAllocGpFifoEx2(ServiceCtx Context) + { + long Position = Context.Request.PtrBuff[0].Position; + + MemReader Reader = new MemReader(Context.Memory, Position); + MemWriter Writer = new MemWriter(Context.Memory, Position + 0xc); + + int Count = Reader.ReadInt32(); + int Flags = Reader.ReadInt32(); + int Unknown8 = Reader.ReadInt32(); + long Fence = Reader.ReadInt64(); + int Unknown14 = Reader.ReadInt32(); + int Unknown18 = Reader.ReadInt32(); + + Writer.WriteInt32(0); + Writer.WriteInt32(0); + + return 0; + } + + private static long NvMapIocCreate(ServiceCtx Context) + { + long Position = Context.Request.GetSendBuffPtr(); + + int Size = Context.Memory.ReadInt32(Position); + + int Id = Context.Ns.Os.NvMapIds.GenerateId(); + + int Handle = Context.Ns.Os.Handles.GenerateId(new HNvMap(Id, Size)); + + Context.Memory.WriteInt32(Position + 4, Handle); + + return 0; + } + + private static long NvMapIocFromId(ServiceCtx Context) + { + long Position = Context.Request.GetSendBuffPtr(); + + int Id = Context.Memory.ReadInt32(Position); + + int Handle = -1; + + foreach (KeyValuePair KV in Context.Ns.Os.Handles) + { + if (KV.Value is HNvMap NvMap && NvMap.Id == Id) + { + Handle = KV.Key; + + break; + } + } + + Context.Memory.WriteInt32(Position + 4, Handle); + + return 0; + } + + private static long NvMapIocAlloc(ServiceCtx Context) + { + long Position = Context.Request.GetSendBuffPtr(); + + MemReader Reader = new MemReader(Context.Memory, Position); + + int Handle = Reader.ReadInt32(); + int HeapMask = Reader.ReadInt32(); + int Flags = Reader.ReadInt32(); + int Align = Reader.ReadInt32(); + byte Kind = (byte)Reader.ReadInt64(); + long Addr = Reader.ReadInt64(); + + HNvMap NvMap = Context.Ns.Os.Handles.GetData(Handle); + + if (NvMap != null) + { + NvMap.Address = Addr; + NvMap.Align = Align; + NvMap.Kind = Kind; + } + + Console.WriteLine($"NvMapIocAlloc at {NvMap.Address:x16}"); + + return 0; + } + + private static long NvMapIocParam(ServiceCtx Context) + { + long Position = Context.Request.GetSendBuffPtr(); + + MemReader Reader = new MemReader(Context.Memory, Position); + + int Handle = Reader.ReadInt32(); + int Param = Reader.ReadInt32(); + + HNvMap NvMap = Context.Ns.Os.Handles.GetData(Handle); + + int Response = 0; + + switch (Param) + { + case 1: Response = NvMap.Size; break; + case 2: Response = NvMap.Align; break; + case 4: Response = 0x40000000; break; + case 5: Response = NvMap.Kind; break; + } + + Context.Memory.WriteInt32(Position + 8, Response); + + return 0; + } + + private static long NvMapIocGetId(ServiceCtx Context) + { + long Position = Context.Request.GetSendBuffPtr(); + + int Handle = Context.Memory.ReadInt32(Position + 4); + + HNvMap NvMap = Context.Ns.Os.Handles.GetData(Handle); + + Context.Memory.WriteInt32(Position, NvMap.Id); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServicePctl.cs b/Ryujinx/OsHle/Services/ServicePctl.cs new file mode 100644 index 000000000..62537f287 --- /dev/null +++ b/Ryujinx/OsHle/Services/ServicePctl.cs @@ -0,0 +1,16 @@ +using Ryujinx.OsHle.Objects; + +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + public static long PctlCreateService(ServiceCtx Context) + { + MakeObject(Context, new AmIParentalControlService()); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServicePl.cs b/Ryujinx/OsHle/Services/ServicePl.cs new file mode 100644 index 000000000..6981637f9 --- /dev/null +++ b/Ryujinx/OsHle/Services/ServicePl.cs @@ -0,0 +1,35 @@ +using Ryujinx.OsHle.Ipc; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + public static long PlGetLoadState(ServiceCtx Context) + { + Context.ResponseData.Write(1); //Loaded + + return 0; + } + + public static long PlGetFontSize(ServiceCtx Context) + { + Context.ResponseData.Write(Horizon.FontSize); + + return 0; + } + + public static long PlGetSharedMemoryAddressOffset(ServiceCtx Context) + { + Context.ResponseData.Write(0); + + return 0; + } + + public static long PlGetSharedMemoryNativeHandle(ServiceCtx Context) + { + Context.Response.HandleDesc = IpcHandleDesc.MakeCopy(Context.Ns.Os.FontHandle); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceSet.cs b/Ryujinx/OsHle/Services/ServiceSet.cs new file mode 100644 index 000000000..f98e8f0de --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceSet.cs @@ -0,0 +1,32 @@ +using ChocolArm64.Memory; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + private const int LangCodesCount = 13; + + public static long SetGetAvailableLanguageCodes(ServiceCtx Context) + { + int PtrBuffSize = Context.RequestData.ReadInt32(); + + if (Context.Request.RecvListBuff.Count > 0) + { + long Position = Context.Request.RecvListBuff[0].Position; + short Size = Context.Request.RecvListBuff[0].Size; + + //This should return an array of ints with values matching the LanguageCode enum. + byte[] Data = new byte[Size]; + + Data[0] = 0; + Data[1] = 1; + + AMemoryHelper.WriteBytes(Context.Memory, Position, Data); + } + + Context.ResponseData.Write(LangCodesCount); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceSm.cs b/Ryujinx/OsHle/Services/ServiceSm.cs new file mode 100644 index 000000000..8af3ed5e4 --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceSm.cs @@ -0,0 +1,48 @@ +using Ryujinx.OsHle.Handles; +using Ryujinx.OsHle.Ipc; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + private const int SmNotInitialized = 0x415; + + public static long SmInitialize(ServiceCtx Context) + { + Context.Session.Initialize(); + + return 0; + } + + public static long SmGetService(ServiceCtx Context) + { + //Only for kernel version > 3.0.0. + if (!Context.Session.IsInitialized) + { + //return SmNotInitialized; + } + + string Name = string.Empty; + + for (int Index = 0; Index < 8 && + Context.RequestData.BaseStream.Position < + Context.RequestData.BaseStream.Length; Index++) + { + byte Chr = Context.RequestData.ReadByte(); + + if (Chr >= 0x20 && Chr < 0x7f) + { + Name += (char)Chr; + } + } + + HSession Session = new HSession(Name); + + int Handle = Context.Ns.Os.Handles.GenerateId(Session); + + Context.Response.HandleDesc = IpcHandleDesc.MakeMove(Handle); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceTime.cs b/Ryujinx/OsHle/Services/ServiceTime.cs new file mode 100644 index 000000000..2b93e3db8 --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceTime.cs @@ -0,0 +1,37 @@ +using Ryujinx.OsHle.Objects; + +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + public static long TimeGetStandardUserSystemClock(ServiceCtx Context) + { + MakeObject(Context, new TimeISystemClock()); + + return 0; + } + + public static long TimeGetStandardNetworkSystemClock(ServiceCtx Context) + { + MakeObject(Context, new TimeISystemClock()); + + return 0; + } + + public static long TimeGetStandardSteadyClock(ServiceCtx Context) + { + MakeObject(Context, new TimeISteadyClock()); + + return 0; + } + + public static long TimeGetTimeZoneService(ServiceCtx Context) + { + MakeObject(Context, new TimeITimeZoneService()); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Services/ServiceVi.cs b/Ryujinx/OsHle/Services/ServiceVi.cs new file mode 100644 index 000000000..d5c86bfc2 --- /dev/null +++ b/Ryujinx/OsHle/Services/ServiceVi.cs @@ -0,0 +1,18 @@ +using Ryujinx.OsHle.Objects; + +using static Ryujinx.OsHle.Objects.ObjHelper; + +namespace Ryujinx.OsHle.Services +{ + static partial class Service + { + public static long ViGetDisplayService(ServiceCtx Context) + { + int Unknown = Context.RequestData.ReadInt32(); + + MakeObject(Context, new ViIApplicationDisplayService()); + + return 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Svc/SvcHandler.cs b/Ryujinx/OsHle/Svc/SvcHandler.cs new file mode 100644 index 000000000..245d24389 --- /dev/null +++ b/Ryujinx/OsHle/Svc/SvcHandler.cs @@ -0,0 +1,79 @@ +using ChocolArm64.Memory; +using ChocolArm64.State; +using System; +using System.Collections.Generic; + +namespace Ryujinx.OsHle.Svc +{ + partial class SvcHandler + { + private delegate void SvcFunc(Switch Ns, ARegisters Registers, AMemory Memory); + + private Dictionary SvcFuncs = new Dictionary() + { + { 0x01, SvcSetHeapSize }, + { 0x03, SvcSetMemoryAttribute }, + { 0x04, SvcMapMemory }, + { 0x06, SvcQueryMemory }, + { 0x08, SvcCreateThread }, + { 0x09, SvcStartThread }, + { 0x0b, SvcSleepThread }, + { 0x0c, SvcGetThreadPriority }, + { 0x13, SvcMapSharedMemory }, + { 0x14, SvcUnmapSharedMemory }, + { 0x15, SvcCreateTransferMemory }, + { 0x16, SvcCloseHandle }, + { 0x17, SvcResetSignal }, + { 0x18, SvcWaitSynchronization }, + { 0x1a, SvcArbitrateLock }, + { 0x1b, SvcArbitrateUnlock }, + { 0x1c, SvcWaitProcessWideKeyAtomic }, + { 0x1d, SvcSignalProcessWideKey }, + { 0x1e, SvcGetSystemTick }, + { 0x1f, SvcConnectToNamedPort }, + { 0x21, SvcSendSyncRequest }, + { 0x22, SvcSendSyncRequestWithUserBuffer }, + { 0x26, SvcBreak }, + { 0x27, SvcOutputDebugString }, + { 0x29, SvcGetInfo } + }; + + enum SvcResult + { + Success = 0, + ErrBadHandle = 0xe401, + ErrTimeout = 0xea01, + ErrBadIpcReq = 0xf601, + } + + private Switch Ns; + private AMemory Memory; + + private static Random Rng; + + public SvcHandler(Switch Ns, AMemory Memory) + { + this.Ns = Ns; + this.Memory = Memory; + } + + static SvcHandler() + { + Rng = new Random(); + } + + public void SvcCall(object sender, SvcEventArgs e) + { + ARegisters Registers = (ARegisters)sender; + + if (SvcFuncs.TryGetValue(e.Id, out SvcFunc Func)) + { + Func(Ns, Registers, Memory); + } + else + { + throw new NotImplementedException(e.Id.ToString("x3")); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Svc/SvcMemory.cs b/Ryujinx/OsHle/Svc/SvcMemory.cs new file mode 100644 index 000000000..c1249b403 --- /dev/null +++ b/Ryujinx/OsHle/Svc/SvcMemory.cs @@ -0,0 +1,126 @@ +using ChocolArm64.Memory; +using ChocolArm64.State; +using Ryujinx.OsHle.Handles; + +namespace Ryujinx.OsHle.Svc +{ + partial class SvcHandler + { + private static void SvcSetHeapSize(Switch Ns, ARegisters Registers, AMemory Memory) + { + int Size = (int)Registers.X1; + + Memory.Manager.SetHeapSize(Size, (int)MemoryType.Heap); + + Registers.X0 = (int)SvcResult.Success; + Registers.X1 = (ulong)Memory.Manager.HeapAddr; + } + + private static void SvcSetMemoryAttribute(Switch Ns, ARegisters Registers, AMemory Memory) + { + long Position = (long)Registers.X0; + long Size = (long)Registers.X1; + int State0 = (int)Registers.X2; + int State1 = (int)Registers.X3; + + //TODO + + Registers.X0 = (int)SvcResult.Success; + } + + private static void SvcMapMemory(Switch Ns, ARegisters Registers, AMemory Memory) + { + long Src = (long)Registers.X0; + long Dst = (long)Registers.X1; + long Size = (long)Registers.X2; + + Memory.Manager.MapMirror(Src, Dst, Size, (int)MemoryType.MappedMemory); + + Registers.X0 = (int)SvcResult.Success; + } + + private static void SvcQueryMemory(Switch Ns, ARegisters Registers, AMemory Memory) + { + long InfoPtr = (long)Registers.X0; + long Position = (long)Registers.X2; + + AMemoryMapInfo MapInfo = Memory.Manager.GetMapInfo(Position); + + MemoryInfo Info = new MemoryInfo(MapInfo); + + Memory.WriteInt64(InfoPtr + 0x00, Info.BaseAddress); + Memory.WriteInt64(InfoPtr + 0x08, Info.Size); + Memory.WriteInt32(InfoPtr + 0x10, Info.MemType); + Memory.WriteInt32(InfoPtr + 0x14, Info.MemAttr); + Memory.WriteInt32(InfoPtr + 0x18, Info.MemPerm); + Memory.WriteInt32(InfoPtr + 0x1c, Info.IpcRefCount); + Memory.WriteInt32(InfoPtr + 0x20, Info.DeviceRefCount); + Memory.WriteInt32(InfoPtr + 0x24, Info.Padding); + + //TODO: X1. + + Registers.X0 = (int)SvcResult.Success; + Registers.X1 = 0; + } + + private static void SvcMapSharedMemory(Switch Ns, ARegisters Registers, AMemory Memory) + { + int Handle = (int)Registers.X0; + long Position = (long)Registers.X1; + long Size = (long)Registers.X2; + int Perm = (int)Registers.X3; + + HSharedMem HndData = Ns.Os.Handles.GetData(Handle); + + if (HndData != null) + { + long Src = Position; + long Dst = HndData.PhysPos; + + if (Memory.Manager.MapPhys(Src, Dst, Size, + (int)MemoryType.SharedMemory, (AMemoryPerm)Perm)) + { + Registers.X0 = (int)SvcResult.Success; + } + } + + //TODO: Error codes. + } + + private static void SvcUnmapSharedMemory(Switch Ns, ARegisters Registers, AMemory Memory) + { + int Handle = (int)Registers.X0; + long Position = (long)Registers.X1; + long Size = (long)Registers.X2; + + HSharedMem HndData = Ns.Os.Handles.GetData(Handle); + + if (HndData != null) + { + Registers.X0 = (int)SvcResult.Success; + } + + //TODO: Error codes. + } + + private static void SvcCreateTransferMemory(Switch Ns, ARegisters Registers, AMemory Memory) + { + long Position = (long)Registers.X1; + long Size = (long)Registers.X2; + int Perm = (int)Registers.X3; + + AMemoryMapInfo MapInfo = Memory.Manager.GetMapInfo(Position); + + Memory.Manager.Reprotect(Position, Size, (AMemoryPerm)Perm); + + long PhysPos = Memory.Manager.GetPhys(Position, AMemoryPerm.None); + + HTransferMem HndData = new HTransferMem(Memory, MapInfo.Perm, Position, Size, PhysPos); + + int Handle = Ns.Os.Handles.GenerateId(HndData); + + Registers.X1 = (ulong)Handle; + Registers.X0 = (int)SvcResult.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Svc/SvcSystem.cs b/Ryujinx/OsHle/Svc/SvcSystem.cs new file mode 100644 index 000000000..6f614d162 --- /dev/null +++ b/Ryujinx/OsHle/Svc/SvcSystem.cs @@ -0,0 +1,163 @@ +using ChocolArm64.Memory; +using ChocolArm64.State; +using Ryujinx.OsHle.Handles; +using Ryujinx.OsHle.Ipc; +using System; + +namespace Ryujinx.OsHle.Svc +{ + partial class SvcHandler + { + private static void SvcCloseHandle(Switch Ns, ARegisters Registers, AMemory Memory) + { + int Handle = (int)Registers.X0; + + Ns.Os.CloseHandle(Handle); + + Registers.X0 = (int)SvcResult.Success; + } + + private static void SvcResetSignal(Switch Ns, ARegisters Registers, AMemory Memory) + { + int Handle = (int)Registers.X0; + + //TODO: Implement events. + + Registers.X0 = (int)SvcResult.Success; + } + + private static void SvcWaitSynchronization(Switch Ns, ARegisters Registers, AMemory Memory) + { + long HandlesPtr = (long)Registers.X0; + int HandlesCount = (int)Registers.X2; + long Timeout = (long)Registers.X3; + + //TODO: Implement events. + + Registers.X0 = (int)SvcResult.Success; + } + + private static void SvcGetSystemTick(Switch Ns, ARegisters Registers, AMemory Memory) + { + Registers.X0 = (ulong)Registers.GetSystemReg(3, 3, 14, 0, 1); + } + + private static void SvcConnectToNamedPort(Switch Ns, ARegisters Registers, AMemory Memory) + { + long StackPtr = (long)Registers.X0; + long NamePtr = (long)Registers.X1; + + string Name = AMemoryHelper.ReadAsciiString(Memory, NamePtr, 8); + + //TODO: Validate that app has perms to access the service, and that the service + //actually exists, return error codes otherwise. + + HSession Session = new HSession(Name); + + Registers.X1 = (ulong)Ns.Os.Handles.GenerateId(Session); + Registers.X0 = (int)SvcResult.Success; + } + + private static void SvcSendSyncRequest(Switch Ns, ARegisters Registers, AMemory Memory) + { + SendSyncRequest(Ns, Registers, Memory, false); + } + + private static void SvcSendSyncRequestWithUserBuffer(Switch Ns, ARegisters Registers, AMemory Memory) + { + SendSyncRequest(Ns, Registers, Memory, true); + } + + private static void SendSyncRequest(Switch Ns, ARegisters Registers, AMemory Memory, bool IsUser) + { + long CmdPtr = Registers.TlsAddr; + long Size = 0x100; + int Handle = 0; + + if (IsUser) + { + CmdPtr = (long)Registers.X0; + Size = (long)Registers.X1; + Handle = (int)Registers.X2; + } + else + { + Handle = (int)Registers.X0; + } + + byte[] CmdData = AMemoryHelper.ReadBytes(Memory, CmdPtr, (int)Size); + + HSession Session = Ns.Os.Handles.GetData(Handle); + + IpcMessage Cmd = new IpcMessage(CmdData, CmdPtr, Session is HDomain); + + if (Session != null) + { + IpcHandler.ProcessRequest(Ns, Memory, Session, Cmd, CmdPtr, Handle); + + byte[] Response = AMemoryHelper.ReadBytes(Memory, CmdPtr, (int)Size); + + Registers.X0 = (int)SvcResult.Success; + } + else + { + Registers.X0 = (int)SvcResult.ErrBadIpcReq; + } + } + + private static void SvcBreak(Switch Ns, ARegisters Registers, AMemory Memory) + { + long Reason = (long)Registers.X0; + long Unknown = (long)Registers.X1; + long Info = (long)Registers.X2; + + throw new Exception($"SvcBreak: {Reason} {Unknown} {Info}"); + } + + private static void SvcOutputDebugString(Switch Ns, ARegisters Registers, AMemory Memory) + { + long Position = (long)Registers.X0; + long Size = (long)Registers.X1; + + string Str = AMemoryHelper.ReadAsciiString(Memory, Position, (int)Size); + + Console.WriteLine($"SvcOutputDebugString: {Str}"); + + Registers.X0 = (int)SvcResult.Success; + } + + private static void SvcGetInfo(Switch Ns, ARegisters Registers, AMemory Memory) + { + long StackPtr = (long)Registers.X0; + int InfoType = (int)Registers.X1; + long Handle = (long)Registers.X2; + int InfoId = (int)Registers.X3; + + switch (InfoType) + { + case 6: Registers.X1 = GetTotalMem(Memory); break; + case 7: Registers.X1 = GetUsedMem(Memory); break; + case 11: Registers.X1 = GetRnd64(); break; + + default: throw new NotImplementedException($"SvcGetInfo: {InfoType} {Handle} {InfoId}"); + } + + Registers.X0 = (int)SvcResult.Success; + } + + private static ulong GetTotalMem(AMemory Memory) + { + return (ulong)Memory.Manager.GetTotalMemorySize(); + } + + private static ulong GetUsedMem(AMemory Memory) + { + return (ulong)Memory.Manager.GetUsedMemorySize(); + } + + private static ulong GetRnd64() + { + return (ulong)Rng.Next() + ((ulong)Rng.Next() << 32); + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Svc/SvcThread.cs b/Ryujinx/OsHle/Svc/SvcThread.cs new file mode 100644 index 000000000..895cb247f --- /dev/null +++ b/Ryujinx/OsHle/Svc/SvcThread.cs @@ -0,0 +1,79 @@ +using ChocolArm64.Memory; +using ChocolArm64.State; +using Ryujinx.OsHle.Handles; +using System.Threading; + +namespace Ryujinx.OsHle.Svc +{ + partial class SvcHandler + { + private static void SvcCreateThread(Switch Ns, ARegisters Registers, AMemory Memory) + { + long EntryPoint = (long)Registers.X1; + long ArgsPtr = (long)Registers.X2; + long StackTop = (long)Registers.X3; + int Priority = (int)Registers.X4; + int ProcessorId = (int)Registers.X5; + + if (Ns.Os.TryGetProcess(Registers.ProcessId, out Process Process)) + { + int Handle = Process.MakeThread( + EntryPoint, + StackTop, + ArgsPtr, + Priority, + ProcessorId); + + Registers.X0 = (int)SvcResult.Success; + Registers.X1 = (ulong)Handle; + } + + //TODO: Error codes. + } + + private static void SvcStartThread(Switch Ns, ARegisters Registers, AMemory Memory) + { + int Handle = (int)Registers.X0; + + HThread HndData = Ns.Os.Handles.GetData(Handle); + + if (HndData != null) + { + HndData.Thread.Execute(); + + Registers.X0 = (int)SvcResult.Success; + } + + //TODO: Error codes. + } + + private static void SvcSleepThread(Switch Ns, ARegisters Registers, AMemory Memory) + { + ulong NanoSecs = Registers.X0; + + if (NanoSecs == 0) + { + Thread.Yield(); + } + else + { + Thread.Sleep((int)(NanoSecs / 1000000)); + } + } + + private static void SvcGetThreadPriority(Switch Ns, ARegisters Registers, AMemory Memory) + { + int Handle = (int)Registers.X1; + + HThread HndData = Ns.Os.Handles.GetData(Handle); + + if (HndData != null) + { + Registers.X1 = (ulong)HndData.Thread.Priority; + Registers.X0 = (int)SvcResult.Success; + } + + //TODO: Error codes. + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Svc/SvcThreadSync.cs b/Ryujinx/OsHle/Svc/SvcThreadSync.cs new file mode 100644 index 000000000..1df75625e --- /dev/null +++ b/Ryujinx/OsHle/Svc/SvcThreadSync.cs @@ -0,0 +1,87 @@ +using ChocolArm64; +using ChocolArm64.Memory; +using ChocolArm64.State; +using Ryujinx.OsHle.Handles; + +namespace Ryujinx.OsHle.Svc +{ + partial class SvcHandler + { + private static void SvcArbitrateLock(Switch Ns, ARegisters Registers, AMemory Memory) + { + int OwnerThreadHandle = (int)Registers.X0; + long MutexAddress = (long)Registers.X1; + int RequestingThreadHandle = (int)Registers.X2; + + AThread RequestingThread = Ns.Os.Handles.GetData(RequestingThreadHandle).Thread; + + Mutex M = new Mutex(Memory, MutexAddress); + + M = Ns.Os.Mutexes.GetOrAdd(MutexAddress, M); + + //FIXME + //M.WaitForLock(RequestingThread, RequestingThreadHandle); + + Memory.WriteInt32(MutexAddress, 0); + + Registers.X0 = (int)SvcResult.Success; + } + + private static void SvcArbitrateUnlock(Switch Ns, ARegisters Registers, AMemory Memory) + { + long MutexAddress = (long)Registers.X0; + + if (Ns.Os.Mutexes.TryGetValue(MutexAddress, out Mutex M)) + { + M.Unlock(); + } + + Registers.X0 = (int)SvcResult.Success; + } + + private static void SvcWaitProcessWideKeyAtomic(Switch Ns, ARegisters Registers, AMemory Memory) + { + long MutexAddress = (long)Registers.X0; + long CondVarAddress = (long)Registers.X1; + int ThreadHandle = (int)Registers.X2; + long Timeout = (long)Registers.X3; + + AThread Thread = Ns.Os.Handles.GetData(ThreadHandle).Thread; + + if (Ns.Os.Mutexes.TryGetValue(MutexAddress, out Mutex M)) + { + M.GiveUpLock(ThreadHandle); + } + + CondVar Signal = new CondVar(Memory, CondVarAddress, Timeout); + + Signal = Ns.Os.CondVars.GetOrAdd(CondVarAddress, Signal); + + Signal.WaitForSignal(ThreadHandle); + + M = new Mutex(Memory, MutexAddress); + + M = Ns.Os.Mutexes.GetOrAdd(MutexAddress, M); + + //FIXME + //M.WaitForLock(Thread, ThreadHandle); + + Memory.WriteInt32(MutexAddress, 0); + + Registers.X0 = (int)SvcResult.Success; + } + + private static void SvcSignalProcessWideKey(Switch Ns, ARegisters Registers, AMemory Memory) + { + long CondVarAddress = (long)Registers.X0; + int Count = (int)Registers.X1; + + if (Ns.Os.CondVars.TryGetValue(CondVarAddress, out CondVar Cv)) + { + Cv.SetSignal(Count); + } + + Registers.X0 = (int)SvcResult.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Utilities/IdPool.cs b/Ryujinx/OsHle/Utilities/IdPool.cs new file mode 100644 index 000000000..836d6310e --- /dev/null +++ b/Ryujinx/OsHle/Utilities/IdPool.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; + +namespace Ryujinx.OsHle.Utilities +{ + class IdPool + { + private HashSet Ids; + + private int CurrId; + private int MinId; + private int MaxId; + + public IdPool(int Min, int Max) + { + Ids = new HashSet(); + + CurrId = Min; + MinId = Min; + MaxId = Max; + } + + public IdPool() : this(1, int.MaxValue) { } + + public int GenerateId() + { + lock (Ids) + { + for (int Cnt = MinId; Cnt < MaxId; Cnt++) + { + if (Ids.Add(CurrId)) + { + return CurrId; + } + + if (CurrId++ == MaxId) + { + CurrId = MinId; + } + } + + return -1; + } + } + + public bool DeleteId(int Id) + { + lock (Ids) + { + return Ids.Remove(Id); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Utilities/IdPoolWithObj.cs b/Ryujinx/OsHle/Utilities/IdPoolWithObj.cs new file mode 100644 index 000000000..621466a82 --- /dev/null +++ b/Ryujinx/OsHle/Utilities/IdPoolWithObj.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace Ryujinx.OsHle.Utilities +{ + class IdPoolWithObj : IEnumerable> + { + private IdPool Ids; + + private ConcurrentDictionary Objs; + + public IdPoolWithObj() + { + Ids = new IdPool(); + + Objs = new ConcurrentDictionary(); + } + + public int GenerateId(object Data) + { + int Id = Ids.GenerateId(); + + if (Id == -1 || !Objs.TryAdd(Id, Data)) + { + throw new InvalidOperationException(); + } + + return Id; + } + + public bool ReplaceData(int Id, object Data) + { + if (Objs.ContainsKey(Id)) + { + Objs[Id] = Data; + + return true; + } + + return false; + } + + public T GetData(int Id) + { + if (Objs.TryGetValue(Id, out object Data) && Data is T) + { + return (T)Data; + } + + return default(T); + } + + public void Delete(int Id) + { + if (Objs.TryRemove(Id, out object Obj)) + { + if (Obj is IDisposable DisposableObj) + { + DisposableObj.Dispose(); + } + + Ids.DeleteId(Id); + } + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + return Objs.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return Objs.GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Utilities/MemReader.cs b/Ryujinx/OsHle/Utilities/MemReader.cs new file mode 100644 index 000000000..9868293a0 --- /dev/null +++ b/Ryujinx/OsHle/Utilities/MemReader.cs @@ -0,0 +1,44 @@ +using ChocolArm64.Memory; + +namespace Ryujinx.OsHle.Utilities +{ + class MemReader + { + private AMemory Memory; + + public long Position { get; private set; } + + public MemReader(AMemory Memory, long Position) + { + this.Memory = Memory; + this.Position = Position; + } + + public byte ReadByte() + { + byte Value = Memory.ReadByte(Position); + + Position++; + + return Value; + } + + public int ReadInt32() + { + int Value = Memory.ReadInt32(Position); + + Position += 4; + + return Value; + } + + public long ReadInt64() + { + long Value = Memory.ReadInt64(Position); + + Position += 8; + + return Value; + } + } +} \ No newline at end of file diff --git a/Ryujinx/OsHle/Utilities/MemWriter.cs b/Ryujinx/OsHle/Utilities/MemWriter.cs new file mode 100644 index 000000000..041b0a970 --- /dev/null +++ b/Ryujinx/OsHle/Utilities/MemWriter.cs @@ -0,0 +1,38 @@ +using ChocolArm64.Memory; + +namespace Ryujinx.OsHle.Utilities +{ + class MemWriter + { + private AMemory Memory; + + public long Position { get; private set; } + + public MemWriter(AMemory Memory, long Position) + { + this.Memory = Memory; + this.Position = Position; + } + + public void WriteByte(byte Value) + { + Memory.WriteByte(Position, Value); + + Position++; + } + + public void WriteInt32(int Value) + { + Memory.WriteInt32(Position, Value); + + Position += 4; + } + + public void WriteInt64(long Value) + { + Memory.WriteInt64(Position, Value); + + Position += 8; + } + } +} \ No newline at end of file diff --git a/Ryujinx/Switch.cs b/Ryujinx/Switch.cs new file mode 100644 index 000000000..9e5ea7d06 --- /dev/null +++ b/Ryujinx/Switch.cs @@ -0,0 +1,42 @@ +using ChocolArm64.Memory; +using Gal; +using Ryujinx.Gpu; +using Ryujinx.OsHle; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx +{ + public class Switch : IDisposable + { + public IntPtr Ram {get; private set; } + + internal NsGpu Gpu { get; private set; } + internal Horizon Os { get; private set; } + internal VirtualFs VFs { get; private set; } + + public Switch(IGalRenderer Renderer) + { + Ram = Marshal.AllocHGlobal((IntPtr)AMemoryMgr.RamSize); + + Gpu = new NsGpu(Renderer); + Os = new Horizon(this); + VFs = new VirtualFs(); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + VFs.Dispose(); + } + + Marshal.FreeHGlobal(Ram); + } + } +} \ No newline at end of file diff --git a/Ryujinx/VirtualFs.cs b/Ryujinx/VirtualFs.cs new file mode 100644 index 000000000..03317f4a1 --- /dev/null +++ b/Ryujinx/VirtualFs.cs @@ -0,0 +1,65 @@ +using System; +using System.IO; + +namespace Ryujinx +{ + class VirtualFs : IDisposable + { + private const string BasePath = "Fs"; + private const string SavesPath = "Saves"; + + public Stream RomFs { get; private set; } + + public void LoadRomFs(string FileName) + { + RomFs = new FileStream(FileName, FileMode.Open, FileAccess.Read); + } + + internal string GetFullPath(string BasePath, string FileName) + { + if (FileName.StartsWith('/')) + { + FileName = FileName.Substring(1); + } + + string FullPath = Path.GetFullPath(Path.Combine(BasePath, FileName)); + + if (!FullPath.StartsWith(GetBasePath())) + { + return null; + } + + return FullPath; + } + + internal string GetGameSavesPath() + { + string SavesDir = Path.Combine(GetBasePath(), SavesPath); + + if (!Directory.Exists(SavesDir)) + { + Directory.CreateDirectory(SavesDir); + } + + return SavesDir; + } + + internal string GetBasePath() + { + return Path.Combine(Directory.GetCurrentDirectory(), BasePath); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && RomFs != null) + { + RomFs.Dispose(); + } + } + } +} \ No newline at end of file