From 3e13b40b353a61fe57d1bc1440e1db9bc133df08 Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Sun, 15 Jul 2018 19:37:27 -0300
Subject: [PATCH] Add config key to dump shaders in local directory (#265)

* Add config key to dump shaders in local directory

* Address feedback
---
 Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs |  5 ++
 Ryujinx.Graphics/Gal/ShaderDumper.cs     | 96 ++++++++++++++++++++++++
 Ryujinx.Graphics/GraphicsConfig.cs       |  4 +
 Ryujinx/Config.cs                        |  2 +
 Ryujinx/Ryujinx.conf                     |  3 +
 5 files changed, 110 insertions(+)
 create mode 100644 Ryujinx.Graphics/Gal/ShaderDumper.cs
 create mode 100644 Ryujinx.Graphics/GraphicsConfig.cs

diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs
index c55a758b4a..ad71775502 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs
@@ -118,6 +118,9 @@ namespace Ryujinx.Graphics.Gal.OpenGL
 
             if (IsDualVp)
             {
+                ShaderDumper.Dump(Memory, Position  + 0x50, Type, "a");
+                ShaderDumper.Dump(Memory, PositionB + 0x50, Type, "b");
+
                 Program = Decompiler.Decompile(
                     Memory,
                     Position  + 0x50,
@@ -126,6 +129,8 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             }
             else
             {
+                ShaderDumper.Dump(Memory, Position + 0x50, Type);
+
                 Program = Decompiler.Decompile(Memory, Position + 0x50, Type);
             }
 
diff --git a/Ryujinx.Graphics/Gal/ShaderDumper.cs b/Ryujinx.Graphics/Gal/ShaderDumper.cs
new file mode 100644
index 0000000000..7cd56b21ef
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/ShaderDumper.cs
@@ -0,0 +1,96 @@
+using System;
+using System.IO;
+
+namespace Ryujinx.Graphics.Gal
+{
+    static class ShaderDumper
+    {
+        private static string RuntimeDir;
+
+        private static int DumpIndex = 1;
+
+        public static void Dump(IGalMemory Memory, long Position, GalShaderType Type, string ExtSuffix = "")
+        {
+            if (string.IsNullOrWhiteSpace(GraphicsConfig.ShadersDumpPath))
+            {
+                return;
+            }
+
+            string FileName = "Shader" + DumpIndex.ToString("d4") + "." + ShaderExtension(Type) + ExtSuffix + ".bin";
+
+            string FilePath = Path.Combine(DumpDir(), FileName);
+
+            DumpIndex++;
+
+            using (FileStream Output = File.Create(FilePath))
+            using (BinaryWriter Writer = new BinaryWriter(Output))
+            {
+                long Offset = 0;
+
+                ulong Instruction = 0;
+
+                //Dump until a NOP instruction is found
+                while ((Instruction >> 52 & 0xfff8) != 0x50b0)
+                {
+                    uint Word0 = (uint)Memory.ReadInt32(Position + Offset + 0);
+                    uint Word1 = (uint)Memory.ReadInt32(Position + Offset + 4);
+
+                    Instruction = Word0 | (ulong)Word1 << 32;
+
+                    //Zero instructions (other kind of NOP) stop immediatly,
+                    //this is to avoid two rows of zeroes
+                    if (Instruction == 0)
+                    {
+                        break;
+                    }
+
+                    Writer.Write(Instruction);
+
+                    Offset += 8;
+                }
+
+                //Align to meet nvdisasm requeriments
+                while (Offset % 0x20 != 0)
+                {
+                    Writer.Write(0);
+
+                    Offset += 4;
+                }
+            }
+        }
+
+        private static string DumpDir()
+        {
+            if (string.IsNullOrEmpty(RuntimeDir))
+            {
+                int Index = 1;
+
+                do
+                {
+                    RuntimeDir = Path.Combine(GraphicsConfig.ShadersDumpPath, "Dumps" + Index.ToString("d2"));
+
+                    Index++;
+                }
+                while (Directory.Exists(RuntimeDir));
+
+                Directory.CreateDirectory(RuntimeDir);
+            }
+
+            return RuntimeDir;
+        }
+
+        private static string ShaderExtension(GalShaderType Type)
+        {
+            switch (Type)
+            {
+                case GalShaderType.Vertex:         return "vert";
+                case GalShaderType.TessControl:    return "tesc";
+                case GalShaderType.TessEvaluation: return "tese";
+                case GalShaderType.Geometry:       return "geom";
+                case GalShaderType.Fragment:       return "frag";
+
+                default: throw new ArgumentException(nameof(Type));
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/GraphicsConfig.cs b/Ryujinx.Graphics/GraphicsConfig.cs
new file mode 100644
index 0000000000..3e3ef4ffa7
--- /dev/null
+++ b/Ryujinx.Graphics/GraphicsConfig.cs
@@ -0,0 +1,4 @@
+public static class GraphicsConfig
+{
+    public static string ShadersDumpPath;
+}
diff --git a/Ryujinx/Config.cs b/Ryujinx/Config.cs
index 940753ba53..0f346122af 100644
--- a/Ryujinx/Config.cs
+++ b/Ryujinx/Config.cs
@@ -29,6 +29,8 @@ namespace Ryujinx
 
             AOptimizations.DisableMemoryChecks = !Convert.ToBoolean(Parser.Value("Enable_Memory_Checks"));
 
+            GraphicsConfig.ShadersDumpPath = Parser.Value("Graphics_Shaders_Dump_Path");
+
             Log.SetEnable(LogLevel.Debug,   Convert.ToBoolean(Parser.Value("Logging_Enable_Debug")));
             Log.SetEnable(LogLevel.Stub,    Convert.ToBoolean(Parser.Value("Logging_Enable_Stub")));
             Log.SetEnable(LogLevel.Info,    Convert.ToBoolean(Parser.Value("Logging_Enable_Info")));
diff --git a/Ryujinx/Ryujinx.conf b/Ryujinx/Ryujinx.conf
index 59f7f859e7..063bb2de4e 100644
--- a/Ryujinx/Ryujinx.conf
+++ b/Ryujinx/Ryujinx.conf
@@ -1,6 +1,9 @@
 #Enable cpu memory checks (slow)
 Enable_Memory_Checks = false
 
+#Dump shaders in local directory (e.g. `C:\ShaderDumps`)
+Graphics_Shaders_Dump_Path =
+
 #Enable print debug logs
 Logging_Enable_Debug = false